How can I parse a date string with Joda-Time datetime which uses the correct timezone WITH daylight saving time?
As an example in scala I try to parse the string "2014-04-07 01:00:00.000" (without timezone information). This date is coming from MySQL and is supposed to be in tz Europe/Berlin +01:00. What I like to have is a joda date time according to 2014-04-07 00:00:00+01:00 which is the timezone Europe/Berlin currently not on DST (GMT +1).
val fmt = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss.SSS").withZone(DateTimeZone.forID("Europe/Berlin"))
val dt = fmt.parseDateTime("2014-04-07 01:00:00.000")
Unfortunately Joda-Time parses the date to 2014-04-07T01:00:00.000+02:00 which is currently the wrong offset (02:00 instead if 01:00)
Any ideas how to make Joda-Time parse the date with the correct DST offset?
Joda-Time is correct. Your assumption of +01:00 for Berlin is incorrect. You did not account for Daylight Saving Time.
According to this page at TimeZoneConverter.com for the time zone "Europe/Berlin", Daylight Saving Time (DST) began on Sun 30-Mar-2014 at 02:00:00 A.M. when local clocks were set forward 1 hour. According to the Wikipedia list of time zone names, that means Berlin shifted from being one hour ahead of UTC (+01:00) to two hours ahead (+02:00).
Related
I am confused with Timezone conversions in Java. I have a few cases which I will list out below.
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
// sdf.setTimeZone(TimeZone.getTimeZone("Asia/kolkata"));
Date date1 = sdf.parse("2021-01-31");
System.out.println(date1); //. O/P - Sun Jan 31 00:00:00 IST 2021
Now lets uncomment the Timezone part and see the time difference
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
sdf.setTimeZone(TimeZone.getTimeZone("Asia/kolkata"));
Date date1 = sdf.parse("2021-01-31");
System.out.println(date1); // O/P - Sun Jan 31 05:30:00 IST 2021
Now lets set the TimeZone to IST and see the time difference
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
sdf.setTimeZone(TimeZone.getTimeZone("IST"));
Date date1 = sdf.parse("2021-01-31");
System.out.println(date1); // O/P - Sun Jan 31 00:00:00 IST 2021
Now lets set the TimeZone to UTC and see the time difference
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
Date date1 = sdf.parse("2021-01-31");
System.out.println(date1); // O/P - Sun Jan 31 05:30:00 IST 2021
Can anybody please explain me why this shift in time is happening (+- 5:30) when I change the Timezone?
For IST and Asia/Kolkata, time should have remain same because they are same Timezone, but why the shift?
Why When using the UTC Timezone, time gets increased by 5:30 hours? What I understand is IST is 5:30 hrs ahead of UTC, so cnverting to UTC should have decreased the time by 5:30 hrs
Why even after converting to UTC, my time displays IST 2021?
I still have confusion here.
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
Date date1 = sdf.parse("2021-01-31");
System.out.println(date1.getTime()); // 1612051200000
SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd");
sdf1.setTimeZone(TimeZone.getTimeZone("Asia/Kolkata"));
Date date2 = sdf1.parse("2021-01-31");
System.out.println(date2.getTime()); // 1612031400000
Why instant of time in UTC is greater than instant of time in Asia/Kolkata ?
Here are some things for you to note:
When a Date is printed, it will be formatted in your computer's local timezone (that's what Date.toString does). Presumably, your computer is in the Asia/Kolkata timezone, so the output is always displayed as a date & time in that timezone.
A Date represents a point in time (i.e. an instant). It is not a tuple of year, month, day, hour, minute, seconds and timezone
Since there are no time in your input string, the time 00:00:00 is used for the time when parsing.
Just a date and a time is not enough to produce a point in time. You also need a timezone to specify a point in time. Since there is no timezone in your input string, the local timezone of your computer is used, or if you have set it, sdf.getTimeZone().
Although a timezone is used in parsing the date, the timezone is not part of the Date object.
Can anybody please explain me why this shift in time is happening (+- 5:30) when I change the Timezone?
When you use the "IST" timezone (first and third code snippet), sdf gets the following pieces of information:
Date: 2021-01-31
Time: 00:00:00
TimeZone: Asia/Kolkata
With these pieces of information, it can produce a point in time, represented by a number of milliseconds since the Java Epoch - 1970-01-01 00:00:00 UTC. This is the Date object. Then you print the Date object, which gets formatted to your local timezone. Your local timezone just so happens to be the same as the one that sdf is provided with, so you see Sun Jan 31 00:00:00 IST 2021.
When you use UTC (second and fourth code snippets), these information are provided to sdf:
Date: 2021-01-31
Time: 00:00:00
TimeZone: UTC
That represents a different point in time than 2021-01-31T00:00:00 in Kolkata. How different? 2021-01-31T00:00:00 in UTC is exactly 5 and a half hours later than 2021-01-31T00:00:00 in Kolkata. Recall that to convert a UTC time to Kolkata, you add 5 and a half hours.
For IST and Asia/Kolkata, time should have remain same because they are same Timezone, but why the shift?
Because you have misspelled Asia/Kolkata. The first "K" in "Kolkata" should be capitalised. Unknown zone IDs are treated as UTC by the TimeZone class. This is why you should move to the new java.time classes. ZoneId throws an exception if you supply it with an unknown zone ID.
Why When using the UTC Timezone, time gets increased by 5:30 hours? What I understand is IST is 5:30 hrs ahead of UTC, so converting to UTC should have decreased the time by 5:30 hrs
You are thinking about formatting dates, not parsing, because remember that the timezone is not part of Date, but part of SimpleDateFormat. Your code does not format Date, only parses them. Without formatting, Dates are always printed in your local timezone.
To see your desired behaviour using SimpleDateFormat, you'd first parse the date string once, and then format it using SimpleDateFormats with different timezones.
Really though, you should change to java.time. Using that API, your zone changing code could be written like so:
ZonedDateTime zdt = LocalDate.parse("2021-01-31")
.atStartOfDay()
.atZone(ZoneId.of("Asia/Kolkata"));
System.out.println(zdt);
ZonedDateTime utcDateTime = zdt.withZoneSameInstant(ZoneId.of("UTC"));
System.out.println(utcDateTime);
// output:
// 2021-01-31T00:00+05:30[Asia/Kolkata]
// 2021-01-30T18:30Z[UTC]
java time
I recommend you use java.time, the modern Java date and time API, for your date work
LocalDate date = LocalDate.parse("2021-01-31");
System.out.println(date);
Output is:
2021-01-31
A LocalDate is a date without time of day and without time zone or UTC offset, so using it frees you completely from all time zone trouble. Furthermore we don’t need any explicit formatter. Your string is in ISO 8601 format, and LocalDate parses the most common ISO 8601 variant as its default. As you can see, it also prints the same ISO 8601 format back when we print it, implicitly calling its toString method.
What went wrong in your code?
The SimpleDateFormat, TimeZone and Date classes that you are using are poorly designed and long outdated. No wonder that their behaviour confuses you.
I am assuming that Asia/Kolkata (or Asia/Colombo or Asia/Calcutta) is the default time zone of your JVM. In your first example the SimpleDateFormat is using your default time zone and is parsing the string into the first moment of the day in that time zone.
In your second example, as Elavya has spotted so well, you have got a lower case k in Asia/kolkata which causes TimeZone not to recognize the intended time zone. And this is where TimeZone excels in bad design: it just tacitly gives you GMT instead. Next the Date class is poorly designed too and still prints the time in the default time zone of the JVM, giving the illusion that the Date object contains a time zone. This has confused very many. The start of the day in GMT is the same point in time as 05:30:00 IST, so this is what you get.
In your third and fourth example, even though the three letter time zone abbreviations are deprecated, IST (contrary to what Eklavya said) is interpreted as Asia/Kolkata and UTC as Etc/UTC. Even though as Eklavya also said, IST is ambiguous.
So in short:
The change happens because the start of the day is a different point in time in different time zones.
Because of your typo in Asia/kolkata. Time zone IDs are case sensitive.
You are not converting to UTC. You are parsing in UTC thereby converting from UTC, and Date.toString() further converts to Asia/Kolkata (IST) as the output also says.
Because the Date object hasn’t got a time zone and because Date.toString() grabs the default time zone of your JVM and uses it for rendering the string to be returned.
Links
Oracle tutorial: Date Time explaining how to use java.time.
Wikipedia article: ISO 8601
All about java.util.Date
Java doc for getTimeZone
ID - the ID for a TimeZone, either an abbreviation such as "PST", a
full name such as "America/Los_Angeles", or a custom ID such as
"GMT-8:00". Note that the support of abbreviations is for JDK 1.1.x
compatibility only and full names should be used.
TimeZone abbreviation is not supported. So you can't use IST
And in TimeZone Doc for Three-letter time zone IDs
For compatibility with JDK 1.1.x, some other three-letter time zone
IDs (such as "PST", "CTT", "AST") are also supported. However, their
use is deprecated because the same abbreviation is often used for
multiple time zones (for example, "CST" could be U.S. "Central
Standard Time" and "China Standard Time"), and the Java platform can
then only recognize one of them.
Problem is IST abbreviation is used for multiple time zones like Irish Standard Time, Isreal Standrad Time, Indian Standard Time. And you mistyped Asia/Kolkata as Asia/kolkata.
So, the GMT zone will return if the given ID cannot be understood from TimeZone.getTimeZone()
As an addition to the accepted answer, for the last part of your question;
Why instant of time in UTC is greater than instant of time in Asia/Kolkata in below code?
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
Date date1 = sdf.parse("2021-01-31");
System.out.println(date1.getTime()); // 1612051200000
SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd");
sdf1.setTimeZone(TimeZone.getTimeZone("Asia/Kolkata"));
Date date2 = sdf1.parse("2021-01-31");
System.out.println(date2.getTime()); // 1612031400000
First, you have a point T in time regardless of timezone. In our example T=2021-01-31 00:00:00.
When we set timezone as UTC and print the time using java.util.Date.getTime() method, it will print milliseconds since the Unix epoch, which occurred at midnight January 1st 1970, UTC. So it will print 1612051200000. As you see the epoch and our date has the same timezone which is UTC. So the time is printed directly, no adjustment necessary for timezone.
Now, when we set timezone as Asia/Kolkata, during SimpleDateFormat.parse, timezone information will be added to date. That means +5:30h(19800000ms) will be added to time T. Therefore our time T is increased by 19800000ms. However T must be pointing to the same point in time. How do we fix that? It is fixed on SimpleDateFormat.parse method by subtracting 19800000ms from the time 1612051200000ms so that getTime() method will now show 1612031400000ms so that our actual time T will still show the same point in time(which is 1612051200000ms) because in this date object we have an extra 19800000ms which comes from timezone.
Given the following code
public static void main(String[] args) {
org.joda.time.format.DateTimeFormatter _timestampFomatNYCJoda = org.joda.time.format.DateTimeFormat.forPattern("yyyyMMdd HHmmss.SSS").withZone(DateTimeZone.forID("America/New_York"));
DateTimeFormatter _timestampFomatNYC = DateTimeFormatter.ofPattern("yyyyMMdd HHmmss.SSS").withZone(ZoneId.of("America/New_York"));
LocalDateTime localDateTime = LocalDateTime.now();
org.joda.time.LocalDateTime jodaLocalDateTime = new org.joda.time.LocalDateTime();
System.out.println("System Time " + new Date());
System.out.println("Java Version " + localDateTime.format(_timestampFomatNYC));
System.out.println("Joda Version " + _timestampFomatNYCJoda.print(jodaLocalDateTime.toDateTime(DateTimeZone.UTC)));
}
Why does the Java Version and Joda Version dont match ? I am running this on IST clock.
Below is the output
System Time Fri Mar 27 17:01:33 IST 2020
Java Version 20200327 170133.933
Joda Version 20200327 130133.938
I can reproduce your results. I can also explain them. Joda-Time and java.time have been designed to behave differently in this case. Let’s look at them in turn.
Joda-Time
In Joda-Time DateTimeFormatter.withZone() gives you a formatter with an override zone, that is, a zone that will always be used for formatting dates and times. In other words, any date and time will be converted to this zone for printing. The documentation says:
When printing, this zone will be used in preference to the zone from
the datetime that would otherwise be used.
When you do new org.joda.time.LocalDateTime(), you are getting a LocalDateTime representing the current date and time in your default time zone. The Local in some class names means without time zone or offset from UTC. I figure that you must have got a value equal to 2020-03-27T17:01:33.938.
Apparently what happens when you format a LocalDateTime with a formatter with an override zone, is that the formatter assumes that your LocalDateTime is in UTC (which yours isn’t) and converts it from there, in your case to America/New_York time zone. Since summer time (DST) is in effect in New York, the offset is -04:00, so 17:01 becomes 13:01.
This is the wrong result. When the time is 17:01 in your time zone, it is not 17:01 UTC, so the conversion is based on a false premise. It is also not 13:01 in New York, so the converted result is telling a lie.
java.time
With java.time setting an override zone on a formatter works similarly for formatting, but with a difference that matters here: the override zone is only used when printing a date-time object that identifies an instant (a point in time). From the docs:
When formatting, if the temporal object contains an instant, then it
will be converted to a zoned date-time using the override zone.
Whether the temporal is an instant is determined by querying the
INSTANT_SECONDS field. If the input has a chronology then it will be
retained unless overridden. If the input does not have a chronology,
such as Instant, then the ISO chronology will be used.
… In all other cases, the override zone is added to the temporal,
replacing any previous zone, but without changing the date/time.
Again LocalDateTime.now() gives you the current date and time of day (a few milliseconds earlier than the query through Joda-Time), 2020-03-27T17:01:33.933. Local still means without offset or time zone.
Because your LocalDateTIme hasn’t got offset or time zone, it cannot identify an unambigous point in time, an instant. Therefore when formatting it neither the date nor the time of day is changed. And since your format pattern contains no time zone or offset, none is printed. So you just get the date and time in your time zone (not in New York), 20200327 170133.933.
To get the date and time in New York time zone
DateTimeFormatter timestampFormat
= DateTimeFormatter.ofPattern("yyyyMMdd HHmmss.SSS");
ZonedDateTime timeInNy = ZonedDateTime.now(ZoneId.of("America/New_York"));
System.out.println(timeInNy.format(timestampFormat));
When I ran this code just now, the output was:
20200327 122359.683
Documentation links
Joda-Time DateTimeFormatter.withZone()
java.time DateTimeFormatter.withZone()
I got a timestamp in the following format:
2017-09-27T16:19:24+0000
How do I know which timezone that is? What's the DateTimeFormatter if I'm using Java 8?
ZonedDateTime
As you stated using Java 8, you can leverage ZonedDateTime by using
ZonedDateTime zdt = ZonedDateTime.parse("2017-09-27T16:19:24+0000", DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ")
Parsing rules are explained in DateTimeFormatter documentation. It is not exactly the ISO 8601 ISO_OFFSET_DATE_TIME as the offset should have been written +00:00 instead of +0000
Time zone vs time offset
Then, you can get the offset information with zdt.getZone(). However, you'll only get the Offset ID:
Z - for UTC (ISO-8601)
+hh:mm or -hh:mm - if the seconds are zero (ISO-8601)
+hh:mm:ss or -hh:mm:ss - if the seconds are non-zero (not ISO-8601)
As one comment said, be careful that time offset is not time zone: A given time zone (e.g. time in France) does not have the same offset the whole year (summer time vs winter time).
The timestamp given has a timezone offset (+0000), which represents +00 hours and +00 minutes from GMT+00.
This timezone pattern can be represented by the character Z for both SimpleDateFormat and DateTimeFormatter's ofPattern method.
The timezone you are handling can be represented by a pattern of yyyy-MM-dd'T'HH:mm:ssZ:
yyyy represents the current year
MM represents the month of the current year
dd represents the current day of the current month
'T' represents a quoted T character
HH represents the current hour of the current day
mm represents the current minute of the current hour
ss represents the current second of the current minute
Z represents the timezone offset from GMT
It looks like ISO 8601 format: dateTime±hhmm. Here hhmm is offset from UTC
The representation 2017-09-27T16:19:24+0000 gives +0000 so baseline UTC.
Timestamps themselves and LocalDateTime wrap a long count of seconds and do not contain a separate time zone info.
Java provides a class that maintains an addition time zone.
ZonedDateTime dt = LocalDateTime.now().atZone(ZoneId.of("Europe/Sofia"));
One needs to be sure that the time was stored as UTC, +0000: a recommendation only.
I wanted to raise issue directly on joda github, but there was a hint, that i should at first ask it on stackoverflow.
If you try to use joda DateTime object in Greenwich Mean Time Zone (UK), you are getting bad DateTime value
Step to reproduce
Write method like this
DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z");
DateTime dt = formatter.parseDateTime("2014-02-16T07:00:00.000Z");
System.out.println(dt);
Expected value
2014-05-16T07:00:00.000+00:00
Actual value
2014-05-16T07:00:00.000+Z
In UK, time is changed every 29 March. This is the last day in year, when time is +00:00. From 30 march till next time change there is +01:00.
If think that joda has problem with processing +00:00 gmt.
UK Time is not GMT... If you want UK time, use a full timezone name: Europe/London.
UK could be in GMT or BST based on the time of the year. The timezone can be mentioned like below:-
DateTime dt = formatter.parseDateTime("2014-02-16T07:00:00.000Z").withZone(DateTimeZone.forID("Europe/London"));
I am trying to convert Date with GMT +5:30 to EST with java 8 ZonedDateTime.
String inputDate = "2015/04/30 13:00";
DateTimeFormatter sourceFormatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm", Locale.US);
LocalDateTime local = LocalDateTime.parse(inputDate, sourceFormatter);
// local : 2015-04-30T13:00
//Combining this local date-time with a time-zone to create a ZonedDateTime.
ZonedDateTime zoned = local.atZone(TimeZone.getTimeZone("GMT+5:30").toZoneId());
// zoned : 2015-04-30T13:00+05:30[GMT+05:30]
ZonedDateTime zonedUS = zoned.withZoneSameInstant(TimeZone.getTimeZone("GMT-5:00").toZoneId());
// zonedUS : 2015-04-30T02:30-05:00[GMT-05:00]
I am expecting 3:30 AM EST but what I am getting is 2:30 AM EST as 1 PM IST= 3:30AM EST. What am I missing?
It seems that whatever service you found was being over-helpful in interpreting what you meant and assumed North American Eastern Daylight Time (EDT) when you specified EST (Eastern Standard Time). Most, not all of the places using EST as standard time are using daylight saving time and hence were on EDT or offset UTC-04:00 on the date you use, April 30, 2015.
If it makes sense in your situation, you should always prefer to give time zone in the region/city format, as Asia/Kolkata and America/New_York. If you intended Eastern Time as in New York or Montréal, one may say that your “time zone” of GMT-5:00 was wrong and the cause of your unexpected result.
So your code becomes for example:
String inputDate = "2015/04/30 13:00";
DateTimeFormatter sourceFormatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm", Locale.US);
LocalDateTime local = LocalDateTime.parse(inputDate, sourceFormatter);
// local : 2015-04-30T13:00
//Combining this local date-time with a time-zone to create a ZonedDateTime.
ZonedDateTime zoned = local.atZone(ZoneId.of("Asia/Kolkata"));
// zoned : 2015-04-30T13:00+05:30[Asia/Kolkata]
ZonedDateTime zonedUS = zoned.withZoneSameInstant(ZoneId.of("America/Montreal"));
// zonedUS : 2015-04-30T03:30-04:00[America/Montreal]
I have made one other change: When using the modern classes from java.time, there is no point in also using the outdated TimeZone class, so I have taken that out. The code is slightly simpler, and more importantly, ZoneId.of(String) includes validation of your time zone string so you will discover any spelling error in the time zone name (like when I just happened to type a ( instead of the / in Asia/Kolkata — such happens all the time).
Most of the above has already been said in comments by Jon Skeet and others. I thought it deserved to go into an answer so it’s plain to see that the question has been answered.
Though the question is old, felt like I could add more to the accepted answer.
A ZonedDateTime is different from an OffsetDateTime.
I would prefer to use ZonedDateTime when I'm getting a time for a specific location like "Asia/Kolkata", "Asia/Shanghai", "US/Pacific" (this time zone will change depending on the day of the year because of Daylight savings).
To illustrate with an example,
var pacific = ZonedDateTime.of(2020,11,01,1,59,0,0,ZoneId.of("US/Pacific"))
var afterAnHour = pacific.plusHours(1)
This will give me a time of
2020-November-01 01:59:00.000 AM -07:00[US/Pacific]
And if i add an hour to it, it will give me a time of
2020-November-01 01:59:00.000 AM -08:00[US/Pacific]
You can see that the hour component is same even after adding an hour to the time. This is because the daylight savings time has kicked in and the time zone is shifted from -07:00 to -08:00.
Now if i use an OffsetDateTime look what happens.
var offsetNow = OffsetDateTime.of(2020,11,01,1,59,0,0,ZoneOffset.of("-07:00"))
var offsetAfterAnHour = offsetNow.plusHours(1)
The offsetNow will be,
2020-November-01 01:59:00.000 -07:00
And adding an hour to it will be,
2020-November-01 02:59:00.000 -07:00
you can see that the hour component has become 2 after adding an hour.
The key point is a ZonedDateTime uses ZoneRules to calculate important properties like Daylight savings time so that it can adjust the time zone accordingly.
While the OffsetDateTime will not change the zone offset for anything.