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.
Related
I want to represent a 'business date', eg a transaction that happened 'on 3 June 2019'. We actively ignore timezones for this purpose, in full knowledge that 'on 3 June 2019' in Japan might be 'on 2 June 2019' in the US - and ordering within the 'day' is equally irrelevant. All dates will be today, or prior dates.
My obvious answer is that this is a LocalDate. However someone else has suggested this would be better represented as an Instant of 2019-06-03T00:00:00.000Z.
Apart from the different calls we'll have to make when converting/formatting this to human-readable dates in a UI, are there actually any differences between the two approaches?
This is a different question to What's the difference between Instant and LocalDateTime? because time is irrelevant to this question, and it only relates to past (or current) dates.
I once worked on a product where we made the mistake of representing a date of birth as an instant.
This was one of those "tiny" design errors that we were still dealing with years later.
The problem was that you couldn't reliably show it in the UI as the user changed time zone; it was error-prone to convert on the backend, because of developers working in different time zones to where the server was running. It sort-of worked in a single time zone, but as the product was extended to other time zones, it just became a total headache.
An instant is a point on the timeline; a local date is a range of times (and not a well-defined range of times, in the sense that it can represent different ranges of instants in different time zones). They represent different things.
If you want to represent a date, store a date. And I don't mean a java.util.Date, which is really an instant.
Use a LocalDate.
LocalDate can be ambiguous
The Answer by Andy Turner is correct and valuable. In addition, I want to point out the inherent ambiguity of LocalDate.
The LocalDate class represents a date-only value, without time-of-day, without time zone. For any given moment, the date varies around the globe by time zone. At a particular moment, it may be “tomorrow” in Tokyo Japan while simultaneously being “yesterday” in Toledo Ohio US. Two different dates in effect at the very same moment.
So while you may think of a date as containing 24 hours, defining a specific range of moments, it does not. You must place the date in the context of a time zone to be able to determine moments. (And, by the way, days are not always 24 hours long.)
LocalDate localDate = LocalDate.of( 2021 , Month.JANUARY , 24 ) ;
ZoneId zoneTokyo = ZoneId.of( "Asia/Tokyo" ) ;
ZonedDateTime startOfDayTokyo = localDate.atStartOfDay( zoneTokyo ) ; // Determine a specific moment.
startOfDayTokyo.toString(): 2021-01-24T00:00+09:00[Asia/Tokyo]
To see that same moment in UTC, extract an Instant object.
Instant instant = startOfDayTokyo.toInstant() ;
Notice the date is 23rd rather than 24th.
instant.toString(): 2021-01-23T15:00:00Z
See that same moment through third wall-clock time, that of Toledo Ohio US in time zone America/New_York.
ZonedDateTime zdtToledo = instant.atZone( ZoneId.of( "America/New_York" ) ) ;
Notice the date is 23rd rather than 24th.
zdtToledo.toString(): 2021-01-23T10:00-05:00[America/New_York]
See this code run live at IdeOne.com.
So when storing a LocalDate, you may want to also store the time zone name as well. In a database table, that would mean two columns.
As an example, think of 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. As another example, consider a 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.
In contrast, when you mean a moment, a specific point on the timeline, use Instant (or OffsetDateTime or ZonedDateTime).
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.
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 3 years ago.
Improve this question
I have heard that postgres supports storing of datetime and time with both timezone and without timezone. Now an application running in JST timezone has to store time on database side. Now for wherever i have read it is suggested that you shall always keep things stored in UTC which can work in case of datetime because upon receiving datetime on application layer i can manipulate it according to my preferred timezone. But if i only want time stored on database side then for that as well shall i store it as UTC time ? If yes then 1 am in JST will result in 4 pm UTC of previous day. so could someone please suggest me what shall be the preferred way of storing time in database ?
As suggest in one of the answer if i read the time in LocalTime then if i store time as 12:00 JST then upon reading on application side it will be 12:00 JST or 12:00 UTC.
If there is even a remote possibility that times will ever be in more than one time zone, then you need to store them in a common well-known time zone, and UTC is the best choice for that.
If you're absolute certain, beyond any shadow of doubt, that the application will only ever be used in a single time zone, and that both application and database servers will run in that time zone, then you can store the times in that local time zone.
Since such certainty is rare, it is recommended to use UTC.
If the original time zone must be retained, you need a separate column to store the time zone, so it can be re-applied when loaded from the database.
tl;dr
Apparently you want to store a time-of-day without a date and without a time zone.
Use the Java class java.time.LocalTime and the standard SQL type TIME WITHOUT TIME ZONE.
LocalTime.of( 15 , 30 )
…and…
myPreparedStatement.setObject( … , LocalTime.of( 15 , 30 ) )
Details
Be aware that an offset-from-UTC is merely a number of hours-minutes-seconds, nothing more. A time zone is much more. A time zone is a history of the past, present, and future changes to the offset used by the people of a particular region.
I have heard that postgres supports storing of datetime and time with both timezone and without timezone.
Actually the SQL standard defines:
TIMESTAMP WITH TIME ZONETrack a moment, a specific point on the timeline. Represents a date, a time-of-day, and an offset-from-UTC or a time zone. Common implementations use UTC.
TIMESTAMP WITHOUT TIME ZONETracks a date and time-of-day but without the context of an offset or zone. So this type cannot represent a moment, is not a point on the timeline. For example, for a value of "noon on the 23rd of January in 2020", we do not know if this means noon in Tokyo, noon in Paris, or noon in Montréal — all very different moments, hours apart.
Now an application running in JST timezone
JST is not a true time zone. Proper time zones have a name in Continent/Region format. Never use the 3-4 letter pseudo-zones such as EST or IST as they are not true time zones, not standardized, and not even unique(!).
ZoneId z = ZoneId.of( "Africa/Tunis" ) ;
If by JST you meant the time in Japan, use Asia/Tokyo.
ZoneId z = ZoneId.of( "Asia/Tokyo" ) ;
to store time on database side to be aware of the time slots.
I have no idea what that means.
Now for wherever i have read it is suggested that you shall always keep things stored in UTC
When tracking actual moments, yes, generally best to do so in UTC, that is, an offset-from-UTC of zero hours-minutes-seconds. Think of UTC as The One True Time, and other offsets & zones as mere variations, as localization issues for presentation of data to users. Do most of your business logic, data storage, data exchange, debugging, and logging in UTC. Keep a second clock on your desk set to UTC, seriously. Programmers and sysadmins should learn to think and work in UTC, leaving behind your parochial time zone while on the job.
which can work in case of datetime because upon receiving datetime on application layer i am manipulate it according to my preferred timezone.
Yes, as discussed above, work your logic in UTC (generally) and present localized to the zone expected by the user. When crucial, confirm with the user their desired time zone. And make a habit of always including the zone/offset info when displaying a date or time, to avoid ambiguity and confusion.
But if i only want time stored on database side then for that as well shall i store it as UTC time. If yes then 1 am in JST will result in 4 pm UTC of previous day. Can someone suggest how shall i store time alone in database side.
Do you mean you want to store just the time-of-day without a date and without a time zone? If so, use:
LocalTime in Java.
TIME WITHOUT TIME ZONE in standard SQL.
The SQL standard bizarrely defines a TIME WITH TIME ZONE type, but this makes no sense. Just think about it. And don’t be surprised; this is not the only anti-feature in the SQL standard. Postgres does offer this type, as following the standard is one of the primary goals of Postgres. Likewise, the java.time framework in Java includes a java.time.OffsetTime class to be compatible, but you will never use it.
Postgres has excellent date-time handling, and does offer the TIME WITHOUT TIME ZONE data types.
LocalTime localTime = LocalTime.of( 15 , 30 ) ;
myPreparedStatement.setObject( … , localTime ) ;
Retrieval.
LocalTime localTime = myResultSet.getObject( … , LocalTime.class ) ;
You might want to apply that time-of-day to a date and time zone to determine a moment by instantiating a ZonedDateTime.
ZoneId z = ZoneId.of( "Asia/Tokyo" ) ;
LocalDate localDate = LocalDate.of( 2019 , Month.JANUARY , 23 ) ;
ZonedDateTime zdt = ZonedDateTime.of( localDate, localTime, z ) ;
See that same moment through the wall-clock time of UTC. Same moment, same point on the timeline, but different wall-clock time.
Instant instant = zdt.toInstant() ;
And adjust to another time zone if desired. Again, same moment, same point on the timeline, but different wall-clock time.
ZonedDateTime zdtMontréal = instant.atZone( ZoneId.of( "America/Montreal" ) ) ;
Perhaps you want to see the time-of-day in Québec for that moment.
LocalTime localTimeMontréal = zdtMontréal.toLocalTime() ;
Here is a table of the various date-time types in Java and in standard SQL.
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 to work with Java 8 dates / times in different time zones. For instance:
LocalDateTime dateTime = LocalDateTime.of(2017, Month.JUNE, 1, 13, 39);
Instant instant = dateTime.atZone(ZoneId.of("Europe/Paris")).toInstant();
and a Time Frame is an instance between to dateTimes
But I don't want to hardcode the Time Zone, that is always a bad practice
I couldn't find any constants in the Java API to represent the diffenent Time Zones like https://en.m.wikipedia.org/wiki/List_of_tz_database_time_zones
Is there any mapping as it was in the short zone IDs ????
https://docs.oracle.com/javase/8/docs/api/java/time/ZoneId.html#of-java.lang.String-java.util.Map-
Time zones are frequently redefined by politicians. New zones appear. Old ones get renamed (ex: Asia/Kolkata). Some are determined to not actually be distinct, and end up pointing to another (ex: America/Montreal). And that is just the names – the offsets within each zone are also often modified by politicians for anomalies such as Daylight Saving Time (DST), or deciding to get off DST altogether or deciding to stay on DST all-year-round or deciding to change the offset by 15 minutes to make some political statement such as distinguishing yourself from your neighboring countries. So there is no simple permanent list.
Java comes with a copy of the tzdata time zone database. If any zones you care about experience a change, you need to update this tzdata in your Java installations. Oracle provides a tool for this chore in their implementation; I do not know about others. Similarly you should also update the tzdata in your host computer’s OS as well as other utilities such as your database like Postgres.
As for references to a ZoneId object in Java, you may define some as constants. The java.time classes are thread-safe. So you may keep a single instance around as a constant.
public class TimeUtils {
static public ZoneId ZONEID_EUROPE_PARIS = ZoneId.of( "Europe/Paris" ) ;
static public ZoneId ZONEID_ASIA_KOLKATA = ZoneId.of( "Asia/Kolkata" ) ;
}
You have a LocalDateTime representing potential moments, not a specific point on the timeline. A LocalDateTime has no time zone or offset information. So a LocalDateTime of noon June 1 this year could mean many different moments, with the first noon happening in Kiribati where the time zone has an offset fourteen hours ahead of UTC. Noon in Bangladesh comes later, and noon in Paris France still hours later. So a LocalDateTime has no real meaning until you apply a time zone for context.
LocalDateTime noon1June2017Anywhere = LocalDateTime.of( 2017 , Month.JUNE , 1 , 12 , 0);
Use those constants where you need a ZoneId.
ZonedDateTime noon1June2017EuropeParis = noon1June2017Anywhere.atZone( TimeUtils.ZONEID_EUROPE_PARIS ) ;
ZonedDateTime noon1June2017AsiaKolkata = noon1June2017Anywhere.atZone( TimeUtils.ZONEID_ASIA_KOLKATA ) ;
Note that noon1June2017EuropeParis and noon1June2017AsiaKolkata are two different moments, different points on the timeline. Noon happens much earlier in India than it does in France.
Let's see those two values in UTC as Instant objects. These two Instant objects are not equal, as the Kolkata one is several hours earlier than the Paris one.
Instant instantNoon1June2017EuropeParis = noon1June2017EuropeParis.toInstant() ; // Extract the same moment but in UTC zone.
Instant instantNoon1June2017AsiaKolkata = noon1June2017AsiaKolkata.toInstant() ; // Extract the same moment but in UTC zone.
Externalize
If the intent of your Question is to externalize the decision of what zone to apply, so that you may change that choice without recompiling your source code again, simply store a string of the zone name such as Europe/Paris as a string in some external resource.
Pass your string to ZoneId.of.
ZoneId z = ZoneId.of( someStringOfZoneName ) ;
Possible storage mechanisms commonly used by folks:
Store the text in a file.
Store the text in a database row to retrieved by your app.
Store the text as an entry in a JNDI facility (LDAP server, a configuration file in your Servlet container, etc.) (see Tutorial)
Query from a Web Service. (See Tutorial)
Ask the user for their preference and store in a variable such as a member of a class, or in a Servlet environment store string as an attribute on the context object or on the session object. You can provide the user with a choice list of all zones by calling ZoneId.getAvailableZoneIds.
You can ask for the JVM’s current default zone: ZoneId.systemDefault. But beware, this can be changed at anytime by any code in any app in that JVM.