import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.text.ParseException;
import java.util.Date;
import java.util.TimeZone;
class Main
public static void main (String[] args) throws ParseException
DateFormat parser = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss");
parser.setTimeZone(TimeZone.getTimeZone("GMT"));
Date date = parser.parse("0000:00:00 00:00:00");
System.out.println(date.toInstant());
My first thought was that this was a time-zone problem, but the output is a whopping 34 days earlier than the expected date.
This is a 3rd-party library so I cannot actually modify the code but if I can understand why it is returning this value then maybe I can tweak the inputs to get the desired output.
In case you're wondering, 0000:00:00 00:00:00
comes from EXIF metadata of images or videos.
–
–
–
–
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.
For any reason, if you have to stick to Java 6 or Java 7, you can use ThreeTen-Backport which backports most of the java.time functionality to Java 6 & 7.
If you are working for an Android project and your Android API level is still not compliant with Java-8, check Java 8+ APIs available through desugaring and How to use ThreeTenABP in Android Project.
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
–
–
–
As explained by other answers, this is a result of processing an invalid timestamp (invalid year, month and day values) with a legacy class (SimpleDateFormat
) that doesn't do proper validation.
In short ... garbage in, garbage out1.
Solutions:
Rewrite the code that uses SimpleDateFormat
to use the new date / time classes introduced in Java 8. (Or use a backport if you have to use Java 7 and earlier.)
Work around the problem by testing for this specific case before you attempt to process the string as a date.
It seems from the context that "0000:00:00 00:00:00" is the EXIF way of saying "no such datetime". If that's the case, then trying to treat it as a datetime seems counter-productive. Treat it as a special case instead.
If you can't rewrite the code or work around the problem, submit a bug report and/or patch against the (3rd party) library and hope for the best ...
1 - Why the discrepancy is exactly 1 year and 34 days is a bit of a mystery, but I'm sure you could figure out the explanation by diving into the source code. IMO, it is not worth the effort. However, I can't imagine why the Gregorian shift would be implicated in this ...
This is because year 0 is invalid, it doesn't
exist.
https://en.m.wikipedia.org/wiki/Year_zero
Month,day are also invalid by being 0.
–
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.