I want to convert TimeZone such as "America/Chicago" to "CST". I can make use of SHORT_IDS map of ZoneID class. However, there are limited number of timezones configured in that map. What if I want to get "Asia/Hong_Kong" to "HKT", then this map will not get me correct answer.
Is there any library which I can make use of, if something is not provided by Java. I am avoiding creating mapping of these timezones in my application.
Any advice here would be greatly appreciated.
ZoneId hongKong = ZoneId.of("Asia/Hong_Kong");
System.out.println(hongKong.getDisplayName(TextStyle.SHORT_STANDALONE, Locale.ROOT));
This outputs:
HKT
Similarly the output for ZoneId.of("America/Chicago") is CT for Central Time (notice that this avoids the hopeless choice between CST for Central Standard Time and CDT for Central Daylight Time).
Please supply your desired locale. In many cases it won’t make any difference, in other cases it will since some time zones have localized abbreviations in some locales.
Unlike the outdated TimeZone class the modern ZoneId validates the time zone string and throws an exception if it is invalid, so we get a nice chance to correct any errors. For example:
java.time.DateTimeException: Invalid ID for region-based ZoneId, invalid format: Asia/Hong Kong
To get the abbreviation for standard time
Edit: In this duplicate question it was asked to have the abbreviation for standard time (as opposed to summer time/DST). So CST, not CT, for America/Chicago, etc. It appeared that only US time zones needed to be supported. If so, this method does it:
private static final DateTimeFormatter ZONE_FORMATTER
= DateTimeFormatter.ofPattern("zzz", Locale.ENGLISH);
private static String getAbbreviationForStandardTime(ZoneId zone) {
ZonedDateTime timeInStandardTime = LocalDate.of(2021, Month.JANUARY, 1)
.atStartOfDay(zone);
if (zone.getRules().isDaylightSavings(timeInStandardTime.toInstant())) {
throw new IllegalArgumentException("Time zones that have summer time on January 1 are not supported");
}
return timeInStandardTime.format(ZONE_FORMATTER);
}
Let’s try it out:
System.out.println(getAbbreviationForStandardTime(ZoneId.of("America/Los_Angeles")));
System.out.println(getAbbreviationForStandardTime(ZoneId.of("America/Denver")));
System.out.println(getAbbreviationForStandardTime(ZoneId.of("America/Chicago")));
System.out.println(getAbbreviationForStandardTime(ZoneId.of("America/New_York")));
Output:
PST
MST
CST
EST
My method includes a check that the time chosen — January 1 — is not in the summer time (DST) part of the year. For many time zones on the southern hemisphere this will fail. If you need to support any of them, the quick fix is to take July 1 instead in those cases.
Use TimeZone.getDisplayName:
TimeZone.getTimeZone("America/Chicago").getDisplayName(false, TimeZone.SHORT)
Ideone demo
But be very careful about using three-letter time zone identifiers. Only use them for display (as implied by the method name); do not use them to identify a time zone otherwise.
Related
I have multiple times in my database that look like this:
String summerTime = "2022-07-21T10:00:00Z";
String winterTime = "2022-11-21T10:00:00Z";
In another hand, I want to get the time part of these dates based on a custom timezone, the timezones that I have, look like this:
GMT-03:00
GMT-02:00
GMT-01:00
GMT+02:00
GMT+01:00
....
When I try to extract the time part using this code:
ZoneId tz = ZoneId.of("GMT+01:00");
ZonedDateTime date1 = ZonedDateTime.parse(summerTime).withZoneSameInstant(tz.toZoneId());
ZonedDateTime date2 = ZonedDateTime.parse(winterTime).withZoneSameInstant(tz.toZoneId());
I get:
11:00
11:00
But if you notice well, the time is the same even on summer or winter dates.
I expect to get:
12:00 // <----- correct
11:00 // <----- correct
I found a solution for my issue, which is using the full name of the time zone, instead of the abbreviation:
ZoneId tz = ZoneId.of("Europe/Paris");
This gives me the correct response:
12:00
11:00
but I can't use this because I should use only the abbreviation.
My question:
Why do the two formats give different results?
How can get the full name from the abbreviation so I can get the correct output?
Why do the two formats gives different result?
Because your first form is a "fixed offset" time zone, effectively. "GMT+01:00" is always on UTC+1, for the whole of time. It doesn't actually map to any specific area of the world, it's just "always UTC+1".
How can get the full name from the abbreviation so I can get the correct output?
You can't. Even if you know the UTC offset at a particular instant in time, there may be multiple time zones that have that UTC offset at that instant, but have different UTC offsets from each other at different times.
For example, currently both Europe/London and Africa/Abidjan are on UTC+0... but in June 2022 (for example) Africa/Abidjan was still be UTC+0, and Europe/London was UTC+1.
I can't use this because I should use only the abbreviation.
That's a fundamental problem, and you should push back on the requirement. It's like saying "I really need the user's full name, but I only have their initials." It's an infeasible requirement.
I have a DateTime field representing a date and time, and a separate zone string which tells its time zone.
I want to convert the time in the DateTime to eastern timezone.
I found several answers explaining this, but all of them use the IANA's naming standard of zone ids of Continent/Region for conversion. I am getting the short form notation of zone ids in the zone field from the user which is like IST, AEST, CST, etc.
Is there a way I can convert time to eastern time format using the short notations?
UPDATE:
I have a limited set of time zones which can be given as input. They are as follows:
JST - Japan Standard Time (+09:00)
CST - China Standard Time (+08:00)
SAST - South African Standard Time (+02:00)
GMT - Greenwich Mean Time (00:00)
EST - Eastern Time Zone (-05:00 / -04:00)
HKT - Hong Kong Time (+08:00)
IST - Indian Standard Time (+05:30)
The conversion strategy should take care of DST. So if input is 2021-01-06T10:30:00 and time zone given is IST. The method while converting this to EST should figure out if DST applies or not and do the conversion accordingly with either -05:00 or -04:00 as applicable.
Java uses IANA time zone IDs in the form region/city. So if you can map your abbreviations to those, you can get through. For the sake of giving you working code, here’s an example but I guarantee that some of the IDs are not the ones you want. You will have to define your own mapping. Some of the long time zone names in your list do not refer to unique time zones, for example Gulf Standard Time and Central European Time. There are many time zones within those regions.
private static final Map<String, String> tzSubset = Map.ofEntries(
Map.entry("NZST", "Antarctica/McMurdo"),
Map.entry("AEST", "Australia/Sydney"),
Map.entry("JST", "Asia/Tokyo"),
Map.entry("HKT", "Asia/Hong_Kong"),
Map.entry("CST", "America/North_Dakota/New_Salem"), // not in China
Map.entry("SGT", "Asia/Singapore"),
Map.entry("IST", "Asia/Calcutta"),
Map.entry("GST", "Asia/Dubai"),
Map.entry("MSK", "Europe/Volgograd"),
Map.entry("SAST", "Africa/Mbabane"),
Map.entry("CET", "Africa/Ceuta"),
Map.entry("GMT", "Etc/GMT"),
Map.entry("BST", "Europe/Belfast"),
Map.entry("BRT", "America/Maceio"),
Map.entry("ET", "America/Indiana/Winamac")
);
private static final ZoneId targetTimeZone = ZoneId.of("America/Toronto");
With the mapping and the desired time zone in place, the conversion is simple enough:
LocalDateTime inputDateTime = LocalDateTime.of(2021, Month.JANUARY, 13, 23, 45);
String inputTimeZone = "BST";
ZoneId zone = ZoneId.of(inputTimeZone, tzSubset);
ZonedDateTime targetDateTime = inputDateTime.atZone(zone)
.withZoneSameInstant(targetTimeZone);
System.out.format("%s in %s equals %s%n", inputDateTime, zone, targetDateTime);
Output from this example snippet is:
2021-01-13T23:45 in Europe/London equals 2021-01-13T18:45-05:00[America/Toronto]
The conversion automatically accounts for summer time (DST) if either of the two time zones involved uses it.
Edit: It seems that you are assuming that the dates and times are current, not historical? You mentioned that America/Sao_Paulo dropped summer time in 2019 — so what if you have got a LocalDateTime from 2019 or earlier? If you don’t plan to handle those correctly, you should definitely do a range check on your date time and refuse to convert it if falls outside your intended bounds. Use the isBefore and/or the isAfter method of LocalDateTime.
Link: Oracle tutorial: Date Time explaining how to use java.time.
Why does below line print false? i think it should print true.
TimeZone.getTimeZone("UTC+5:30").hasSameRules(TimeZone.getTimeZone("GMT+5:30")
The answer is in the JavaDoc of TimeZone#getTimeZone:
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"
Returns:
the specified TimeZone, or the GMT zone if the given ID cannot be understood.
And (from the class documentation)
[...] The syntax of a custom time zone ID is:
CustomID:
GMT Sign Hours : Minutes
GMT Sign Hours Minutes
GMT Sign Hours
The ID "UTC+5:30" is not a valid TimeZone ID (as per the specification of the method/class) and is interpreted as "GMT" zone, which is clearly distinct from the "GMT+5:30" zone.
Since you are located in India, you should use
ZoneId india = ZoneId.of("Asia/Kolkata");
Two messages:
The TimeZone class has design problems and is long outdated. The same goes for its friends like Calender and SimpleDateFormat. So don’t use them. Instead use java.time, the modern Java date and time API. Its replacement for TimeZone is the ZoneId class (and ZoneOffset for an offset from UTC, but don’t use that as a time zone, it isn’t one, see the next item).
Don’t use an offset from either UTC or GMT as a time zone. It works in your particular case, of course, but it may leave the reader wondering why you chose +05:30, which Asia/Kolkata clearly conveys. Also Asia/Kolkata is future-proof in case at some point in time the politicians change the UTC offset for India or introduce summer time (DST). While this is unlikely for India, it happens all the time in other places in the world, so It’s better to make it a habit to use the region/city format always.
For just one of the many design advantages of the modern API, try the modern version of your code:
ZoneId.of("UTC+5:30").getRules().equals(ZoneId.of("GMT+5:30").getRules())
This throws: java.time.DateTimeException: Invalid ID for offset-based ZoneId: UTC+5:30. Now you know from the outset what’s wrong: UTC+5:30 is not a valid time zone ID.
Link: Oracle tutorial: Date Time explaining how to use java.time.
I am aware that SimpleDateFormat.parse rely on the Calendar API which depends on local JVM timezone (computer's). Assume JVM timezone is IST.
SimpleDateFormat srcDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
srcDateFormat.setTimeZone(TimeZone.getTimeZone("EST"));
Date objDt = srcDateFormat.parse("2018-10-16 11:28:25"); //Time : 21:58:25
From the output it seems it converts from EST to IST(JVM local timezone).
SimpleDateFormat srcDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
srcDateFormat.setTimeZone(TimeZone.getTimeZone("IST"));
Date objDt = srcDateFormat.parse("2018-10-16 11:28:25"); //Time : 11:28:25
It keeps time unmodified in this case. In this case I set timezone same as JVM local timezone.
Please help me to understand the behavior of the parse method. Nevertheless, I am curious to know the reason behind such behavior.
I know that java.util.Date and java.text.SimpleDateFormat legacy classes are obsolete now.
References:
Why SimpleDateFormat.format() and SimpleDateFormat.parse() are giving different time though setting only one TimeZone?
How do I convert date/time from one timezone to another?
SimpleDateFormat parses a string to wrong time
https://www.codeproject.com/Tips/1190426/When-Parsing-Formatting-Dates-in-Java-Make-Sure-Yo
First, you are correct that Date and SimpleDateFormat are legacy classes and now obsolete. So I recommend you don’t use them and use java.time, the modern Java date and time API, instead. Among many advantages it is much more explicit about conversions between time zones, which I would expect to help you understand what the code does.
Second, you are doing a number of things incorrectly or at least inadequately:
Don’t store date-times as strings. Always store them as date-time objects in Java. When you do this, you will never need to convert a date-time string from one zone to another. Instant (a class from java.time) is a point in time and as far as I can see the one you should use here.
Don’t rely in three letter time zone abbreviations. Very many of them are ambiguous, including both of IST and EST, and the latter isn’t a true time zone, so what you get at this time of year (when America/New_York zone uses EDT rather than EST), I don’t know.
And repeating myself, use the modern classes, not the obsolete ones.
Out of curiosity what happened?
An old-fashioned Date represents a point in time independently of time zone (internally it stores its value as a count of milliseconds since the epoch, but this is an implementation detail that we need not know or concern ourselves with).
In your first example your string is parsed into a point in time that corresponds to 16:28:25 UTC, 21:58:25 in India, 12:28:25 in New York or 11:28:25 in Jamaica. I mention Jamaica because it’s one of the few places that happens to use Eastern Standard Time (EST) all year. Most of the locations that use EST only do so in winter, not at this time of year. When you look at the Date in your debugger, the debugger calls toString on the Date to get a string to show you. toString in turn uses the JVM’s time zone for generating the string. In your case it’s Asia/Kolkata, which is why you get 21:58:25.
In the second case the same string is parsed into a point in time that corresponds to 05:58:25 UTC, 11:28:25 in India, 01:58:25 in New York or 00:58:25 in Jamaica. Your debugger again calls toString, which again uses your JVM’s time zone and converts back into 11:28:25 IST. When you parse and print in the same time zone, you get the same time of day back.
Links
EST – Eastern Standard Time / Eastern Time (Standard Time)
Oracle tutorial: Date Time explaining how to use java.time.
Time Zone Converter – Time Difference Calculator, online, practical for converting between Asia/Kolkata, UTC, Kingston (Jamaica), New York and other time zones.
We are storing time in like '22-NOV-17 05.33.51.937000000 PM' format with server default timezone CST. We have half an our time comparison in many places. So CST to CDT and CDT to CST are facing issues because on retrieval time for database we can not identify the time zone. So it is breaking our time comparison on CST to CDT and CDT to CST time changes.
We can not change our storing logic like store with timezone and store in UTC timezone because it will breaking our existing logic in many places.
So is there any way to identity date timezone like CST or CDT, stored in database with '22-NOV-17 05.33.51.937000000 PM' format.
We are storing time in like '22-NOV-17 05.33.51.937000000 PM' format
does not make sense with your comment
We are storing as a timestamp in database
In Oracle databases, a TIMESTAMP does not have a format - it is stored in the database as 11 bytes representing year (2 bytes), month, day, hours, minutes, seconds (1 byte each) and fractional seconds (4 bytes). It is only when whatever interface you are using (SQL/Plus, SQL Developer, Toad, Java, PHP, etc.) to talk to the database decides to show it to you, the user, that that interface will format it as a string (but the database will just keep it as bytes without any format).
Assuming you are using SQL/Plus or SQL Developer then you can find the default format using:
SELECT value FROM NLS_SESSION_PARAMETERS WHERE parameter = 'NLS_TIMESTAMP_FORMAT';
And change the default format using:
ALTER SESSION SET NLS_TIMESTAMP_FORMAT = 'YYYY-MM-DD HH24:MI:SSXFF9';
Or for TIMESTAMP WITH TIME ZONE
ALTER SESSION SET NLS_TIMESTAMP_TZ_FORMAT = 'YYYY-MM-DD HH24:MI:SSXFF9 TZR';
So is there any way to identity date timezone like CST or CDT, stored in database with '22-NOV-17 05.33.51.937000000 PM' format.
No, without any other meta-data that could identify the source of the timestamp and indicate which location it came from (i.e. is there another column that links to the user who entered the data that could be mapped to a physical location and so a time zone) then it is impossible to determine which time zone it is from.
You will either need to:
change your database column to TIMESTAMP WITH TIME ZONE and store the time zone; or
convert all the values to the same time zone when you are storing them.
I am assuming by CST and CDT you mean North American Central Standard Time and Central Daylight Time such as observed in Rainy River, Chicago and Mexico (the city) among other places. More on this ambiguity later.
For 99.977 % of all times it is fairly easy to know whether they are standard time or daylight saving time. Only times from the two hours around the transition from DST to standard time are ambiguous, and as said in the comments, there is no way to know from the time stamp which is the right way to resolve this ambiguity.
java.time
This answer will take you as far into the future as possible without taking you away from Java 7. You can still use java.time, the modern Java date and time API also known as JSR-310. It has been backported to Java 6 and 7 in the ThreeTen Backport, so it’s a matter of getting this and adding it to your project (just until one day you upgrade to Java 8 or later).
I am taking your word for your date-time string format. What we can do with it:
DateTimeFormatter storedFormatter = new DateTimeFormatterBuilder()
.parseCaseInsensitive()
.appendPattern("d-MMM-uu hh.mm.ss.SSSSSSSSS a")
.toFormatter(Locale.US);
ZoneId zone = ZoneId.of("America/Mexico_City");
String storedTime = "22-NOV-17 05.33.51.937000000 PM";
LocalDateTime dateTime = LocalDateTime.parse(storedTime, storedFormatter);
// First shot -- will usually be correct
ZonedDateTime firstShot = ZonedDateTime.of(dateTime, zone);
System.out.println(firstShot);
This prints:
2017-11-22T17:33:51.937-06:00[America/Mexico_City]
You can see that it picked an offset of -06:00, which means that the time is in standard time (CDT is -05:00).
Since your month abbreviation is in all uppercase, I needed to tell the formatter to parse case insensitively. If America/Mexico_City time zone is not appropriate for you, pick a better one, for example America/Rainy_River or America/Chicago.
Ambiguous times in fall
I once had to parse a log file containing date-times without indication of standard time and summer time (DST). Since we assumed time would always move forward, we failed at the transition to standard time, and one hour of the log file was lost. In this case we might have solved it using the information that times were in summer time until the leap backward by an hour, from there they were in standard time. You may want to think about whether something similar will be possible for you.
Other options include just taking DST time every time — this is what the above code will do — or taking an average and living with the error thus introduced.
We can at least detect the ambiguous times:
ZoneOffset standardOffset = ZoneOffset.ofHours(-6);
ZoneOffset dstOffset = ZoneOffset.ofHours(-5);
// check if in fall overlap
ZonedDateTime standardDateTime
= ZonedDateTime.ofLocal(dateTime, zone, standardOffset);
ZonedDateTime dstDateTime
= ZonedDateTime.ofLocal(dateTime, zone, dstOffset);
if (! standardDateTime.equals(dstDateTime)) {
System.out.println("Ambiguous, this could be in CST or CDT: " + dateTime);
}
Now if the string was 29-OCT-17 01.30.00.000000000 AM, I get the message
Ambiguous, this could be in CST or CDT: 2017-10-29T01:30
ZonedDateTime.ofLocal() will use the provided offset for resolving the ambiguity if it is a valid offset for the date-time and zone.
Non-existing times in the spring
Similarly we can detect if your date-time falls in the gap where the clock is moved forward in the transition to DST:
// Check if in spring gap
if (! firstShot.toLocalDateTime().equals(dateTime)) {
System.out.println("Not a valid date-time, in spring gap: " + dateTime);
}
This can give a message like
Not a valid date-time, in spring gap: 2018-04-01T02:01
I suggest you can safely reject such values. They cannot be correct.
Avoid the three letter time zone abbreviations
CST may refer to Central Standard Time (in North and Central America), Australian Central Standard Time, Cuba Standard Time and China Standard Time. CDT may mean Central Daylight Time or Cuba Daylight Time. The three and four letter abbreviations are not standardized and are very often ambiguous. Prefer time zone IDs in the region/city format, for example America/Winnipeg.