Edit: It turns out the problem is not about Jackson, but about time adjustment in Thailand on 1 April 1920.
How does com.fasterxml.jackson.databind.ObjectMapper works? I thought it used Unix timestamp.
I tried converting a java.util.Date with mapper.writeValueAsString().
When I convert the string back to Date with mapper.readerFor(Date.class).readValue(), the result is correct.
However, when I trying removing the last 3 digits and and put the same string into some converter websites, the result is off for some minutes and seconds.
Please see the code below.
Date wayBack = new SimpleDateFormat("yyyy-MM-dd").parse("1900-01-31");
System.out.println(wayBack); // Wed Jan 31 00:00:00 ICT 1900
ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.writeValueAsString(wayBack)); // -2206420924000
Date deserialised = mapper.readerFor(Date.class).readValue(mapper.writeValueAsString(wayBack));
System.out.println(deserialised); // Wed Jan 31 00:00:00 ICT 1900
Below is a screenshot from http://www.onlineconversion.com/unix_time.htm
Please note that 7-hour off is expected because of my timezone but I don't understand the 17:56 minutes different.
EDIT - Here is my attempt to provide a better answer than my first one.
Background
Before looking at the code in the question, some background notes:
The epoch value (in seconds) at midnight 31st Jan 1900 in Bangkok is -2206420924:
LocalDateTime localDateTime = LocalDateTime.parse("1900-01-31T00:00:00");
ZoneId z = ZoneId.of("Asia/Bangkok");
ZonedDateTime ict_1 = ZonedDateTime.of(localDateTime, z);
System.out.println("Epoch seconds: " + ict_1.toEpochSecond());
System.out.println("ICT datetime : " + ict_1);
The above prints this:
Epoch seconds: -2206420924
ICT datetime : 1900-01-31T00:00+06:42:04[Asia/Bangkok]
The epoch value (in seconds) for UTC midnight on the same date is -1570060800:
ZonedDateTime utcDateTime = ZonedDateTime.parse("1900-01-31T00:00:00Z");
System.out.println("Epoch seconds: " + utcDateTime.toEpochSecond());
System.out.println("UTC datetime : " + utcDateTime);
The above prints this:
Epoch seconds: -2206396800
UTC datetime : 1900-01-31T00:00Z
The time at midnight in Bangkok on 31st January 1900 was 24,124 seconds further into the past than the time at midnight in Greenwich, UK (the prime meridian - or UTC).
That is to say, on that date Bangkok was 6 hours, 42 minutes and 4 seconds ahead of UTC time (or GMT as I believe it was then called - as UTC had not been established at that time).
The Specific Code in the Question
First, I changed my default time zone to match the one used in the question:
System.setProperty("user.timezone", "Asia/Bangkok");
The below line from the question does the following:
(1) The SimpleDateFormat constructor, in which the date format string does not specify a locale, uses the default locale.
(2) Then the parse() method creates the Date object:
Date wayBack = new SimpleDateFormat("yyyy-MM-dd").parse("1900-01-31");
At this point we can check the date object:
System.out.println(wayBack);
System.out.println(wayBack.getTime());
This prints the following:
Wed Jan 31 00:00:00 ICT 1900
-2206420924000 // epoch milliseconds
This matches what we saw earlier, in the background section.
When you use an online tool such as the one mentioned in the question, you will see the above milliseconds value reported as the following GMT (UTC) datetime:
GMT: Tuesday, January 30, 1900 5:17:56 PM
For the above output I used this tool.
Again, this is what we expect - when it's midnight in Bangkok, it's still the afternoon of the day before in Greenwich, UK.
The remainder of the code (including the Jackson object mapper transformations) are all subject to this initial set-up of your Date object.
For the question: "How does com.fasterxml.jackson.databind.ObjectMapper works? I thought it used Unix timestamp." It shows the same behavior as the core Java date object. I believe your assumption is correct.
Regarding the Unusual Offset
Regarding the ICT offset of +06:42:04 shown above:
On April 1st 1920, an adjustment was made to the local ICT (Indochina Time), to align it with UTC time (with an offset of +7 hours, as you note). The local clocks were set forward by 17 minutes and 56 seconds, to round up the UTC (GMT) offset to 7 hours.
See this link for a specific reference to the 17 minutes & 56 seconds change.
This is why you will not see that unusual offset from April 1920 onwards.
Further Links
See this answer regarding the newer java.time classes which should be used instead of java.util.Date.
See this question and its answers for a related deep-dive into the topic of historic time zone adjustments.
Related
I have a problem with the dates and their passage from Date to LocalDate, mainly the error is with the dates that have the year 1700.
I have tried two different dates and whenever the year is 1700, it puts me one day less.
Date dto = ...;
Instant inst = dto.toInstant();
LocalDate localDate = LocalDate.ofInstant(inst, ZoneId.systemDefault());
Date dto It is a variable that retrieves the date of a query. But the method only gets dto variable as input parameter.
Tue Dec 14 00:00:00 CET 1700 -> Date
1700-12-13T23:00:00Z -> Instant
1700-12-13 -> LocalDate
Sat Jan 01 00:00:00 CET 1994 -> Date
1993-12-31T23:00:00Z -> Instant
1994-01-01 -> LocalDate
I use:
jackson.time-zone: ${TZ:Europe/Madrid}
What is the problem with this year?
This is because whoever produced that Date and ZoneId.systemDefault() have different opinions on what Madrid's offset is from UTC, back in 1700.
Whoever produced that Date mistakenly thinks that Madrid is UTC+1 back in 1700-12-14, so it produced a Date that represents the midnight of 1700-12-14 in the zone UTC+1 ("CET"):
Tue Dec 14 00:00:00 CET 1700
This has a corresponding Instant, because we can pinpoint a certain point in time using this information. This is what toInstant gives you. Instant.toString always displays in UTC (more specifically ISO 8601), so you see:
1700-12-13T23:00:00Z
1700-12-14T00:00:00+01:00 is indeed the same point in time as 1700-12-13T23:00:00Z.
When you do LocalDate.ofInstant, you use ZoneId.systemDefault(). ZoneId.systemDefault() thinks that Madrid had the offset of UTC-0:14:44. This is because Madrid had been observing Local Mean Time, and did not standardise timezones until the 1900s.
Therefore, LocalDate.ofInstant subtracts 14 minutes and 44 seconds from 1700-12-13T23:00:00 to compute what the date would be at offset -0:14:44, and lo and behold, it is still 1700-12-13.
I would recommend that you not to use Date if possible. If what you are doing is related to time at all, you should work with LocalDates directly.
Current time is Sat Apr 04 15:02:00 AEST 2020.
In the following snippet, I create a Date object and add 86400000L milliseconds (1 day) to it:
Date date = new Date();
date.setTime(date.getTime() + 86400000L);
System.out.println(date);
The output is Sun Apr 05 14:02:00 AEST 2020. I don't understand why the result adds only 23 hours to my current time, instead of 24 hours.
Any help would be appreciated.
The code works just fine. The AEST on your output means that the date regards Australian Eastern Standard Time. Googling for AEST dst shows that on Sunday, April 5, 3:00 am 2020 the clock will "go back" 1 hour. Thus adding 24 hours just before the DST change, will only move the time 23 hours forward.
If you run that code tomorrow, you'll not have this "problem".
Do use java.time, the modern Java date and time API, for your date and time work.
ZonedDateTime currentTime = ZonedDateTime.now(ZoneId.of("Australia/Sydney"));
System.out.println(currentTime);
ZonedDateTime tomorrowSameTime = currentTime.plusDays(1);
System.out.println(tomorrowSameTime);
Output when running just now:
2020-04-04T16:00:30.579484+11:00[Australia/Sydney]
2020-04-05T16:00:30.579484+10:00[Australia/Sydney]
Please observe: we got the same time of day tomorrow, 16:00. Because summer time (daylight saving time) ends, the UTC offset for tomorrow is different, +10:00 instead of +11:00. And importantly, while I find + 86400000L pretty close to unreadable for adding a day, .plusDays(1) conveys the intention very clearly.
Please insert a different Eastern Australian time zone if required.
What went wrong in your code? cherouvim has explained this very nicely in the other answer, no need for me to repeat. Only allow me to add that the Date class is not only poorly designed — giving rise to your confusion — it is also long outdated. I recommend you don’t use it. And as cherouvim notes in a comment, programming with dates is hard. Don’t trust that you can yourself convert 1 day to 86 400 000 milliseconds. Leave all date and time calculations to proven library methods.
Link: Oracle tutorial: Date Time explaining how to use java.time.
This question already has answers here:
Convert timestamp in milliseconds to string formatted time in Java
(10 answers)
Closed 4 years ago.
I have two timestamps as milliseconds, which were created in different time zones and cannot convert to the same time zone.
Date_1 = 1525694035615 - Mon May 07 2018 11:53:55
Date_2 = 1525686835730 - Mon May 07 2018 09:53:55
Does any body know how to identify time zone and get values in UTC+0?
When I transform these timestamps always get values that these timestamps have timezone +0.
If your milliseconds timestamps created in Java they are always milliseconds from
midnight, January 1, 1970 UTC
And have nothing about time zone.
what you see:
Mon May 07 2018 11:53:55
Mon May 07 2018 09:53:55
Just string representations of those moments in time in the current JVM/Computer Time Zone
If you need to have a String representation in UTC time zone there are number of classes to do that. In all JDK there are Calendar implementations (e.g. GregorianCalendar). From Java 8 there is new API inspired by Joda time.
It should work better with timezones handling.
If you are getting timezone+0 that is because that is the default timezone given any fixed time in any amount (milliseconds in your example). You must tell it what timezone you are in if you want a different one that UTC+0.
Does any body know how to … get values in UTC+0?
I’m taking the easy part first:
OffsetDateTime dateTime = Instant.ofEpochMilli(1_525_694_035_615L)
.atOffset(ZoneOffset.UTC);
System.out.println(dateTime);
This prints:
2018-05-07T11:53:55.615Z
This agrees with what you said in the question. The Z in the end means UTC.
…how to identify time zone…
We can’t exactly. If you know the time to which the timestamp corresponds in the timezone where it was produced, we can calculate the offset. Since this is rather boring when the answer is UTC, I am taking a different example:
long timestamp = 1_525_708_128_067L;
LocalDateTime dateTimeInUnknownTimezone = LocalDateTime.of(2018, Month.MAY, 7, 18, 48, 48);
LocalDateTime utcDateTime = Instant.ofEpochMilli(timestamp)
.atOffset(ZoneOffset.UTC)
.toLocalDateTime()
.truncatedTo(ChronoUnit.SECONDS);
int offsetInSeconds = (int) ChronoUnit.SECONDS.between(utcDateTime, dateTimeInUnknownTimezone);
ZoneOffset offset = ZoneOffset.ofTotalSeconds(offsetInSeconds);
System.out.println(offset);
+03:00
Since the date-time has precision of seconds only, I am also truncating the UTC time to seconds to get the offset precise. From the offset of +03:00 we cannot unambiguously determine a time zone, though, since many time zones use this offset.
I have a Date Object in following:
Date date=new Date("Mon, 05 May 2014 12:31:12 +0000")
I want to get Timestamp of date Object then :
date.getTime()
1399293072000
but this value not correct , correct value in following :
1399276872000
//*** for get timestamp use of http://www.epochconverter.com ***\\
why ?
First things first, from the JavaDoc for Date
Date(String s)
Deprecated.
As of JDK version 1.1, replaced by DateFormat.parse(String s).
So the constructor you are using has been deprecated since 1997!
Next, onto the JavaDoc for Date.parse which the construtor uses:
It accepts many syntaxes; in particular, it recognizes the IETF
standard date syntax: "Sat, 12 Aug 1995 13:30:00 GMT". It also
understands the continental U.S. time-zone abbreviations, but for
general use, a time-zone offset should be used: "Sat, 12 Aug 1995
13:30:00 GMT+0430" (4 hours, 30 minutes west of the Greenwich
meridian). If no time zone is specified, the local time zone is
assumed. GMT and UTC are considered equivalent.
Empathsis mine.
So, what timezone are you in? Presumably not UTC.
You should specify local time zone while constructing Date object
Example:
Date date=new Date("Mon, 05 May 2014 12:31:12 GMT+0530");
Using Mon, 05 May 2014 12:31:12 +0000 at http://www.epochconverter.com I get 1399285872000 which is different from your timestamp.
Or you're passing different values, or there's a bug somewhere or
The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of seconds that have elapsed since January 1, 1970 (midnight UTC/GMT), not counting leap seconds (in ISO 8601: 1970-01-01T00:00:00Z).
where the javadoc for java.util.Date.getTime() doesn't mention leap seconds.
I have the following problem using Joda-Time for parsing and producing date and time around Daylight Saving Time (DST) hours. Here is an example (please, note that March 30th 2008 is Daylight Saving change in Italy):
DateTimeFormatter dtf = DateTimeFormat.forPattern("dd/MM/yyyy HH:mm:ss");
DateTime x = dtf.parseDateTime("30/03/2008 03:00:00");
int h = x.getHourOfDay();
System.out.println(h);
System.out.println(x.toString("dd/MM/yyyy HH:mm:ss"));
DateTime y = x.toDateMidnight().toDateTime().plusHours(h);
System.out.println(y.getHourOfDay());
System.out.println(y.toString("dd/MM/yyyy HH:mm:ss"));
I get the following output:
3
30/03/2008 03:00:00
4
30/03/2008 04:00:00
When i parse hour I get hour is 3. In my data structure I save the day storing midnight time, and then I have some value for each hour of the day (0-23). Then, when I write out the date, I re-compute the full date time making midnight plus hour. When I sum 3 hours to my midnight I get 04:00:00! And if I parse it again, I get hour 4!
Where is my mistake? Is there some way to get hour 2 when I parse or get hour three when I print out?
I have also tried to build output by hand:
String.format("%s %02d:00:00", date.toString("dd/MM/yyyy"), h);
but in this case for hour 2, I produce 30/03/2008 02:00:00 which is not a valid date (since hour 2 does not exist) and cannot be parsed any more.
Thank you in advance for your help.
Filippo
When I sum 3 hours to my midnight I get 04:00:00! And if I parse it again, I get hour 4! Where is my mistake?
You mentioned already that this date is exactly when the time changes. So there is no mistake. March 30, 2010 00:00 CEST (the timezone in Italy) is precisely speaking March 29, 2010 23:00 UTC. When you add 3 hours, you will get March 30, 2010 02:00 UTC. But this is post the moment, that we switch times (which happens on 01:00 UTC), so when you convert time to local timezone you get March 30, 04:00. That's correct behavior.
Is there some way to get hour 2 when I parse or get hour three when I print out?
No, because March 30, 2010 02:00 CEST does not exist. Precisely at March 30, 2010 01:00 UTC we switch time from +1 hour to +2 hours versus UTC, so March 30, 2010 00:59 UTC is March 30, 2010: 01:59 CEST, but March 30, 2010 01:00 UTC become March 30, 2010 03:00 CEST. No 02:xx hour exist on that particular date.
BTW. In a week you can expect another "fun". Can you tell what date in UTC this refers to:
October 31, 2010 02:15 CEST ?
Well, the funny part is, we do not know. It could be either 0ctober 31, 2010 00:15 UTC (before actual time switch) or October 31, 2010 01:15 UTC (after the switch).
That's exactly why you should always store date and times in relation to UTC and convert them to local time zone before displaying, otherwise you risk an ambiguity.
HTH.
The data structure you are saving your data is not very optimal for the days with daylight saving time. Your day in this particular day should only have 23 hours.
If you do:
DateTimeFormatter dtf = DateTimeFormat.forPattern("dd/MM/yyyy HH:mm:ss").withLocale(Locale.US);
DateTime x = dtf.parseDateTime("30/03/2008 00:00:00");
DateTimeFormatter parser = DateTimeFormat.fullDateTime();
System.out.println("Start:"+parser.print(x));
DateTime y = x.plusHours(4);
System.out.println("After add of 4:"+parser.print(y));
You get the expected result, that the time is 05:00.
I recommend that you change the way you store your day and use a date. If not, you must handle daylight saving time when storing the hour of day.
You might do something like this:
In the case where we move the time forward one hour, as this case, you must store 4 and not 5 as the time for 5. And when you calculate the time, you should use the plusHours() method to get the actual time. I think you might get away with something like:
public class DateTest {
private static final int HOUR_TO_TEST = 2;
public static void main(String[] args) {
DateTimeFormatter dtf = DateTimeFormat.forPattern("dd/MM/yyyy HH:mm:ss");
DateTime startOfDay = dtf.parseDateTime("30/03/2008 00:00:00");
/* Obtained from new DateTime() in code in practice */
DateTime actualTimeWhenStoring = startOfDay.plusHours(HOUR_TO_TEST);
int hourOfDay = actualTimeWhenStoring.getHourOfDay();
int hourOffset = startOfDay.plusHours(hourOfDay).getHourOfDay();
System.out.println("Hour of day:" + hourOfDay);
System.out.println("Offset hour:" + hourOffset);
int timeToSave = hourOfDay;
if (hourOffset != hourOfDay) {
timeToSave = (hourOfDay + (hourOfDay - hourOffset));
}
System.out.println("Time to save:" + timeToSave);
/* When obtaining from db: */
DateTime recalculatedTime = startOfDay.plusHours(timeToSave);
System.out.println("Hour of time 'read' from db:" + recalculatedTime.getHourOfDay());
}
}
...or basicly something like that. I'd write a test for it if you choose for going down this route. You can change the HOUR_TO_TEST to see that it moves passed the daylight saving time.
Building on the correct answers by Paweł Dyda & Knubo…
ISO 8601 For String Format
You should never store (serialize) a date-time as a string in the format you mentioned: "30/03/2008 03:00:00". Problems:
Omitted time zone.
Day, Month, Year order is ambiguous.
Should have been translated to UTC time.
If you must serialize a date-time value to text, use a reliable format. The obvious choice is the ISO 8601 standard format. Even better is converting the local time to UTC (Zulu) time zone and then out to ISO 8601 format. Like this: 2013-11-01T04:48:53.044Z
No Midnight
The midnight methods in Joda-Time are deprecated in favor of the Joda-Time method withTimeAtStartOfDay() (see doc). Some days do not have a midnight.
Example Code in Joda-Time 2.3
Some comments about this source code:
// © 2013 Basil Bourque. This source code may be used freely forevery by anyone taking full responsibility for doing so.
// Joda-Time - The popular alternative to Sun/Oracle's notoriously bad date, time, and calendar classes bundled with Java 7 and earlier.
// http://www.joda.org/joda-time/
// Joda-Time will become outmoded by the JSR 310 Date and Time API introduced in Java 8.
// JSR 310 was inspired by Joda-Time but is not directly based on it.
// http://jcp.org/en/jsr/detail?id=310
// By default, Joda-Time produces strings in the standard ISO 8601 format.
// https://en.wikipedia.org/wiki/ISO_8601
Example showing 23 hours in the day of DST (Daylight Saving Time) in Rome Italy, while the day after has 24 hours. Note that the time zone (for Rome) is specified.
// Time Zone list: http://joda-time.sourceforge.net/timezones.html
org.joda.time.DateTimeZone romeTimeZone = org.joda.time.DateTimeZone.forID("Europe/Rome");
org.joda.time.DateTime dayOfDstChange = new org.joda.time.DateTime( 2008, 3, 30, 0, 0, romeTimeZone ) ; // Day when DST
org.joda.time.DateTime dayAfter = dayOfDstChange.plusDays(1);
// How many hours in this day? Should be 23 rather than 24 on day of Daylight Saving Time "springing ahead" to lose one hour.
org.joda.time.Hours hoursObjectForDay = org.joda.time.Hours.hoursBetween(dayOfDstChange.withTimeAtStartOfDay(), dayAfter.withTimeAtStartOfDay());
System.out.println( "Expect 23 hours, got: " + hoursObjectForDay.getHours() ); // Extract an int from object.
// What time is 3 hours after midnight on day of DST change?
org.joda.time.DateTime threeHoursAfterMidnightOnDayOfDst = dayOfDstChange.withTimeAtStartOfDay().plusHours(3);
System.out.println( "Expect 4 AM (04:00) for threeHoursAfterMidnightOnDayOfDst: " + threeHoursAfterMidnightOnDayOfDst );
// What time is 3 hours after midnight on day _after_ DST change?
org.joda.time.DateTime threeHoursAfterMidnightOnDayAfterDst = dayAfter.withTimeAtStartOfDay().plusHours(3);
System.out.println( "Expect 3 AM (03:00) for threeHoursAfterMidnightOnDayAfterDst: " + threeHoursAfterMidnightOnDayAfterDst );
Example of storing a date-time by first translating to UTC. Then upon restoring the date-time object, adjust to the desired time zone.
// Serialize DateTime object to text.
org.joda.time.DateTimeZone romeTimeZone = org.joda.time.DateTimeZone.forID("Europe/Rome");
org.joda.time.DateTime dayOfDstChangeAtThreeHoursAfterMidnight = new org.joda.time.DateTime( 2008, 3, 30, 0, 0, romeTimeZone ).withTimeAtStartOfDay().plusHours(3);
System.out.println("dayOfDstChangeAtThreeHoursAfterMidnight: " + dayOfDstChangeAtThreeHoursAfterMidnight);
// Usually best to first change to UTC (Zulu) time when serializing.
String dateTimeSerialized = dayOfDstChangeAtThreeHoursAfterMidnight.toDateTime( org.joda.time.DateTimeZone.UTC ).toString();
System.out.println( "dateTimeBeingSerialized: " + dateTimeSerialized );
// Restore
org.joda.time.DateTime restoredDateTime = org.joda.time.DateTime.parse( dateTimeSerialized );
System.out.println( "restoredDateTime: " + restoredDateTime );
// Adjust to Rome Italy time zone.
org.joda.time.DateTime restoredDateTimeAdjustedToRomeItaly = restoredDateTime.toDateTime(romeTimeZone);
System.out.println( "restoredDateTimeAdjustedToRomeItaly: " + restoredDateTimeAdjustedToRomeItaly );
When run:
dayOfDstChangeAtThreeHoursAfterMidnight: 2008-03-30T04:00:00.000+02:00
dateTimeBeingSerialized: 2008-03-30T02:00:00.000Z
restoredDateTime: 2008-03-30T02:00:00.000Z
restoredDateTimeAdjustedToRomeItaly: 2008-03-30T04:00:00.000+02:00