What is logic of manipulation with timezones in Java/Kotlin? - java

Let's assume that I have client's time saved in my database as 2020-09-22T10:50:37.276240900
I need to present this date in web-service for client app depending on client timezone, for example I need to add 2 hours to saved date if client lives in UTC+2 timezone.
So what am I doing for ?
Getting date from entity and adding timezone to time taken from database (startDate: LocalDateTime)
entity.startDate.atZone(ZoneId.of("Europe/Vienna"))
what gives me the value of ZonedDateTime 2020-09-22T10:50:37.276240900+02:00[Europe/Vienna]
This value is what I'm expecting for, basically "initial time plus 2 hours". After that I would to format this time to have output with this 2 hours of being added, some kind of this
12:50 22.09.2020
but when I do format like this
entity.startDate
.atZone(ZoneId.of("Europe/Vienna"))
.format(DateTimeFormatter.ofPattern(NotificationListener.EUROPEAN_DATE_FORMAT, Locale.ENGLISH))
where const val EUROPEAN_DATE_FORMAT = "HH:mm dd.MM.yyyy"
I get this output 10:50 22.09.2020 which looks like my format is not applied properly, so I cannot see my 2 hours.
So my questions are:
am I correct to adding timezone of client app in described way ?
how to apply timezone in more precise way and format this date to see timezone zone applied ?

LocalDateTime.atZone does not "move" the point in time. In fact it tries to present the point in time where the local time in the given timezone is exactly what the LocalDateTime shows.
In other words: if your LocalDateTime represented 10:00 at some date, then the ZonedDateTime output of atZone will also represent 10:00 local time at the specified time zone (except in cases where that local time doesn't exist due to DST changes).
So if your stored time is actually in UTC, you need to add one more step:
ZonedDateTime utcTime = entity.startDate.atZone(ZoneOffset.UTC);
ZonedDateTime localTime = utcTime.withZoneSameInstant(ZoneId.of("Europe/Vienna"));
Alternatively you can avoid calculating the localTime each time and instead configure the DateTimeFormatter to use a given time zone (which means it'll do the necessary calculations internally) using DateTimeFormatter.withZone. If you do this then you can pass the utcTime to it directly.

Related

Java: Fix incorrect timezone in date object

An external API returns an object with a date.
According to their API specification, all dates are always reported in GMT.
However, the generated client classes (which I can't edit) doesn't set the timezone correctly. Instead, it uses the local timezone without converting the date to that timezone.
So, long story short, I have an object with a date that I know to be GMT but it says CET. How can I adjust for this mistake withouth changing my local timezone on the computer or doing something like this:
LocalDateTime.ofInstant(someObject.getDate().toInstant().plus(1, ChronoUnit.HOURS),
ZoneId.of("CET"));
Thank you.
tl;dr ⇒ use ZonedDateTime for conversion
public static void main(String[] args) {
// use your date here, this is just "now"
Date date = new Date();
// parse it to an object that is aware of the (currently wrong) time zone
ZonedDateTime wrongZoneZdt = ZonedDateTime.ofInstant(date.toInstant(), ZoneId.of("CET"));
// print it to see the result
System.out.println(wrongZoneZdt.format(DateTimeFormatter.ISO_ZONED_DATE_TIME));
// extract the information that should stay (only date and time, NOT zone or offset)
LocalDateTime ldt = wrongZoneZdt.toLocalDateTime();
// print it, too
System.out.println(ldt.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
// then take the object without zone information and simply add a zone
ZonedDateTime correctZoneZdt = ldt.atZone(ZoneId.of("GMT"));
// print the result
System.out.println(correctZoneZdt.format(DateTimeFormatter.ISO_ZONED_DATE_TIME));
}
Output:
2020-01-24T09:21:37.167+01:00[CET]
2020-01-24T09:21:37.167
2020-01-24T09:21:37.167Z[GMT]
Explanation:
The reason why your approach did not just correct the zone but also adjusted the time accordingly (which is good when desired) is your use of a LocalDateTime created from an Instant. An Instant represents a moment in time which could have different representations in different zones but it stays the same moment. If you create a LocalDateTime from it and put another zone, the date and time are getting converted to the target zone's. This is not just replacing the zone while keeping the date and time as they are.
If you use a LocalDateTime from a ZonedDateTime, you extract the date and time representation ignoring the zone, which enables you to add a different zone afterwards and keep the date and time as it was.
Edit: If the code is running in the same JVM as the faulty code, you can use ZoneId.systemDefault() to get the same time zone as the faulty code is using. And depending on taste you may use ZoneOffset.UTC instead of ZoneId.of("GMT").
I am afraid you will not get around some calculations here. I'd strongly suggest to follow an approach based on java.time classes, but alternatively you might use the java.util.Calendar class and myCalendar.get(Calendar.ZONE_OFFSET) for those calculations:
https://docs.oracle.com/javase/8/docs/api/java/util/Calendar.html#ZONE_OFFSET

What is the difference between ZonedDateTime.withZoneSameInstant and ZonedDateTime.withZoneSameLocal?

Let's say I have a ZonedDateTime:
ZonedDateTime zonedDateTime =
ZonedDateTime.of(LocalDateTime.now(), ZoneId.of("US/Pacific"));
I would like to know which date/time it is let's say in Berlin.
I have two methods :
zonedDateTime.withZoneSameInstant(ZoneId.of("Europe/Berlin")); // probably this is the right one to get the corresponding date/time in Berlin
zonedDateTime.withZoneSameLocal(ZoneId.of("Europe/Berlin"));
The docs for the withZoneSameLocal method say: "The local date-time is only changed if it is invalid for the new zone..." and it's not clear when this really can happen (any example ? =)).
Which date/time each of them represents and what is the difference?
If you want to convert a timestamp from one timezone to another, use withZoneSameInstant(). withZoneSameLocal() will change the zone but keep all the other fields the same. The exception is where it would be an invalid date in that timezone.
For example,
ZonedDateTime dtUTC = ZonedDateTime.parse("2019-03-10T02:30:00Z");
ZoneId pacific = ZoneId.of("US/Pacific");
System.out.println(dtUTC.withZoneSameInstant(pacific));
System.out.println(dtUTC.withZoneSameLocal(pacific));
prints
2019-03-09T18:30-08:00[US/Pacific]
2019-03-10T03:30-07:00[US/Pacific]
The first line is the original timestamp converted to another timezone. The second tries to preserve the date/time fields, but 2:30 is not a valid time on that date (because of the Daylight Savings jump), so it shifts it by an hour.

Convert database time to IST datetime

I have a time value stored in my database in HH:mm:ss format (using MySQL's time type). This time is to be considered as a value of IST timezone. The server on which my Java code runs follows the UTC timezone.
How can I get a formatted datetime in yyyy-MM-dd HH:mm:ss in IST (or in UTC millis)? Following is what I've tried till now:
// ... Code truncated for brevity
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalTime time = resultSet.getTime("send_time").toLocalTime();
LocalDateTime datetime = time.atDate(LocalDate.now());
System.out.println(datetime.format(formatter));
The above correctly prints the datetime on my local machine, which is on IST, but I'm concerned about how it will behave on the remote server.
Your approach is fine and should work regardless of your computer's time zone since there is no time zone information in either LocalTime or LocalDateTime. One possible issue is with LocalDate.now() which returns today's date in the computer's local time zone, not in IST. You may want to replace it with LocalDate.now(ZoneId.of("Asia/Calcutta")).
Or as commented by #OleV.V. you could use the new driver facilities to derive a LocalTime directly:
LocalTime time = resultSet.getObject("send_time", LocalTime.class);
Note possible caveats with your approach:
if the time zone you use introduces DST, you may end up with two identical times in your DB that were actually different instants - using UTC to store times is probably more robust
time in mysql can store values smaller than 00:00 and larger than 23:59:59.999999, in which case you may experience unexpected behaviours on the Java side.

best way to convert datetimes in globally synchronised system architecture

We are working on a Customer Data Integration project (using Java 8), which has a central database that is kept synchronised with local databases in other countries.
When a new or updated contact request comes from a local system to our central system, a modifiedAt value is passed (which is the local datetime stamp value in their time zone)
We convert this into UTC and store it in our database. (To do this we store the time zone offsets for each system). When any system requests that contact object, we convert the stored modifiedAt value from UTC into their local datetime.
Is this the best way to do this? What about issues with daylight savings times? Does the central system need to keep track of when DST starts and stops for each of the local systems?
Thanks in advance
Don't store the timezone offset. Store the timezone itself.
The offset of "Europe/Paris" is different in the winter and in the summer, due to DST.
But if I know that the timezone is "Europe/Paris", I'm able to reliably convert any French local date to a UTC timestamp, because I can find the right offset for that local date.
(actually, I can convert almost any date reliably, because some local dates are ambiguous, when the time goes back from 3AM to 2AM).
Why don't the local systems provide a UTC timestamp directly, instead of providing a local datetime?
Totally agree with JB Nizet's answer: you should store the time zone instead of just the offset. In Java 8, you can use the ZonedDateTime class to accomplish this.
It contains methods such as:
public static ZonedDateTime of(LocalDateTime localDateTime, ZoneId zone)
which allow to easily convert a local dateTime to a zoned dateTime. Then, you can move this zoned dateTime to UTC with similar methods, i.e:
public ZonedDateTime withZoneSameInstant(ZoneId zone)
You are looking at storing two information Instant and ZoneId.
In your database, you store the time as Instant. Whenever any of your server asks for the time, you convert that to ZonedDateTime using Instant value stored in database, and ZoneId either passed by server, or stored in database as well.
This allows you to easily query databases in cases you want "All objects that were updated in last hour".
Instant to ZonedDateTime
Instant instant = Instant.now();
// Japan = UTC+9
ZonedDateTime zonedDateTime = instant.atZone(ZoneId.of("Asia/Tokyo"));
ZonedDateTime to Instant
zonedDateTime.toInstant();

android java time

i'm building an android application which have a chat.
in this chat i each message to have its time sent signature.
my question is as follow:
lets say that the time in my country is X. my friend is abroad and his time is X minus 7 hours.
i'm sending him a message at 16:00 local time.
i want to avoid the situation that he will get at 09:00 a message which it signature will be 16:00 (which is a time in future if you're looking in the eyes of that friend in his country).
is there a way that in my phone the message will be written as 16:00 and in his phone it will be written as 09:00 ? i there a way to convert a time to a local time ?
System.currentTimeMillis() does give you the number of milliseconds since January 1, 1970 00:00:00 UTC. Date object does not save your local timezone.
You can use DateFormats to convert Dates to Strings in any timezone:
DateFormat df = DateFormat.getTimeInstance();
df.setTimeZone(TimeZone.getTimeZone("gmt"));
String gmtTime = df.format(new Date());
linked response
You should keep all time communications using UTC time. Then localize it for display based on the devices current timezone setting.
Use a long to save your time information as milliseconds since "epoch" (which is January 1, 1970, 00:00:00 GMT). It can be retreived with the Date.getTime() method and new Date objects are easily created using the Date(long millis) constructor. The Date objects are then displayed using the local timezone settings on each device.
EDIT:
Epoch is a defined point in time which is expressed differently in different time zones: 1970-01-01 00:00:00 GMT but
1969-12-31 19:00:00 EST. The timestamp is just the number of milliseconds elapsed since that time. So, for example the timestamp 1341169200 corresponds to 2012-07-01 19:00:00 GMT and 2012-07-01 14:00:00 EST.
You will need to save the time zone which your message will be saved in, and transfer it (or send the unix epoch time) and then on the other side make sure you read it in with the Locale time (using the Android documentation for things like http://developer.android.com/reference/java/util/Calendar.html can help).
Take a look at the answer over here:
https://stackoverflow.com/a/6094475/346232
You need to change the time to UTC and then convert on the device to the timezone.
Avoid java.util.Date/.Calendar
The java.util.Date/.Calendar classes bundled with Java (and Android) are notoriously troublesome, flawed in both design and implementation.
Joda-Time
The Joda-Time library is the way to go. This library inspired the java.time package now built into Java 8 (not available on Android).
UTC
As other answers suggested, the best practice (generally) is to keep your business logic and data storage/communication in UTC time zone (which some think of as no time zone or an "anti" time zone). Adjust to a specific time zone only when expected by the user or data-consumer.
Time Zone
The DateTime class in Joda-Time represents a date-time value along with an assigned time zone.
Note that it is best to specify a time zone in all your operations. Otherwise you will be implicitly relying on the JVM’s current default time zone. This is risky because that zone can change – even at runtime at any moment by any code in any thread of any app running within your app’s JVM. And use proper time zone names, never the 3-4 letter codes.
Example Code
Example code in Joda-Time 2.7.
DateTime sent = DateTime.now( DateTimeZone.getDefault() ) ;
DateTime sentUtc = nowMine.withZone( DateTimeZone.UTC ) ; // Generally, use this for your work, including communicating to other threads and apps and such.
When ready to display to the other user, adjust to the expected time zone.
DateTimeZone zone = DateTimeZone.forID( "America/Montreal" ) ; // Or DateTimeZone.getDefault() if you want to rely on their JVM’s current default. To be absolutely sure of expected time zone, you really must ask the user.
DateTime sentMontréal = sentUtc.withZone( zone );
To generate a textual representation of those date-time objects, search the many Questions and Answers on StackOverflow.com on that subject. Search for terms like "joda" and "DateTimeFormatter" and "DateTimeFormat".

Categories

Resources