Jodatime time difference between 2 timezones - java

What api do I have to use to get the time difference between 2 timezones without inputing any datetime.
What I am doing right now, is make a temporary date (midnight) from one of the timezone then convert to utz and then convert again to the other timezone and then compute the duration of the two. It is working but is there a simpler api to get the time difference by just giving the name of the zones?
Too long for comment, so I put it here.
server is on diff. timezone, data/informations come from clients from different timezones and save in the db in utc. Problem arise when the client request a transaction history in a certain date. It seems it is not a simple conversion of the requested date (client timezone) to utc, I need to add the time difference of the server timezone and the client timezone to the converted utc time to get the correct date. (so i do it like I said above). Now I found out that the time difference should be added or subtracted, depending on whos timezone is ahead. Well anyway thanks for everybody's inputs. Need the project to run asap so they decided to use just one timezone for the meantime. Implemented or not I will seek a solution to this for future projects. :)

I'm not sure if I understood your question correctly. Are you trying to get offsets of different timezone without using any additional API?
Below code will do that using plain Java:
String[] ids = TimeZone.getAvailableIDs();
HashMap<String, Integer> map = new HashMap<String, Integer>();
for (String id : ids) {
TimeZone tz = TimeZone.getTimeZone(id);
map.put(id, tz.getOffset(new Date().getTime()) / 1000 / 60); //in minutes
}
System.out.println(map.toString());

private final static int tzDiff = TimeZone.getDefault().getRawOffset() - TimeZone.getTimeZone("America/New_York").getRawOffset();

Joda-Time in maintenance mode
Since you asked your Question, the creator of Joda-Time went on to lead JSR 310 to bring a new set of date-time classes to Java. Those classes arrived in Java 8.
Since then, the Joda-Time project was put into maintenance mode, with the project recommending migration to java.time.
Comparing time zones requires a moment
get the time difference between 2 timezones without inputing any datetime.
That makes no sense. You must specify a moment, a point on the time line, to compare offsets in effect in two different time zones.
Understand that an offset is merely a number of hours-minutes-seconds ahead or behind the prime meridian of time-keeping, UTC.
A time zone is much more. A time zone is a history of the past, present, and future changes to the offset in use by the people of a particular region as decided by their politicians.
By definition, the offset in use varies over time for any particular time zone. So it makes no sense to attempt a comparison of time zones without a specific point in time.
What I am doing right now, is make a temporary date (midnight) from one of the timezone then convert to utz and then convert again to the other timezone and then compute the duration of the two.
I am not sure of your goal, but here is code for doing something similar.
Specify your pair of time zones in which you are interested.
ZoneId zTokyo = ZoneId.of( "Asia/Tokyo" ) ;
ZoneId zEdmonton = ZoneId.of( "America/Edmonton" ) ;
Specify a date.
LocalDate ld = LocalDate.of( 2022 , Month.JANUARY , 23 ) ;
Determine when the day of that date begins in Tokyo time zone. Do not assume the day starts at 00:00. Some dates in some zones may start at a different time. Let java.time determine the first moment of the day.
ZonedDateTime firstMomentOfDayTokyo = ld.atStartOfDay( zTokyo ) ;
Adjust to see that some moment as it looks in the wall-clock time of Edmonton Alberta Canada.
ZonedDateTime zdtEdmonton = firstMomentOfDayTokyo.atZoneSameInstant( zEdmonton ) ;
See that same moment again with an offset of zero by extracting an Instant object.
Instant instant = firstMomentOfDayTokyo.toInstant() ;
Here is the crucial point to understand: firstMomentOfDayTokyo, zdtEdmonton, and instant all represent the very same moment, the simultaneous same point on the time line. Imagine three people in a conference call originating from Japan, Canada, and Iceland (where their time zone is always zero offset). If they all simultaneously looked up at the clock hanging on their respective wall, they would all see different local time-of-day yet be experiencing the same simultaneous moment.
Now that we have an Instant object in hand, we can proceed to solving your challenge: Getting the offset in use for each of those two time zones.
Fetch the time zone rules for each.
ZoneRules rulesTokyo = zTokyo.getRules() ;
ZoneRules rulesEdmonton = zEdmonton.getRules() ;
Get the offset in effect in each zone at that moment. Notice the use of ZoneOffset class here rather than ZoneId.
ZoneOffset offsetTokyo = rulesTokyo.getOffset( instant ) ;
ZoneOffset offsetEdmonton = rulesEdmonton.getOffset( instant ) ;
Calculate the difference between each zone.
Duration d = Duration.ofSeconds( offsetTokyo.getTotalSeconds() - offsetEdmonton.getTotalSeconds() ) ;
But this offset comparisons between time zones is not the best solution to your underlying problem. Read on for a better solution.
Querying database for a day
You said:
server is on diff. timezone
You should always do your Java programming is such a way as to not be affected by the current default time zone of your servers. Always specify the otherwise optional time zone (or offset) arguments to the various date-time related Java methods.
save in the db in utc
Good. It is generally best to store moments after adjusting to UTC. Some relational databases such as Postgres make such an adjustment when receiving inputs to a column of a type akin to the SQL standard type TIMESTAMP WITH TIME ZONE.
Retrieve such a value in JDBC 4.2 and later using java.time classes.
OffsetDateTime odt = myResultSet.getObject( … , OffsetDateTime.class ) ;
Write such a value to the database.
myPreparedStatement.setObject( … , odt ) ;
You said:
problem arise when the client request a transaction history in a certain date. It seems it is not a simple conversion of the requested date (client timezone) to utc, I need to add the time difference of the server timezone and the client timezone to the converted utc time to get the correct date.
No, not the best approach. You are thinking in terms of local time zones.
When you go to work as a programmer, DBA, or system-admin, you should forget about your local time zone. Learn to think in UTC, with an offset of zero. Logging, data storage, data exchange, and most of your business logic should all be done in UTC. Keep a second clock on your desk set to UTC; I’m serious, your life will be easier.
As an example, let's say your user wants to query for all records with a timestamp the occurred at some point during one full day as seen in Tokyo Japan.
To query for a day's worth of records, do much the same as we saw above. There we got the first moment of the day in Tokyo.
ZoneId zTokyo = ZoneId.of( "Asia/Tokyo" ) ;
LocalDate ld = LocalDate.of( 2022 , Month.JANUARY , 23 ) ;
ZonedDateTime firstMomentOfDayTokyo = ld.atStartOfDay( zTokyo ) ;
That is the beginning of the span of time (a day) over which we want to query.
Generally best to define a span of time using Half-Open approach. In this approach the beginning is inclusive while the ending is exclusive. So a day starts at first moment of a date and runs up to, but does not include, the first moment of the following date.
ZonedDateTime firstMomentOfFollowingDayTokyo = ld.plusDays( 1 ).atStartOfDay( zTokyo ) ;
The OffsetDateTime is the class that maps to the TIMESTAMP WITH TIME ZONE type defined in standard SQL. So extract a OffsetDateTime from each.
OffsetDateTime start = firstMomentOfDayTokyo.toOffsetDateTime() ;
OffsetDateTime end = firstMomentOfFollowingDayTokyo.toOffsetDateTime() ;
Write your SQL like this:
SELECT *
FROM event_
WHERE when_ >= ?
AND when_ < ?
;
Do not use the SQL command BETWEEN for this work. That command is Fully-Closed rather than Half-Open.
In JDBC:
myPreparedStatement.setObject( 1 , start ) ;
myPreparedStatement.setObject( 2 , end ) ;
Keep in mind that a full day in a particular time zone is not necessarily 24-hours long. A day may be 23, 23.5, 25, or some other number of hours long because of anomalies in political time such as Daylight Saving Time (DST).

Related

Spring Boot Java Time zone Bangkok (GMT+8) or another time zone with GMT+8 with ZonedDateTime using java.sql.Timestamp

Good day, it's not some errors. But can someone explain to me what happened here?
I'am using free sql database with Spring framework so basically my sql database was on the cloud. Then I'm inserted date time value to my database using my local time zone (Indonesia (GMT+8)). But the time zone is not correct with my time zone even I'm already used localdatetime.now()
This was the case :
Set up my localdatetime zone
LocalDateTime ldt = LocalDateTime.now();
Using my system default time zone
ZonedDateTime zdt = ZonedDateTime.of(ldt, ZoneId.systemDefault());
This is the output (My time zone showing at 22:47)
'2022-10-26 16:05:52'
Using (GMT+8)
ZonedDateTime gmt = zdt.withZoneSameInstant(ZoneId.of("GMT+8"));
This is the output (My time zone showing at 23:10)
''2022-10-26 17:10:30''
Using (GMT+12)
This is the output (My time zone showing at 23:15)
''2022-10-26 21:15:47''
Using (GMT+14) I'm even dont know anymore are this time zone literally correct
This is the output (My time zone showing at 23:20)
''2022-10-26 23:20:47'' This was my time zone (basically, this is the right one)
Using GMT+14 was right according to my own clock, but my time zone (Indonesia, GMT+8). Then I'm checked to my server where my database are located at the cloud, then I found it at Asia Pacific.
This is my lines of code :
LocalDateTime ldt = LocalDateTime.now();
ZonedDateTime zdt = ZonedDateTime.of(ldt, ZoneId.systemDefault());
ZonedDateTime gmt = zdt.withZoneSameInstant(ZoneId.of("GMT+14"));
Timestamp timestamp = Timestamp.valueOf(gmt.toLocalDateTime());
transaction.setDate(timestamp);
transactionServices.saveTransaction(transaction);
So are this happened because where my database are located or because something else? Can someone explain so I can improve my code.
I'am expecting using GMT+8 not GMT+14, I'm affraid this can be a problem when this application is produced.
LocalDateTime.now()
I cannot imagine a scenario where calling LocalDateTime.now is the right thing to do.
ZonedDateTime zdt = ZonedDateTime.of(ldt, ZoneId.systemDefault());
Skip the LocalDateTime variable ldt. Just ask ZonedDateTime to capture the current moment as seen in your desired time zone.
ZoneId z = ZonedId.systemDefault() ;
ZonedDateTime zdt = ZonedDateTime.now( z ) ;
ZonedDateTime gmt = zdt.withZoneSameInstant(ZoneId.of("GMT+8"));
For an offset, use the subclass ZoneOffset rather than ZoneID.
ZoneOffset offset = ZoneOffset.of( 8 ) ;
And, this code does not make sense in couple ways. Firstly, if you want to represent a moment as seen through an offset, use OffsetDateTime.
OffsetDateTime odt = OffsetDateTime.now( offset ) ;
Secondly, you should generally 👉 prefer a time zone to a mere offset. When adding or subtracting to move through time, using a mere offset means you will fail to account for changes to the offset used by the people in a particular time zone. For example, you will fail to account for Daylight Saving Time (DST) cut-overs. DST is only one example; politicians frequently change the offset of the time zones under their jurisdiction for various reasons including diplomatic, martial, practical, and fashionable.
A reminder of definitions:
An offset is merely a number of hours, minutes, and seconds ahead of, or behind, UTC.
A time zone is much more. A time zone is a named history of the past, present, and future changes to the offset used by the people of a particular region as decided by their politicians.
For example, the time zone Australia/Perth currently uses an offset of +08:00.
ZoneId z = ZoneId.of( "Australia/Perth" ) ;
ZonedDateTime zdt = ZonedDateTime.now( z ) ;
By the way, stick with standard ISO 8601 formats for date-time values. So use +08:00 rather than "GMT+8". And I recommend always (a) including both hours and minutes, and (b) using padding zero for single-digit numbers. I have seen more than one library that expects only such values.
Tripping through time zones
You say your own time zone is in Indonesia. I will presume you mean Asia/Jakarta.
Capture the current moment as seen there.
ZoneId zJakarta = ZoneId.of( "Asia/Jakarta" ) ;
ZonedDateTime zdtJakarta = ZonedDateTime.now( zJakarta ) ;
Adjust to UTC, meaning an offset from UTC of zero hours-minutes-seconds. Simply extract a Instant. An Instant is always in UTC, by definition.
Instant instant = zdt.toInstant() ;
Adjust from UTC to another time zone.
ZoneId zEdmonton = ZoneId.of( "America/Edmonton" ) ;
ZonedDateTime zdtEdmonton = instant.atZone( zEdmonton ) ;
Or skip the Instant class. Move from Indonesia time to Canada time.
ZonedDateTime zdtEdmonton = zdtJakarta.withZoneSameInstant( zEdmonton ) ;
👉 Note that all of these moment objects (zdtJakarta, instant, zdtEdmonton) refer to the very same simultaneous moment. All three represent the same point on the time line, just different wall-clock/wall-calendar time.
Avoid legacy classes
Your code:
Timestamp timestamp = Timestamp.valueOf(gmt.toLocalDateTime());
transaction.setDate(timestamp);
👉 Never use the terribly flawed java.sql.Timestamp. This is one of the bloody awful date-time classes that are now legacy, years ago supplanted by the modern java.time classes.
Do not bother trying to study the behavior of these classes. Unless you want a masterclass in how to not design an object-oriented time-tracking framework.
Sun, Oracle, and the JCP community all gave up on the legacy classes with the adoption of JSR 310. I suggest you do the same.
Database
To write a moment to a database, 👉 use OffsetDateTime. Standard SQL lacks the concept of a ZonedDateTime.
To store a moment, a point on the time line, be sure your database table column is of a type akin to the SQL standard type TIMESTAMP WITH TIME ZONE rather than WITHOUT.
ZonedDateTime zdt = … ;
OffsetDateTime odt = zdt.toOffsetDateTime() ; // Discard time zone, keeping only the offset.
myPreparedStatement.setObject( … , odt ) ;
Retrieve.
OffsetDateTime odt = myResultSet.getObject( … , OffsetDateTime.class ) ;
ZonedDateTime zdt = odt.withOffsetSameInstant( myZoneId ) ;
You said:
So are this happened because where my database are located
Do not write code that depends on the current default time zone of your database session, the server OS, or your JVM. If you write database code as I showed here, you are in control of the time zone adjustments.
Not a moment
Notice that nowhere did I use the LocalDateTime class. That is because your Question is asking about moments, specific points on the time line.
A LocalDateTime contains only a date and a time-of-day. The class purposely lacks the concept of a time zone or offset. Therefore, the values in LocalDateTime objects are inherently ambiguous. If you say "noon on the 23rd of January 2023", I have no idea if you mean noon in Tokyo, noon in Toulouse, or noon in Toledo — three different moments several hours apart.
👉 LocalDateTime cannot represent a moment.
The equivalent type in standard SQL is TIMESTAMP WITHOUT TIME ZONE.
All this and more has been covered many many times already on Stack Overflow. Search to learn more.

Getting the startime and endtime of the day in epochmillis for different timezones - java

I am trying to get the start time (00:00:00) and the end time (23:59:59) of a day in the PST time zone. I have tried the following code, but for some reason, I am only getting the start and end times in UTC. I have tried changing the timezone to include "America/Los_angeles", but the output timestamp is always showing start and end times for GMT/UTC.
My code:
val time_zone = ZoneId.of("America/Los_Angeles")
val today_date = LocalDate.now(time_zone).plusDays(0)
val start_time = today_date + " " + "00:00:00"
val end_time = today_date + " " + "23:59:59"
val date_format = new SimpleDateFormat("yyyy-MM-dd");
val start_millis = date_format.parse(start_time).getTime();
val end_millis = date_format.parse(end_time).getTime();
start_millis
Output:
res375: Long = 1656460799000
In the epoch converter, 1656460799000 gives me this:
Anything I am missing here? Should I update any package, etc.?
java.time
The modern approach uses the java.time classes only.
No need to ever use SimpleDateFormat, Date, Calendar, and the other terrible legacy date-time classes. If need be, you can convert to and fro via new conversion methods added to the old classes.
Start of day
I am trying to get the start time (00:00:00)
Do not assume the day starts at 00:00. Some dates in some zones start at another time such as 01:00. Let java.time determine the first moment of the day using LocalDate#atStartOfDay.
End of day
the end time (23:59:59) of a day
You would be missing an entire last second of the day with that approach.
Date-time work is commonly done with the Half-Open approach. In Half-Open, the beginning is inclusive while the ending is exclusive. So a day starts with the first moment of the day, and runs up to, but does not include, the first moment of the following day. Half-Open approach neatly contains that full last second of the day.
Time zones
PST time zone.
There is no such thing as a time zone named PST. Such 2-4 letter pseudo-zones are used by the popular media to indicate a hint about the time zone. But these pseudo-zones are not standardized, and are not even unique! Use only for localized presentation to humans, never for data storage or data exchange.
Real time zones are named with Continent/Region.
Perhaps by “PST” you meant “Pacific Standard Time”, which often indicates America/Tijuana, or America/Los_Angeles or America/Vancouver or others.
Or perhaps by “PST” you meant “Philippines Standard Time” covering the Asia/Manila time zone.
Example code
Capture the current moment as seen in a time zone.
ZoneId z = ZoneId.of( "America/Los_Angeles" ) ;
ZonedDateTime zdt = ZonedDateTime.now( z ) ;
Extract the date.
LocalDate today = zdt.toLocalDate() ;
Determine the first moment of the day.
ZonedDateTime zdtStartOfDay = today.atStartOfDay( z ) ;
And determine the first moment of the following day.
ZonedDateTime zdtStartOfFollowingDay = today.plusDays( 1 ).atStartOfDay( z ) ;
You may want to see the length of time. Not all days are 24 hours.
Duration d = Duration.between( zdtStartOfDay , zdtStartOfFollowingDay ) ;
Adjust both moments to UTC by extracting an Instant object. That class represents a moment as seen in UTC.
Instant start = zdtStartOfDay.toInstant() ;
Instant end = zdtStartOfFollowingDay.toInstant() ;
For each, get the count of milliseconds since the epoch reference of first moment of 1970 as seen in UTC, 1970-01-01T00:00Z.
long startMilli = start.toEpochMilli() ;
long endMilli = end.toEpochMilli() ;
However, I strongly recommend against tracking time as a count of milliseconds. This approach is confusing, as at least a couple dozen epoch reference points are commonly used. And a long cannot be interpreted by a human reader, so mistakes may go unnoticed.
Instead, data storage and data exchange should generally be done as text using the standard ISO 8601 formats. The java.time classes use these standard formats by default when parsing/generating text.
String startText = start.toString() ;
String endText = end.toString() ;
ThreeTen-Extra
You may want to add the ThreeTen-Extra library to your project. This gives you access to the Interval class, to represent a span of time as a pair of Instant objects.
Interval allDayLongToday = org.threeten.extra.Interval.of( start , end ) ;
This class provides several helpful methods. These include contains, encloses, abuts, union, intersection, and more.
Instant invoiceRecorded = … some `Instant` ;
boolean invoiceRecordedToday = allDayLongToday.contains( invoiceRecorded ) ;
Just add this section to your code:
date_format.setTimeZone(TimeZone.getTimeZone("PST"));
Then it will work as you want :)

Is there way to make jpql/native-query zone agnostic

I have a date field in our PostgreSQL table where date is being saved including time stamp and zone (using "localdatetime" in java side when capturing). But requirement is to pull up record checking only the date part (ignore the time and zone), so that all users from all over the world can see the same result if filter by date.
Please let me know should I provide more explanation.
Wrong data types means lost information
postgres table where date is being saved including time stamp and zone ( using "localdatetime" in java side when capturing )
That is a contradiction in types.
The TIMESTAMP WITH TIME ZONE type in PostgreSQL does not save a time zone. Read the documentation carefully. It explains that any time zone or offset information provided along with the date and time-of-day is used to adjust to an offset from UTC of zero hours-minutes-seconds. In other words, the date and time are adjusted “to UTC”, as we say in shorthand.
That zone or offset information you may have provided is then discarded. Postgres does not remember the original offset or zone. Any value retrieved from a column of type TIMESTAMP WITH TIME ZONE is always in UTC. If you care about the original zone or offset, you must store that yourself in a second column.
But you have a problem. You did not provide any indicator of time zone or offset when sending the date-time to the database. You incorrectly used the LocalDateTime class which purposely lacks any indicator of time zone or offset. The LocalDateTime class represents a date with time-of-day, and nothing else. Thus the contradiction mentioned above. You provided two things (date, time-of-day) to a column that needs three things (date, time-of-day, and zone/offset).
I presume that Postgres took your LocalDateTime value and stored it as if it it were a date and time in UTC. So you have lost information. For example, if one row’s input was meant to be noon on the 23rd of January 2022 in Tokyo Japan 🇯🇵, while another row’s input was meant to be noon on that same 23rd date in Toulouse France 🇫🇷, and yet a third row’s input was meant for noon also on that 23rd but as seen in Toledo Ohio US 🇺🇸, then you will be incorrectly recording three different moments that happened several hours apart as if they all happened at another moment, a fourth moment, noon on the 23rd as seen in UTC, 2022-02-23T12:00Z.
After recording such a mish-mash of erroneous values, there is no going back. Unless you have a way of knowing for certain the intended zone of each row, your information is lost, and your stored column is worthless.
Querying for a day
Let’s set aside the issue of having invalidly stored moments. Let’s now focus on the issue of how to query for a day’s worth of rows with a column of the type TIMESTAMP WITH TIME ZONE.
You need to understand that for any given moment, the date varies by time zone. At any moment it may be “tomorrow” in Sydney Australia 🇦🇺 while simultaneously “yesterday” in Edmonton Alberta Canada 🇨🇦. So you need the context of a time zone (or offset) to perceive a date.
If you want to query for a day as seen in Edmonton, specify the time zone.
ZoneId z = ZoneId.of( "America/Edmonton" ) ;
Specify the desired date.
LocalDate ld = LocalDate.of( 2022 , Month.JANUARY , 23 ) ;
Determine a span of time representing all the moments of that day. The best approach to define such a span is the Half-Open approach. In Half-Open, the beginning is inclusive while the ending is exclusive. So the span starts with the first moment of the day and runs up to, but does not include, the first moment of the following day.
To get start of day, let java.time determine that. Days do not always start at 00:00.
ZonedDateTime start = ld.atStartOfDay( z ) ;
ZonedDateTime end = ld.plusDays( 1 ).atStartOfDay( z ) ;
The ZonedDateTime does not map to any data type in SQL. So convert to an OffsetDateTime object for exchanging with the database.
OffsetDateTime odtStart = start.toOffsetDateTime() ;
OffsetDateTime odtEnd = end.toOffsetDateTime() ;
Pass those through your prepared statement.
myPreparedStatement.setObject( … , odtStart ) ;
myPreparedStatement.setObject( … , odtEnd ) ;
In your SQL, do not use BETWEEN. That command is fully closed. For Half-Open, write your SQL query as looking for values that (a) are not before the start (“not before” is an abbreviated way of saying “is equal to or later than”), and (b) are before the end.
Assuming 24.12.2021 15:00 Berlin (Germany) is in the database.
People from Germany will see 15:00.
But people from London must see either 16:00 or "15:00 wall clock in Berlin".
Since you said
from all over the world can see the same resut
There will be a difference between 15:00 and 16:00: the number 5 and the number 6, they are different.
You could give everyone the same results by printing
15:00 wall clock in Berlin
But this needs to transport the Timezone Berlin beside the datetime.

I want to calculate time in Asia/Dubai timezone from the time of Asia/Kolkata using java 8 Date Time API

ZoneId dubai = ZoneId.of("Asia/Dubai");
LocalDate localDate = LocalDate.now();
LocalTime localTime = LocalTime.now();
ZonedDateTime zonedDateTime = ZonedDateTime.of(localDate, localTime, dubai);
System.out.println("Dubai Tiime:"+zonedDateTime);
Above code is still printing the time of my current zone (i.e Asia/Kolkata)
Also i tried the following code to achieve the same but it is also printing time in my current zone(Asia/Kolkata):
ZoneOffset offset = ZoneOffset.of("+04:00");
LocalDateTime localDateTime = LocalDateTime.now();
OffsetDateTime plusFour = OffsetDateTime.of(localDateTime, offset);
System.out.println("Dubai Time :"+plusFour);
I am unable to figure out why its not providing desired result.
The answer by Kokorin is correct. Here's a bit more discussion.
Problems
When you called the now method and passed no arguments, you failed to specify a time zone. In that omission, java.time silently applied your JVM’s current default time zone in determining the current local time and current local date.
You claim your JVM’s current default time zone is Asia/Kolkata (India time). If when you ran that code it was 15:30 time in your office, your code is saying “let's take my 15:30 and use that as input to represent a wall-clock time in Dubai”. So while the current moment in Dubai was actually 14:00 (an hour and half closer to UTC than India I presume, not sure), you created a date-time for an hour and a half in the Dubai’s future: 15:30.
When you passed dubai in the line ZonedDateTime.of( localDate, localTime, dubai ) you assumed you were asking for an adjustment between time zones. But in fact you were assigning a time zone to a plain (“Local”) date and time that had no time zone at all. All three of the Local… classes store no time zone internally; their very purpose is to ignore time zone. Your code did not match your intentions.
Note how in this revision to your code I pass your ZoneId object to both now methods. This would solve your problem.
ZoneId dubai = ZoneId.of ( "Asia/Dubai" );
LocalDate localDate = LocalDate.now ( dubai );
LocalTime localTime = LocalTime.now ( dubai ); // Capturing `14:00` in Dubai rather than than `15:30` in India as in your version of code.
ZonedDateTime zonedDateTime = ZonedDateTime.of ( localDate , localTime , dubai );
System.out.println ( "Dubai Tiime:" + zonedDateTime );
But this is still bad code. If those pair of .now methods were called over the stroke of midnight, you would have very wrong information (off by about 24 hours).
Solutions
Instead you should capture the current moment atomically. Either user Kokorin's code, or use my code shown next.
An Instant is a moment on the timeline in UTC with a resolution of nanoseconds.
Instant instant = Instant.now();
ZoneId zoneId_Dubai = ZoneId.of( "Asia/Dubai" );
ZonedDateTime zdt_Dubai = ZonedDateTime.ofInstant( instant , zoneId_Dubai );
As a shortcut, call the static method ZonedDateTime.now.
ZonedDateTime zdt_Dubai = ZonedDateTime.now( zoneId_Dubai );
To see the same moment but with your own wall-clock time, adjust into India time.
ZonedDateTime zdt_Kolkata = zdt_Dubai.withZoneSameInstant( ZoneId.of( "Asia/Kolkata" ) );
BIG TIP: Always pass the optional time zone arguments. While I tremendously respect the work that went into java.time, I consider making the time zone arguments optional on various methods to be a design flaw. The silent implicit application of your JVM’s current default time zone is just too easy a trap to fall into for so many programmers. By the way, ditto for Locale, always specify.
Another Tip: Think, work, and store in UTC. As a programmer you must learn to think in UTC, get your head out of “my time in Kolkata” and “their time in Dubai”. You will drive yourself crazy and make your brain hurt. While programming, know that the only one true time is UTC. All the other Dubai/Kolkata/Montréal/Auckland times are smoke and mirrors, mere illusions. Use the Instant class in much of your code, make it your “go to” class when doing date-time work (only apply a time zone for display to the user). Use UTC in your database. Do your logging in UTC. Keep your servers on UTC (or Iceland) time zone. Use UTC when serializing date-time values to storage or in data-exchange (and use ISO 8601 formats btw). Keep a clock on your desk or screen displaying UTC. Later, when you go home from work, then you can slip back into your own local "India time" thinking.
The problem is that you instantiate ZonedDateTime with your local date and time.
This will do what you want:
ZonedDateTime dubaiDT = Instant.now().atZone(dubaiZone);

Postgres UTC to Java ZonedDateTime

Follow-up question to my original issue.
Because I know that any date time used is against a known timezone, rather than where the user may be submitting their request from, I take a LocalDateTime, convert to UTC and persist. Then, when the appointment is retrieved I convert the saved time to the meeting location timezone (stored in db). However, it would seem that the values I save are actually being saved in my local timezone.
I receive a date time value in the Rest Controller such as:
startLocalDateTime: 2016-04-11T10:00
endLocalDateTime: 2016-04-11T10:30
Appointment has two ZoneDateTime fields:
#Column(name = "startDateTime", columnDefinition= "TIMESTAMP WITH TIME ZONE")
private ZonedDateTime startDateTime;
#Column(name = "endDateTime", columnDefinition= "TIMESTAMP WITH TIME ZONE")
private ZonedDateTime endDateTime;
Then I change the values to UTC and store on my entity to store to Postgres:
appointment.setStartDateTime(startLocalDateTime.atZone(ZoneId.of( "UTC" )))
appointment.setEndDateTime(endLocalDateTime.atZone(ZoneId.of( "UTC" )))
and I store that in Postgres (columnDefinition= "TIMESTAMP WITH TIME ZONE") When I look at the record in pgadminIII I see:
startDateTime "2016-04-11 04:00:00-06"
endDateTime "2016-04-11 04:30:00-06"
So these appear to be stored properly in UTC format (please correct me if I am doing anything wrong so far). I then retrieve them from the database and they are returned as:
Appointment
startdatetime: 2016-04-11T04:00-06:00[America/Denver]
enddatetime: 2016-04-11T04:30-06:00[America/Denver]
Those values are sent back as JSON:
{
"appointmentId":50,
"startDateTime":"2016-04-11T04:00",
"endDateTime":"2016-04-11T04:30"
}
So even though I am saving them as UTC, when I retrieve them they are in MST (my local) timezone, rather than UTC, and I am unable to convert them back to the actual time.
Still struggling with the persistence. I have tried using the java.sql.timestamp, java.sql.Date, java.util.Date, and java.time.ZonedDateTime on my entity. My Postgres is still a "timestamp with time zone". But because I am using Spring-Data-JPA and need to query with the same type. If I use Date - should that be sql.Date or util.Date?
You said:
I take a LocalDateTime, convert to UTC and persist.
Nope. Stick with that LocalDateTime for booking appointments, no UTC involved for appointments.
When booking appointments that should appear as a certain time-of-day regardless of how politicians redefine the offset used within the time zone(s) under their jurisdiction, store a date with time-of-day but keep the time zone separate.
Politicians around the world have shown a strange penchant for frequently redefining the offset of their zones, adjusting to match or differ their neighbors, or to adopt or drop the foolishness known as Daylight Saving Time (DST). These changes are done with little forewarning, or even none at all. So what is right now looks like 2:30 PM on a future day could become 1:30 PM, 2:00 PM, 3:30 PM, or who knows what the politicians might come up with, if we stored as a moment (date, time-of-day, zone/offset, all together).
Java
To represent a date and time-of-day only, use LocalDateTime. This class purposely lacks any concept of time zone or offset-from-UTC. This lack means this class does not track moments, is not a point on the timeline. So we generally do not use this class in business apps. Booking future appointments is one of the few cases where we do want this class.
LocalDate ld = LocalDate.of( 2020 , Month.JANUARY , 23 ) ; // 2020-01-23.
LocalTime lt = LocalTime.of( 14 , 30 ) ; // 2:30 PM.
LocalDateTime ldt = LocalDateTime.of( ld , lt ) ;
See this code run live at IdeOne.com.
ldt.toString(): 2020-01-23T14:30
Store that in your database using JDBC 4.2 and later.
myPreparedStatement.setObject( … , ldt ) ;
Retrieve from database.
LocalDateTime ldt = myResultSet.getObject( … , LocalDateTime.class );
We need to track the intended time zone. Is this an appointment for a dentist office in Québec? Represent that fact as well. Keep in mind that a time zone is a history of past, present, and future (planned) changes to the offset-from-UTC used by the people of a particular region.
Use the ZoneId class to represent a time zone.
ZoneId z = ZoneId.of( "America/Montreal" ) ;
Save that zone name as text to your database. Get its identifying name by calling ZoneId::toString. Do not use ZoneId::getDisplayName as that is for generating localized text for display to a user, but is not a formal identifier for the zone.
myPreparedStatement.setString( … , z.toString() ) ;
When your retrieve from database, reconstitute the ZoneId.
String zoneIdString = myResultSet.getString( … ) ;
ZoneId z = ZoneId.of( zoneIdString ) ;
When you are generating a calendar for these appointments, if you need to determine (tentatively) points on the timeline, combine the date-with-time-of-day and the time zone together. Applying the ZoneId to the LocalDateTime produces a ZonedDateTime. This ZonedDateTime is a moment, is a point on the timeline, unlike LocalDateTime.
ZonedDateTime zdt = ldt.atZone( z ) ;
See this code run live at IdeOne.com.
zdt.toString(): 2020-01-23T14:30-05:00[America/Montreal]
Of course, we do not store that ZonedDateTime. As discussed above this moment that right now appears to be 2:30 PM on the 23rd would turn into 1:30 PM or 3:30 PM if those busy politicians changed the time zone rules between now and then. We only use this ZonedDateTime tentatively, temporarily.
Database
We store a LocalDateTime only in a column of type TIMESTAMP WITHOUT TIME ZONE. You were incorrectly using WITH rather than WITHOUT, a major problem. For the WITHOUT type, Postgres stores the date and the time-of-day as given in the input. Even if a zone or offset were included with the input, Postgres would ignore the zone or offset.
Be aware that TIMESTAMP WITH TIME ZONE is just the opposite in Postgres. The time zone or offset info accompanying an input is used to adjust the date and time to UTC. That UTC value is then stored. So values in a column of this type are all in UTC. Unfortunately, many tools have the anti-feature of dynamically applying a default time zone to the retrieved value before passing on to you. This creates the false illusion of a time zone being stored when in fact the value is in UTC. Retrieving as a OffsetDateTime will always tell you the truth. I say OffsetDateTime because, oddly, the JDBC 4.2 spec requires support for that class but not for the more commonly used Instant & ZonedDateTime classes. Your JDBC driver may support the other two classes, optionally. But all of this paragraph is neither here nor there, as we are not using WITH columns for future appointments.
As mentioned above, for future appointments should be stored in three (3) separate columns:
TIMESTAMP WITHOUT TIME ZONE for the date and the time-of-day.
TEXT (or similar type) for the identifying name of the intended time zone by which we want to view that appointment.
TEXT (or similar) for the duration of the appointment, in standard ISO 8601 format. (see below)
Points in your Question
You said:
I receive a date time value in the Rest Controller such as:
startLocalDateTime: 2016-04-11T10:00
So parse as a LocalDateTime. Such ISO 8601 compliant strings can be parsed directly by the java.time classes.
LocalDateTime ldt = LocalDateTime.parse( "2016-04-11T10:00" ) ;
You said:
endLocalDateTime: 2016-04-11T10:30
No. Do not store the ending time. The ending time you expect now could be different if time zone anomalies occur at that point. For example, what if your politicians adopt Daylight Saving Time (DST), then during the time of this appointment the "Spring ahead" 1-hour change of DST occurred? Then your half-hour meeting should run from 10:00 AM to 11:30 AM, while your recorded end-time would incorrectly read "10:30 AM" ending.
Generally the best way to handle appointments is to record the duration of the appointment, the span-of-time, rather than on the clock. As mentioned above, an appointment consists of three separate pieces of data: (1) starting date with time-of-day, (2) the intended time zone, and (3) duration of appointment. The ending should be dynamically calculated as needed, using fresh current time zone data, for temporary use.
The ISO 8601 standard includes a textual format for recording such durations: PnYnMnDTnHnMnS where P marks the beginning, and T separates any years-months-days from any hours-minutes-seconds. The java.time classes Period and Duration can parse such strings.
Duration d = Duration.ofHours( 1 ) ;
String output = d.toString() ;
See this code run live at IdeOne.com.
output: PT1H
You can dynamically apply this duration to the LocalDateTime.
LocalDateTime ending = ldt.plus( d ) ;
Ditto for ZonedDateTime.
ZonedDateTime ending = zdt.plus( d ) ;
You said:
Appointment has two ZoneDateTime fields:
Nope. Make that a LocalDateTime field, not ZonedDateTime.
You said:
#Column(name = "startDateTime", columnDefinition= "TIMESTAMP WITH TIME ZONE")
Nope. Make that a column of type "TIMESTAMP WITHOUT TIME ZONE" to track a date and time-of-day without the context of zone/offset.
You said:
Then I change the values to UTC and store on my entity to store to Postgres:
Nope. Booking appointments is one of the few cases where we do not want UTC. When tracking a moment, we generally do want to use UTC. But future appointments are not moments. We do not know the moment until we apply a time zone to the date-with-time-of-day, and we cannot do that ahead of time because we cannot trust politicians.
You said:
Still struggling with the persistence.
Cannot help you there. I do not use either Spring or JPA, as they solve problems I do not have. I use straight JDBC.
You said:
So these appear to be stored properly in UTC format (please correct me if I am doing anything wrong so far).
Nope, do not use UTC, do not store in UTC, not for appointments.
Do use UTC for moments though. For example, your logs should all be reporting each moment of an incident in UTC. Programmers & sysadmins would do well to learn to think in UTC while on the job. Keep a second clock on your desk set to UTC.
You said:
when I retrieve them they are in MST (my local) timezone, rather than UTC, and I am unable to convert them back to the actual time.
We are not using UTC, nor any other time zone or offset-from-UTC in our appointment booking. So your problem vaporizes.
Tip: As I said above, your tools are likely lying to you about time zones. Set the tool and your session to UTC, if need be, to defeat this anti-feature.
You said:
I have tried using the java.sql.timestamp, java.sql.Date, java.util.Date, and java.time.ZonedDateTime on my entity.
Never use java.sql.Timestamp. Replaced by OffsetDateTime (and Instant).
Never use java.sql.Date. Replaced by LocalDate.
Never use java.util.Date. Replaced by Instant.
Use only the modern java.time classes that years ago supplanted those terrible date-time classes bundled with the earliest versions of Java. Those awful classes became legacy as of the adoption of JSR 310.
The jdbc driver has some knowledge about the timezone you are currently in. Generally I have gotten around this in the past by having the database do the timezone conversion for me, some derivative of "timestamp without time zone AT TIME ZONE zone" or "timestamp with time zone at time zone 'UTC'". It is in the guts of the postgres jdbc driver that it is figuring out what timezone the JVM is at and is using it in the save.

Categories