IST mapped to wrong ZoneId in java.time library - java

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.

Related

Java different timezone offset in same timezone

I'm using "Asia/Bangkok" zone id.
That offset is from GMT UTC+07:00.
but when I did followings, then it is not +7:00 when set "01/01/1900 7:00:00.000"
SimpleDateFormat dateFormat = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss.SSS");
Date date = dateFormat.parse("01/01/1900 7:00:00.000");
System.out.println(date);
System.out.println(date.getTimezoneOffset());
Date date2 = dateFormat.parse("01/01/1900 6:00:00.000");
System.out.println(date2);
System.out.println(date2.getTimezoneOffset());
The result is
Mon Jan 01 07:00:00 ICT 1900
-402
Mon Jan 01 06:00:00 ICT 1900
-420
I wondered if the offset had changed around 7:00 a.m. on January 1, 1900, so I looked it up on Wikipedia.
https://en.wikipedia.org/wiki/Time_in_Thailand
It was UTC+6:42, but from 1880 to 1920.
I have 3 questions.
Why it happen different time offset between "01/01/1900 7:00:00.000" and "01/01/1900 6:00:00.000"
Where can I see time zone history in Java.
How can I ignore different time offset in same Timezone.
-- additional question --
I understand that I should use LocalDateTime.
What is the best way to ignore offset and convert Date to LocalDateTime?
For example, in the following case, the value of convertedDate2 was converted based on an offset of -402.
DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("MM/dd/yyyy H:mm:ss.SSS");
LocalDateTime originalLdate = LocalDateTime.parse("01/01/1900 7:00:00.000", dateFormatter);
LocalDateTime originalLdate2 = LocalDateTime.parse("01/01/1900 6:00:00.000", dateFormatter);
System.out.println(originalLdate);
System.out.println(originalLdate2);
SimpleDateFormat dateFormat = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss.SSS");
Date date = dateFormat.parse("01/01/1900 7:00:00.000");
Date date2 = dateFormat.parse("01/01/1900 6:00:00.000");
LocalDateTime convertedDate = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
LocalDateTime convertedDate2 = date2.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
System.out.println(convertedDate);
System.out.println(convertedDate2);
LocalDateTime convertedDate3 = LocalDateTime.parse(dateFormat.format(date), dateFormatter);
LocalDateTime convertedDate4 = LocalDateTime.parse(dateFormat.format(date2), dateFormatter);
System.out.println(convertedDate3);
System.out.println(convertedDate4);
The result is
1900-01-01T07:00
1900-01-01T06:00
1900-01-01T07:00
1900-01-01T05:42:04
1900-01-01T07:00
1900-01-01T06:00
If I convert it once to String and then to LocalDateTime, as in convertedDate3 and convertedDate4,
then I could convert as my expectation, but I wonder this is the most efficient way or not?
Java runtime timezone information for each version is available here
https://www.oracle.com/java/technologies/tzdata-versions.html
Inside the linked file (for a specific version) you can find links to the actual data used
https://www.iana.org/time-zones/repository/releases/tzcode2021a.tar.gz
https://www.iana.org/time-zones/repository/releases/tzdata2021a.tar.gz
https://www.iana.org/time-zones/repository/releases/tzdb-2021a.tar.lz
Inside the tzdata*.tar.gz you can find a file called asia which contains the data for Bangkok as well.
It contains these entries
# Thailand
# Zone NAME STDOFF RULES FORMAT [UNTIL]
Zone Asia/Bangkok 6:42:04 - LMT 1880
6:42:04 - BMT 1920 Apr # Bangkok Mean Time
7:00 - +07
Link Asia/Bangkok Asia/Phnom_Penh # Cambodia
Link Asia/Bangkok Asia/Vientiane # Laos
So the -402 timezone should be used for all dates before 1/4/1920, but it seems the implementation is using the -402 offset only from 1/1/1900 0:00:00.000 UTC (from 1/1/1900 6:42:04.000 in your timezone) and until 1/4/1920 in your timezone and -420 otherwise. I am not sure, if that is intended or a bug.
How can I ignore different time offset in same Timezone.
If you are actually using timezones in your application, then you should not ignore them.
However, if you are making an application that is intended to be used just in your local timezone, then you can use a DateTime class without timezone information, such as java.time.LocalDateTime.
Also worth noting: even if these timezones would be correct, the historical dates might still be inaccurate, due to modern time rules being applied for all time (see below). So in the end it depends on what your use case is.
A date-time without a time-zone in the ISO-8601 calendar system. The ISO-8601 calendar system is the modern civil calendar system used today in most of the world. It is equivalent to the proleptic Gregorian calendar system, in which today's rules for leap years are applied for all time. For most applications written today, the ISO-8601 rules are entirely suitable. However, any application that makes use of historical dates, and requires them to be accurate will find the ISO-8601 approach unsuitable.
java.util.Date and java.text.SimpleDateFormat are very old classes. Although they mostly work, they are difficult to use properly, especially where timezones are concerned.
Date.getTimezoneOffset is deprecated. Do not use deprecated methods.
The proper way to work with timezone rules is using the java.time, java.time.zone, and java.time.format packages:
ZoneId zone = ZoneId.systemDefault();
DateTimeFormatter dateFormatter =
DateTimeFormatter.ofPattern("MM/dd/yyyy H:mm:ss.SSS");
LocalDateTime date =
LocalDateTime.parse("01/01/1900 7:00:00.000", dateFormatter);
System.out.println(date);
System.out.println(zone.getRules().getOffset(date));
LocalDateTime date2 =
LocalDateTime.parse("01/01/1900 6:00:00.000", dateFormatter);
System.out.println(date2);
System.out.println(zone.getRules().getOffset(date2));
The entire history of a timezone is in the ZoneRules:
System.out.println();
zone.getRules().getTransitions().forEach(System.out::println);
System.out.println();
zone.getRules().getTransitionRules().forEach(System.out::println);
You also asked:
What is the best way to ignore offset and convert Date to LocalDateTime?
You can’t. It is not possible to convert a Date to a LocalDateTime without assuming a timezone.
A Date is a wrapper for the number of milliseconds since 1970-01-01 00:00:00 UTC. You cannot generate a LocalDateTime from that without knowing which timezone to apply to that millisecond count. For example, noon Eastern Time in the US is a different number of milliseconds since 1970 than noon Greenwich time.
You may not realize it, but when you use SimpleDateFormat, you are specifying a timezone. Every SimpleDateFormat has a timezone property. Since your code never set that timezone explicitly, your date format used the system’s default timezone.
That is one reason to avoid DateFormat and SimpleDateFormat: the implicit use of the default timezone leads to errors and confusing behavior (though it is predictable behavior). When you use the java.time package and its subpackages, there is no ambiguity, and far less chance of confusion.

Does a 'z' in a datetime String have different outputs in different locales?

Not long ago, I provided an answer to a question about how to extract the time zone from a ZonedDateTime parsed from a String.
It worked in general, but there were different outputs of the same code on OP's system an my one, which somehow doesn't let me go anymore.
The related question asked how to get a ZoneId from a ZonedDateTime and I provided a way. Admittedly, it is not the accepted answer, but still seemed worth an upvote from someone.
The special thing about my answer is concerning the 'z' in the pattern used to parse the time String. There is a time zone name in that String representing the zone "Australia/Adelaide" by "... ACST" (Australian Central Standard Time).
When I parse it on my system and print/format the ZonedDateTime using the DateTimeFormatter.ISO_ZONED_DATE_TIME, it prints the time in "America/Manaus" and having extracted the ZoneId, it is still that one from South America. OP stated in a comment below my answer, that on his system, at least one of the output lines shows the desired/correct ZoneId.
How is that possible? Does the system default locale have any influence on parsing the 'z' in datetime Strings?
This is the code from my answer in the question plus an output of my ZoneId.systemDefault():
public static void main(String args[]) throws Exception {
String time = "2 Jun 2019 03:51:17 PM ACST";
String pattern = "d MMM yyyy hh:mm:ss a z"; // z detects the time zone (ACST here)
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
// parse a time object using the formatter and the time String
ZonedDateTime zdt = ZonedDateTime.parse(time, formatter);
// print it using a standard formatter
System.out.println(zdt.format(DateTimeFormatter.ISO_ZONED_DATE_TIME));
// extract the zone id
ZoneId zoneId = zdt.getZone();
ZoneId sysDefault = ZoneId.systemDefault();
// print the zone id
System.out.println("Time zone of parsed String is " + zoneId);
System.out.println("System default time zone is " + sysDefault);
// retrieve the instant seconds
Instant instant = zdt.toInstant();
// print the epoch seconds of another time zone
System.out.println("Epoch seconds in Australia/Adelaide are "
+ instant.atZone(ZoneId.of("Australia/Adelaide")).toEpochSecond());
}
and its output is this:
2019-06-02T15:51:17-04:00[America/Manaus]
Time zone of parsed String is America/Manaus
System default time zone is Europe/Berlin
Epoch seconds in Australia/Adelaide are 1559505077
Can anyone point me to a mistake I have made or confirm and explain the influence of different system default ZoneIds on parsing a String to a ZonedDateTime?
I too have experienced ambiguous time zone abbreviations being parsed differently on different JVMs (also when providing a locale, so that is not the only issue). I don't think the exact behaviour is documented. In some cases the JVMs default time zone was chosen, I don't know if it will always be when it matches, though.
You can control the choice of time zone in ambiguous cases through the overloaded DateTimeFormatterBuilder.appendZoneText(TextStyle, Set<ZoneId>) method.
An example where locale makes a difference: Europe/Berlin and many other European time zones will be formatted into Central European Time or CET in many locales. In a German locale they instead become Mitteleuropäische Zeit or MET.
The Answer by Ole V.V. is correct.
ISO 8601
Furthermore, strings such as "2 Jun 2019 03:51:17 PM ACST" should not be parsed. Such formats should never be used to exchange date-time values. Such strings should be used only for presentation to the human user, not for data-exchange.
Would you try the exchange the monetary amount of USD 23.67 as twenty three dollars and sixty-seven cents in United States dollars or as vingt-trois dollars et soixante-sept cents en dollars des États-Unis?
To exchange date-time values via text, always use the standard ISO 8601 formats. The sensible formats are designed to be unambiguous, easy to parse by machine, and easy to read by humans across cultures.
If using a time zone, use the ZonedDateTime::toString method to generate text. This method wisely extends the ISO 8601 format to append the name of the time zone in square brackets.
ZoneId z = ZoneId.of( "Australia/Adelaide" ) ;
String output = ZonedDateTime.now( z ).toString() ;
2020-02-10T06:30:57.756491+10:30[Australia/Adelaide]
Run that code live at IdeOne.com.
Parsing.
ZonedDateTime zdt = ZonedDateTime.parse( output ) ;
Even better, use UTC values when the time zone is not directly relevant. To capture and communicate the current moment in UTC, use Instant.
Again, call toString & parse to generate and parse text in standard ISO 8601 format. The Z on the end means UTC, an offset of zero hours-minutes-seconds, and is pronounced “Zulu”.
String output = Instant.now().toString() ;
2020-02-09T20:07:42.718473Z

How to set Z as timezone in SimpleDateFormat

Code sample:
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z");
System.out.println(dateFormat.getTimeZone());
System.out.println(dateFormat.parse(time));
// dateFormat.setTimeZone(TimeZone.getTimeZone("Asia/Kolkata"));
I don't want to use the commented section.
Zone should be set based on IST that I am giving in the input string:
String time ="2018-04-06 16:13:00 IST";
Current machine zone is: America/New_York. How should I get zone changes to IST based on z?
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z");
String time ="2018-04-06 18:40:00 IST";
dateFormat.setTimeZone(TimeZone.getTimeZone("Asia/Kolkata"));
Above is running correctly but I don't want to set zone explicitly. It should be choosen based on IST I am giving in input time string.
"I don't want to set zone explicitly"
Sorry to disappoint you, but that's not possible with SimpleDateFormat. Timezone abbreviations like IST are ambiguous - as already said in the comments, IST is used in many places (AFAIK, in India, Ireland and Israel).
Some of those abbreviations might work sometimes, in specific cases, but usually in arbitrary and undocumented ways, and you can't really rely on that. Quoting the javadoc:
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.
Due to the ambiguous and non-standard characteristics of timezones abbreviations, the only way to solve it with SimpleDateFormat is to set a specific timezone on it.
"It should be set to based on Z"
I'm not really sure what this means, but anyway...
Z is the UTC designator. But if the input contains a timezone short-name such as IST, well, it means that it's not in UTC, so you can't parse it as if it was in UTC.
If you want to output the date with Z, then you need another formatter set to UTC:
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z");
String time = "2018-04-06 18:40:00 IST";
dateFormat.setTimeZone(TimeZone.getTimeZone("Asia/Kolkata"));
// parse the input
Date date = dateFormat.parse(time);
// output format, use UTC
SimpleDateFormat outputFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ssX");
outputFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
System.out.println(outputFormat.format(date)); // 2018-04-06 13:10:00Z
Perhaps if you specify exactly the output you're getting (with actual values, some examples of outputs) and what's the expected output, we can help you more.
DateTimeFormatter formatter
= DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss z", Locale.ENGLISH);
String time ="2018-04-06 16:13:00 IST";
ZonedDateTime dateTime = ZonedDateTime.parse(time, formatter);
System.out.println(dateTime.getZone());
On my Java 8 this printed
Asia/Jerusalem
So apparently IST was interpreted as Israel Standard Time. On other computers with other settings you will instead get for instance Europe/Dublin for Irish Summer Time or Asia/Kolkata for India Standard Time. In any case the time zone comes from the abbreviation matching the pattern letter (lowercase) z in the format pattern string, which I suppose was what you meant(?)
If you want to control the choice of time zone in the all too frequent case of ambiguity, you may build your formatter in this way (idea stolen from this answer):
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
.appendPattern("uuuu-MM-dd HH:mm:ss ")
.appendZoneText(TextStyle.SHORT,
Collections.singleton(ZoneId.of("Asia/Kolkata")))
.toFormatter(Locale.ENGLISH);
Now the output is
Asia/Kolkata
I am using and recommending java.time over the long outdated and notoriously troublesome SimpleDateFormat class.
Link: Oracle tutorial: Date Time explaining how to use java.time.

Date conversion from yyyy-MM-dd HH:mm:ss to ISO date yyyy-MM-dd'T'HH:mm:ssXXX format issue

I'm trying to convert date format from yyyy-MM-dd HH:mm:ss to ISO date format yyyy-MM-dd'T'HH:mm:ss+5:30, and tested it by below code and it was working fine when ran on eclipse and causing an issue on deployment to server through jar.
The issue is date(input: 2016-01-08 10:22:03) is converted to something like, 2016-01-08T10:22:03Z instead of 2016-01-08T10:22:03+5:30.
Note: I'm using Java 8.
Following is the code used to convert the date,
SimpleDateFormat outputDate = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");
SimpleDateFormat inputDate = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String FinalDate = outputDate.format(inputDate.parse(pickupDate));
System.out.println(finalDate);
Other weird experience is, on some machine the issue is not reproducible and in some machine the issue exists. Is it something machine or JVM dependent? Please help.
Thank you in advance.
As by documentation of SimpleDateFormat:
For formatting, if the offset value from GMT is 0, "Z" is produced. If
the number of pattern letters is 1, any fraction of an hour is
ignored. For example, if the pattern is "X" and the time zone is
"GMT+05:30", "+05" is produced.
So my guess is probably to check the timezone of your server. Since it thinks that the timezone of the entered date is GMT 0.
java.time
If using Java 8 or later, you should be using the java.time classes rather than those notoriously troublesome date-time classes, java.util.Date/.Calendar.
ISO 8601
Your input strings are close to the standard ISO 8601 format. The java.time classes use these standard formats by default when parsing and generating textual representations of date-time values. No need to define a coded parsing pattern for such standard inputs.
To fully comply with ISO 8601, replace that SPACE in the middle with a T.
String inputStandardized = "2016-01-08 10:22:03".replace( " " , "T" );
Parsing Without Time Zone Or Offset
This string has no offset-from-UTC or time zone, so we first create a LocalDateTime.
LocalDateTime localDateTime = LocalDateTime.parse( inputStandardized );
Such objects are a vague idea of a date-time but are not really on the timeline. To define a real moment on the timeline we must apply a time zone.
ZoneId zoneId = ZoneId.of( "Asia/Kolkata" );
ZonedDateTime zdt = localDateTime.atZone( zoneId );
Apply Time Zone
Note that that particular date + time may not be valid in the specified time zone; java.time adjusts as necessary. Be sure to read the documentation to understand the adjustment behavior.
Formatted Strings
The toString method on ZonedDateTime by default generates a String in the format you desire, except extended to append the name of the time zone in square brackets.
String output = zdt.toString();
2016-01-08T10:22:03+05:30[Asia/Kolkata]
This extension to include time zone name makes much sense. A time zone is not just an offset-from-UTC, it also includes the present and historical rules for handling anomalies such as Daylight Saving Time (DST).
If you really do not want that appended time zone name, against my advice, you can use an alternate formatting pattern, ISO_OFFSET_DATE_TIME, already defined in java.time as a constant.
DateTimeFormatter formatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME ;
String output = zdt.format( formatter );
2016-01-08T10:22:03+05:30
Pad Hour Of Offset
By the way, you can avoid problems by always including a leading padding zero in the hours of your offset-from-UTC. So use +05:30 rather than +5:30 as seen in the Question.

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