I had a situation where the Java runtime returned sort of "inverted" millisecond values when reading dates from the database (in java.sql.Date). The millisecond value was approximately the same amount of days, but counted backwards from year 0.
The problem was solved by just restarting the Java runtime.
But: I found out that Java handles these "inverted" values almost correctly except of the week day.
When you run the following code:
System.out.println(new java.util.Date(253402214400000l));
System.out.println(new java.util.Date(-377648784000000l));
You will get the following output:
Fri Dec 31 01:00:00 CET 9999
Tue Dec 31 01:00:00 CET 9999
Another example:
System.out.println(new java.util.Date(-294192000000l));
System.out.println(new java.util.Date(-123967324800000l));
Result:
Mon Sep 05 01:00:00 CET 1960
Mon Sep 05 01:00:00 CET 1960
When using online converters, the result will be different for the particular second line. It will result in a negative date (the year is negative) close to the real, positive date:
Example1:
253402214400000 = Fri Dec 31 9999 01:00:00
-377648784000000 = Tue Oct 15 -9998 02:00:00
Example 2:
-294192000000 = Mon Sep 05 1960 02:00:00
-123967324800000 = Mon Aug 19 -1959 02:00:00
I have not found any information about this "topic".
So, what's the myth behind "inverted" dates? Why does Java handle them almost correctly? And what is the sense of a JDBC ResultSet returning "inverted" millisecond values when calling resultSet.getDate(1).getTime()?
When you are passing a negative number in the Date constructor then it is considered as number of milleseconds before 1/1/1970. The Javadoc says:
date - milliseconds since January 1, 1970, 00:00:00 GMT not to exceed
the milliseconds representation for the year 8099. A negative number
indicates the number of milliseconds before January 1, 1970,
You can see the result which you get when you try to provide the Long.MIN_VALUE and Long.MAX_VALUE in the Date constructor.
DateFormat df = new SimpleDateFormat("d MMM yyyy G, HH:mm:ss.S Z");
System.out.println(df.format(new Date(Long.MIN_VALUE)));
System.out.println(df.format(new Date(Long.MAX_VALUE)));
Ideone Demo
I found out that Java handles these "inverted" values almost correctly except of the week day.
In your first example, the two dates are not the same - one is BC and the other one AD (which explains why the day of the week is different):
Date d1 = new Date(253402214400000l);
Date d2 = new Date(-377648784000000l);
DateFormat fmt = new SimpleDateFormat("yyyy G");
System.out.println(fmt.format(d1)); //9999 AD
System.out.println(fmt.format(d2)); //9999 BC
So your observation is just a coincidence (however there may be a date formatter somewhere that has gone wild and negates the years or the years may actually be negative in your database).
The difference with online converters is probably due to how the year 0 is taken into account and/or variations in the calendar used for the calculations.
Related
For improving performance of some legacy code, I am considering a replacement of java.text.SimpleDateFormat by java.time.format.DateTimeFormatter.
Among the tasks performed is parsing date/time values that had been serialized using java.util.Date.toString. With SimpleDateFormat, it was possible to turn them back into the original timestamps (neglecting fractional seconds), however I am facing problems when attempting to do the same with DateTimeFormatter.
When formatting with either, my local timezone is indicated as CET or CEST, depending on whether daylight savings time is in effect for the time to be formatted. However it appears that at parsing time, both CET and CEST are treated the same by DateTimeFormatter.
This creates a problem with the overlap occurring at the end of daylight savings time. When formatting, 02:00:00 is created twice, for times one hour apart, but with CEST and CET timezone names - which is fine. But at parsing time, that difference can't be reclaimed.
Here is an example:
long msecPerHour = 3600000L;
long cet_dst_2016 = 1477778400000L;
DateTimeFormatter formatter =
DateTimeFormatter.ofPattern("EEE MMM dd HH:mm:ss zzz yyyy", Locale.ENGLISH);
ZoneId timezone = ZoneId.of("Europe/Berlin");
for (int hours = 0; hours < 6; ++hours) {
long time = cet_dst_2016 + msecPerHour * hours;
String formatted = formatter.format(Instant.ofEpochMilli(time).atZone(timezone));
long parsedTime = Instant.from(formatter.parse(formatted)).toEpochMilli();
System.out.println(formatted + ", diff: " + (parsedTime - time));
}
which results in
Sun Oct 30 00:00:00 CEST 2016, diff: 0
Sun Oct 30 01:00:00 CEST 2016, diff: 0
Sun Oct 30 02:00:00 CEST 2016, diff: 0
Sun Oct 30 02:00:00 CET 2016, diff: -3600000
Sun Oct 30 03:00:00 CET 2016, diff: 0
Sun Oct 30 04:00:00 CET 2016, diff: 0
It shows that the second occurrence of 02:00:00, inspite of the different timezone name, was treated like the first one. So the result effectively is off by one hour.
Obviously the formatted string has all information available, and SimpleDateFormat parsing in fact honored it. Is it possible to roundtrip through formatting and parsing, using DateTimeFormatter, with the given pattern?
It is possible for a specific case:
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
.appendPattern("EEE MMM dd HH:mm:ss ")
.appendText(OFFSET_SECONDS, ImmutableMap.of(2L * 60 * 60, "CEST", 1L * 60 * 60, "CET"))
.appendPattern(" yyyy")
.toFormatter(Locale.ENGLISH);
This maps the exact offset to the expected text. Where this fails is when you need to deal with more than one time-zone.
To do the job properly requires a JDK change.
It seems like a bug. I tested in Java 17 and it's still the same behaviour. I dug into the parsing logic and I can see why this happens.
One of the first things that happens is TimeZoneNameUtility.getZoneStrings(locale) is called. This gives you a 2D array of Strings
[
[
"Europe/Paris",
"Central European Standard Time", "CET",
"Central European Summer Time", "CEST",
"Central European Time", "CET"
],
// others
]
It builds a prefix tree out of them. All items in here get mapped to the 0th item - "Europe/Paris". When it's parsing, it descends the prefix tree one character at a time e.g. C... E... T..., then returns a match if there was one. Since CEST and CET map to the same thing, they're effectively just aliases of one another.
Later on that string is passed to ZoneId.of() which means the fact of whether it's summertime or not has been thrown away.
It does seem in Java 18 that there have been significant changes in this code, so maybe they're addressing that.
The general workaround
JodaStephen, the main author of java.time, in his answer shows a workaround for the case of CET and CEST (Central European Time and Central European Summer Time). I present a workaround that I believe will work in all time zones having different abbreviations for standard time and summer time (DST).
public static ZonedDateTime parse(String text) {
ZonedDateTime result = ZonedDateTime.parse(text, FORMATTER);
if (result.format(FORMATTER).equals(text)) {
return result;
}
// Default we get the earlier offset at overlap,
// so if it didn’t work, try the later offset
result = result.withLaterOffsetAtOverlap();
if (result.format(FORMATTER).equals(text)) {
return result;
}
// As a last desperate attempt, try earlier offset explicitly
result = result.withEarlierOffsetAtOverlap();
if (result.format(FORMATTER).equals(text)) {
return result;
}
// Give up
throw new IllegalArgumentException();
}
The method could use any formatter with a time zone name or abbreviation as long as it’s supposed to give the same output from formatting as the input it parses (so optional parts are a no-no, for example). I have assumed a formatter equivalent to yours:
private static final DateTimeFormatter FORMATTER
= DateTimeFormatter.ofPattern("EEE MMM dd HH:mm:ss zzz yyyy", Locale.ROOT);
Your trouble was with a millisecond value of 1 477 789 200 000, which was formatted into Sun Oct 30 02:00:00 CET 2016 and then parsed to 1 477 785 600 000 for a difference of -3 600 000 milliseconds. So let’s try my method with that one.
private static final ZoneId TIME_ZONE = ZoneId.of("Europe/Berlin");
long trouble = 1_477_789_200_000L;
String formatted = Instant.ofEpochMilli(trouble).atZone(TIME_ZONE).format(FORMATTER);
ZonedDateTime zdt = parse(formatted);
long parsedTime = zdt.toInstant().toEpochMilli();
System.out.println(formatted + ", diff: " + (parsedTime - trouble));
Output is:
Sun Oct 30 02:00:00 CET 2016, diff: 0
But don’t parse three letter time zone abbreviations
All of the above said, even with a workaround for that case of the fall overlap, you are on shaky ground when trying to parse time zone abbreviations. Most of the most common ones are ambiguous, and you don’t know what you get from parsing. In the case of CET and CEST, they are common abbreviations for very many European time zones that at present share offset +01:00 during standard time and +02:00 during summer time, but historically have had their own offset each and are likely to go separate ways again since the EU has decided to give up summer time completely. Next year one time zone may use CET all year and another CEST all year. My code above does not account for that.
Instead simply take the output from ZonedDateTime.toString and parse it back using the one-arg ZonedDateTime.parse(CharSequence).
I have the following date object Wed Nov 01 00:00:00 GMT 2017. This obviously is in GMT, however, I'd like to consider it to be in a different timezone.
As an example, I'd like to consider the above date in the following timezone US/Mountain and I'd like to then convert it to UTC, resulting in Wed Nov 01 07:00:00 UTC.
I've tried to find a way to change the timezone of a date, while preserving the time, but failed.
Thanks
I understand from you comment that you have got a java.util.Date instance. It prints as (for example) Wed Nov 01 00:00:00 GMT 2017. This is what its toString method produces. The Date doesn’t have a time zone in it. Usually Date.toString() grabs the JVM’s time zone setting and renders the date in this time zone. So it appears you are running GMT time zone? You can read more in this popular blog entry: All about java.util.Date.
If you can, avoid having a Date. The modern Java date and time API known as java.time or JSR-310 is so much nicer to work with, both in general and not least for time zone magic like yours. Then use assylias’ answer.
For this answer I am assuming that you got a Date from some legacy API that you cannot change (or cannot afford to change just now). I still recommend the modern API for the change you desire. The output from the following snippet I give as comments in the code.
System.out.println(oldFashionedDateObject); // Wed Nov 01 00:00:00 GMT 2017
// first thing, convert the Date to an instance of a modern class, Instant
Instant pointInTime = oldFashionedDateObject.toInstant();
// convert to same hour and minute in US/Mountain and then back into UTC
ZonedDateTime convertedDateTime = pointInTime.atOffset(ZoneOffset.UTC)
.atZoneSimilarLocal(ZoneId.of("US/Mountain"))
.withZoneSameInstant(ZoneOffset.UTC);
System.out.println(convertedDateTime); // 2017-11-01T06:00Z
// only assuming you absolutely and indispensably need an old-fashioned Date object back
oldFashionedDateObject = Date.from(convertedDateTime.toInstant());
System.out.println(oldFashionedDateObject); // Wed Nov 01 06:00:00 GMT 2017
As assylias, I got Wed Nov 01 06:00:00. According to Current Local Time in Denver, Colorado, USA summer time (DST) ended on November 5 this year.
With the java time API, you can:
Parse the string as a ZonedDateTime
Use the zonedDateTime.withZoneSameLocal and zonedDateTime.withZoneSameInstant to convert the result
Something like this:
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("EEE MMM dd HH:mm:ss z uuuu");
ZonedDateTime gmt = ZonedDateTime.parse("Wed Nov 01 00:00:00 GMT 2017", fmt);
ZonedDateTime mountain = gmt.withZoneSameLocal(ZoneId.of("US/Mountain"));
ZonedDateTime utc = mountain.withZoneSameInstant(ZoneOffset.UTC);
System.out.println(utc.format(fmt));
which, by the way, outputs: Wed Nov 01 06:00:00 Z 2017 (the DST will be in effect on November 3rd only).
Why does this happens? For the month and day, I think Java is assuming the previous valid month and day, but I don't understand why year is 2.
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
Date result = sdf.parse("0000/00/00");
System.out.println(result.toString());
Output is:
Sun Nov 30 00:00:00 GMT 2
The Gregorian calendar does not have year 0.
Year 0 corresponds to 1BCE (Before Common Era, also known as BC).
Because you supply 0 for the month and 0 for the day, it rolls back to the previous month and previous year.
I.e. 30-Nov-0002 BCE.
Date#toString does not include BCE / CE suffix. It would be superfluous in the vast majority of cases.
If you are going to work with dates that far back then you need to consult with an historian.
By default SimpleDateFormat tries to parse even incorrect input. You can switch this off using setLenient method:
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
sdf.setLenient(false);
Date result = sdf.parse("0000/00/00");
This way you will have an exception which is probably more appropriate in your case:
Exception in thread "main" java.text.ParseException: Unparseable date: "0000/00/00"
at java.text.DateFormat.parse(DateFormat.java:366)
at Snippet.main(Snippet.java:11)
The starting point for Date would be 00010101
ei Year - 1 , Month - Jan and Date - 1
What you have given input is 00000000
Will start with month - 00 means Jan - 1 ie Dec
Day 00 mean 1 Dec - 1 ie 30th Nov
This explains the 1st part of the output. Sun Nov 30 00:00:00 GMT
The year is given 00 that mean year 1 minus 1 . ie 1 BC
And as year rolls back another time for month and date its 2 BC.
Hence the year is shown as 2.
It seems that Java and JavaScript give different results for negative millisecond values.
JAVA:
System.out.println(new Date(-12220000000000L));
Wed Sep 26 12:33:20 MST 1582
JavaScript:
console.log(new Date(-12220000000000));
Date {Wed Oct 06 1582 12:33:20 GMT-0700 (LMT)}
There is a 10 day difference in their outputs. But for some values, the difference is less than 10 days. I used W3C TryIt editor to test JavaScript output. I don't know exactly where the deviation begins. Am I doing something wrong here?
The default date for the switch from Julian to Gregorian calendar in Java's GregorianCalendar class "is October 15, 1582 (Gregorian). Previous to this, dates will be in the Julian calendar."
Hence, the OP's Java date of Sep 26, 1582 is a Julian date. The difference between Julian and Gregorian dates in 1582 was 10 days.
If the JavaScript implementation in question doesn't respect the Julian to Gregorian switch but works with Gregorian dates regardless of the point in time we are at the situation the OP experienced.
Using nashorn, you can see when they deviate:
> function printDate(x) { System.out.println(new Date(x) + " - " + new java.util.Date(x)); }
> printDate(-12219292800000)
Fri Oct 15 1582 00:00:00 GMT+0000 (GMT) - Fri Oct 15 00:00:00 GMT 1582
> printDate(-12219292800001)
Thu Oct 14 1582 23:59:59 GMT+0000 (GMT) - Thu Oct 04 23:59:59 GMT 1582
This corresponds to a switch from the Julian to Gregorian calendar.
Java's date library takes this change into account, but JavaScript's doesn't.
Both java getTime() and javascript getTime() returns many milliseconds have passed since January 1, 1970, 00:00:00 GMT. Negative argument calculate the date to the past.
I found a bug in jdk . Reading this it seems in jdk 7 there is a lack of 10 days from Oct 5 1582 to Oct 15 1582. I think the 10 days difference from javascript getTime() comes from this bug.
I've just came upon a very strange behaviour of Java's Date class when I try to create two dates consequently:
Date startDate = new Date(1282863600000L);
System.out.println(startDate);
Date endDate = new Date(1321919999000L);
System.out.println(endDate);
The output is respectively:
Fri Aug 27 00:00:00 BST 2010
Mon Nov 21 23:59:59 GMT 2011
Has anyone seen something like that? Both date are initialized in an identical manner but when printed the first is shown in BST and the latter in GMT?
I tried to find explanation about that but I didn't. Can someone help me?
Thanks in advance!
This is documented behaviour.
From Date.toString():
Converts this Date object to a String of the form:
dow mon dd hh:mm:ss zzz yyyy
zzz is the time zone (and may reflect daylight saving time). Standard time zone abbreviations include those recognized by the method parse. If time zone information is not available, then zzz is empty - that is, it consists of no characters at all.
You are using a locale that uses British Summer Time and creating a date where a day-light-saving rule applies. This would be the expected form of the date at that time to a local user.
For me the output of this code is
Fri Aug 27 01:00:00 CEST 2010
Tue Nov 22 00:59:59 CET 2011
The exact result depends on the default locale Java is using on your system.
The difference is that CEST is the central european summer time, while CET is the central european time (i.e. not summer time).
You seem to be running in a british locale (en_GB or similar), so your output shows the British Summer Time and the Greenwich Mean Time respectively.
The first date you specify falls into the respective summer times and the second doesn't. So Java chooses the appropriate time zone for each locale/time combination.
After a lovely session of trying different long values I got this:
Date startDate1 = new Date(1284245999999L);
Date startDate2 = new Date(1284246000000L);
System.out.println(startDate1);
System.out.println(startDate2);
Date endDate = new Date(1321919999000L);
System.out.println(endDate);
The output was:
Sun Sep 12 01:59:59 IDT 2010
Sun Sep 12 01:00:00 IST 2010 <-- Long value is greater, but due to DST changes, actual time is one hour earlier
Tue Nov 22 01:59:59 IST 2011
Note that incrementing the long by 1 from 1284245999999L to 1284246000000L takes us "back in time" because of the transition from standard time to daylight savings time.
That is how Java time calculation behaves - the number of milliseconds since 1/1/1970 does not change, but the time it represents is based on the timezone.