Convert Time of a particular time zone to other accounting daylight savings - java

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.

Related

How do I tell Joda Time that the time I am giving it is for a specific time zone offset, even though the offset isn't given in the String?

I am working with an API that provides me with a ModifyDate field that is being given in CST (-06:00), but when passing the string in to Joda time and setting the time zone to America/Phoenix, Joda time thinks that the date/time I gave it is in UTC time zone because there is no offset information being given by the API (the time being returned is in CST, confirmed with the developers).
Side note: I am in Arizona where we do not recognize daylight savings time, so I can't just apply a static offset of -1 hour.
Here's an example of what I'm dealing with:
Field returned by the API:
"modifyDate": "2020-02-11T12:23:39.817Z"
Trying to format the date with Joda time:
DateTime time1 = new DateTime("2020-02-11T12:23:39.817Z", DateTimeZone.forID("CST6CDT"));
System.out.println(time1);
DateTime time2 = new DateTime(time1, DateTimeZone.forID("America/Phoenix"));
System.out.println(time2);
System.out.println("----------------------------");
DateTime time3 = DateTime.parse("2020-02-11T12:23:39.817Z");
System.out.println(time3);
System.out.println(time3.toInstant());
System.out.println(time3.withZone(DateTimeZone.forID("America/Phoenix")));
System.out.println(time3.toDateTimeISO());
System.out.println(time3.toDate());
System.out.println("--------------------------------");
Output:
2020-02-11T06:23:39.817-06:00
2020-02-11T05:23:39.817-07:00
----------------------------
2020-02-11T12:23:39.817Z
2020-02-11T12:23:39.817Z
2020-02-11T05:23:39.817-07:00
2020-02-11T12:23:39.817Z
Tue Feb 11 05:23:39 MST 2020
--------------------------------
As you can see in the first two outputs, by trying to apply the time zone for CST, the time provided is offset by -6 (to be expected if the time provided was in UTC). By setting the time zone to America/Phoenix, the offset is -7 (also to be expected). However, as I mentioned, the time that I am passing into DateTime is not UTC, it is CST.
How can I tell DateTime (or even some other library, for that matter) that the time being provided is in CST? Again, keeping in mind that when daylight savings time changes, the offset needs to be managed properly.
In this case, the time being provided by the API was incorrectly being provided as UTC, even though the time is CST, as pointed out by OleV.V. The cleanest solution to this problem was to use DateTime.withZoneRetainFields(), as mentioned by shmosel.
For whatever reason, however, if I created the DateTime object by using the constructor, I couldn't adjust the time zone with withZoneRetainFields(), instead, I had to use DateTime.parse().
I adjusted for the time zone being off by using the following logic:
DateTime time4 = DateTime.parse("2020-02-11T12:23:39.817Z").withZoneRetainFields(DateTimeZone.forID("CST6CDT"));
System.out.println(time4);
System.out.println(time4.withZone(DateTimeZone.forID("America/Phoenix")));
Output (correct)
2020-02-11T12:23:39.817-06:00
2020-02-11T11:23:39.817-07:00
Hopefully, this will help someone else if they come across the same problem.

converting timezone to 3 characters ZoneId

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.

How to get time from user with respect to timezone

Good day,
I am working on a project reporting.
and its my first time i have to deal with datetime.
I have database mongodb, as we know mongodb stores date time in UTC.
now i would like to show data from users provided date and time zone.
for example if i am login in my system i can set my timezone from dropdown. say i choose GMT+05:00 now if i choose date start and end as 2018-07-05 and 2018-07-06
how can i get the proper time with user specified time zone.
I guess if user has selected the time zone GMT+05:00 then date must be start from 2018-07-04 19:00:00 and 2018-07-05 19:00:00 minus 5 hours from given time.
how can i achieve this is java.
String userTimeZone = "Asia/Samarkand";
String userDate = "2018-07-05";
ZoneId zone = ZoneId.of(userTimeZone);
Instant dbInstant = LocalDate.parse(userDate)
.atStartOfDay(zone)
.toInstant();
System.out.println(dbInstant);
This prints what you had expected:
2018-07-04T19:00:00Z
I don’t know MongoDB’s JDBC driver, but I assume it would be happy to accept an Instant and store it in UTC in the database.
GMT+05:00 is not really a time zone, it’s a GMT offset. If your user is in a time zone that uses the same UTC offset always, it would work. But politicians tend to change their minds, so even if that time zone doesn’t use summer time (DST), it may do in a couple of years. And very many time zones already do. Therefore your user should pick a proper time zone like Asia/Tashkent, for example.
Edit: I understand from your comment that MongoDB expects a java.util.Date object. Funny and old-fashioned, but in that case the conversion is straightforward when you know how:
Date dbDate = Date.from(dbInstant);
System.out.println(dbDate);
On my computer in Europe/Copenhagen time zone this printed:
Wed Jul 04 21:00:00 CEST 2018
Don’t be fooled: this is the correct time. Date.toString (implicitly called through System.out.println) grabs my JVM’s time zone setting and uses it for generating the string. The Date itself doesn’t have a time zone in it and holds the same point in time as the Instant.
Link: Oracle tutorial: Date Time
If you already have the user selected time zone what you need to do is parse the date from DB to GMT:
Date dateFromDb = getDateFromDb(); // date from db
LocalDateTime localDateTime = LocalDateTime.ofInstant(new Date().toInstant(), ZoneId.of("GMT")); // parsing date to GMT but using LocalDateTime
Date correctDate = Date.from(localDateTime); // transforming into java.util.Date

Convert Time from one time zone to another using Java 8 Time

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.

Time Zones in Java / GWT (Client-side)

[Client-side GWT class]
I have a Date Object...
Date dataObject = DateTimeFormat.getFormat("yyyy-MM-dd'T'HH:mm:ss.SSS")
.parse("2009-10-12T00:00:00.000);
This works fine. However when I do a:
dateObject.getTime();
It returns a UNIX Time milliseconds using a GMT with daylight savings, therefore making it a UNIX Time I cannot use. I need it in UTC. How do I do this?
Currently I'm parsing a date and it is giving me back:
'Thu Apr 16 08:46:20 GMT+100 2009' # '1239867980191'
However the date I'm passing in is 1 hour less than this time (7:46 and not 8:46!).
How do I pass in the fact it's UTC? Or if it can't use UTC (which would be ridiculous), how do I use GMT without the daylight savings?
Your last edit makes things clearer.
Basically, you are confused, and you already get what you want.
1239867980191 milliseconds since the Epoch translates to Thursday, April 16th, 2009, at 7:46:20.191 in the GMT time zone. The very same instant translates to the same day, but 8:46:20.191 in the GMT+01 time zone. If your input string specified "7:46:20.191" and you indeed got 1239867980191 from Date.getTime() then congratulations, the parsing code understood your "7:46:20.191" as to be interpreted in the GMT time zone, and did it properly.
If afterwards you get "8:46:20" when printing, this is only because you use the GMT+01 time zone for displaying that instant. Note that the string contains GMT+100 precisely to notify you that it uses that time zone for display purposes. The instant which the Date instance represents is nonetheless exactly the instant you wish it to contain. Remember that a Date instance represents an instant in time, for which no notion of time zone applies: time zones are used to convert instants into calendar elements (days, hours...) and back.
To convert a Date to a displayable string, use DateTimeFormat.format(Date, TimeZone) which lets you specify which time zone you want to use for that string.
Since the Calendar class is not supported in GWT, maybe something hackish like this will work:
final String timezone = "GMT-07:00";
DateTimeFormat dtf = DateTimeFormat.getFormat("yyyy-MM-dd'T'HH:mm:ssZZZZ");
long unix = dtf.parse("2009-10-12T00:00:00" + timezone).getTime();
This way you can provide the correct timezone info - though, that should be the default behaviour.
It is the other way round. A Date instance holds the time in milliseconds since the Epoch, using the UTC time scale (i.e. leap seconds are ignored). This is what Date.getTime() returns and that's what you want.
The culprit here is the parser, which interprets the date you give as a string in your local time zone. If you want DateTimeFormat to interpret the string as a date-and-time given in the UTC time zone, append an explicit time zone to the parsed string:
DateTimeFormat.getFormat("yyyy-MM-dd'T'HH:mm:ssZZZZ")
.parse("2009-10-12T00:00:00.000" + " GMT");
(The above assumes that I understood GWT documentation properly; I have not tried.)
Just to be clear in my notations: for all practical purposes, there is no difference between "GMT" and "UTC", and there is no daylight saving in the GMT time zone. Other time zones are often defined as "GMT plus or minus some offset" and the offset may change between summer and winter. For instance, the time zone in New York is somewhat equivalent to "GMT-04" in summer and "GMT-05" in winter.
I keep seeing formats with ZZZZ being suggested... but why?
"yyyy-MM-dd'T'HH:mm:ss.SSSZ" would match
"2009-10-12T00:00:00.000-0000"
The last part being the offset from UTC; California (to use someone else's example time) would be -0800, -0700 in summer.
As a side note, GMT is also always -0000. That's why Britain's summer time zone is BST (British Summer Time, +0100).
Try the Calendar object.
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
Date dataObject = DateTimeFormat.getFormat("yyyy-MM-dd'T'HH:mm:ss.SSS")
.parse("2009-10-12T00:00:00.000);
cal.setTime(dataObject);
cal.getTimeInMillis();
According to the API, getTimeInMillis() returns "the current time as UTC milliseconds from the epoch."
EDIT: as _bravado pointed out, the Calendar API is currently not available for GWT (Issue 603). While this would get the appropriate time in a Java application, it isn't going to work here. There is information in the group about using GMT.
EDIT: Missing a closing bracket on the the Calendar.getInstance() call

Categories