Different timezone id for the same short timezone name - java

I have a string "02:00:00 Feb 05, 2023 PST" which I parse:
ZonedDateTime d = ZonedDateTime.parse(nextPaymentDate, DateTimeFormatter.ofPattern("H:m:s MMM d, uuuu z"));
Comparing it a constructed date:
ZonedDateTime expectedDate = ZonedDateTime.of(2023, 2, 5, 2, 0, 0, 0, ZoneId.of(ZoneId.SHORT_IDS.get("PST")));
fails, because:
Expected :2023-02-05T02:00-08:00[America/Los_Angeles]
Actual :2023-02-05T02:00-08:00[America/Tijuana]
Setting exact timezone id (e.g. fixing America/Tijuana) does not work, as the timezone is parsed differently on another machine (as America/Los_Angeles instead of America/Tijuana like on my machine).
How can I assert the dates are equal in this case?

The time zone abbreviations used by Java when parsing depend on the locale, so you cannot compare the result to a fixed value. See this answer.
I would convert the ZonedDateTime objects to OffsetDateTime before comparing them, or even to Instant, as Joachim suggested.

First of all, I suggest you ask the publisher of your data to avoid using abbreviated timezone IDs. In most cases, these abbreviations are 3 characters long but they may be 2 or 4 characters long (e.g. CT and AEST) as commented by Basil Bourque.
Given below is a note from the Java 7 Timezone documentation:
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.
There is already a good suggestion by Joachim Sauer. Alternatively, you can retrieve the ZoneId from the parsed date-time and use it while constructing your ZonedDateTime.
Demo:
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("H:m:s MMM d, uuuu z", Locale.ENGLISH);
String strNextPaymentDate = "02:00:00 Feb 05, 2023 PST";
ZonedDateTime zdtNextPaymentDate = ZonedDateTime.parse(strNextPaymentDate, parser);
ZonedDateTime zdtExpectedDate = ZonedDateTime.of(2023, 2, 5, 2, 0, 0, 0, zdtNextPaymentDate.getZone());
System.out.println(zdtNextPaymentDate);
System.out.println(zdtExpectedDate);
}
}
Output:
2023-02-05T02:00-08:00[America/Los_Angeles]
2023-02-05T02:00-08:00[America/Los_Angeles]
ONLINE DEMO
Learn more about the modern Date-Time API from Trail: Date Time.

Related

Kotlin date parsing is not working properly

So I have been looking up on how to properly parse an incoming datetime, problem is that this string contains the zone as well which apparently can't be parsed for some reason.
To give an example of the incoming date time string:
2021-10-05T10:00:00.0000000
Now I tried to the following:
var dateTimeString = "2021-10-05T10:00:00.0000000"
var formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ")
var date = LocalDate.parse(dateTimeString, formatter)
I tried replacing Z with nothing and with ZZZZ, but that didn't work I assume it doesn't work because of the plus or minus sign not being present. FYI, I receive the date in this format because of the Microsoft Graph API when retrieving the calendar events.
Any idea as to how this date should be formatted properly?
Edit: This comes from Microsoft Graph. Basically they give like a date as an object:
"start": {
"dateTime": "2021-10-05T10:00:00.0000000",
"timeZone": "UTC"
}
This is the page of the documentation that explains this date object: dateTimeTimeZone resource type.
Update:
I was finally able to solve this date issue, what I did was the following:
var inputDateTime = "2021-10-05T10:00:00.0000000"
var inputTimeZone = "UTC"
var zonedDateTime = ZonedDateTime.parse(
inputDateTime,
DateTimeFormatter.ISO_DATE_TIME.withZone(ZoneId.of(inputTimeZone))
).withZoneSameInstant(ZoneId.systemDefault()).toLocalDateTime()
This way, the date would be converted correctly to the right time zone and to the right Date/Time.
As I can see from docs you've provided https://learn.microsoft.com/en-us/graph/api/resources/datetimetimezone?view=graph-rest-1.0
dateTime String A single point of time in a combined date and time representation ({date}T{time}; for example, 2017-08-29T04:00:00.0000000).
timeZone String Represents a time zone, for example, "Pacific Standard Time". See below for more possible values.
dateTime object has no zone encoded. And all 7 zeroes represent fractions of a second. In such case it's regular ISO_DATE_TIME and you don't need to create your own formatter.
The following code should work
var dateTimeString = "2021-10-05T10:00:00.0000000"
var date = LocalDate.parse(dateTimeString, DateTimeFormatter.ISO_DATE_TIME)
Your Date-Time string does not have timezone information
Your Date-Time string, 2021-10-05T10:00:00.0000000 does not have timezone information. The .0000000 represents the fraction of a second and as of now, java.time is capable of handling up to 9 digits of precision (i.e. nanosecond of precision).
Since it does not have timezone information, it represents a local Date-Time and hence should be parsed into LocalDateTime.
You do not need a DateTimeFormatter for your Date-Time string
The modern Date-Time API is based on ISO 8601 and does not require using a DateTimeFormatter object explicitly as long as the Date-Time string conforms to the ISO 8601 standards. Your Date-Time string is already in the ISO 8601 format.
Demo:
import java.time.LocalDateTime;
public class Main {
public static void main(String args[]) {
var dateTimeString = "2021-10-05T10:00:00.0000000";
var ldt = LocalDateTime.parse(dateTimeString);
System.out.println(ldt);
}
}
Output:
2021-10-05T10:00
ONLINE DEMO
How to get ZonedDateTime out of the LocalDateTime instance?
You can use LocalDateTime#atZone to convert attach a ZoneId to a LocalDateTime resulting into a ZonedDateTime.
ZonedDateTime zdt = ldt.atZone(ZoneId.of("Etc/UTC"));
Note: If you need an Instant, you can get it from this instant of ZonedDateTime e.g.
Instant instant = zdt.toInstant();
An Instant represents an instantaneous point on the timeline, normally represented in UTC time. The Z in the output is the timezone designator for a zero-timezone offset. It stands for Zulu and specifies the Etc/UTC timezone (which has the timezone offset of +00:00 hours).
Learn more about the modern Date-Time API* from Trail: Date Time.
* 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. Note that Android 8.0 Oreo already provides support for java.time.
For the case that the last 4 digits in your example String are not representing a time zone:
Parse it without a formatter (no need since it would be perfect ISO standard if the last 4 digits are just additional fractions of second), but also consider the time zone you get with the JSON:
import java.time.LocalDateTime
import java.time.ZoneId
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
fun main() {
// the time String from JSON
var dateTimeString = "2021-10-05T10:00:00.0000000"
// the zone from JSON (may not always work, but does with UTC)
var timeZone = "UTC"
// create the zone from the timezone String
var zoneId = ZoneId.of(timeZone)
// then parse the time String using plain LocalDateTime and add the zone afterwards
var zdt: ZonedDateTime = LocalDateTime.parse(dateTimeString).atZone(zoneId)
// print some results
println("Full ZonedDateTime: ${zdt.format(DateTimeFormatter.ISO_ZONED_DATE_TIME)}")
println("(Local)Date only: ${zdt.toLocalDate()}")
}
Full ZonedDateTime: 2021-10-05T10:00:00Z[UTC]
(Local)Date only: 2021-10-05
Please note that parsing the time zones currently supported by Windows won't work this easy (except from UTC), but the time zones supported by the calendar API are (mostly) sufficient for the creation of a java.time.ZoneId.
As a supplement: Your documentation mentions Pacific Standard Time as a time zone string that may come as part of your MS Graph dateTimeTimeZone. I don’t think the other answers can handle that one, so I should like to show how you do handle it in Java.
private static final DateTimeFormatter ZONE_FORMATTER
= DateTimeFormatter.ofPattern("zzzz", Locale.ENGLISH);
static ZoneId parseZone(String timeZoneString) {
try {
return ZoneId.from(ZONE_FORMATTER.parse(timeZoneString));
} catch (DateTimeParseException dtpe) {
return ZoneId.of(timeZoneString);
}
}
I believe that this handles the strings mentioned in the documentation and also UTC from your question. I am demonstrating just three different ones:
String[] msTimeZoneStrings = { "UTC", "Pacific Standard Time", "Pacific/Honolulu" };
for (String zoneString : msTimeZoneStrings) {
ZoneId zid = parseZone(zoneString);
System.out.format("%-21s parsed to %s%n", zoneString, zid);
}
Output:
UTC parsed to UTC
Pacific Standard Time parsed to America/Los_Angeles
Pacific/Honolulu parsed to Pacific/Honolulu
In the formatter I use zzzz is for time zone name like Pacific Standard Time. My parse method tries this formatter first. It fails for UTC and Pacific/Honolulu. When the DateTimeParseException is caught, the method next tries the ZoneId.of method also used in the other answers. It handles UTC and all the time zone ID sin region/city format mentioned in the documentation.
Combine the ZoneId obtained from my method with the parsed LocalDateTime to get a ZonedDateTime the way that deHaar is already showing in their answer.

IST mapped to wrong ZoneId in java.time library

I am trying to parse a ZonedDateTime from a String of the format (yyyy-MM-dd HH:mm z). The input String (2019-08-29 00:00 IST) generates a UTC timestamp.
Debugging led me to a point where the ZoneId for IST was mapped to
Atlantic/Reykjavik which doesn't make sense. It should be mapped Asia.
timestamp = ZonedDateTime.parse(timeInput, DATE_WITH_TIMEZONE_FORMATTER)
.toInstant().toEpochMilli()
where
DateTimeFormatter DATE_WITH_TIMEZONE_FORMATTER = DateTimeFormatter
.ofPattern(DATE_TIME_WITH_TIMEZONE_PATTERN).withChronology(IsoChronology.INSTANCE);
Am I missing something here ?
First, if there’s any way you can avoid it, don’t rely on three letter time zone abbreviations. They are ambiguous, probably more often than not. When you say that IST should be mapped to Asia, it still leaves the choice between Asia/Tel_Aviv and Asia/Kolkata open (plus the aliases for those two, Asia/Jerusalem and Asia/Calcutta). In other places in the world IST may mean Irish Summer Time and apparently also Iceland Standard Time (or something like it; it certainly makes sense). It’s not the first time I have seen IST recognized as Atlantic/Reykjavik.
If you can’t avoid having to parse IST, control the interpretation through the two-arg appendZoneText method of a DateTimeFormatterBuilder. It accepts a set of preferred zones:
DateTimeFormatter DATE_WITH_TIMEZONE_FORMATTER = new DateTimeFormatterBuilder()
.appendPattern("yyyy-MM-dd HH:mm ")
.appendZoneText(TextStyle.SHORT, Collections.singleton(ZoneId.of("Asia/Kolkata")))
.toFormatter(Locale.ENGLISH);
Substitute your preferred preferred zone where I put Asia/Kolkata. The rest shouldn’t cause trouble:
String timeInput = "2019-08-29 00:00 IST";
ZonedDateTime zdt = ZonedDateTime.parse(timeInput, DATE_WITH_TIMEZONE_FORMATTER);
System.out.println("Parsed ZonedDateTime: "+ zdt);
long timestamp = zdt
.toInstant().toEpochMilli();
System.out.println("timestamp: " + timestamp);
Output:
Parsed ZonedDateTime: 2019-08-29T00:00+05:30[Asia/Kolkata]
timestamp: 1567017000000
Link: Time Zone Abbreviations – Worldwide List (you will notice that IST comes three times in the list and that many other abbreviations are ambiguous too).
I do not know why you're getting Iceland, even in the master TZDB IST appears only in reference to Irish Standard Time (and prior to 1968 erroneously Irish Summer Time), Israel Standard Time and India Standard Time. The appearance of Iceland may be an error in Java's timezone database.
After further investigation I have found that the problem seems to occur only if the current Locale's language is set to some non-null value. If you create a Locale without a specified language you get Asia/Kolkata, but if the language is present (any language) it returns Atlantic/Reykjavik. This is highly likely to be a bug in Java's implementation.
String input = "2019-08-29 00:00 IST";
Locale loc = new Locale.Builder().setRegion("US").build(); // Note no language
System.out.println(loc.toString());
DateTimeFormatter DATE_WITH_TIMEZONE_FORMATTER =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm z").withLocale(loc);
ZonedDateTime zdt = ZonedDateTime.parse(input, DATE_WITH_TIMEZONE_FORMATTER);
System.out.println(zdt);
This produces
_US
2019-08-29T00:00+05:30[Asia/Kolkata]
But changing
Locale loc = new Locale.Builder().setLanguage("ta").build();
produces
ta
2019-08-29T00:00Z[Atlantic/Reykjavik]
Regardless, the bare timezone IST is ambiguous out of context. To avoid confusion, if you want IST to always be Asia/Kolkata you may have to modify the incoming data prior to parsing.
Avoid using the three-letter time zone ID. Given below is an extract from as old as Java 6 documentation:
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.
Corresponding to the Indian Standard Time which has a time offset of UTC+05:30, you can build a custom formatter using .appendZoneText(TextStyle.SHORT, Set.of(ZoneId.of("Asia/Kolkata"))) with DateTimeFormatterBuilder as shown below:
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
.appendPattern("uuuu-MM-dd HH:mm")
.appendLiteral(' ')
.appendZoneText(TextStyle.SHORT, Set.of(ZoneId.of("Asia/Kolkata")))
.toFormatter(Locale.ENGLISH);
Now, let's use this custom formatter:
Instant instant = ZonedDateTime.parse("2019-08-29 00:00 IST", formatter).toInstant();
System.out.println(instant);
System.out.println(instant.toEpochMilli());
Output:
2019-08-28T18:30:00Z
1567017000000
Learn more about the the modern date-time API from Trail: Date Time.

BST Time is wrong

With the below code, every timezone is printing the value correctly except BST
​import java.text.*;
def format = "yyyy-MM-dd HH:mm:ssXXX"
def dt = new Date();
println dt;
SimpleDateFormat utcFormat = new SimpleDateFormat(format)
utcFormat.setTimeZone(TimeZone.getTimeZone("UTC"))
println utcFormat.format(dt)
SimpleDateFormat istFormat = new SimpleDateFormat(format)
istFormat .setTimeZone(TimeZone.getTimeZone("IST"))
println istFormat.format(dt)
SimpleDateFormat cetFormat = new SimpleDateFormat(format)
cetFormat.setTimeZone(TimeZone.getTimeZone("CET"))
println cetFormat.format(dt)
SimpleDateFormat bstFormat = new SimpleDateFormat(format)
bstFormat.setTimeZone(TimeZone.getTimeZone("BST"))
println bstFormat.format(dt)
​
Output:
Mon Mar 26 09:04:14 UTC 2018
2018-03-26 09:04:14Z
2018-03-26 14:34:14+05:30
2018-03-26 11:04:14+02:00
2018-03-26 15:04:14+06:00
Here BST time is wrong. Whats wrong with it?
You should avoid using the abbreviated timezone name. Check the following note from the TimeZone documentation:
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.
In your case, probably the system has mapped BST to Bishkek Standard Time which has a timezone offset of +06:00 hours. The standard naming convention is Region/City e.g. Europe/London, Europe/Paris, Asia/Kolkata etc. where City generally refers to the biggest city of a country in the region. In case of doubt, execute the following statement to get all timezone IDs:
System.out.println(ZoneId.getAvailableZoneIds());
java.time
The java.util Date-Time API and their formatting API, SimpleDateFormat are outdated and error-prone. It is recommended to stop using them completely and switch to the modern Date-Time API*.
Solution using java.time, the modern Date-Time API:
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
public class Main {
public static void main(String args[]) {
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ssXXX", Locale.ENGLISH);
Instant now = Instant.now();
System.out.println(now.atZone(ZoneOffset.UTC).format(dtf));
System.out.println(now.atZone(ZoneId.of("Asia/Kolkata")).format(dtf));
System.out.println(now.atZone(ZoneId.of("Europe/Paris")).format(dtf));
System.out.println(now.atZone(ZoneId.of("Europe/London")).format(dtf));
}
}
Output from a sample run:
2021-10-12 12:24:03Z
2021-10-12 17:54:03+05:30
2021-10-12 14:24:03+02:00
2021-10-12 13:24:03+01:00
ONLINE DEMO
Learn more about the modern Date-Time API from Trail: Date Time.
* 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. Note that Android 8.0 Oreo already provides support for java.time.
It seems you expect BST to be British Summer Time, but in this case it represents Bangladesh Standard Time.
Also see What does Java's BST ZoneId represent?
From Answer:
the below code works
SimpleDateFormat bstFormat = new SimpleDateFormat(format)
bstFormat.setTimeZone(TimeZone.getTimeZone("Europe/London"))
println bstFormat.format(dt)
I ran your code and found that the time for BST was just right. Adding the offset of 6 hours to your UTC time of 09:04:14 gives 15:04:14. I think you're confused with the acronym of the time zone.
If your timezone database in JVM is incorrect, you can get the British Summertime Timezone by TimeZone.getTimeZone("Europe/London");

Understanding specific UTC time format YYYY-MM-DDTHH:MM:SS.SSSZ

I have two related questions.
Assume a program running in (British Standard Time)BST generates a date time value for current time in UTC (YYYY-MM-DDTHH:MM:SS.SSSZ) format.
Also assume current time in London is 2016-06-01 12:33:54.
If the current time given by the program is 2016-06-01T11:33:54.000Z , is the program wrong?
How is summertime offset for BST noted in the corresponding time format for YYYY-MM-DDTHH:MM:SS.SSSZ
I assume YYYY-MM-DDTHH:MM:SS+0001. Am I correct ?
Firstly please have a read of the iso8601 information. It's becoming more common place to deal with times in different time zones (e.g. server time zone and client time zone) and the standard is really useful.
In particular please read about UTC or "Zulu" time here.
The program is correct, since the London time is one hour ahead of "UTC" time in summer
The trailing 'Z' is a short notation for UTC (Zulu). You could also write "+00:00" instead of 'Z'. The SS.SSS refer to seconds and milliseconds - not related to the time zone. In devnull's comment he shows you how to apply an offset for summer.
Edit:
There's been some discussion in the comments about whether iso8601 timezone includes timezone or not, and whether timezone will in fact be printed out.
This depends completely on the date/time implementation. If we are using SimpleDateFormat then timezone is supported and will be printed.
Here's a code example to illustrate
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
formatter.setTimeZone(TimeZone.getTimeZone("UTC"));
System.out.println(formatter.format(new Date()));
formatter.setTimeZone(TimeZone.getTimeZone("Europe/London"));
System.out.println(formatter.format(new Date()));
Output
2016-06-02T12:53:14.924Z
2016-06-02T13:53:14.925+01:00
Naturally, if you are using a different date/time library such as joda-time, then the implentation details will be different.
Edit: As #DerrylThomas pointed out with SimpleDateFormat wise to use lower case y for years - unless it's intended to use week year - explained in a bit of detail in another answer to a similar question https://stackoverflow.com/a/56911450.
if the current time given by the program is 2016-06-01T11:33:54.000Z ,
is the program wrong?
The format is correct and conforms to ISO 8601 but it does not represent Europe/London time. In London, in 2016, the DST started at Sunday, March 27, 1:00 am and ended at Sunday, October 30, 2:00 am and therefore a date-time representation for Europe/London during this time should have a timezone offset of +01:00 hours. The Z at the end specifies Zulu time which is UTC time and thus has a timezone offset of +00:00 hours. The same instant can be represented for Europe/London as 2016-06-01T12:33:54+01:00.
java.time
The java.util date-time API and their formatting API, SimpleDateFormat are outdated and error-prone. It is recommended to stop using them completely and switch to java.time, the modern date-time API* .
Even Joda-Time should not be used anymore. Notice the following note at the Home Page of Joda-Time
Joda-Time is the de facto standard date and time library for Java
prior to Java SE 8. Users are now asked to migrate to java.time
(JSR-310).
java.time API is based on ISO 8601 and the date-time string, 2016-06-01T11:33:54.000Z can be parsed into java.time.ZonedDateTime and java.time.OffsetDateTime without needing a date-time parsing/formatting type.
Demo:
import java.time.ZoneId;
import java.time.ZonedDateTime;
public class Main {
public static void main(String[] args) {
ZonedDateTime zdt = ZonedDateTime.parse("2016-06-01T11:33:54.000Z");
System.out.println(zdt);
ZoneId zoneId = ZoneId.of("Europe/London");
ZonedDateTime zdtInLondon = zdt.withZoneSameInstant(zoneId);
System.out.println(zdtInLondon);
}
}
Output:
2016-06-01T11:33:54Z
2016-06-01T12:33:54+01:00[Europe/London]
How to deal with Daylight Saving Time (DST)?
As mentioned earlier, the date-time string, 2016-06-01T11:33:54.000Z can also be parsed into java.time.OffsetDateTime without needing a date-time parsing/formatting type. However, OffsetDateTime has been designed to deal with a fixed timezone offset whereas ZonedDateTime has been designed to deal with a timezone and thus it take care of DST automatically. You can convert a ZonedDateTime to OffsetDateTime using ZonedDateTime#toOffsetDateTime if required.
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
public class Main {
public static void main(String[] args) {
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.SSS z", Locale.ENGLISH);
String strDateTime = "2016-03-01T11:33:54.000 Europe/London";
ZonedDateTime zdt = ZonedDateTime.parse(strDateTime, dtf);
System.out.println(zdt);
strDateTime = "2016-06-01T11:33:54.000 Europe/London";
zdt = ZonedDateTime.parse(strDateTime, dtf);
System.out.println(zdt);
}
}
Output:
2016-03-01T11:33:54Z[Europe/London]
2016-06-01T11:33:54+01:00[Europe/London]
Notice how the timezone offset has automatically changed from Z to 01:00 to reflect DST change. On the other hand,
import java.time.OffsetDateTime;
public class Main {
public static void main(String[] args) {
String strDateTime = "2016-03-01T11:33:54.000+01:00";
OffsetDateTime odt = OffsetDateTime.parse(strDateTime);
System.out.println(odt);
strDateTime = "2016-06-01T11:33:54.000+01:00";
odt = OffsetDateTime.parse(strDateTime);
System.out.println(odt);
}
}
Output:
2016-03-01T11:33:54+01:00
2016-06-01T11:33:54+01:00
In this case, you do not talk about a timezone (e.g. Europe/London); rather, you talk about a fixed timezone offset of +01:00 hours.
Learn more about the modern date-time API from Trail: Date Time.
* 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.

Java Date Conversion - UTC to Local - works differently depending on the timezone

I'm experiencing a problem when converting strings to a UTC data, and then to various timezones. It appears that my program behaves differently depending on whether I convert to EST or PST. Here is my code:
SimpleDateFormat utcFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
utcFormat.setTimeZone(java.util.TimeZone.getTimeZone("UTC"));
Date date = utcFormat.parse("2014-08-18 17:00:17");
SimpleDateFormat localFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
localFormat.setTimeZone(java.util.TimeZone.getTimeZone("PST"));
System.out.println(localFormat.format(date));
If I run the code above, here is my output:
2014-08-18 10:00:17
This reflects a 7 hour offset from the UTC time provided: 2014-08-18 17:00:17. This is what I would have expected. Now if I change that date to 2014-11-18 17:00:17 (changed the month from August to November), here is the output produced:
2014-11-18 09:00:17
This is fine too as far as I can tell. The output reflects an 8 hour offset from UTC, and I believe this is due to the fact that November is not in Daylight Savings time, while August is.
The problem I'm having is that the same code above works differently if I change the time zone from "PST" to "EST". When I change to EST I get the same time output no matter whether my date is in August or November.
Here is the output using EST and 2014-08-18 17:00:17
2014-08-18 12:00:17
Here is the output using EST and 2014-11-18 17:00:17
2014-11-18 12:00:17
In both cases, the output represents a 5 hour offset from UTC which makes sense only during November, not during August.
Can anyone explain to me what I am doing wrong?
Instead of using EST, you should use America/New_York or US/Eastern (these are aliases). The three letter timezone abbreviations are ambiguous and you can't be sure what you're getting.
From the Documentation for TimeZone
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.
Instead of "EST", "US/Eastern" will be much clearer as to your intent.
These are the supported US aliases.
US/Alaska
US/Aleutian
US/Arizona
US/Central
US/East-Indiana
US/Eastern
US/Hawaii
US/Indiana-Starke
US/Michigan
US/Mountain
US/Pacific
US/Pacific-New
US/Samoa
#Compass is right.
Here is the code you would use:
public static void main(String[] args) {
SimpleDateFormat utcFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
utcFormat.setTimeZone(java.util.TimeZone.getTimeZone("UTC"));
Date date = null;
try {
date = utcFormat.parse("2014-08-18 17:00:17");
} catch (ParseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
SimpleDateFormat localFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
localFormat.setTimeZone(java.util.TimeZone.getTimeZone("US/Eastern"));
System.out.println(localFormat.format(date));
}
The answer by Dave Morrissey is correct.
Can anyone explain to me what I am doing wrong?
Yes. You are using a terrible and confusing date-time library.
Avoid java.util.Date
The java.util.Date and .Calendar classes are notoriously troublesome, flawed in both design and implementation. Use a decent library. In Java that means either Joda-Time or the new java.time package in Java 8 (inspired by Joda-Time, defined by JSR 310).
Time Zone
While a j.u.Date has no time zone, in both Joda-Time and java.time a date-time object does indeed know its own assigned time zone. Makes this work much easier and more sensible.
Time Zone Names
Use proper time zone names. Avoid the 2, 3, or 4 letter codes as they are neither standardized nor unique. Most of those proper names are Continent/CityOrRegion.
Daylight Saving Time
You should not worry about Daylight Saving Time. Let the date-time library do the heavy lifting there. All you need to do is be sure your library is using a fresh version of the time zone database. Politicians enjoy redefining DST.
ISO 8601
Both Joda-Time and java.time support ISO 8601 formats as their defaults in parsing and generating string representations of date-time values.
Joda-Time Example
Here is some example code in Joda-Time 2.4. All of the DateTime objects in this example represent the same simultaneous moment in the history of the Universe but adjusted to show the wall-clock time as seen by a person in each locality.
String inputRaw = "2014-08-18 17:00:17"; // Nearly in [ISO 8601][7] format.
String input = inputRaw.replace( " ", "T" );
DateTime dateTimeUtc = DateTime.parse( input, DateTimeZone.UTC );
DateTime dateTimeLosAngeles = dateTimeUtc.withZone( DateTimeZone.forID( "America/Los_Angeles" ) );
DateTime dateTimeNewYork = dateTimeUtc.withZone( DateTimeZone.forID( "America/New_York" ) );
DateTime dateTimeMontréal = dateTimeUtc.withZone( DateTimeZone.forID( "America/Montreal" ) );
DateTime dateTimeKolkata = dateTimeUtc.withZone( DateTimeZone.forID( "Asia/Kolkata" ) );
That's because EST is ET outside of saving and its shift is constant and it complementary zone for daylight saving period is EDT.
Ergo you should use ET to get the expected behavior.
More on Wikipedia

Categories

Resources