Related
I’ve red multiple articles and discussions and still I have some uncertainty: I’m not sure if I should use Instant or any other type to store Booking – in the sense of “Online booking” (so participants that are from different countries/time zones needs meet in the same moment on the timeline). I tend to use LocalDateTime, since DB and Backend is set to UTC and since incoming “create booking” json message contains ISO 8601 (with offset) startDateTime & endDateTime fields.
Let’s take this setup: 1. Database (UTC, Oracle, MSSQL), Java 11 Backend (UTC, SpringBoot, Spring JDBC template), Angular 13 Frontend.
Sources:
https://apiux.com/2013/03/20/5-laws-api-dates-and-times
https://medium.com/decisionbrain/dates-time-in-modern-java-4ed9d5848a3e
What's the difference between Instant and LocalDateTime?
java.time.LocalDate vs Instant for a 'business date'
https://mattgreencroft.blogspot.com/2014/12/java-8-time-choosing-right-object.html
What is clear for now:
Use ISO 8601 in REST API
Accept any time zone
Store it in UTC
Return it in UTC
Don’t use time if you don’t need it
Use "date-time values as count-from-epoch" as less as possible, since debugging and logging becomes very difficult. Use it just for technical stuff (e.g. ping).
Next:
And e.g. article 2) is telling: Many applications can be written only using LocalDate, LocalTime, and Instant, with the time zone added at the UI layer.
And the 3) is telling: So business app developers use Instant and ZonedDateTime classes most commonly. Nearly all of your backend, database, business logic, data persistence, data exchange should all be in UTC. But for presentation to users you need to adjust into a time zone expected by the user. This is the purpose of the ZonedDateTime class.
Let’s summarize & verify examples:
LocalTime
Company has a policy that lunchtime starts at 12:30 PM at each of its factories worldwide.
LocalDate
LocalDate + time zone (in separate db column)
Birthday - if it were crucial to know someone’s age to the very day, then a date-only value is not enough. With only a date, a person appearing to turn 18 years old in Tokyo Japan would still be 17 in Toledo Ohio US.
Due date in a contract. If stated as only a date, a stakeholder in Japan will see a task as overdue while another stakeholder in Toledo sees the task as on-time.
LocalDateTime
Christmas starts at midnight on the 25th of December 2015.
Booking – in the sense of “local” dentist appointment
Instant
Some critical function must start every day at 1:30am (LocalDateTime & ZonedDateTime are wrong in this case, because e.g. during switching from/to standard time to summer time, function runs twice/not at all).
store Booking – in the sense of “Online booking” (so participants that are from different countries/time zones needs meet in the same moment on the timeline). I tend to use LocalDateTime, since DB and Backend is set to UTC and since incoming “create booking” json message contains ISO 8601 (with offset) startDateTime & endDateTime fields.
You did not really define what you mean by "booking".
If what you meant is to pinpoint a moment, a specific point on the timeline, then LocalDateTime is exactly the wrong class to use.
For example, suppose a missile launching company has offices in Japan, Germany, and Houston Texas US. Staff at all offices want to be in a group conference call during a launch. We would specify the moment of launch with an offset of zero from UTC. For that we use Instant class. The Z in the string below means an offset of zero, +00:00, and is pronounced “Zulu”.
Instant launch = Instant.parse( "2022-05-23T05:31:23.000Z" ) ;
Let's tell each office their deadline to join the call.
ZonedDateTime tokyo = launch.atZone( ZoneId.of( "Asia/Tokyo" ) ) ;
ZonedDateTime berlin = launch.atZone( ZoneId.of( "Europe/Berlin" ) ) ;
ZonedDateTime chicago = launch.atZone( ZoneId.of( "America/Chicago" ) ) ; // Houston time zone is "America/Chicago".
We have four date-time objects, all representing the very same simultaneous moment.
As for the current default time zone of your database session and of your backend server, those should be irrelevant to your programming. As a programmer, those are out of your control, and are subject to change. So always specify your desired/expected time zone.
As for the rest of your Question, I cannot discern a specific question. So all I can do is comment on your examples.
LocalTime
Company has a policy that lunchtime starts at 12:30 PM at each of its factories worldwide.
Yes, LocalTime is correct for representing a time-of-day for any place in the world.
Let's ask if lunch has started at the factory in Delhi India.
LocalTime lunchStart = LocalTime.of( 12 , 30 ) ;
ZoneId zKolkata = ZoneId.of( "Asia/Kolkata" ) ;
ZonedDateTime nowKolkata = ZonedDateTime.now( zKolkata ) ;
boolean isBeforeLunchKolkata = nowKolkata.toLocalTime().isBefore( lunchStart ) ;
Actually, we have a subtle bug there. On some dates in some zones, the time 12:30 may not exist. For example, suppose that time zone on that date has a Daylight Saving Time (DST) cutover, "Springing ahead" one hour at noon to 1 PM. In that case, there would be no 12:30. The time 13:30 would be the "new 12:30".
To fix this bug, let's adjust from nowKolkata to use a different time of day. During this adjustment, java.time considers any anomalies such as DST, and handles them.
Note that this moving to a new time-of-day results in a new fresh separate ZonedDateTime. The java.time classes use immutable objects.
ZonedDateTime lunchTodayKolkata = nowKolkata.with( lunchStart ) ;
Now we should rewrite that test for whether lunch has started.
boolean isBeforeLunchKolkata = nowKolkata.isBefore( lunchTodayKolkata ) ;
Let's ask how long until lunch starts. Duration class represents a span of time not attached to the timeline, on the scale of hours-minutes-seconds.
Duration duration = Duration.between( nowKolkata , lunchTodayKolkata ) ;
We can use that duration as another way of determining if lunch has started there at that factory. If the duration is negative, we know we passed the start of lunch — meaning we would have to go back in time to see lunch start.
boolean isAfterLunchKolkata = duration.isNegative() ;
boolean isBeforeLunchKolkata = ! duration.isNegative() ;
LocalDate
The LocalDate class represents a date only, without a time-of-day, and without the context of a time zone or offset-from-UTC.
Be aware that for any given moment the date varies around the globe by zone. So right now it can be “tomorrow” in Tokyo Japan while simultaneously “yesterday” in Toledo Ohio US. So a LocalDate is inherently ambiguous with regard to the timeline.
Birthday - if it were crucial to know someone’s age to the very day, then a date-only value is not enough. With only a date, a person appearing to turn 18 years old in Tokyo Japan would still be 17 in Toledo Ohio US.
If you want to be precise about someone's age, you need their time of birth in addition to the date of birth and time zone of birth. Just the date and time zone of their birth place is not enough to narrow down their age to the first moment of their 18th year.
The time zone for Toledo Ohio US is America/New_York.
LocalDate birthDate = LocalDate.of( 2000 , Month.JANUARY , 23 ) ;
LocalTime birthTime = LocalTime.of( 3 , 30 ) ;
ZoneId birthZone = ZoneId.of( "America/New_York" ) ;
ZonedDateTime birthMoment = ZonedDateTime.of( birthDate , birthTime , birthZone ) ;
To determine if the person is 18 years of age precisely, add 18 years to that moment of birth. Then compare to current moment.
ZonedDateTime turning18 = birthMoment.plusYears( 18 ) ;
ZonedDateTime nowInBirthZone = ZonedDateTime.now( birthZone ) ;
boolean is18 = turning18.isBefore( nowInBirthZone ) ;
Due date in a contract. If stated as only a date, a stakeholder in Japan will see a task as overdue while another stakeholder in Toledo sees the task as on-time.
True.
A contract needing moment precision must state a date, a time-of-day, and a time zone.
Let's say a contract expires after the 23rd of May next year as seen in Chicago IL US.
I suggest avoiding the imprecise notion of "midnight". Focus on the first moment of a day on a certain date in a certain zone. So "after the 23rd" means the first moment of the 24th.
Some dates in some zones may start a time other than 00:00. Let java.time determine the first moment of a day.
LocalDate ld = LocalDate.of( 2023 , Month.MAY , 23 ) ;
ZoneId zChicago = ZoneId.of( "America/Chicago" ) ;
ZonedDateTime expiration = ld.plusDays( 1 ).atStartOfDay( zChicago ) ;
boolean isContractInEffect = ZonedDateTime.now( zChicago ).isBefore( expiration ) ;
LocalDateTime
Christmas starts at midnight on the 25th of December 2015.
Use LocalDateTime when you mean a date and time as seen in any locality.
Christmas starts at the first moment of the 25th of December in 2015 in every locality. So yes, use LocalDateTime.
LocalDateTime xmas2015 = LocalDateTime.of( 2015 , Month.DECEMBER , 25 , 0 , 0 , 0 , 0 ) ;
That class purposely lacks the context of a time zone or offset-from-UTC. So an object of this class is inherently ambiguous with regard to the timeline. This class cannot represent a moment.
In other words, Santa arrives in Kiribati (the most advanced time zone, at 14 hours ahead of UTC) before arriving in Japan, and arrives still later in India, still later in Europe, and so on. The red sleigh chases each new day dawning successively in zone after zone around the world all night long.
Let's determine the moment Christmas started then in Jordan.
ZoneId zAmman = ZoneId.of( "Asia/Amman" ) ;
ZonedDateTime xmasAmman = xmas2015.atZone( zAmman) ;
Adjust that moment to the time zone for Denver Colorado US.
ZoneId zDenver = ZoneId.of( "America/Denver" ) ;
ZonedDateTime xmasStartingInAmmanAsSeenInDenver = xmasAmman.withZoneSameInstant( zDenver ) ;
If you interrogate that xmasStartingInAmmanAsSeenInDenver object, you will see that when Christmas was starting in Jordan, the date in Denver was still the 24th, the day before Christmas. Christmas would not reach Colorado for several more hours.
Booking – in the sense of “local” dentist appointment
Appointments in the future that are intended to stick with their assigned time-of-day without regard to any changes to the time zone's rules must be tracked as three separate pieces:
Date with time-of-day
Time zone by which to interpret that date & time.
Duration, for how long the appointment will last.
Let's book an appointment for next January 23rd at 3 PM in New Zealand.
LocalDateTime ldt = LocalDateTime.of( 2023 , 1 , 23 , 15 , 0 , 0 , 0 ) ;
ZoneId z = ZoneId.of( "Pacific/Auckland" ) ;
Duration d = Duration.ofHours( 1 ) ;
Each of those three items must be stored in your database.
The first can be stored in a column of a type akin to the SQL standard type TIMESTAMP WITHOUT TIME ZONE. Notice the "WITHOUT", not "WITH".
The time zone can be stored as text, by its standardized name in format of Continent/Region. Never use 2-4 letter pseudo-zones such as IST, CST, etc.
I would store the duration in standard ISO 8601 format, PnYnMnDTnHnMnS. The P marks the beginning, which the T separates the two portions. So 1 hour is PT1H.
When you need to create a schedule, you must determine a moment. To do that, combine your parts.
LocalDateTime ldt = myResultSet.getObject( … , LocalDateTime.class ) ;
ZoneId z = ZoneId.of( myResultSet.getString( … ) ) ;
Duration d = Duration.parse( myResultSet.getString( … ) ) ;
ZonedDateTime start = ldt.atZone( z ) ;
ZonedDateTime end = start.plus( d ) ;
I and others have explained this multiple times, so search to learn more. The key point is that politicians around the world have shown a penchant for frequently changing the rules of the time zones in their jurisdictions. And they do so with surprisingly little forewarning. So the moment of that 3PM appointment might come an hour earlier that you expect now, or a half-hour later, or who knows what. Expecting your time zones of interest to remain stable is to doom your software to an eventual fail. You may think me paranoid. Let's check back in some years… when all the customers in your booking app are arriving at the wrong time.
Instant
Some critical function must start every day at 1:30am (LocalDateTime & ZonedDateTime are wrong in this case, because e.g. during switching from/to standard time to summer time, function runs twice/not at all).
No, incorrect, not Instant. If you want to run a task at 1:30 AM daily, you need to use LocalTime combined with a ZoneId. This is similar in concept to the appointments discussion directly above.
If we want the server in a Chicago office to run that task in the wee hours, we would define the date-time and zone.
ZoneId z = ZoneId.of( "America/Chicago" ) ;
LocalTime targetTime = LocalTime.of( 1 , 30 ) ;
To determine the next run, capture the current moment.
ZonedDateTime now = ZonedDateTime.now( z ) ;
See if we are before the target time, then calculate time to wait. We use "not after" as a shorter way of saying "if before or equal to". Think about if the current moment happens to be exactly 1:30:00.000000000 AM.
if( ! now.toLocalTime().isAfter( targetTime ) ) { // If before or equal to the target time.
Duration d = Duration.between( now , now.with( targetTime ) ) ;
…
}
If the target time has passed for today, add a day. Then calculate time to elapse until the next run.
else { // Else, after target time. Move to next day.
LocalDate tomorrow = now.toLocalDate().plusDays( 1 ) ;
ZonedDateTime nextRun = ZonedDateTime.of( tomorrow , targetTime , z ) ;
Duration d = Duration.between( now , nextRun ) ;
…
}
The ZonedDateTime.of method automatically adjusts if your specified time-of-day does not exist on that date in that zone.
If you are using an executor service to run your task at that same local time, then you cannot use ScheduledExecutorService with the methods scheduleAtFixedRate or scheduleWithFixedDelay. Use the schedule method with a single delay.
Pass a reference to that executor service to the constructor of your task, your Runnable/Callable. Then write your task in such as way that the last thing it does in submit itself to the executor service with a fresh newly-calculated delay. On most days that delay will be 24 hours. But on anomalous days such as Daylight Saving Time (DST) cutover, the delay might be something like 23 hours or 25 hours. So each task run schedules the next task run.
Neither. For appointments at a given location (be it for a video call meeting or simply a barber's appointment), you want ZonedDateTime.
Story time!
Let's say you're planning to be in Amsterdam somewhere in summer 2024, and for some strange reason you wanna make sure you look great, so you go a little nuts and decide to make an appointment at a barber's, for 14:00, May 2nd, 2024 (that'll be a wednesday), at some specific barbershop in Amsterdam.
We could calculate precisely how many seconds need to pass before your barbershop appointment is up: There will be some moment in time, and you can tell anybody on the planet to wait those exact number of seconds and then clap their hands. When they clap their hands? That exact time you have your appointment. It will be very early in the morning in California if someone were to clap in California, of course, but you could do that. That's the point - you're talking about a specific moment in time, which means it'll be a different time of day depending on where on the planet.
But, and this is not even hypothetical: The EU has decided a while ago that the EU should stop with the daylight savings time. The EU is busy with a few other things at the moment so enforcement of this directive is still being hashed out, but it's been decided - so now the debate is about when the EU stops with summer time and which time zone(s) the what used to be known as 'central european time', used by the vast majority of EU countries (it's a very large timezone), needs to become.
Notably, the EU has not decided on that, yet. In fact, each country is as of yet free to pick whatever they please. It is therefore quite plausible that e.g. The Netherlands, which is on the western edge of this very large zone, and where your barbershop appointment it, will decide to stick with what is currently 'winter time', because germany is likely to do that and citizens of both countries have grown rather used to it being the same time in both countries. But, sticking with summer time is also quite plausible. We simply don't know yet.
One day, in parliament, a hammer will come down. When that hammer comes down, that is the very moment in time when the dutch timezone definition has officially changed, and let's say they choose to stick with wintertime, at that exact moment that hammer comes down, your barber appointment just moved by 1 hour. It's still at 14:00, May 2nd, 2024, in Amsterdam - but the number of seconds until your appointment shifts by 3600 seconds the very moment that hammer lands in the dutch parliament in The Hague. Had you stored this moment in time in UTC, it would now be wrong. Had you stored this moment in time in terms of seconds left to go, or seconds from UTC epoch midnight newyears 1970, it would now be wrong. So don't store appointments in those ways.
That explains the pragmatic difference between Instant+timezone, and ZonedDateTime objects.
A ZonedDateTime object represents the notion of 'I have an appointment, in Europe/Amsterdam, at 14:00, on May 2nd, in 2024.
Given that that is exactly what it represents, if your tzdata files are updated to reflect that hammer coming down, that's okay. It's still 14:00, May 2nd, in 2024. In terms of 'how many seconds until the appointment', the moment in time represented by that object would shift by 1 hour the moment your JVM is updated with the new tz info.
Instant is different. Instant represents an exact moment in time in terms of 'how many seconds until that moment'. The act of storing that appointment as an Instant would involve calculating the amount of seconds until it happens and then storing that. And, as we just determined, that is incorrect and will mean you're going to miss your appointment. It's not how an appointment works (which works in terms of a year, month, day, hour, minute, and second, and a possibly implied timezone - no agenda any human uses is based on the idea of 'what does your day look like 134823958150154 seconds from now?') - and you should always use the data type (and store data) in terms of how it works, and not some conversion of it, because if you store conversions, you 'encode' your reliance on it into your storage and stuff goes wrong if your conversion no longer works. Which is why storing it as an Instant is the mistake here.
The story also explains why the only non-stupid way to store timezone info is as something like Europe/Amsterdam - because something like "CET" (Central European Time) may not even exist 2 years hence. There is absolutely no guarantee every single last one of the many many countries that are in CET today will pick the same zone when the EU directive grows teeth, whenever that may be. So if you store the appointment as 'in the CET zone', then all you know, come 2024, is that you now have no idea when your barbershop's appointment actually is. Not all that useful.
Coordinating a time to meet, without the context of place
Every so often you want to coordinate a time to meet, but not a place to meet - a virtual meeting, for example. Usually these are still 'localized' (somebody is deemed the host, they plan the meeting for, say, 17:30 their time, all participants store that ZonedDateTime construct, and their agenda systems will tell them when to log in for that meeting at the appropriate time, regardless of where on the planet they are). So I'd just do that.
But given that the meeting is virtual, 'a timezone is implied' no longer has to neccessarily be true. In which case it is fine to store things as an Instant (and a ZonedDateTime where the zone is UTC is so close to the concept of Instant, there is basically no difference at all), in which case e.g. someone in The Netherlands would see their appointment move by an hour the moment that hammer comes down. Assuming they did it right and indicated to their calendaring tool that the appointment is in the UTC zone.
It's possible you want this instead. But it's more common to go with the 'host zone' route. (there isn't that much of a difference; the calendaring tool needs to support the concept of saving an appointment with the timezone (and not convert it to the right time in the local zone, as that will break if either the host zone or the target zone decides to make a change to their tz data, which happens all the time - once a calendaring tool supports this, you could choose to use UTC as host zone for such a virtual meeting).
DBs
You've more or less correctly concluded that there are 3 fundamentally different, and therefore utterly incompatible notions of date and time; you cannot convert one to the other without losing something on the way. These are:
LocalDate (and LocalTime, and LocalDateTime): If you want to wake up 08:00 and you want that alarm to continue to wake you up at 08:00 locally even if you fly to different zones, this is the thing you want.
ZonedDateTime: For appointments.
Instant: To log that something happens or will happen X seconds from now or X seconds in the past; a slam dunk for e.g. timestamps on log messages, and obviously the one to use for space stuff (that solar flare will occur on X - when that hammer comes down in dutch parliament, and a newspaper calculated the solar flare out to an exact date, hour, and minute and printed that earlier, that is now wrong, of course, and they'd have to print a correction. Had they printed an instant, unwieldy as that might be to us humans, they would not have had to do that).
Unfortunately, most languages (including java's obsolete java.util.Date and java.util.Calendar approaches) mess this up and didn't realize time is just that complicated, and that the above 3 concepts cannot be converted without losing an important nuance.
As a consequence, DBs sometimes play fast and loose too. More to the point, JDBC which is usually the 'glue' that java apps use to talk to DBs, has an API that involves the types java.sql.Date and java.sql.Timestamp, and both of those are based on j.u.Date (they extend it), and j.u.Date, name notwithstanding, is equivalent to Instant - it stores a moment-in-time, does not store a timezone in the first place, and will break if timezones up and change definition on you.
So, the actual JDBC calls you have to use to get the right data type in and out is tricky. The theoretical correct answer is LDT and ZDT directly:
ResultSet rs = somePreparedStatement.query();
/* WRONG */ {
Date x = rs.getDate(1);
ZonedDateTime y = convertToZDT(x);
}
/* RIGHT */ {
ZonedDateTime y = rs.getObject(1, ZonedDateTime.class);
}
However, many JDBC drivers don't support this. Very painful. Similarly, to replace question marks in prepared statements with dates, the correct move is ps.setObject(1, someZdtInstance), not with a j.u.Date instance or even an java.sql.Date instance. Whether they support that, oof. You'd have to check - many do not.
I suggest you check the DB docs about the type(s) for dates they support and how it actually stores them. If it sounds good (that is, it stores an actual year, month, day, hour, minute, second, and full timezone name, all in a single column), use that. If none of the data types support that, do it yourself and make 7 columns.
Assuming you found a data type that does the job, figure out if .getObject(x, ZDT.class) works, and ps.setObject(x, zdtInstance). Let's hope so. If it does, that's your answer. If it does not, kludge around to figure out what the workaround is: Write code that converts and check what arrives - after all, if the DB is storing actual y/m/d/h/m/s/zone info, and you give it a j.u.Timestamp object which does not have that info, then the DB is going to have to be converting that back to ymdhmsz info again. Assuming that works out fine, then this workaround is not going to break on you. Even if the appointment is in Amsterdam and that hammer comes down between now and then.
I am trying to understand LocalDate and LocalDateTime. Since they do not carry zone info, how does it work for now() on two different time zone.
Example:
Server 1(EST time zone):
LocalDateTime.now() -> 2020-04-06T23:00:00.040
LocalDate.now(). -> 2020-04-06
Server 2(UTC time zone):
LocalDateTime.now() -> What would be the value?
LocalDate.now(). -> What would be the value? (Note in EST, time it executed was 11 PM)
Also,
If I convert below date string to LocalDateTime and then toLocalDate, what would be the outcome?
2020-04-06T23:00:00.000
Offsets vary over time
On April 6, 2020, most time zones on the east coast of North America such as America/Montreal and America/New_York use an offset of four hours behind UTC.
So, in those zones 11 PM on 2020-04-06 is simultaneously 3 AM on the 7th in UTC. Add four hours to 2020-04-06T23:00 to get 2020-04-07T03:00. Adding four hours brings us from the wall-clock time used in America/Port-au-Prince and America/Nassau to UTC.
The offset used by the time zones mentioned above are four hours behind UTC only half the year. The politicians in those locations have decided to observe Daylight Saving Time (DST) for about half the year. So half the year they jump their clocks ahead an hour for an offset-from-UTC of -04:00, and later in the year they fall back an hour for an offset-from-UTC of -05:00. For example, earlier in the year 11 PM in America/New_York would be 04:00 in UTC rather than the 03:00 time seen in April.
Standard time, in January, is five hours behind UTC. So, 11 PM plus five hours is 4 AM next day.
ZoneId z = ZoneId.of( "America/New_York" ) ;
ZonedDateTime zdt = ZonedDateTime.of( 2020 , 1 , 6 , 23 , 0 , 0 , 0 , zNewYork ) ;
Instant instant = zdt.toInstant() ; // 2020-01-07T04:00Z
Daylight Saving Time, in April, is four hours behind UTC. So, 11 PM plus four hours is 3 AM next day.
ZoneId z = ZoneId.of( "America/New_York" ) ;
ZonedDateTime zdt = ZonedDateTime.of( 2020 , 4 , 6 , 23 , 0 , 0 , 0 , zNewYork ) ;
Instant instant = zdt.toInstant() ; // 2020-04-07T03:00Z
By the way, DST is only one of many reasons politicians have for changing the offset used by the time zone(s) of their jurisdiction. Politicians around the world have shown a penchant for changing their offsets surprisingly often. Your programming should always expect a time zone to change its offset. Just because your particular zone of concern does not currently observe DST does not mean the offset will never change.
As for parsing 2020-04-06T23:00:00.000 as a LocalDateTime, you will get 2020-04-06T23:00:00.000. Ignoring time zones is the entire point of LocalDateTime. So no adjustments are made.
You may be thinking of LocalDateTime as representing a particular locality. But, no, it represents any locality or all localities. But never any one particular locality. For a particular locality, use ZonedDateTime.
Pseudo time zones
Another point: EST is not a time zone. Such 2-4 letter letter codes are not real time zones, are not standardized, and are not even unique. For example, by EST, did you mean Australian Eastern Standard Time or North American Eastern Standard Time? Is CST Central Standard Time or China Standard Time? Avoid these pseudo-zones in your date-time-handling.
And you happened to use the wrong pseudo-code. On April 6 2020, most of those east coast time zones are observing Daylight Saving Time (DST). So they would be considered to be in “EDT” rather than “EST”. Avoid the problem by specifying the real time zone name.
Real time zones are named using Continent/Region format. See Wikipedia for a list of real time zones.
Never call LocalDateTime.now
I cannot imagine a case where calling LocalDateTime.now would be the right thing to do. Determining the current moment requires a time zone (or offset). And LocalDateTime by definition has no time zone or offset. When a programmer writes LocalDateTime.now, you can bet they do not fully understand the necessary concepts.
When you call LocalDateTime.now, the JVM’s current default time zone is implicitly used to capture the current time as seen by the people in the region of that zone. And then the fact of that time zone is deleted.
This:
LocalDateTime.now()
…is the same as this:
LocalDateDate.now( ZoneId.systemDefault() )
…which is the same as getting the current moment as seen in a particular time zone, followed by removing the time zone information:
ZonedDateTime.now( ZoneId.systemDefault() ).toLocalDateTime()
For more code examples demonstrating LocalDateTime.now, see the correct Answer by Ole V.V.
Where to use LocalDateTime
If LocalDateTime is not appropriate for getting the current moment, and is not appropriate for tracking any moment, what is the appropriate use for this class? Three things: representing any locality, representing all localities, and booking future appointments.
Any locality would be something like stating when Christmas starts. This year Christmas starts at 2020-12-25T00:00 wherever you are in the world. Of course this means Christmas starts first in Kiribati after midnight, later in Japan after midnight, even later in Tunisia after midnight, and still later in Chicago after midnight.
All localities would be something like stating our company policy that lunch breaks at all our factories in Delhi, Düsseldorf, and Detroit are scheduled for 12:30. So on April 6 2020, the break will be at 2020-04-06T12:30:00. This break will occur first in Delhi, several hours later in Düsseldorf, and even more hours later in Detroit.
Booking future appointments where you intend to keep the same time-of-day regardless of changes to the time zone’s offset must recorded without the offset. If your next dental appointments is in six months at 3 PM, we want to record the 3 PM without regard for the offset. If the offset were to be changed by politicians, we still want the appointment to start when the clock strikes three on that date in that zone.
Set the appointment.
LocalDateTime appointment = LocalDateTime.of( 2021 , 1 , 23 , 15 , 0 , 0 , 0 ) ;
Determine a moment for producing a calendar. Every time you do this, you may get a different result if the politicians have changed the rules of this time zone.
ZoneId z = ZoneId.of ( "America/Panama" ) ;
ZonedDateTime dueToArrive = appointment.atZone( z ) ;
LocalDate
As for LocalDate, we saw in the examples above that the date depends on time zone. For any given moment, the date varies around the globe by time zone. It may be “tomorrow” in Tokyo Japan while still “yesterday” in Montréal Québec Canada.
So, you must specify a time zone when asking for the current date.
LocalDate ld = LocalDate.now( ZoneId.of( "Asia/Kolkata" ) ) ;
If you omit the time zone, the JVM’s current default time zone is implicitly applied. So LocalDate.now() becomes LocalDate.now( ZoneId.systemDefault() ). I recommend specifying your desired/expected zone explicitly, rather than rely on implicit default.
Server time zone
You said:
server which runs in EST vs UTC
FYI, servers should generally be set to UTC. Most of your thinking, programming, logging, and so on should be done in UTC. Learn to think of UTC as The One True Time, with all other zones but mere variations.
As a programmer, you should never rely on the default time zone. That is far outside your control. That default can be changed so easily by the user or sysadmin. Furthermore, any code in any thread of any app within the JVM can instantly change the JVM’s current default time with a call to TimeZone.setDefault. So even during execution of your app, the default can be changed at any moment.
To prevent confusion pass explicit time zone to now() and see for yourself:
ZoneId easternTime = ZoneId.of("America/Montreal");
System.out.println(LocalDateTime.now(easternTime));
System.out.println(LocalDateTime.now(ZoneOffset.UTC));
System.out.println(LocalDate.now(easternTime));
System.out.println(LocalDate.now(ZoneOffset.UTC));
Output when I ran just now:
2020-04-06T09:56:17.381558
2020-04-06T13:56:17.385215
2020-04-06
2020-04-06
While you are correct that LocalDateTime and LocalDate don’t contain any time zone information, their now methods do use time zones. Either the one passed to them, or if you use the no-arg variant, the default time zone of the JVM.
You also asked:
Also, If I convert below date string to LocalDateTime and then
toLocalDate, what would be the outcome?
2020-04-06T23:00:00.000
Why not try out that too?
LocalDateTime ldt = LocalDateTime.parse("2020-04-06T23:00:00.000");
System.out.println(ldt);
LocalDate ld = ldt.toLocalDate();
System.out.println(ld);
2020-04-06T23:00
2020-04-06
Converting from LocalDateTime to LocalDate involves no time zone (or UTC offset) whatsoever. The time part is simply discarded and the date part kept unchanged.
I'm running the below expressions on December 7th, 2018.
I'm seeing a discrepancy whereby this:
ZonedDateTime.now(ZoneId.of("America/New_York")).minusDays(30)
returns (correctly):
2018-11-07T22:44:11.242576-05:00[America/New_York]
whereas conversion to an instant:
ZonedDateTime.now(ZoneId.of("America/New_York")).minusDays(30).toInstant()
seems to mess up the result by adding an extra day to it:
2018-11-08T03:58:01.724728Z
I need an instant conversion to use its result in the following code as Date:
... = Date.from(t.toInstant())
An equivalent Python code (Django) works correctly:
datetime.datetime.now('America/New_York')+datetime.timedelta(days=-30)
evaluating to: datetime: 2018-11-07 20:13:55.063888-05:00
What's causing the discrepancy?
What should I use so that Java conversion to Date resulted in the November 7th being returned, just like in Python's case? Basically, I'm looking to an equivalent translation of that Python code into Java, or in pseudocode:
`datetime.X = datetime.now(deployment_zone) - (N_days)`,
where `deployment_zone` is configurable (i.e. `America/New_York`)
`N_days` is configurable (i.e. 30)
Update for #Basil Bourque:
When I formulated the original question, I (per SO rules) tried to simplify it to a digestible form which probably destroyed most of the necessary context making it vague. Let me try again.
As I explained in the comments, I'm converting the existing Python code (which is more actively maintained and which client wants to keep intact) to existing Java code (legacy that has not been properly maintained and strayed away from the Python's logic some time back). Both code bases need to be functionally on par with each other. Java needs to do what Python is already doing.
Python code is as follows (I'm lumping all into one place for succinctness, in reality it's distributed across a couple of files):
analytics.time_zone=America/New_York
TIME_ZONE = props.getProperty('analytics.time_zone', 'UTC')
TZ = pytz.timezone(TIME_ZONE)
def days_back(num_days=0):
adjusted_datetime = datetime.datetime.now(TZ)+datetime.timedelta(days=-num_days)
return DateRangeUtil.get_start_of_day(adjusted_datetime)
class DateRangeUtil():
#staticmethod
def get_start_of_day(date):
return date.astimezone(TZ).replace(hour=0, minute=0, second=0, microsecond=0)
which basically takes the configured time zone, in which it obtains the current instant, subtracts a specified number of days from it, converts it to the beginning of that date and thus receives the lower bound of the range to use while querying the DB, something like Start time: datetime: 2018-11-07 20:13:55.063888-05:00
When I started on the Java side, it had:
public final static DateRange parse(String dateRange) {
//.....
int days = ...
return new DateRange(toBeginningOfDay(daysBack(days)), toEndOfDay(daysBack(0)));
private final static Date daysBack(int days) {
return toDate(LocalDateTime.now().minusDays(days));
}
private final static Date toBeginningOfDay(Date d)
{
Calendar c=Calendar.getInstance();
c.setTime(d);
c.set(HOUR_OF_DAY,0);
c.set(MINUTE,0);
c.set(SECOND,0);
c.set(MILLISECOND, 0);
return c.getTime();
}
private final static Date toDate(LocalDateTime t) {
return Date.from(t.atZone(ZoneId.systemDefault()).toInstant());
}
That code didn't work and introduced the discrepancy which I describe in my original question. I started experimenting and introduced ZonedDateTime into the picture. While investigating, I found that it's the call to .toInstant() that seems to be a culprit and wanted to understand what's behind it in more depth.
In his answer, #ernest_k suggested a solution which seemed to have worked, but I still didn't quite understood which is clear from questions in the comments to his response.
The changes I made based on #ernest_k response are as follows:
private final static Date daysBack(int days) {
return toDate(ZonedDateTime.now(ZoneId.of("America/New_York")).minusDays(days).toLocalDateTime());
private final static Date toDate(LocalDateTime t) {
return Date.from(t.toInstant(ZoneOffset.UTC));
}
This seems to produce the desired outcome: However conversion from local to zoned and then back again seemed too much, so I experimented a bit more and found that simply the LocalDateTime does the trick as well:
private final static Date toDate(LocalDateTime t) {
return Date.from(t.toInstant(ZoneOffset.UTC));
}
private final static Date daysBack(int days) {
return toDate(LocalDateTime.now().minusDays(days));
}
I can see that LocalDate (and perhaps LocalDateTime) has a convenient atStartOfDay() which seems to be a fitting candidate for elimination of Dates out of the picture while replacing the legacy toBeginningOfDay(Date d) method above. Not sure it's doing the same thing - I haven't yet experimented with that idea, so the suggestions are most welcome.
So, with all of the tribulations above, my question started around toInstant() behavior, and when it's passed a zone id, whether it converts TO an instant in that zone, or FROM it, or what?
I guess for the situation I'm describing we only care that the lower time bound in the DB query is formed by comparing some consistent marker of current time (its upper bound) to what it was in the same place (time zone?) in the past N days, so comparing it with UTC should server the purpose.
Does that then make passing the zone in unnecessary?
Now, that a solution seems to have been found, the question revolves around the soundness of the approach described above and the solution that's been stumbled upon - is it the most optimal one, best practices around Java timing libs, etc. The code needs to work for any time zone in which the code bases will end up being deployed, that's why the zone is passed in via configuration.
Also, I wonder if things change when/if the DB itself is deployed off-premise from the rest of the codebase and is configured to persist data in some other time zone. But that might be another question.
tl;dr
ZonedDateTime.toInstant() adjusts a moment from a time zone to UTC. You end up with the same moment, different wall-clock time, and possibly a different date for the same simultaneous point on the timeline. What you are seeing is not a problem, not a discrepancy.
Your problem is not with subtracting 30 days. The real problems:
Not understanding that time zone affects the date
Conflating dates with days
Furthermore, your Question is vague. Saying “30 days ago” can mean at least three different things:
30 * 24 hours
A range from 22:44 thirty calendar days ago in New York time zone to 22:44 now in New York time
The entire day today as seen in New York and the entire days going back 30 days on the calendar as seen in New York.
All three possibilities are covered below, with example code, labeled with ➥.
⑦🕥 🇺🇸📞 ↔ 📞🇮🇸 ⑧🕞
On the 7th of December, shortly before midnight (22:44), Alice in her New York apartment decides to call her friend Bob in Reykjavík, Iceland. Bob can't believe his phone is ringing, and looking over at the clock on his bedside table sees the time is almost 4 AM (03:44). And Bob's fancy digital clock shows the date as the 8th of December, not the 7th. Same simultaneous moment, same point on the timeline, different wall-clock time, different date.
The people of Iceland use UTC as their time zone, year-round. New York is five hours behind UTC in December 2018, and so five hours behind Iceland. In New York it is “yesterday” the 7th while in Iceland it is “tomorrow” the 8th. Different dates, same moment.
So forget about subtracting the thirty days. Any time you take a moment in New York that is close to midnight, and then adjust to UTC, you will be moving the date forward.
No discrepancy, no extra day added. For any given moment, the date varies around the globe by time zone. With a range in time zones of about 26-27 hours, it is always “tomorrow” and “yesterday” somewhere.
Another Answer suggests involving LocalDateTime into this problem. That is ill-advised. That class purposely lacks any concept of time zone or offset-from-UTC. That means a LocalDateTime cannot represent a moment. A LocalDateTime represents potential moments along the range of 26-27 hours mentioned above. Makes no sense to involve that class here.
Instead, use OffsetDateTime for a moment viewed with an offset-from-UTC, versus [ZonedDateTime][2] which uses a time zone.
What is the difference between an offset and zone? An offset is merely a number of hours-minutes-seconds, nothing more, nothing less. A zone, in contrast, is much more. A zone is a history of the past, present, and future changes to the offset used by the people of particular region. So a time zone is always preferable to a mere offset, as it brings more information. If you want UTC specifically, you need only an offset, an offset of zero hours-minutes-seconds.
OffsetDateTime odt = zdt.toOffsetDateTime().withOffsetSameInstant( ZoneOffset.UTC ) ; // Adjust from a time zone to UTC.
The zdt and odt seen here both represent the same moment, the same point on the timeline, different wall-clock time, like Alice and Bob example above.
Days != Dates
If you want to query for a range of thirty days ago, you must define what you mean by “days”.
Days
➥ Do you mean 30 chunks of 24-hour long spans of time? If so, work with Instant. This class represents a moment in UTC, always in UTC.
ZoneId z = ZoneId.of( "America/New_York" ) ;
ZonedDateTime zdtNow = ZonedDateTime.now( z ) ;
Instant instantNow = zdt.toInstant() ; // Adjust from time zone to UTC. Same moment, different wall-clock time.
Instant instantThirtyDaysAgo = instantNow.minus( 30 , ChronoUnit.DAYS ) ; // Subtract ( 30 * 24 hours ) without regard for dates.
You may be able to exchange an Instant with your database via your JDBC driver. But Instant is optional, while support for OffsetDateTime is required by JDBC 4.2 and later. If that is the case, let's re-write that code.
ZoneId z = ZoneId.of( "America/New_York" ) ;
ZonedDateTime zdtNow = ZonedDateTime.now( z ) ;
OffsetDateTime odtNow = zdt.toOffsetDateTime().withOffsetSameInstant( ZoneOffset.UTC ) ; // Adjust from time zone to UTC. Same moment, different wall-clock time.
OffsetDateTime odtThirtyDaysAgo = odtNow.minusDays( 30 ) ;
Your SQL might be something like the following.
Note what we are using the Half-Open approach to defining a span-of-time, where the beginning is inclusive while the ending is exclusive. This is generally best practice, as it avoid the problem of finding the infinitely divisible last moment, and it provides for neatly abutting spans without gaps. So we do not use the SQL command BETWEEN, being fully-closed (inclusive on both ends).
SELECT * FROM event_ WHERE when_ >= ? AND when_ < ? ;
Set values for the placeholders in your prepared statement.
myPreparedStatement.setObject( 1 , odtThirtyDaysAgo ) ;
myPreparedStatement.setObject( 2 , odtNow ) ;
Dates
➥ If by “30 days ago” you meant 30 boxes on the calendar hanging on the wall in a New York office, that is a very different problem.
Same time-of-day
And if so, do you mean from the current moment and moving back 30 days to the same time-of-day?
ZoneId z = ZoneId.of( "America/New_York" ) ;
ZonedDateTime zdtNow = ZonedDateTime.now( z ) ;
ZonedDateTime zdtThirtyDaysAgo = zdtNow.minusDays( 30 ) ; // `ZonedDateTime` will try to keep the same time-of-day but will adjust if that time on that date in that zone is not valid.
With the code seen above, the ZonedDateTime class will try to use the same time-of-day on the earlier date. But that time may not be valid on that date in that zone, because of anomalies such as Daylight Saving Time (DST) cutover. In such an anomaly, the ZonedDateTime class adjusts to a valid time. Be sure to study the JavaDoc to understand the algorithm and to see if it suits your business rules.
Pass to your prepared statement.
myPreparedStatement.setObject( 1 , zdtThirtyDaysAgo ) ;
myPreparedStatement.setObject( 2 , zdtNow ) ;
Entire day
➥ Or by “30 days ago” do you mean dates, and by dates you mean all-day-long?
If so, we need to focus on the date-only value, by using LocalDate class, without a time-of-day and without a time zone.
ZoneId z = ZoneId.of( "America/New_York" ) ;
LocalDate today = LocalDate.now( z ) ;
LocalDate tomorrow = today.plusDays( 1 ) ;
LocalDate thirtyDaysAgo = tomorrow.minusDays( 30 ) ;
Now we need to go from the date to a specific moment by assigning a time-of-day and a time zone. We want the time to be the first moment of the day. Do not assume that means 00:00. Because of anomalies such as DST, the day may start at another time such as 01:00. Let java.time determine the first moment of the day on that date in that zone.
ZonedDateTime zdtStart = thirtyDaysAgo.atStartOfDay( z ) ;
ZonedDateTime zdtStop = tomorrow.atStartOfDay( z ) ;
Pass to your prepared statement.
myPreparedStatement.setObject( 1 , zdtStart ) ;
myPreparedStatement.setObject( 2 , zdtStop ) ;
That "extra day" is not really an extra day. 2018-11-07T22:44:11 in New York is equivalent to 2018-11-08T03:58:01 in UTC (it's the same point in time). The difference is just 5 hours, not a day (and when I google this, I see New York is GMT-5).
ZonedDateTime#toInstant returns an Instant instance representing the same point in time (in UTC):
Converts this date-time to an Instant.
This returns an Instant representing the same point on the time-line as this date-time. The calculation combines the local date-time and offset.
If you want to not use the offset when converting to instant, then you should perhaps use LocalDateTime:
ZonedDateTime.now(ZoneId.of("America/New_York"))
.toLocalDateTime()
.toInstant(ZoneOffset.UTC)
This tells it to convert as though it were already UTC time (but a warning is appropriate here: this changes the date/time value)
First, avoid the need for an old-fashioned Date if you can. java.time, the modern Java date and time API, gives you all the functionality you need.
Sometimes we do need a Date for a legacy API that we cannot change or don’t want to upgrade just now. Java is giving you what I think you want. Demonstration:
ZonedDateTime nov7 = ZonedDateTime.of(2018, 11, 7, 22, 44, 0, 0,
ZoneId.of("America/New_York"));
Instant inst = nov7.toInstant();
System.out.println("As Instant: " + inst);
Date oldFashionedDate = Date.from(inst);
System.out.println("As Date: " + oldFashionedDate);
Output from this was:
As Instant: 2018-11-08T03:44:00Z
As Date: Wed Nov 07 22:44:00 EST 2018
Admitted, to get this output I had to change my JVM’s default time zone to America/New_York first.
Date and Instant are roughly equivalent but print differently. Meaning their toString methods behave differently, which may be confusing. Each is a point in time, none of them is a date (despite the name of one of them). It is never the same date in all time zones.
Date.toString picks up your JVM’s time zone setting and uses it for generating the string it returns. Instant.toString on the other hand always uses UTC for this purpose. This is why the same point in time is printed with different date and time. Fortunately they both also print a bit of time zone information so the difference is at least visible. Date prints EST, which, albeit ambiguous, in this case means Eastern Standard Time. Instant prints Z for offset zero from UTC or “Zulu time”.
I have written below code which is running, and giving output. But I'm not sure It's a right one.
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
Date date = new Date();
sdf.setTimeZone(TimeZone.getTimeZone("GMT-7"));
String value = sdf.format(date);
System.out.println(value);
Date date2 = sdf.parse(value);
long result = date2.getTime();
System.out.println(result);
return result;
The above code what I'm trying is, I just need to get the current time of GMT time zone and convert it as epoch format which is gonna used in Oracle db.
Can someone tell me that format, and the above code is right?
First, you should not store time since the epoch as a timestamp in your database. Look into the date-time datatypes that your DMBS offers. In Oracle I think that a date column will be OK. For most other DBMS you would need a datetime column. timestamp and timestamp with timezone may be other and possibly even sounder options depending on your exact requirements.
However, taking your word for it: Getting the number of milliseconds since the epoch is simple when you know how:
long millisecondsSinceEpoch = System.currentTimeMillis();
System.out.println(millisecondsSinceEpoch);
This just printed:
1533458641714
The epoch is defined in UTC, so in this case we need to concern ourselves with no other time zones.
If you needed seconds rather than milliseconds, it’s tempting to divide by 1000. However, doing your own time conversions is a bad habit since the libraries already offers them, and using the appropriate library methods gives clearer, more explanatory and less error-prone code:
long secondsSinceEpoch = Instant.now().getEpochSecond();
System.out.println(secondsSinceEpoch);
1533458641
You said:
I just need to get the current time of GMT time zone…
Again taking your word:
OffsetDateTime currentTimeInUtc = OffsetDateTime.now(ZoneOffset.UTC);
System.out.println(currentTimeInUtc);
long millisecondsSinceEpoch = currentTimeInUtc.toInstant().toEpochMilli();
System.out.println(millisecondsSinceEpoch);
2018-08-05T08:44:01.719265Z
1533458641719
I know that GMT and UTC are not exactly the same, but for most applications they can be (and are) used interchangeably.
Can someone tell me (if) the above code is right?
When I ran your code just now, its output agreed with mine except the milliseconds were rounded down to whole thousands (whole seconds):
1533458641000
Your code has some issues, though:
You are using the old, long out-dated and poorly designed classes SimpleDateFormat, Date and TimeZone. The first in particular has a reputation for being troublesome. Instead we should use java.time, the modern Java date and time API.
Bug: In your format pattern string you are using lowercase hh for hour of day. hh is for hour within AM or PM, from 1 through 12, so will give you incorrect results at least half of the day. Uppercase HH is for hour of day.
Don’t use GMT-7 as a time zone. Use for example America/Los_Angeles. Of course select the time zone that makes sense for your situation. Edit: You said:
I just want to specify the timezone for sanjose. GMT-7 is refer to
sanjose current time.
I believe many places are called San Jose. If you mean San Jose, California, USA, you are going to modify your program to use GMT-8 every time California goes back to standard time and opposite when summer time (DST) begins?? Miserable idea. Use America/Los_Angeles and your program will work all year.
Since you ask for time in the GMT time zone, what are you using GMT-7 for at all?
There is no point that I can see in formatting your Date into a string and parsing it back. Even if you did it correctly, the only result you would get would be to lose your milliseconds since there are no milliseconds in your format (it only has second precision; this also explained the rounding down I observed).
Links
Oracle tutorial: Date Time explaining how to use java.time, the modern Java date and time API.
San Jose, California on Wikipedia
Why not use Calendar class?
public long getEpochTime(){
return Calendar.getInstance(TimeZone.getTimeZone("GMT-7")).getTime().getTime()/1000; //( milliseconds to seconds)
}
It'll return the current Date's Epoch/Unix Timestamp.
Based on Harald's Comment:
public static long getEpochTime(){
return Clock.system(TimeZone.getTimeZone("GMT-7").toZoneId() ).millis()/1000;
}
Here is a solution using the java.time API
ZonedDateTime zdt = LocalDateTime.now().atZone(ZoneId.of("GMT-7"));
long millis = zdt.toInstant().toEpochMilli();
How can I get the current local wall clock time (in number of millis since 1 Jan 1970) in London? Since my application can run on a server in any location, I think I need to use a TimeZone of "Europe/London". I also need to take Daylight Savings into account i.e. the application should add an hour during the "summer".
I would prefer to use the standard java.util libraries.
Is this correct?
TimeZone tz = TimeZone.getTimeZone("Europe/London") ;
Calendar cal = Calendar.getInstance(tz);
return cal.getTime().getTime() + tz.getDSTSavings();
Thanks
I'm not sure what this quantity represents, since the "number of millis since 1 Jan 1970" doesn't vary based on location or daylight saving. But, perhaps this calculation is useful to you:
TimeZone london = TimeZone.getTimeZone("Europe/London");
long now = System.currentTimeMillis();
return now + london.getOffset(now);
Most applications are better served using either UTC time or local time; this is really neither. You can get the UTC time and time in a particular zone like this:
Instant now = Instant.now(); /* UTC time */
ZonedDateTime local = now.atZone(ZoneId.of("Europe/London"));
Others have said that it may well not be a good idea to do this - I believe it depends on your situation, but using UTC is certainly something to consider.
However, I think you've missed something here: the number of seconds which have occurred since January 1st 1970 UTC (which is how the Unix epoch is always defined - and is actually the same as in London, as the offset on that date was 0) is obtainable with any of these expressions:
System.currentTimeMillis()
new Date().getTime()
Calendar.getInstance().getTime().getTime()
If you think about it, the number of milliseconds since that particular instant doesn't change depending on which time zone you're in.
Oh, and the normal suggestion - for a much better date and time API, see Joda Time.
To get the current time in London:
SimpleDateFormat f = new SimpleDateFormat("dd MMM yyyy HH:mm:ss z");
f.setTimeZone(TimeZone.getTimeZone("Europe/London"));
System.out.println(f.format(GregorianCalendar.getInstance().getTime()));