I have one question about zones in java.
I have a user case when user can set time zone and some schedule. For example run task on Mondays and Sundays in 11pm with timezone America/Los_Angeles.
If my server time zone is UTC+0:00 I have a problem how to detect a day of week and new time correctly (it will be about Tuesday and Monday on 6am). So my question is how to run user tasks correctly according to server timezone and user timezone.
Update
I have a cron expression where I set hours, minutes and day of week.
When user creates a new task he can set custom timezone(for example to run task on mondays 11pm with custom timezone UTC-7:00)
So if my understanding is right I need to convert his hour setting (11pm) to corresponding server time. So if my server timezone is UTC+3:00 I need to convert 11pm to 9am and it will be not Monday (but Tuesday) on my server. Then if I will run cron on Tuesday 9am it will look to user like task runs on 11pm on Monday. Is my approach correct? I hope you understand my question. Thanks in advance.
The short answer: Count in milliseconds since the epoch.
The long answer:
First, it is very important to understand how computer time works. I would recommend this article., but the gist is, that everything is computed since the epoch, 1.1.1970. Second, I figured an example will tell you more than 1000 words:
ZonedDateTime dateTime = ZonedDateTime.parse("2016-10-20T11:34:57+02:00[Europe/Zurich]"); // UTC + 2
long millisEurope = dateTime.toInstant().toEpochMilli();
System.out.println(millisEurope); // 1476956097000
dateTime = dateTime.withZoneSameLocal(ZoneId.of("America/Los_Angeles")); // UTC - 7
long millisAmerica = dateTime.toInstant().toEpochMilli();
System.out.println(millisAmerica); // 1476988497000
// The difference between UTC + 2 and UTC - 7 == -9
System.out.println((millisEurope - millisAmerica) / 1000 / 60 / 60);
The ZonedDateTime will always use UTC (+ 0) plus the defined time zone.
EDIT (after the update):
Yes, if you get the time and time zone set by the user, you would have to convert it to your own time zone to be executed on the same point in time.
The time zone setting of your server’s operating system and JVM should be irrelevant to your programming. Both can change at any moment, during runtime, so do not rely on it. Always specify the desired/expected time zone in the optional argument to the various date-time methods.
Be aware that you cannot schedule time-zone-sensitive moments far in advance. Politicians are notorious for often changing the time zone definitions, sometimes with very little advance notice.
If you want to set an alarm for the next Monday at 11 AM in the context of the user’s time zone, first you need the user’s time zone. You may be able to detect a default time zone, but ultimately the only reliable way is to ask the user for their desired/expected time zone.
Specify a proper time zone name in the format of continent/region, such as America/Montreal, Africa/Casablanca, or Pacific/Auckland. Never use the 3-4 letter abbreviation such as EST or IST as they are not true time zones, not standardized, and not even unique(!).
Keep the alarm’s day-of-week as a DayOfWeek enum object.
DayOfWeek alarmDow = DayOfWeek.MONDAY ;
Keep the alarm’s time-of-day as a LocalTime.
LocalTime alarmTimeOfDay = LocalTime.parse( "11:00:00" );
This class lacks a date and lacks any offset-from-UTC or time zone. So it has no meaning until you adjust into a time zone.
Keep the alarm’s time zone as a ZoneId.
ZoneId z = ZoneId.of( "America/Montreal" );
With those parts in hand, you can schedule the alarm.
Get the current moment as an Instant. The Instant class represents a moment on the timeline in UTC with a resolution of nanoseconds (up to nine (9) digits of a decimal fraction).
Instant now = Instant.now();
Apply the desired time zone to get a ZonedDateTime.
ZonedDateTime zdtNow = instant.atZone( z );
To determine the future moment of the alarm, use the TemporalAdjuster interface to manipulate date-time values. The class TemporalAdjusters (note the plural s) provides implementations. First extract a date-only value, as we will be assigning the alarm’s LocalTime as the time-of-day.
LocalDate today = zdtNow.toLocalDate();
LocalDate dateOfNextOrSameDow = today.with( TemporalAdjusters.withNextOrSame( alarmDow ) );
Apply the desired time-of-day.
ZonedDateTime zdtAlarm = ZonedDateTime.of( dateOfNextOrSameDow , alarmTimeOfDay , z ) ;
This particular alarm date-time may have already passed earlier today. So test. If so, add a week to get the next occurrence of the desired day-of-week.
if( zdtAlarm.isBefore( zdtNow ) ) { // If already passed…
zdtAlarm = zdtAlarm.plusWeeks( 1 ); // …go to next day-of-week occurrence.
}
Related
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.
How to get maximum time value of the current date in miliseconds in Java ?
E.g. 18th July 23.59 in epoch miliseconds where current time would be anything of 18th July
Half-Open
Seems like your Question involves determining the last moment of the day. Such a goal is ill-advised.
The last moment of the day is infinitely divisible as a fraction of a second, such as 2017-01-23T23:59:59.999Z. But different software uses different granularities when resolving such date-time values. The old legacy date-time classes is Java use milliseconds, or 3 decimal places. The new java.time classes use nanoseconds, or 9 decimal places. Other software such as the Postgres database use microseconds, for 6 decimal places. Other software uses other resolutions such as 5 decimal places. So you will be getting different values in different scenarios for the same date.
A better approach to defining spans of time is commonly used in date-time work, and avoids this ambiguity of last-moment-of-the-day: Half-Open. In this approach the beginning is inclusive while the ending is exclusive. So a single full day starts at the first moment of one date and runs up to, but does not include, the first moment of the following day. For UTC, that would be 2017-01-23T00:00:00Z to 2017-01-24T00:00:00Z.
ZoneId z = ZoneId.of( "America/Montreal" ) ;
LocalDate today = LocalDate.now( z ) ;
ZonedDateTime todayStart = today.atStartOfDay( z ) ;
long secondsSinceEpoch = todayStart.toEpochSecond() ; // Whole seconds since epoch of 1970-01-01T00:00:00Z.
long millisecondsSinceEpoch = todayStart.toInstant().toEpochMilli() ; // Milliseconds since epoch of 1970-01-01T00:00:00Z.
See this code run live at IdeOne.com.
todayStart: 2017-07-19T00:00-04:00[America/Montreal]
millisecondsSinceEpoch: 1500436800000
For the following day (tomorrow), add one day to the LocalDate.
LocalDate tomorrow = today.plusDays( 1 ) ;
ZonedDateTime tomorrowStart = tomorrow.atStartOfDay( z ) ;
There’s room for a bit of interpretation in your question. I suggest:
ZoneId zone = ZoneId.systemDefault();
long endOfDay = LocalDate.now(zone)
.plusDays(1)
.atStartOfDay(zone)
.toInstant()
.minusMillis(1)
.toEpochMilli();
System.out.println(endOfDay);
On my computer it just printed
1500501599999
This corresponds to a ZonedDateTime of 2017-07-19T23:59:59.999+02:00[Europe/Berlin].
Please note that the end of the day is time zone dependent, so you need to decide a time zone for the operation. Please fill in your desired time zone instead of ZoneId.systemDefault().
Since Java doesn’t include an “end of the day” operation I opted for adding one day, taking the beginning of the day in the time zone in question and then subtracting 1 millisecond (because you asked for milliseconds since the epoch). If you want, you may instead subtract a nanosecond, a second or a minute, for example. If you want to subtract a whole minute, the simplest is to use .minusMinutes(1) before toInstant().
The code should be safe even on a day and in a time zone where a summer time transition (DST changeover) happens at midnight.
I'm not entirely sure how the specific date plays into this, but if you want to find the last millisecond of the day, I would use LocalTime.MAX.toNanoOfDay()/1000000. This takes the maximum nanosecond of the day, then divides by 1000000 to convert to milliseconds. If you wanted to combine this with a date, I would combine this value with a LocalDate or LocalDateTime value.
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);
I have a timestamp that is similar to POSIX Time with the sole exception that it is not reckoned in UTC.
Instead, it is the number of milliseconds that have elapsed since midnight, Jan 1 1970 in a particular local time zone. In order to make this value into an Instant, I must first know its offset (in milliseconds) to UTC/GMT.
So the problem is this: knowing the local time zone id, eg. "America/Chicago" and a count of milliseconds since the local Epoch, how do I make an Instant (which must be constructed with milliseconds since the POSIX Epoch)?
It does not seem that any of the java.time API constructors accept a millisecond parameter in a local Epoch.
I have a solution in which I first convert the local millisecond date-time into the local Gregorian calendar date-time (from which I can then construct a LocalDateTime and get the offset to UTC), but this seems like a lot of churning for what seems like it ought to be pretty simple.
Calculate the instant of your modified epoch:
ZoneId tz = ZoneId.of("America/Chicago");
Instant modifiedEpoch = ZonedDateTime.of(1970, 1, 1, 0, 0, 0, 0, tz).toInstant();
Then add your millis:
Instant instant = modifiedEpoch.plusMillis(millis);
Wrong Way To Track Date-Time
First I have to say this use of count-from-epoch integers for date-time values in various time zones rather than in UTC is a really, really bad idea. I’ve seen some bad ways to handle date-time including inventing one or two bad ways myself. But this one is the worst. Whoever thought this up should be sentenced to a year of daily readings of StackOverflow answers marked "java", "date", and "Jon Skeet".
Using count-from-epoch to handle date-time in your app code is like using bit arrays to handle text. We have classes/interfaces such as CharSequence, String, StringBuilder, Printer, Reader and so on to handle the nitty-gritty complicated details of text, characters, character encoding, collations, and such for us to make writing apps easier. Imagine trying to debug, troubleshoot, and log textual data as bit arrays. Crazy, right? Trying to debug, troubleshoot, and log date-time data as long integers is crazy too.
Ditto for date-time, where we had Joda-Time and now have its successor java.time (Tutorial) built into Java 8 and later.
Secondly, implicitly adjusting a count-from-epoch into a time zone and then losing that fact makes a bad practice even worse.
Fix
The way to fix this is to get that count-from-epoch in some arbitrary time zone translated into a local date and local time where local means the wall-clock time as seen by people in than time zone.
With that local date-time in hand, we create a date-time object that has the assigned time zone, a ZonedDateTime. A ZonedDateTime is basically an Instant (a point on the timeline in UTC) plus a ZoneId (a time zone).
Since the author of the Question failed to supply any sample data, let's create a value in this screwy fashion. Get the current moment in Chicago time zone. Get a legitimate count-from-epoch, adjusting from nanosecond resolution to millisecond. Then arbitrarily add/subtract the offset from UTC for that time zone.
In this example we use the time zone America/Chicago. It's offset for our sample, with Daylight Saving Time, is -05:00. In milliseconds, 5 * 60 * 60 * 1,000 = 18,000,000.
// First, create sample data, a count-from-epoch but not in UTC, instead adjusted for the time zone’s offset.
ZoneId zoneId = ZoneId.of( "America/Chicago" );
// 2015-09-19T12:34:56.000-05:00[America/Chicago]
ZonedDateTime zdtTemp = ZonedDateTime.of( 2015 , 9 , 19 , 12 , 34 , 56 , 0 , zoneId );
long millisecondsFromEpoch = zdtTemp.toInstant().toEpochMilli(); // Loosing data, goin from nanosecond
long offsetInMillisecondsForChicagoInDaylightSavingTime = 18_000_000L; // Offset of `-05:00` is in milliseconds, 5 * 60 * 60 * 1,000 = 18,000,000.
long input = ( millisecondsFromEpoch - offsetInMillisecondsForChicagoInDaylightSavingTime );
Dump to console.
System.out.println( "zoneId : " + zoneId );
System.out.println( "zdtTemp : " + zdtTemp );
System.out.println( "millisecondsFromEpoch : " + millisecondsFromEpoch );
System.out.println( "offsetInMillisecondsForChicagoInDaylightSavingTime : " + offsetInMillisecondsForChicagoInDaylightSavingTime );
System.out.println( "input : " + input );
Now, do the job. Take that screwy input number, pretending it is in UTC even though we know it is not, to produce an Instant. From the Instant, get a LocalDateTime. Now push that LocalDateTime into a time zone to get what we finally want, a ZonedDateTime.
// With example data in hand, proceed to convert to a valid date-time object.
Instant instantPretendingToBeInUtcButNotReally = Instant.ofEpochMilli( input );
LocalDateTime localDateTimeOfPretendInstant = LocalDateTime.ofInstant( instantPretendingToBeInUtcButNotReally , ZoneOffset.UTC );
ZonedDateTime zdt = localDateTimeOfPretendInstant.atZone( zoneId );
Dump to console.
System.out.println( "instantPretendingToBeInUtcButNotReally : " + instantPretendingToBeInUtcButNotReally );
System.out.println( "localDateTimeOfPretendInstant : " + localDateTimeOfPretendInstant );
System.out.println( "zdt : " + zdt );
When run.
zoneId : America/Chicago
zdtTemp : 2015-09-19T12:34:56-05:00[America/Chicago]
millisecondsFromEpoch : 1442684096000
offsetInMillisecondsForChicagoInDaylightSavingTime : 18000000
input : 1442666096000
instantPretendingToBeInUtcButNotReally : 2015-09-19T12:34:56Z
localDateTimeOfPretendInstant : 2015-09-19T12:34:56
zdt : 2015-09-19T12:34:56-05:00[America/Chicago]
CAVEAT I did this in rush. Please comment or fix any errors.
Because chronological time units are interconvertible, at first blush it might seem that you could have a local date-time in the following double precision format:
57272.5
where...
57272 is the day number reckoned from the modified Julian day number epoch (Nov 17, 1858).
0.5 is local time expressed as a fraction of one day, e.g. 0.5 = 12:00 noon local time.
There is nothing wrong with expressing a local date-time in this manner. However, numbers are numbers and so instead of a count of days since the modified Julian day number epoch, one can convert this to a count of milliseconds since the POSIX epoch (seemingly) very simply as:
localMillis = ( dayNumber - POSIX_EPOCH_AS_MJD) / (86400.0 * 1000.0);
This is where the notion of "milliseconds since the local epoch" has come from in this case. The mistake here, though, is that there IS NOT a simple one-to-one correspondence between POSIX Epoch millis and "local" epoch millis (the definition of POSIX Time requires that the count be milliseconds from the Epoch in UTC). This is because the local number contains one or more local offsets from UTC that are not guaranteed to be historically consistent (depending on legislation, etc).
These "local" millis can be used as a local time stamp, but they need to be adjusted for historical daylight savings and time zone offsets with the same care that any other time stamp should be. So why use them? I can't think of a reason. Having a time stamp in this format was a mistake.
The solution to this problem that has been employed:
Convert the "local millis" to a modified Julian day number with the local time expressed as a fraction of one day
Transform the modified Julian day number to a local Gregorian calendar date and time (algorithm adapted from "Astrological Algorithms", 2nd Ed. by J. Meeus).
Create a LocalDateTime instance from the local calendar date-time obtained above
Combine the LocalDateTime with the local ZoneId to contruct a ZonedDateTime, from which an Instant is obtained
POSIX time as UTC milliseconds from the POSIX Epoch is obtained from the Instant
A code example for this procedure follows:
public static long epochMillisFromLocalMillis(ZoneId tz, long millis) {
double dayNum = (millis / (86400.0 * 1000.0)) + POSIX_EPOCH_AS_MJD;
int[] ymd_hmsm = toVectorFromDayNumber(dayNum);
LocalDateTime ldt = LocalDateTime.of (
ymd_hmsm[MJD.YEAR],
ymd_hmsm[MJD.MONTH],
ymd_hmsm[MJD.DAY],
ymd_hmsm[MJD.HOURS],
ymd_hmsm[MJD.MINUTES],
ymd_hmsm[MJD.SECONDS],
ymd_hmsm[MJD.MILLIS] * 1000000);
long utcMillis = ZonedDateTime
.of(ldt, tz)
.toInstant()
.toEpochMilli();
return utcMillis;
}
Thanks to Basil Bourque and assylias for their insights on this peculiar problem.
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).