Note that there is no differentiation between year-of-era and year in the legacy API. The year, 0
is actually 1 BC
. The month, 0
and day, 0
are invalid values but instead of throwing an exception SimpleDateFormat
parses them erroneously.
The reason for the month being converted to 11
:
The SimpleDateFormat
decreases the month numeral in the text by 1
because java.util.Date
is 0
based. In other words, month, 1
is parsed by SimpleDateFormat
as 0
which is month Jan
for java.util.Date
. Similarly, month, 0
is parsed by SimpleDateFormat
as -1
. Now, a neagtive month is treated by java.util.Date
as follows:
month = CalendarUtils.mod(month, 12);
and the CalendarUtils#mod
has been defined as follows:
public static final int mod(int x, int y) {
return (x - y * floorDivide(x, y));
}
public static final int floorDivide(int n, int d) {
return ((n >= 0) ?
(n / d) : (((n + 1) / d) - 1));
}
Thus, CalendarUtils.mod(-1, 12)
returns 11
.
java.util.Date
and SimpleDateFormat
are full of such surprises. It is recommended to stop using them completely and switch to the modern date-time API.
The modern date-time API:
The modern date-time API differentiates between year-of-era and year using y
and u
respectively.
y
specifies the year-of-era (era is specified as AD
or BC
) and is always a positive number whereas u
specifies the year which is a signed (+/-) number.
Normally, we do not use +
sign to write a positive number but we always specify a negative number with a -
sign. The same rule applies for a year. As long as you are going to use a year of the era, AD
, both, y
and u
will give you the same number. However, you will get different numbers when you use a year of the era, BC
e.g. the year-of-era, 1 BC
is specified as year, 0
; the year-of-era, 2 BC
is specified as year, -1
and so on.
You can understand it better with the following demo:
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
public class Testing {
public static void main(String[] args) {
System.out.println(LocalDate.of(-1, 1, 1).format(DateTimeFormatter.ofPattern("u M d")));
System.out.println(LocalDate.of(-1, 1, 1).format(DateTimeFormatter.ofPattern("y M d")));
System.out.println(LocalDate.of(-1, 1, 1).format(DateTimeFormatter.ofPattern("yG M d")));
System.out.println();
System.out.println(LocalDate.of(0, 1, 1).format(DateTimeFormatter.ofPattern("u M d")));
System.out.println(LocalDate.of(0, 1, 1).format(DateTimeFormatter.ofPattern("y M d")));
System.out.println(LocalDate.of(0, 1, 1).format(DateTimeFormatter.ofPattern("yG M d")));
System.out.println();
System.out.println(LocalDate.of(1, 1, 1).format(DateTimeFormatter.ofPattern("u M d")));
System.out.println(LocalDate.of(1, 1, 1).format(DateTimeFormatter.ofPattern("y M d")));
System.out.println(LocalDate.of(1, 1, 1).format(DateTimeFormatter.ofPattern("yG M d")));
}
}
Output:
-1 1 1
2 1 1
2BC 1 1
0 1 1
1 1 1
1BC 1 1
1 1 1
1 1 1
1AD 1 1
How does modern date-time API treat 0000:00:00 00:00:00
?
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
class Main {
public static void main(String[] args) {
DateTimeFormatter parser = DateTimeFormatter.ofPattern("uuuu:MM:dd HH:mm:ss")
.withZone(ZoneOffset.UTC)
.withLocale(Locale.ENGLISH);
ZonedDateTime zdt = ZonedDateTime.parse("0000:00:00 00:00:00", parser);
}
}
Output:
Exception in thread "main" java.time.format.DateTimeParseException: Text '0000:00:00 00:00:00' could not be parsed: Invalid value for MonthOfYear (valid values 1 - 12): 0
....
With DateTimeFormatter#withResolverStyle(ResolverStyle.LENIENT)
:
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.ResolverStyle;
import java.util.Locale;
public class Main {
public static void main(String[] args) {
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss", Locale.ENGLISH)
.withResolverStyle(ResolverStyle.LENIENT);
String str = "0000-00-00 00:00:00";
LocalDateTime ldt = LocalDateTime.parse(str, dtf);
System.out.println(ldt);
}
}
Output:
-0001-11-30T00:00