How to store a Java Instant in a MySQL database - java

With Java Date objects the easiest way to go was to store them as MySql DateTime objects (in UTC). With the switch to Instant this approach won't work anymore because MySQL DateTime does not offer the precision to store nanoseconds. Just truncating them could lead to unexpected comparison results between a newly created Instant objects and the ones read from the database.
BigDecimal timestamps don't strike me as an elegant solution: writing select queries manually becomes more difficult because you have to convert the timestamp everywhere to make it readable, and the handling in Java is somewhat clunky compared to Instant or even Long values.
What's the best way to go here? Probably not varchar, right?

Truncate to microseconds
Obviously we cannot squeeze the nanoseconds resolution of an Instant into the microseconds resolution of the MySQL data types DateTime and Timestamp.
While I do not use MySQL, I imagine the JDBC driver is built to ignore the nanoseconds when receiving an Instant, truncating the value to microseconds. I suggest you try an experiment to see, and perhaps examine source code of your driver that complies with JDBC 4.2 and later.
Instant instant = Instant.now().with( ChronoField.NANO_OF_SECOND , 123_456_789L ) ; //Set the fractional second to a spefic number of nanoseconds.
myPreparedStatement.setObject( … , instant ) ;
…and…
Instant instant2 = myResultSet.getObject( … , Instant.class ) ;
The JDBC 4.2 spec requires support for OffsetDateTime but oddly does not require the two more commonly used types, Instant and ZonedDateTime. If your JDBC driver does not support Instant, convert.
OffsetDateTime odt = myResultSet.getObject( … , OffsetDateTime.class ) ; // Use `OffsetDateTime` if your JDBC driver does not support `Instant`.
Instant instant2 = odt.toInstant() ; // Convert from `OffsetDateTime` to `Instant`.
Then compare.
Boolean result = instant.equals( instant2 ) ;
System.out.println( "instant: " + instant + " equals instant2: = " + instant2 + " is: " + result ) ;
You wisely are concerned about values drawn from the database not matching the original value. One solution, if acceptable to your business problem, is to truncate any nanoseconds to microseconds in your original data. I recommend this approach generally.
The java.time classes offer a truncatedTo method. Pass a ChronoUnit enum object to specify the granularity. In this case, that would be ChronoUnit.MICROS.
Instant instant = Instant().now().truncatedTo( ChronoUnit.MICROS ) ;
Currently this approach should suffice as you are unlikely to have any nanoseconds in your data. Mainstream computers today do not sport hardware clocks capable of capturing nanoseconds, as far as I know.
Count from epoch
If you cannot afford to lose any nanosecond data that may be present, use a count-from-epoch.
I usually recommend against tracking date-time as a count from an epoch reference date. But you have few other choices in storing your nanosecond-based values in a database such as MySQL and Postgres limited to microsecond-based values.
Store pair of integers
Rather than using the extremely large number of nanoseconds since an epoch such as 1970-01-01T00:00Z, I suggest following the approach taken by the internals of the Instant class: Use a pair of numbers.
Store a number of whole seconds as an integer in your database. In a second column store as an integer the number of nanoseconds in the fractional second.
You can easily extract/inject these numbers from/to an Instant object. Only simple 64-bit long numbers are involved; no need for BigDecimal or BigInteger. I suppose you might be able to use a 32-bit integer column for at least one of the two numbers. But I would choose 64-bit integer column types for simplicity and for direct compatibility with the java.time.Instant class’ pair of longs.
long seconds = instant.getEpochSecond() ;
long nanos = instant.getNano() ;
…and…
Instant instant = Instant.ofEpochSecond( seconds , nanos ) ;
When sorting chronologically, you'll need to do a multi-level sort, sorting first on the whole seconds column and then sorting secondarily on the nanos fraction-of-second column.

Related

Converting LocalDateTime to Instant give different values

I am trying to use LocalDateTime to manipulate dates in my application but I have noticed that getting epoch seconds from it returns different values from what I expected
val now1 = Instant.now().epochSecond - 60
val now2 = Instant.now().minusSeconds(60).epochSecond
val now3 = LocalDateTime.now().minusSeconds(60).toEpochSecond(ZoneOffset.UTC)
val now4 = System.currentTimeMillis() / 1000 - 60
Output
Now1 = 1674501451
Now2 = 1674501451
Now3 = 1674512251
Now4 = 1674501451
Notice how Now3 has a different value. What is happening?
tl;dr
Your JVM’s current default time zone is three hours ahead of UTC. Thus your result.( 1_674_512_251L - 1_674_501_451L ) = 3 hours.
👉 You are making inappropriate use of LocalDateTime class.
Moment versus not-a-moment
I am trying to use LocalDateTime to manipulate dates
Don't.
If you are representing a moment, a specific point on the timeline, 👉 do not use LocalDateTime. That class holds a date with a time-of-day but lacks the context of a time zone or offset-from-UTC. To represent a moment use:
Instant — A moment as seen in UTC (an offset from UTC of zero hours-minutes-seconds).
OffsetDateTime - A moment as seen with a particular offset.
ZonedDateTime - A moment as seen through a particular time zone.
I cannot imagine any scenario where calling LocalDateTime.now is the right thing to do.
Example code:
Instant instant = Instant.now() ;
ZoneOffset offset = ZoneId.of( "Africa/Dar_es_Salaam" ).getRules().getOffset( instant ) ;
OffsetDateTime odt = OffsetDateTime.now( offset ) ;
ZoneId zone = ZoneId.of( "Africa/Dar_es_Salaam" ) ;
ZonedDateTime zonedDateTime = ZonedDateTime.now( zone )
Dump to console.
System.out.println( instant + " | " + instant.getEpochSecond() ) ;
System.out.println( odt + " | " + odt.toEpochSecond() ) ;
System.out.println( zonedDateTime + " | " + zonedDateTime.toEpochSecond() ) ;
See this code run at Ideone.com. Note that all three are within the same second, give or take a second as the clock may rollover to the next second by the second or third call to now.
2023-01-24T14:27:06.416597Z | 1674570426
2023-01-24T17:27:06.478680+03:00 | 1674570426
2023-01-24T17:27:06.487289+03:00[Africa/Dar_es_Salaam] | 1674570426
LocalDateTime#toEpochSecond
Calling the toEpochSecond method on LocalDateTime is tricky conceptually. As a concept, LocalDateTime has no epoch reference point. It is just a date with a time. For example, “noon on January 23 of 2023” by itself has no specific meaning, cannot be nailed down to a point on the timeline. So it is meaningless to compare “noon on January 23 of 2023” to a reference point such as the first moment of 1970 as seen in UTC, 1970-01-01T00:00Z.
Practically, the LocalDateTime class is implemented as if it were in UTC. So internally, the class counts a number of whole seconds since 1970-01-01T00:00Z plus a count of nanoseconds for a fractional second. But you should not be thinking of this implementation detail when writing code for business logic. For business logic, calling LocalDateTime#toEpochSecond makes no sense.
Technically speaking
So the technical answer to your Question is that your delta of 3 hours (1_674_501_451L - 1_674_512_251L) came from the fact that your JVM’s current default time zone at that moment used an offset of +03:00, three hours ahead of UTC. So your call to LocalDateTime.now captured a date and time three hours ahead of UTC. But then you asked for toEpochSecond which treats that very same date and time as if it were in UTC.
Logically speaking
Of course that date and time were not meant to represent a moment in UTC. So logically speaking, your code is nonsensical. You should not be comparing a count from epoch for a LocalDateTime (which is a non-moment) to other classes such as Instant (which is a moment).
In other words, you are comparing apples and oranges.
Serializing date-time data
So, if LocalDateTime#toEpochSecond is inappropriate for business logic, why does that class offer such a method?
That method is useful for serializing a LocalDateTime value for storage or data-exchange. Some other systems may present date-with-time values in this manner.
ISO 8601
However, using a count-from-reference is a poor way to communicate date-time values. The values are ambiguous, as their granularity is implicit, and their particular epoch reference point is also implicit. And, such values make validation and debugging difficult for human readers.
I strongly recommend using standard ISO 8601 text rather than a count when storing or exchanging date-time values outside Java. Regarding your example standard textual format would be:
2023-01-23T22:17:31
General advice: Avoid LocalDateTime class unless you are very clear on its appropriate use.
For business apps, we are generally tracking moments. For example, "when does this contract come into effect", "when was this employee hired", "when does the purchase of this house close". So LocalDateTime is not called for.
The two business scenarios for LocalDateTime are (a) making appointments where we want the moment to float if time zone rules change, such as clinic, salon, and studio bookings, and (b) where we mean several moments, each with the same wall-clock time but occurring at different points on the timeline, such as "Announce this corporate message at noon in each of our company factories in Delhi, Düsseldorf, and Detroit.". Search Stack Overflow to learn more on this, as I and others have posted multiple times already.
On Ideone.com, see some code I noodled around with in writing this Answer.
You need to use LocalTime#now(ZoneId zone) with ZoneOffset.UTC in order to get the local time at UTC; otherwise, the system picks up the local time of system's timezone.
Demo:
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.temporal.ChronoUnit;
import java.util.concurrent.TimeUnit;
class Main {
public static void main(String[] args) {
Instant now = Instant.now();
var now1 = TimeUnit.SECONDS.convert(now.toEpochMilli(), TimeUnit.MILLISECONDS) - 60;
var now2 = TimeUnit.SECONDS.convert(now.minusSeconds(60).toEpochMilli(), TimeUnit.MILLISECONDS);
var now3 = LocalDateTime.now(ZoneOffset.UTC).minusSeconds(60).toEpochSecond(ZoneOffset.UTC);
var now4 = System.currentTimeMillis() / 1000 - 60;
System.out.println(now1);
System.out.println(now2);
System.out.println(now3);
System.out.println(now4);
}
}
Output from a sample run:
1674506413
1674506413
1674506413
1674506413
ONLINE DEMO
Learn more about the modern Date-Time API from Trail: Date Time.

Proper way to get EPOCH timestamp in kotlin

I want to get the EPOCH timestamp in kotlin in "seconds":"nanoseconds" format.
Note: Please look at the accepted answer for the right solution.
Edit:
It's my current solution and I'm sure there would be some better way to achieve this,
import java.time.Instant
import java.time.temporal.ChronoUnit;
import kotlin.time.Duration.Companion.seconds
fun main() {
val epochNanoseconds = ChronoUnit.NANOS.between(Instant.EPOCH, Instant.now())
val epochSeconds = epochNanoseconds/1.seconds.inWholeNanoseconds
val remainingFractionOfNanoSeconds = epochNanoseconds%1.seconds.inWholeNanoseconds
println("$epochSeconds:$remainingFractionOfNanoSeconds")
}
example output:
1670251213:849754000
Another example (from the comments): For 1670251213 seconds 50000 nanoseconds, also known as 1670251213.00005 seconds in decimal, I want 1670251213:50000 (means :).
Is there any way to get seconds and remaining nanoseconds directly from java.time.Instant or any other library available to achieve this conveniently?
Solution from the accepted answer:
import java.time.Instant
fun main() {
val time = Instant.now()
println("${time.epochSecond}:${time.nano}")
}
tl;dr
You are working too hard.
Ask the Instant object for its count of whole seconds since 1970-01-01T00:00Z. Make a string of that, append the COLON character. Then append the count of nanoseconds in the fractional second of the Instant.
instant.getEpochSecond()
+ ":"
+ instant.getNano()
1670220134:130848
Details
Neither the legacy date-time classes (Calendar, Date, etc.) nor the modern java.time classes support International Atomic Time (TAI) that you requested. Time-keeping on conventional computers (and therefore Java) is nowhere near as accurate as an atomic clock.
Perhaps you used that term loosely, and really just want a count of nanoseconds since the epoch reference of first moment of 1970 as seen with an offset from UTC of zero hours-minutes-seconds (1970-01-01T00:00Z) within the limits of conventional computer hardware.
If so, the Instant class will work for you. But beware of limitations in the implementations of Java based on the OpenJDK codebase.
In Java 8, the first with java.time classes, the current moment is captured with a resolution of milliseconds.
In Java 9+, the current moment is captured with a resolution of microseconds (generally, depending on the limits of your computer hardware).
Note that in all versions (8, 9, and later), an Instant is capable of nanosecond resolution. The limitations bulleted above relate to capturing the current moment from the computer hardware clock.
The internal representation of a moment in the Instant class comprises two parts:
A count of whole seconds since 1970-01-01T00:00Z.
A fractional second represented by a count of nanoseconds.
The Instant class provides a pair of accessor methods (getters) to see both of these numbers.
getEpochSecond
getNano
Your Question is not entirely clear, you seem to be asking for those two numbers with a COLON character between them.
Instant instant = Instant.now() ;
String nanos = Long.toString( instant.getNano() ) ;
String output =
instant.getEpochSecond()
+ ":"
+ instant.getNano()
;
instant.toString(): 2022-12-05T06:12:33.294698Z
output: 1670220753:294698
If you want to pad zeros to the right of your fractional second, use String.format.
Instant instant = Instant.now() ;
String nanos = Long.toString( instant.getNano() ) ;
String output =
instant.getEpochSecond()
+ ":"
+ String.format( "%1$" + nanos.length() + "s", nanos ).replace(' ', '0') // Pad with leading zeros if needed.
;
See this code run at Ideone.com.
instant.toString(): 2022-12-05T06:12:33.294698Z
output: 1670220753:294698000
Alternatively, you could instantiate a BigDecimal object.

Convert BigInt date into a Date

So Our database has a column BigInt REVISIONTS used as part of java Hibernate Envers
It initially contained timestamps from Java Date.
E.g) ts=1561637560383
I used to convert to Date using new Date(ts)
But since Date cannot contain timezones and we needed UTC date, we had to store UTC directly as BigInt and applied a fix suggested by hibernate. Because of this now our timestamps are like this
E.g) ts=20190827202449 now this is not a timestamp anymore but an actual UTC LocaleDateTime stored as bigint
Now querying this i get long and if I use new Date(ts) i am getting incorrect date of course since this is not a timestamp but Hibernate Date with Temporal.Timestamp stored the UTC as is.
I am thinking of converting Long to string and use formatting to convert back when retrieving.
Are there any other cleaner method of converting ?
UTC fix for Envers
How to save UTC (instead of local) timestamps for Hibernate Envers revision info?
You shouldn't be using Date at all. Never. Ever.
I think storing the time as a Unix Timestamp is pretty fine. They're always in UTC and represent a unique instant on the timeline.
Envers supports both Date and Long/long to be defined as revision timestamp. You should use Long.
Formatting it using a timezone or timezone offset can be easily done with the newer Java Date and Time API available in the java.time package.
With Instant.ofEpochSecond(yourTimestamp) you can create an Instant. With atOffset or atZone you can combine the bare timestamp with a certain timezone or timezone offset.
Your Question is quite unclear. But this might help.
Avoid legacy date-time classes
to convert to Date using new Date(ts)
Never use java.util.Date. That terrible class was supplanted years ago by the java.time classes, specifically by Instant.
Instant
E.g) ts=1561637560383
You are not clear about exactly what that value represents. I will guess it is a count of milliseconds since the epoch reference of first moment of 1970 in UTC, 1970-01-01T00:00Z.
long count = 1_561_637_560_383L ;
If that is a textual value, parse using Long class.
long count = Long.parseLong( "1561637560383" ) ;
Instant instant = Instant.ofEpochMilli( count ) ;
See this code run live at IdeOne.com.
instant.toString(): 2019-06-27T12:12:40.383Z
Tip: In your database, store date-time values using date-time data type.
If your database is too primitive to support date-time types, store as text in UTC using ISO 8601 format.
String output = instant.toString() ; // Ex: 2019-06-27T12:12:40.383Z
…and…
Instant instant = Instant.parse( "2019-06-27T12:12:40.383Z" ) ;
Get count of milliseconds since epoch reference.
long count = instant.toEpochMilli() ;
Convert
When you must use Date to interoperate with old code not yet updated to java.time, convert. Call new to…/from… methods added to the old classes.
java.util.Date d = Date.from( instant ) ;
Instant instant = d.toInstant() ;

Add a Double variable representing minute into a Date in Java

I have a Origin-Destination matrix representing the elapsed time between 2 point in minute. I wonder what is the best way to add the elapsed time to a Date.
Date clock;
SimpleDateFormat reqDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
clock = reqDF.parse(line[25]);
Double distance = distDB.find_distance(O, D, mode, facility_id);
Date current_time = clock + distance;
Does it makes more easier if I use a Calendar type instead of Date ?
Thanks
tl;dr
Instant.parse( "2016-12-23T01:23:45Z" )
.plusNanos( Math.round(
yourNumberOfMinutesAsDouble *
TimeUnit.MINUTES.toNanos( 1 ) )
)
Avoid old date-time classes.
Actually, you should using neither java.util.Date nor java.util.Calendar. These old legacy classes are notoriously troublesome, confusing, and flawed. Now supplanted by the java.time classes.
For the old classes, the Answer by Jireugi is correct. For java.time classes, read on.
Avoid using a fractional number for elapsed time
Not a good idea using a double or Double to represent elapsed time. Firstly, that type uses floating-point technology which trades away accuracy for speed of execution. So you will often have extraneous extra digits to the right of your decimal fraction.
Secondly, this is an awkward way to handle time, given that we have sixty seconds to a minute, and sixty minutes to an hour, and so on.
In Java, use the Period or Duration classes. See Oracle Tutorial.
In text, use the standard ISO 8601 format PnYnMnDTnHnMnS where P marks the beginning and T separates the years-months-days from the hours-minutes-seconds. So two and a half minutes would be PT2M30S.
Nanoseconds
If we must work with the Double as a number of minutes for elapsed time, let’s convert from your fractional decimal number to a whole number (integer).
Note that the old legacy date-time classes are limited to a resolution of milliseconds while the java.time classes can go as fine as nanoseconds. So we want to convert your Double to a whole number of nanoseconds.
Rather than hard-code a magic number of the number of nanoseconds in a minute for this calculation, let's use the TimeUnit enum. That class can calculate the number of nanoseconds in a minute for us ( 60 * 1_000_000_000L ).
Finally, the Math.round function returns the closest long to the argument, with ties rounding to positive infinity.
long nanoseconds = Math.round(
yourNumberOfMinutesAsDouble *
TimeUnit.MINUTES.toNanos( 1 )
);
Instant
If working with date-time values in UTC, use the Instant class. Each Instant represents a moment on the timeline in UTC with a resolution of nanoseconds.
Instant Instant.parse( "2016-12-23T01:23:45Z" )
Instant future = Instant.plusNanos( nanoseconds );
Duration
Rather than passing around a Double as your elapsed time, I strongly suggest you pass around Duration objects.
Duration duration = Duration.ofNanos( Math.round( yourNumberOfMinutesAsDouble * TimeUnit.MINUTES.toNanos( 1 ) ) );
You can do math with a Duration such as plus and minus.
Instant instant = Instant.parse( "2016-12-23T01:23:45Z" )
Instant future = instant.plus( duration );
You can generate a String representation of the Duration in standard ISO 8601 format such as PT8H6M12.345S by merely calling toString. And Duration can parse such strings as well.
String output = duration.toString(); // PT8H6M12.345S
…or going the other direction…
Duration duration = Duration.parse( "PT8H6M12.345S" );
You could try this using Math.round:
Date clock;
SimpleDateFormat reqDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
clock = reqDF.parse(line[25]);
Double distance = distDB.find_distance(O, D, mode, facility_id);
long clockTimeMs = clock.getTime();
long distTimeInMs = Math.round(distance * 60000);
Date current_time = new Date(clockTimeMs + distTimeInMs);
Here the Date gets converted into a milliseconds timestamp.
Since the distance is in minutes, you need to convert it also into milliseconds by multiplying it by 60 * 1000 (= 60000) before you can add it to the start time ("clock"). Finally a new Date gets created that represents the distance from the start time.
Please find details of the Date class here: https://docs.oracle.com/javase/6/docs/api/java/util/Date.html
If i understood your situation, you want to get current date, then using matrix [i, j] value (which is time - in minutes) you want to update the time to give an indication of - if you start from Origin (i), till what time you would reach to Destination (j).
I would rather use java's Calendar class. it gives you more flexibility. using 'Calendar' class, you can getTime(), which returns 'Date' type instance and then use this class to add/delete your time offset (there are methods provided by this class for such manipulations) and convert the 'Date' back to Calendar.

Jodatime time difference between 2 timezones

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).

Categories