How to manage time zones across multiple systems in java? - java

I have a web application that returns a booking time based on the country where the event was performed.
Eg: If the booking was created in India at 02-JUNE-2020 1700 IST,then time returned is:
2020-06-02T17:00:00+0530
If the booking was created in Thailand at 02-JUNE-2020 1700 Thai Time,then time returned is:
2020-06-02T17:00:00+0700
Now I have to store all this in a system in UK time,so the data would be:
for India,in UK system: 2020-06-02T12:30:00+0100
for Thailand,in UK system: 2020-06-02T11:00:00+0100
I know I can use the zone indicator of +0530 to convert to milliseconds offset by using
TimeZone.getAvailableIDs(milliseconds);
and find the corresponding timezone to do a reverse integration.
But is there an easy way to translate the IST to UK time directly in java ?

+0530 is not actually indicative of any particular time zone. A time zone could be, say, Europe/Amsterdam. This is +0100 in winter and +0200 in summer, and the zone Europe/Paris has the exact same offset at the exact same dates. Whilst unlikely, it is entirely possible that 5 years from now this is no longer the case. Note that +0100 does not accurately describe Europe/Amsterdam (it'd be wrong in summer), and cannot disambiguate between amsterdam and paris, which is why it's not good enough, generally. If this is just what you've been given and you can't change it, yea, getAvailableIDs is one way to at least attempt to convert +0530 into a zone, but note that usually you get many answers, so I don't know how you'd figure out how to pick the 'right' zone. Consider changing the system so that you get this timezone, the full ID, as part of the input instead.
Let's say you have obtained the zone, somehow.
Given 2020-06-02T17:00:00+0530 - you can translate this to the exact moment in time that the event being described by this timestamp has/will occur. That's presumably important; if you want an alarm to go off at that time anywhere on the planet you can now make that happen. That you store this 'in UK time' is just an implementation detail, that doesn't matter: You're storing the instant in time this event occurs, and not the way the human-oriented system that created this timestamp would refer to it (which is, presumably: '5 in the evening, on the second of june in 2020, in india').
But, you indicate a need to convert back from this 'exact instant in time' back to the zoned time.
Why?
If the answer is: So that it is familiar to the human, because the human I will end up printing this string to is definitely in india, you potentially have some issues if you go with the not-human-relevant zone ID of '+0530'; you optimally want to go with the more human-relevant zone ID of 'Asia/Kolkata', for example.
Okay, and now in java please!
an instant in time is best represented with an instance of java.time.Instant. This has no timezone info; it just marks a moment in time. (internally it stores as UTC, but that is an implementation detail. these things are timezoneless).
Once you have an Instant, and you have a TimeZone, you can do:
Instant x = ....; // obtain an instance somehow.
ZoneId zone = ZoneId.of("Asia/Kolkata"); // get a zone somehow.
ZonedDateTime zdt = x.atZone(zone);
You can print a zdt, for example with a java.time.format.DateTimeFormatter instance, and it'll render it as somebody in india would prefer.
If you have instead stored, say, a string containing the text 2020-06-02T12:30:00+0100, you can go from there to an instant rather easily, and then you can .atZone your way back to indian time.

Related

Convert time zone short id to ZoneId

A client's API that I am integrating with returns a time with the time zone as a short id (3 letter id). e.g.
9:00 MST
13:30 EDT
17:00 MDT
I realize this is bad practice (especially with the daylight savings), and short ids should be avoided, but unfortunately I have no way to get the client to change their api. Is there a clean way to convert these short ids to a property ZoneId? ZoneId.SHORT_IDS looks like the closest, but it doesn't include daylights savings. Worst case, I think I can just make my own map of short ids to ZoneIds since it's limited to the US
Time with zone makes no sense
13:30 EDT
A time-of-day plus a time zone makes no sense. Without the context of a date, there is no real meaning here.
The SQL-92 standard declares a TIME WITH TIME ZONE type, but neglects to define its meaning. I am not alone in being mystified by the SQL committee’s intention. And also, importantly, that data type is a misnomer: the SQL standard means offset when they say “time zone”.
Java offers an OffsetTime class. I presume this class exists merely for mapping through JDBC to that screwy SQL type of TIME WITH TIME ZONE. The Javadoc offers no real explanation as to the meaning of this class.
So what is your goal? I suggest you edit your Question to indicate what kind of processing you intend to do with such inputs. Perhaps we can guide you from there.
Pseudo time zones are not unique
You asked:
Is there a clean way to convert these short ids to a property ZoneId?
No.
These 2-4 character pseudo time zones are not defined, are not standardized, and are not unique.
CST — Do you mean Central Standard Time in the Americas? Or do you mean China Standard Time? Or Cuba Standard Time?
IST — Do you mean India Standard Time? Or Ireland Standard Time?
PST — Pacific Standard Time or Pitcairn Standard Time?
BST — Bangladesh Standard Time, Bougainville Standard Time, or British Summer Time?
AMST — Amazon Summer Time or Armenia Summer Time?
So, no you cannot cleanly determine a time zone from a pseudo zone. You can only guess. If you know for certain the domain of possible values you expect to receive, and you are certain as to their intended actual time zone, then you can create your own mapping.
But even then, there is no class in Java nor data type in SQL to represent a date with time zone. You would have to convert that time zone to an offset from UTC. And for that conversion, you would need a date to determine a moment to determine the offset in use at that moment by the people of that zone… which leads us back in a circle to point at the top of this Answer: A time with only a zone (or offset), but without the context of a date, makes no sense.
Reject senseless data
You said:
but unfortunately I have no way to get the client to change their api
Providing a time-of-day with an ambiguous pseudo time zone is like providing a money amount tagged with "dollar" while asking to convert to "francs".
We don't know if "dollar" means Canada Dollar (CAD), United States Dollar (USD), or a Australian dollar (AUD), or any of the twenty currencies with that name.
We don't know if franc means the franc CFA in West Africa, the Swiss franc, or some other currency with that name.
We don't have a date on which to look up a conversion rate.
So what would you do with such a request? What can you do? You would have to either ask for much more explicit definitions, or you would have to refuse the request.

Calculate time from date taken with different timezone

I have a MySQL database which is storing a datetime value, let's say 2020-10-11 12:00:00. (yyyy-mm-dd hh:mm:ss format)
The type of this date (in mysql) is DATETIME
When I retrieve this data in my controller, it has the java 7 type "Date". But it adds a timezone CEST due to my locale I suspect. Here I already find confusing that when displaying this date which is not supposed to have a timezone attached it actually has... and the debugger says it is "2020-10-11 12:00:00 CEST".
My problem is that date was not stored with the CEST timezone. It was stored with the America/New_York one, for example. EDIT: What I mean with this line, is that the date was stored from new york using the timezone of new york. So, it was really 12:00:00 AM there, but here in Madrid it was 18:00:00 PM. I need that 18:00:00.
So in New York, someone did an insert at that time. Which means that the time in Europe was different. I need to calculate which time was in Europe when in America was 12AM. But my computer keeps setting that date to CEST when I retrieve it so all my parsing attempts are failing... This was my idea:
Date testingDate // This date is initialized fetching the "2020-10-11 12:00:00" from mySql
Calendar calendar = new GregorianCalendar()
calendar.setTime(testingDate)
calendar.setTimeZone(TimeZone.getTimeZone("America/New_York")
SimpleDateFormat localDateFormatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
TimeZone localTimeZone = TimeZone.getTimeZone("Europe/Madrid")
localDateFormatter.setTimeZone(localTimeZone)
String localStringDate = localDateFormatter.format(calendar.getTime())
Date newDate = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(localStringDate)
Here my idea is that: I create a brand new calendar, put on it the time that I had on America and I also say hey this calendar should have the America Timezone. So when I get the time of it using a formatter from Europe it should add the corresponding hours to it. It makes a lot of sense in my head but it is just not working in the code D: And I really don't want to calculate the time difference by myself and adding or substracting the hours because that would look extremely hardcoded in my opinion.
Can any one give me some ideas of what I'm interpreting wrong or how should I tackle this problem in a better way?
Important: I'm using java 7 and grails 2.3.6.
My problem is that date was not stored with the CEST timezone. It was stored with the America/New_York one, for example.
From what I know of MySQL, this is impossible.
Calendar calendar = new GregorianCalendar()
No, don't. The calendar API is a disaster. Use java.time, the only time API in java that actually works and isn't completely broken / very badly designed. If you can't (java 7 is extremely out of date and insecure, you must upgrade!), there's the jsr310 backport. Add that dependency and use it.
Let me try to explain how to understand time first, because otherwise, any answer to this question cannot be properly understood:
About time!
There are 3 completely different concepts and they are all often simplified to mean 'time', but you shouldn't simplify them - these 3 different ideas are not really related and if you ever confuse one for another, problems always occur. You cannot convert between these 3 concepts unless you do so deliberately!
"solarflares time": These describe the moment in time as a universal global concept that something occurred or will occur. "That solar flare was observed at X" is a 'solarflares' time. Best way to store this is millis since epoch.
"appointment time": These describe a specific moment in time as it was or will be in some localized place, but stated in a globally understandable way. "We have a zoom meeting next tuesday at 5" is one of these. It's not actually constant, because locales can decide to adopt new timezones or e.g. move the 'switch date' for daylight savings. For example, if you have an appointment at your dentist on 'november 5th, at 17:00, 2021', and you want to know how many hours are left until your appointment starts, then the value should not change just because you flew to another timezone and are looking at this number from there. However, it should change if the country where you made the appointment in decided to abolish daylight savings time. That's the difference between this one and the 'solarflares' one. This one can still change due to political decisions.
"wake-up-alarm time": These describe a more mutable concept: Some way humans refer to time which doesn't refer to any specific instant or is even trying to. Think "I like to wake up at 8", and thus the amount of time until your alarm will go off next is continually in flux if you are travelling across timezones.
Now, on to your question:
I have a MySQL database which is storing a datetime value, let's say 2020-10-11 12:00:00. (yyyy-mm-dd hh:mm:ss format)
Not so fast. What exact type does that column have? What is in your CREATE TABLE statement? The key thing to figure out here is what is actually stored on disk? Is it solarflare, appointment, or wakeup-alarm? There's DATE, DATETIME and TIMESTAMP, and over the years, mysql has significantly changed how these things are stored.
I believe that, assuming you are using the modern takes on storage (So, newish mysql and no settings to explicitly emulate old behaviour), e.g. a DATETIME stores sign, year, day, hour, minute, and second under the hood, which means it is wakeup alarm style: There is no timezone info in this, therefore, the actual moment in time is not set at all and depends on who is asking.
Contrast to TIMEZONE which is stored as UTC epoch seconds, so it's solarflares time, and it doesn't include any timezone at all. You'd have to store that separately. As far as I know, the most useful of the 3 time representations (appointment time) is not a thing in mysql. That's very annoying; mysql tends to be, so perhaps par for the course.
In java, all 3 concepts exist:
solarflares time is java.time.Instant. java.util.Date, java.sql.Timestamp, System.currentTimeMillis() are also solarflares time. That 'Date' is solarflares timestamp is insane, but then there is a reason that API was replaced.
appointment time is java.time.ZonedDateTime
wakeup-alarm time is java.time.LocalDateTime.
When I retrieve this data in my controller, it has the java 7 type "Date".
Right. So, solarflares time.
Here's the crucial thing:
If the type of time stored in MySQL does not match the type of time on the java side, pain happens.
It sure sounds like you have wakeup-alarm time on disk, and it ends up on java side as solarflares time. That means somebody involved a timezone conversion. Could have happened internally in mysql, could have happened in flight between mysql and the jdbc driver (mysql puts it 'on the wire' converted), or the jdbc driver did it to match java.sql.Timestamp.
The best solution is not to convert at all, and the only real way to do that is to either change your mysql table def to match java's, so, make that CREATE TABLE (foo TIMESTAMP), as TIMESTAMP is also solarflares time, or, to use, at the JDBC level, not:
someResultSet.getTimestamp(col);
as that returns solarflares time, but:
someResultSet.getObject(col, LocalDateTime.class);
The problem is: Your JDBC driver may not support this. If it doesn't, it's a crappy JDBC driver, but that happens sometimes.
This is still the superior plan - plan A. So do not proceed to the crappy plan B alternative unless there is no other way.
Plan B:
Acknowledge that conversion happens and that this is extremely annoying and errorprone. So, make sure you manage it, carefully and explicitly: Make sure the proper SET call is set up so that mysql's sense of which timezone we are at matched. Consider adding storing the timezone as a column in your table if you really need appointment time. etcetera.
Thanks to #rzwitserloot I was able to find out a solution.
First I'll get the data from the database. I'll get rid of any timezone added by the driver / mysql by converting it to a LocalDateTime. Then, I'll create a new ZonedDateTime using the Timezone that was used when storing the data in the database.
Once I have a ZonedDateTime, it is time to convert it using my current timezone. I'll get a new ZonedDateTime object with the proper time.
Then I just add a few more lines to convert it back to my main "Date" class:
I've used the ThreeTen backport as suggested.
Date dateMySQL //Initialized with the date from mysql
Calendar calendar = new GregorianCalendar()
calendar.setTime(dateMySQL)
org.threeten.bp.LocalDateTime localDateTime = org.threeten.bp.LocalDateTime.of(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH)+1,
calendar.get(Calendar.DAY_OF_MONTH), calendar.get(Calendar.HOUR_OF_DAY), calendar.get(Calendar.MINUTE),
calendar.get(Calendar.SECOND))
String timezone //Initialized with the timezone from mysql (Ex: "America/New_York")
ZonedDateTime zonedDateTime = ZonedDateTime.of(localDateTime, ZoneId.of(timezone))
ZonedDateTime utcDate = zonedDateTime.withZoneSameInstant(ZoneId.of("Europe/Madrid"))
calendar.setTimeInMillis(utcDate.toInstant().toEpochMilli())
Date desiredDate = calendar.time
dateMySQL: "2020-10-11 10:00:00" // CEST due to my driver
timezone: "America/New_York"
desiredDate: "2020-10-11 19:00:00" // CEST Yay!

Get timezone shortcut based on UTC offset in Java

I want to get the timezone shortcut like EST (for eastern standard), PST (pacific), and so on based on the UTC offset. I realize it's not a simple problem and there can be more than one location based on a particular offset, but that's okay.
I'm trying to get it using Util Calendar object but I don't seem to get a string but rather just the offset.
public String foo(int offset)
{
....
return TimeZoneShortcut;
}
Thanks in advance.
The answer by user2580516 is correct. I can add a bit more.
Avoid Three-Letter Codes
The three-letter time zone IDs are neither standardized nor unique. Avoid them.
For example, IST is used to mean India Standard Time or Irish Standard Time. There are many such collisions.
Time Zone Names
Instead of 3-letter codes, use proper time zone names. Examples: "Europe/Paris", "America/Montreal", and "Asia/Kolkata".
There does not seem to be an official standard for time zone names. That surprises me; hopefully I'm wrong and someone can fill me in. At any rate, a commonly used list is take from the tz database (formerly known as the Olson database), as listed in this Wikipedia page.
The excellent date-time library, Joda-Time, has a method to generate a list of its currently known time zone names.
The time zone names change over time, some are added, and their rules change too. All that is determined by politicians and bureaucrats, so changes are last-minute and not always sensible. So you should take care to keep your date-time library up-to-date, or at least update its contained time zone database.
Impossible Question – Cannot Determine Time Zone
A time zone is more than just an numerical offset from UTC/GMT. A time zone also contains the set of rules for Daylight Saving Time (DST) and other anomalies.
So you cannot infer a time zone from an offset. You can guess, but you cannot be sure.
For example, take the offset of +01:00. Is that "Europe/Paris" or "Africa/Lagos"? Both have an offset of one hour ahead of UTC. So does it matter which you use? Yes… France observes Daylight Saving Time but Nigeria does not. Assigning the wrong time zone means your date-time calculations will be wrong.
Another twist… Perhaps that +01:00 was recorded in London during the summer time. In summer, London observes DST and moves its clocks 1 hour ahead. While standard time there is +00:00 (on UTC/GMT), DST moves them one hour ahead of that.
Yet another twist… Even if you say "just pick one", which one? For +00:00 in just standard time, there are at least 2 three-letter codes (CET and MET) and 37 named time zones crossing two continents.
Perhaps you are thinking, "I can use the date to figure out if DST was in effect". Nope, DST starts and ends on different dates in various time zones sharing the same offset. Furthermore, some countries (time zones) are sensible enough to not fool with DST.
So regarding your question being "not a simple problem … but that's okay" is wrong. It's not a problem, it's impossible. Like the question, "Given a birthday, determine an individual person". You can determine that a person or time zone is not correct, but you cannot determine which is correct.
Record Time Zone With Time
If knowing the time zone (its locality and rules) is important to you, you must record the zone information along with the date-time. This may mean an extra field in your database for example.
Java 8 brings a new java.time.8 package, inspired by Joda-Time, defined by JSR 310. The designers have come to realize the importance of the time zone as a part of a date-time value. As a result, their designs include:
The main date-time class starts with the word "Zoned" to stress that the class includes time zone info: ZonedDateTime
Their toString implementation on the ZonedDateTime class extends the ISO 8601 format by appending the name of the time zone in brackets. Instead of:2014-02-14T20:51:55.427-08:00it outputs2014-02-14T20:51:55.427-08:00[America/Los_Angeles]
Use TimeZone.getAvailableIDs(), and select one (maybe the first) that has only three letters. You'll have to adjust the offset to numeric milliseconds to pass into that function. Use of the three letter IDs is deprecated, but it sounds like you are okay with that.

C# Ticks convert to java util date; date is 5 hours behind why?

I need help. I have been trying to figure out why java util date is 5 hours behind after converting from C# ticks.
in C#, the date is 6/8/2013 11:02:07 AM, I convert this date into ticks then pass it to java as long.
code snippet:
taken:
- long TICKS_AT_EPOCH = 621355968000000000L;
- long TICKS_PER_MILLISECOND = 10000;
java.util.Date date = new java.util.Date((ctime - TICKS_AT_EPOCH) / TICKS_PER_MILLISECOND);
Now java util date is Sat Jun 08 06:02:07 CDT 2013
Notice that the hour is 5 hours difference.
Any suggestions why?
You are constructing a java.util.Date based on milliseconds since 1/1/1970 UTC. You appear to be correcting from the fact that .net's System.DateTime.Ticks are based on 1/1/0001 and are 10,000 ticks to a millisecond. That is correct, but you have forgotten to adjust to UTC.
In .Net, the value coming from DateTime.Ticks is highly dependent on the DateTime.Kind property. There are three possible kinds of DateTime values.
DateTimeKind.Utc - This kind means that the value represents UTC time. It usually comes from a call to DateTime.UtcNow, but can also be constructed directly, and often is. For example, you might be retrieving UTC times from a database. You can feed the ticks from here directly into your conversion, and it will work.
DateTimeKind.Local - This usually comes from a call to DateTime.Now. The values are representative of the local time zone. You will need to convert to UTC before checking the ticks. You can do the following:
DateTime dt = DateTime.Now;
int utcTicks = dt.ToUniversalTime().Ticks;
Be aware that if the time happens during a daylight saving "fall-back" style transition, the result might be incorrect. The DateTime class has no idea about time zones. It just reflects the current local clock. If the value in dt is ambiguous, ToUniversalTime() will assume that the value is representative of standard time, even if you just retrieved it while in daylight time. This is just one of the many confusing and probablematic aspects of DateTime in .net.
DateTimeKind.Unspecified - This is the most common kind of DateTime you will encounter, and usually comes from DateTime.Parse() or a constructor like new DateTime(...). Unfortunately, there is nothing in here that will tell you about the time zone these dates are representative of. You can still try calling .ToUniversalTime(), but the framework will make the assumption that these times are representative of your local time zone, as if the kind was Local. That assumption could be completely wrong, depending on how you sourced the data. There really is no safe way to transform an Unspecified DateTime to a UTC value (ticks or otherwise).
There are some solutions, such as using DateTimeOffset instead of DateTime, or using the Noda Time library instead of the built-in types. You can read more about these problems here and here.
The time is not 5 hours behind, it's exactly the same time. The problem is with the way you print it.
You need to tell C# and Java to use the same time-zone when converting the date to string. One of them is using UTC and the other CDT.
java.util.date automatically corrects for your time zone. See this question: How to set time zone of a java.util.Date?
The ctime is UTC (Universal Coordinated Time), which is a time standard referenced to Greenwich. You're expressing your time in Central time. There's your difference.

Arizona TimeZone Day Light Saving

I am trying to get current time in specific time zones. I tried following code.
Calendar j = new GregorianCalendar(TimeZone.getTimeZone("US/Mountain"));
j.setTimeInMillis(Calendar.getInstance().getTimeInMillis());
System.out.println(j.get(Calendar.HOUR_OF_DAY)+":"+j.get(Calendar.MINUTE));
TimeZone tz = TimeZone.getTimeZone("US/Mountain");
System.out.println("tz.getRawOffset()"+tz.getRawOffset()/3600);
System.out.println(tz.getDSTSavings());
System.out.println
(tz.inDaylightTime(new Date()));
The answer I got is surprising -
16:57 - wrong by 1 hr
tz.getRawOffset()-7000
3600000
true - why daylight saving is true for Arizona?
How to get the correct wall clock time of Phoenix or any other city in US?
Arizona is in the Mountain timezone, but doesn't observe DST. If you specify the timezone "US/Mountain", then the computer will apply the rules used by most states in the Mountain time zone, which include observing daylight savings time. To get the rules for Arizona (which don't include DST), you want the timezone "US/Arizona" (or "America/Phoenix"). In the Navajo nation, you want the timezone named "Navajo".
To save yourself some of the trouble, always try to use the names from "America/*" where you can pick the name of a city that has the same timezone rules as the place you're interested in.
To get the correct time in the correct timezone for any given city in the world, you simply have to familiarize yourself with the names in the Olson timezone database and their meanings. While you usually think of the term "time zone" to mean the time of day in the middle of the winter (when everybody observes standard time), in the Olson database a timezone name represents the entire history of daylight savings time rules and timezone rules for a particular region.
As an example, even though Indiana now observes Eastern time and observes DST (except for a few counties right near Chicago which are on Central time like Chicago), before 2006 they didn't observe DST. A timezone named "US/Indiana" (or "America/Indianapolis") was created to cover this region, an even today, you would still want to use the timezone "America/Indianapolis" when talking about Indiana, so that queries about dates and times before 2006 could be answered correctly.
Most of Arizona does not observe Daylight Savings Time, but the Navajo Nation inside Arizona does observe DST.
I am providing the modern answer. Don’t use the classes Calendar, GregorianCalendar, TimeZone and Date. They are all poorly designed and fortunately all long outdated.
java.time
It’s simple when you know how:
ZonedDateTime arizonaExceptNavajo = ZonedDateTime.now(ZoneId.of("America/Phoenix"));
System.out.println(arizonaExceptNavajo);
When I ran this code just now, the output was:
2019-10-23T04:07:23.034-07:00[America/Phoenix]
The US/Mountain time zone ID is deprecated. It’s a link to America/Denver, and America/Denver does use summer time (daylight saving time, DST). Modern time zone IDs have the form region/city where region is either a continent like America or an ocean like Pacific.
As others have said, summer time is used on one place in Arizona, the Navajo Nation. The Navajo time zone ID mentioned is deprecated too. Use America/Denver:
ZonedDateTime navajoNation = ZonedDateTime.now(ZoneId.of("America/Denver"));
System.out.println(navajoNation);
2019-10-23T05:07:23.037-06:00[America/Denver]
Since summer time is still in effect, the time of day is one hour ahead compared to the one for Phoenix above.
Links
Oracle tutorial: Date Time explaining how to use java.time.
Navajo Nation on Wikipedia.
The zone information can be obtained directly for many major cities worldwide. This includes Phoenix, which has the zone identifier "America/Phoenix".
You can find the list of available zone identifiers using the method TimeZone.getAvailableIDs() or by manually inspecting the contents of the JRE lib/zi directory.
As other posters have noted, the "US/Arizona" zone information is distinct from "US/Mountain".

Categories

Resources