Issue
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.
Solution
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.
Answered By - Basil Bourque
Answer Checked By - Marilyn (JavaFixing Volunteer)