Should I use Instant or DateTime or LocalDateTime in Java entities? - java

In my Java (with Spring Boot and Spring Data JPA) applications, I generally use Instant. On the other hand, I would like to use the most proper data type for time values.
Could you please clarify me about these issues? What data type should I prefer for keeping date and time when:
1. To keep time precisely as timestamp (I am not sure if Instant is the best option)?
2. For normal cases when I just need date and time (as far as I know, the old library was obsolete, but not sure which library should I use).
I also consider the TimeZone, but not sure if using LocalDateTime with UTC solves my problem.
Any help would be appreciated.

Let's assume we need to cover the full span of date and time concerns. If there is a certain concern you don't have, that either collapses various types into 'well then they are interchangible' or simply means you don't need to use a certain part of the API. The point is, you need to understand what these types represent, and once you know that, you know which one to apply. Because even if various different java.time types all technically do what you want, code is more flexible and a lot simpler to read if the types you use represent the things you want them to. For the same reason String[] student = new String[] {"Joe McPringle", "56"}; is perhaps mechanically a way to represent a student's name and age, but things are just a lot simpler if you write a class Student { String name; int age; } and use that instead.
Local alarm clock
Imagine you want to wake up at 07:00 in the morning. Not because you have an appointment, you just like to be a fairly early riser.
So you set your alarm for 07:00 in the morning, go to sleep, and your alarm promptly goes off at 7. So far, so good. However, you then hop in a plane and fly from Amsterdam to New York. (it is 6 hours earlier in new york). You then go to sleep again. Should the alarm go off at 01:00 at night, or at 07:00 in the morning?
Both answers are correct. The question is, how do you 'store' that alarm, and to answer that question, you need to figure out what the alarm is attempting to represent.
If the intent is '07:00, whereever I might be at the time the alarm is supposed to go off', the correct data storage mechanism is java.time.LocalDateTime, which stores time in human terms (years, months, days, hours, minutes, and seconds) and not in computery terms (we'll get there later), and does not include a time zone at all. If the alarm is supposed to go off every day, then you don't want that either, as LDT stores date and time, hence the name, you'd use LocalTime instead.
That's because you wanted to store the concept of 'the alarm should go off at 7 o'clock' and nothing more than that. You had no intention of saying: "The alarm should go off when people in Amsterdam would agree it is currently 07:00", nor did you have the intent of saying: "When the universe arrives at this exact moment in time, sound the alarm". You had the intent of saying: "When it is 07:00 where-ever you are now, sound the alarm", so store that, which is a LocalTime.
The same principle applies to LocalDate: It stores a year/month/day tuple with no notion of where.
This does draw some perhaps wonky conclusions: Given a LocalDateTime object, it is not possible to ask how long it'll take until that LDT arrives. It is also not possible for any given moment in time to be compared to an LDT, because these things are apples and oranges. The notion 'Feb 18th, 2023, 7 in the morning on the dot' isn't a singular time. After all, in New York that 'moment' occurs a full 6 hours earlier than it would in Amsterdam. You can only compare 2 LocalDateTimes.
Instead, you would have to first 'place' your LDT somewhere, by converting it to one of the other types (ZonedDateTime or even Instant) by asking the java.time API: Okay, I want this particular LDT in a certain time zone.
Hence, if you are writing your alarm app, you would have to take the stored alarm (a LocalTime object), convert it to an Instant (which is what the nature of 'what time is it now, i.e. System.currentTimeMillis()' works on), by saying: That LocalTime, on the current day in the current local timezone, as an instant, and THEN comparing those two results.
Human appointments
Imagine that, just before jetting off to New York, you made an appointment at your local (in Amsterdam) barber. Their agenda was kinda busy so the appointment was set for June 20th, 2025, at 11:00.
If you stay in New York for a few years, the correct time for your calendar to remind you that you have an appointment with your barber's in an hour is certainly not at 10:00 on june 20th 2025 in New York. You'd have missed the appointment by then. Instead, your phone should chirp at you that you have an hour left to get to your barber's (a bit tricky, from New York, sure) at 04:00 in the middle of the night.
It sure sounds like we can say that the barber's appointment is a specific instant in time. However, this is not correct. The EU has already adopted legislation, agreed upon by all member states, that all EU countries shall abolish daylight savings time. However, this law does not provide a deadline, and crucially, does not provide a time zone that each EU member state needs to pick. The Netherlands is therefore going to change time zones at some point. They will likely choose to stick either to permanent summer time (in which case they'd be at UTC+2 permanently, vs. their current situation where they are at UTC+2 in summer and UTC+1 in winter, with, notably, different dates when the switch happens vs. New York!), or stay on winter time, i.e. UTC+1 forever.
Let's say they choose to stick to winter time forever.
The day the gavel slams down in the dutch parliament building enshrining into law that the dutch will no longer advance the clocks in march is the day your appointment shifts by one hour. After all, your barber is not going to go into their appointment book and shift all appointments by an hour. No, your appointment will remain on June 20th, 2025, at 11:00. If you have a running clock ticking down the seconds until your barber appointment, when that gavel comes down it should jump by 3600 seconds.
This belies the point: That barber appointment truly is not a singular moment in time. It's a human/political agreement that your appointment is when Amsterdam universally agrees it is currently June 20th, 2025, 11:00 – and who knows when that moment will actually occur; it depends on political choices.
So, you cannot 'solve' this by storing an instant in time, and it shows how the concept 'instant in time' and 'year/month/day hour:minute:second in a certain timezone' are not quite interchangible.
The correct data type for this concept is a ZonedDateTime. This represents a date time in human terms: year/month/day hour:second:minute, and the timezone. It doesn't shortcut by storing a moment in time in epochmillis or some such. If the gavel comes down and your JDK updates its timezone definitions, asking "how many seconds until my appointment" will correctly shift by 3600 seconds, which is what you want.
Because this is for appointments and it doesn't make sense to store just the time of an appointment but not the date, there is no such thing as a ZonedDate or a ZonedTime. Unlike the first thing which comes in 3 flavours (LocalDateTime, LocalDate, and LocalTime), there's only ZonedDateTime.
The universe/log time
Imagine you are writing a computer system that logs that an event occurred.
That event, naturally, has a timestamp associated with it. Turns out that due to severe political upheaval, the laws of the land decide that retrospectively the country has been in a different timezone than what you thought when the event occurred. Applying the same logic as the barber's case (where the actual moment in time jumps by 3600 seconds when the gavel comes down) is incorrect. The timestamp represents a moment in time when a thing happened, not an appointment in a ledger. It should not jump by 3600.
Timezone really has no purpose here. The point of storing 'timestamp' for a log event is so you know when it happened, it doesn't matter where it happened (or if it does, that is fundamentally a separate notion).
The correct data type for this is java.time.Instant. An instant doesn't even know about time zones at all, and isn't a human concept. This is 'computery time' - stored as millis since an agreed upon epoch (midnight, UTC, 1970, new years), no timezone information is necessary or sane here. Naturally there is no time-only or date-only variant, this thing doesn't even really know what 'date' is - some fancypants human concept that computery time is not concerned with in the slightest.
Conversions
You can trivially go from a ZonedDateTime to an Instant. There's a no-args method that does it. But note:
Create a ZonedDateTime.
Store it someplace.
Convert it to an Instant, store that too.
Update your JDK and get new time zone info
Load the ZDT.
Convert it to an Instant a second time.
Compare the 2 ZDTs and the 2 instants.
Results in different results: The 2 instants would not be the same, but the ZDTs are the same. The ZDT represents the appointment line in the barber's book (which never changed - 2025 june 20th, 11:00), the instant represents the moment in time that you are supposed to show up which did change.
If you store your barber's appointment as a java.time.Instant object, you will be an hour late to your barber's appointment. That's why it's important to store things as what they are. A barber's appointment is a ZonedDateTime. storing it as anything else would be wrong.
Conversions are rarely truly simple. There is no one way to convert one thing to another - you need to think of what these things represent, what the conversion implies, and then follow suit.
Example: You are writing a logging system. The backend parts store log events into a database of some sort, and the frontend parts read this database and show the log events to an admin user for review. Because the admin user is a human being, they want to see it in terms they understand, say, the time and date according to UTC (it's a programmer, they tend to like that sort of thing).
The logging system's storage should be storing the Instant concept: Epoch millis, and without timezone because that is irrelevant.
The frontend should read these as Instant (it is always a bad idea to do silent conversions!) - then consider how to render this to the user, figure out that the user wants these as local-to-UTC, and thus you would then on the fly, for each event to be printed to screen, convert the Instant to a ZonedDateTime in the zone the user wants, and from there to a LocalDateTime which you then render (because the user probably does not want to see UTC on every line, their screen estate is limited).
It would be incorrect to store the timestamps as UTC ZonedDateTimes, and even more wrong to store them as LocalDateTimes derived by asking for the current LocalDT in UTC as the event happens and then storing that. Mechanically all these things would work but the data types are all wrong. And that will complicate matters. Imagine the user actually wants to see the log event in Europe/Amsterdam time.
A note about timezones
The world is more complicated than a handful of timezones. For example, almost all of mainland europe is currently 'CET' (Central European Time), but some think that refers to european winter time (UTC+1), some thing that refers to the current state in central europe: UTC+1 in winter, UTC+2 in summer. (There's also CEST, Central European Summer Time, which means UTC+2 and isn't ambiguous). When EU countries start applying the new law to get rid of daylight savings, its likely e.g. The Netherlands on the west edge of the CET zone picks a different time than Poland on the eastern edge. Hence, 'all of central europe' is far too broad. 3-letter acronyms also are by no means unique. Various countries use 'EST' to mean 'eastern standard time', it's not just the eastern USA for example.
Hence, the only proper way to represent timezone names is using strings like Europe/Amsterdam or Asia/Singapore. If you need to render these as 09:00 PST for residents of the west coast of the USA, that's a rendering issue, so, write a rendering method that turns America/Los_Angeles into PST, which is an issue of localization, and has nothing to do with time.

The Answer by rzwitserloot is correct and wise. In addition, here is a summary of the various types. For more info, see my Answer on a similar Question.
To keep time precisely as timestamp (I am not sure if Instant is the best option)?
If you want to track a moment, a specific point on the timeline:
InstantA moment as seen with an offset-from-UTC of zero hours-minutes-seconds. This class is the basic building-block of the java.time framework.
OffsetDateTimeA moment as seen with a particular offset, some number of hours-minutes-seconds ahead of, or behind, the temporal meridian of UTC.
ZonedDateTimeA moment as seen with a particular time zone. A time zone is a named history of the past, present, and future changes to the offset used by the people of a particular region, as decided by their politicians.
If you want to track just the date and time-of-day, without the context of an offset or time zone, use LocalDateTime. This class does not represent a moment, is not a point on the timeline.
For normal cases when I just need date and time
If you are absolutely sure that you want only a date with time-of-day, but do not need the context of an offset or time zone, use LocalDateTime.
using LocalDateTime with UTC
That is a contradiction, and makes no sense. A LocalDateTime class has no concept of UTC, nor any concept of offset-from-UTC or time zone.
Spring Data JPA
The JDBC 4.2+ specification maps SQL standard data types to Java classes.
TIMESTAMP WITH TIME ZONE columns map to OffsetDateTime in Java.
TIMESTAMP WITHOUT TIME ZONE columns map to LocalDateTime in Java.
DATE columns map to LocalDate.
TIME WITHOUT TIME ZONE columns map to LocalTime.
The SQL standard also mentions TIME WITH TIME ZONE, but this type is meaningless (just think about it!). The SQL committee has never explained what they had in mind, as far as I know. If you must use this type, Java defines the ZoneOffset class to match.
Note that JDBC does not map any SQL types to Instant nor ZonedDateTime. You can easily convert to/from the mapped type OffsetDateTime.
Instant instant = myOffsetDateTime.toInstant() ;
OffsetDateTime myOffsetDateTime = instant.atOffset( ZoneOffset.UTC ) ;
… and:
ZonedDateTime zdt = myOffsetDateTime.atZoneSameInstant( myZoneId ) ;
OffsetDateTime odt = zdt.toOffsetDateTime() ; // The offset in use at that moment in that zone.
OffsetDateTime odt = zdt.toInstant().atOffset( ZoneOffset.UTC ) ; // Offset of zero hours-minutes-seconds from UTC.
I also consider the TimeZone
The TimeZone class is part of the terrible legacy date-time classes that were years ago supplanted by the modern java.time classes. Replaced by ZoneId and ZoneOffset.

You should take a look at the Java Date and Time API introduced with Java 8. Each class like Instant, LocalDateTime, ZonedDateTime etc. has a documentation as JavaDoc. If you have problems understanding the documentation, please provide a more specific question.

Related

Handling Daylight Saving Time with OffsetDateTime in Java when time needs to remain the same in local time regardless of DST

I'm using Spring Boot (Java 8) to develop the backend for a restaurant app which exposes some REST APIs. Everything related to dates and times is handled with OffsetDateTime objects and through the app I use, by default, the offset of "Europe/Rome". Those objects are also persisted in the DB. Everything is working fine, except... daylight saving time kicked in and now all the business hours of the restaurant are off by one hour.
This obviously happens because pre-DST the offset of "Europe/Rome" is +01:00, while post-DST is +02:00. So, to give you an example, the opening hour of the restaurant (saved inside the DB pre-DST) is 08:30:00+01:00, but it becomes 09:30:00+02:00 when accessed post-DST from the API (because it's automatically converted with the default zone of the system). I need the business hours of the restaurant to remain the same in local time, regardless of DST.
How can I handle this situation and offer consistent data to my users?
Offset versus time zone
"Europe/Rome" is the name of a time zone, not an offset.
An offset-from-UTC is simply a number of hours-minutes-seconds ahead of, or behind, the baseline of UTC. As you move eastward, local times are more and more hours ahead of UTC, while moving westward to the Americas the local times are more and more hours behind UTC.
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.
Specify a proper time zone name in the format of Continent/Region, such as America/Montreal, Africa/Casablanca, or Pacific/Auckland. Never use the 2-4 letter abbreviation such as EST or IST as they are not true time zones, not standardized, and not even unique(!).
You said:
Everything related to dates and times is handled with OffsetDateTime objects
No, you should not use that single class in all situations. OffsetDateTime class is for representing a moment where you know the offset but not the time zone. ZonedDateTime should be used when you know the time zone. And for moments specifically in UTC (an offset of zero), generally best to use Instant.
The OffsetDateTime is rarely the right class to choose. Time zone is always preferable to a mere offset. So usually you will want either ZonedDateTime or Instant. Generally programmers and sysadmins should be thinking, working, logging, and debugging in UTC, so for that use Instant. For presentation to users in their expected time zone, or where business rules require, use ZonedDateTime. The one big exception is JDBC work for database access — the JDBC 4.2 specification inexplicably requires compliant drivers to support OffsetDateTime but not Instant nor ZonedDateTime. Fortunately conversion between the classes is quite simple and easy.
Moment
You need to understand the difference between a moment and not-a-moment. A moment is a specific point on the timeline. Forget about time zones and offsets, if someone in Casablanca Morocco calls someone in Tokyo Japan, they are sharing the same moment. To track a moment, use one of the three classes shown above in blue blocks: Instant, OffsetDateTime, ZonedDateTime.
Instant phoneCallStartedUtc = Instant.now() ; // A moment as seen in UTC.
ZonedDateTime phoneCallStartedCasablanca = phoneCallStartedUtc.atZone( ZoneId.of( "Africa/Casablanca" ) ) ;
ZonedDateTime phoneCallStartedTokyo = phoneCallStartedUtc.atZone( ZoneId.of( "Asia/Tokyo" ) ) ;
See this code run live at IdeOne.com.
phoneCallStartedUtc.toString(): 2021-03-28T20:33:58.695669Z
phoneCallStartedCasablanca.toString(): 2021-03-28T21:33:58.695669+01:00[Africa/Casablanca]
phoneCallStartedTokyo.toString(): 2021-03-29T05:33:58.695669+09:00[Asia/Tokyo]
All three of those objects, phoneCallStartedUtc, phoneCallStartedCasablanca, and phoneCallStartedTokyo, represent the very same simultaneous moment. Their only difference is being viewed through different wall-clock time (what people see when they glance up at a clock on their wall).
Not a moment
Future appointments, such as when a restaurant will open, or when a patient will next see their dentist, cannot be tracked as a moment.
We may know the date and time-of-day when we intend for the restaurant to open, but we cannot know the moment. That is because the clock time used in a time zone is defined by politicians.
Politicians around the world have shown a penchant for changing the clock definition rules in the time zone(s) of their jurisdiction. They move the clock around as a political statement to distinguish themselves from neighboring jurisdictions, or the opposite, to align with neighbors. Politicians change their zone rules to manipulate financial markets. They move the clock during times of war and occupation for symbolic as well as logistical reasons. Politicians move the clock when they join in fads, such as Daylight Saving Time (DST). Later, they move their clocks again when they join a different fad, such as staying on DST year-round.
The lesson to learn here is that time zone rules change, they change with surprising frequency, and they change with little or even no forewarning. So, for example, 3 PM on next Tuesday may not actually occur at the moment you expect right now on Wednesday. Next 3 PM might be an hour earlier than expect now, a half-hour later than you expect now, no one knows.
To track future appointments that are tracked by the clock such as your restaurant opening, use LocalTime for the time of day, and LocalDate for the date. Where appropriate, you may combine those two into LocalDateTime object. Separately track the the intended time zone. Then at runtime when you need to calculate the scheduling of events, apply the time zone to the LocalDateTime to get a ZonedDateTime. The ZonedDateTime will determine a moment, but you cannot store that moment as politicians may move the clock on you, ripping the rug from beneath you.
// Store these three values.
ZoneId zoneIdRome = ZoneId.of( "Europe/Rome" ) ;
LocalTime restaurantOpening = LocalTime.of( 9 , 30 ) ; // 09:30 AM.
LocalDate nextTuesday = LocalDate.now( zoneIdRome ).with( TemporalAdjusters.next( DayOfWeek.TUESDAY ) ) ;
// Do NOT store this next value. Use this next value only on-the-fly at runtime.
ZonedDateTime momentWhenRestaurantIsExpectedToOpenNextTuesday =
ZonedDateTime.of( nextTuesday , restaurantOpening , zoneIdRome ) ;
See that code run live at IdeOne.com.
restaurantOpening.toString(): 09:30
nextTuesday.toString(): 2021-03-30
momentWhenRestaurantIsExpectedToOpenNextTuesday.toString(): 2021-03-30T09:30+02:00[Europe/Rome]
If you want to track when the restaurant actually did open, then you would be tracking moments. Then you would be using one of three classes discussed above, likely Instant.
Instant momentWhenRestaurantActuallyOpened = Instant.now() ; // 09:41 AM, eleven minutes late because the manager could not find the door keys that had dropped to the floor.
By the way, some future appointments are not driven by the clock. For example, rocket launches are scheduled according to nature, the expected weather conditions. Politicians may alter the clock, change the time zone rules, but that does not change the moment when the rocket will launch. Such a change in time zone by the politicians will change how the media report on the planned launch, but the launch itself will not happen earlier or later. So for rocket launches and such we would use one of the three moment-oriented classes discussed above, almost certainly Instant class to focus on UTC only and avoid all time zones entirely.
These concepts and classes have all been addressed many many times already on Stack Overflow. Search to learn more.

java.sql.Date and Time Timezone differs

private GregorianCalendar formatDate(Date dateStatus, Time timeStatus) {
GregorianCalendar calendar = (GregorianCalendar) GregorianCalendar.getInstance(TimeZone.getTimeZone("UTC"));
calendar.setTime(new Date(dateStatus.getTime() + timeStatus.getTime()));
return calendar;
}
The above code returns the Calendar value in milliseconds. But i am getting different value in local and while running the test in jenkins causing the testcase to fail.
Local and Jenkins Server running in different timezones.
Jenkins Error:
Expected: 1554866100000
got: 1554903900000
How can i handle this?
java.sql.Date is an extremely unfortunate API design messup. That class extends java.util.Date, and that class is a lie. It does not represent a date at all (check the source code if you are understandably skeptical). It represent a moment in time, devoid of any timezone information, based on milliseconds since UTC new year's 1970. Anything you care to coerce from a Date object that isn't a very large number or a direct swap to a more appropriate type that doesn't lie (such as java.time.Instant) is therefore suspect: It is picking a timezone implicitly and mixing that in, in order to provide you your answer. This is why most of Date's methods, such as .getYear(), are marked deprecated: In the java core libs, a deprecation marker usually doesn't actually mean "This will be removed soon", it actually means: "This is fundamentally broken and you should never call this method". (See: Thread.stop).
Nevertheless JDBC API (what you use to talk to DBs) was built on top of this; java.sql.Date as well as java.sql.Timestamp extend java.util.Date and thereby inherit the messup. This means date handling in this fashion will convert timestamps in the DB that have full timezone info into timezoneless constructs, and then java-side you can add new timezones, which is a bad way of doing things.
Unfortunately date/time is extremely complicated (see below) and databases have wildly different ways of storing it; usually multiple slightly different date/time types, such as 'TIMESTAMP', 'TIME WITH TIME ZONE', etcetera.
Because of this there is no unique advice that works regardless of context: The right answer depends on your JDBC driver version, DB engine, DB engine version, and needs. This means the best approach is generally to first understand the fundamentals so that you can adapt and figure out what would work best for your specific situation.
java.util.Calendar is even worse. Again a lie (it represents time. Not a calendar!), the API is extremely badly designed (it is very non-java-like). There is a reason this second try at date/time API resulted in yet another date/time library (java.time): It's bad. Don't use it.
So, let me try to explain the basics:
You like to wake up at 8 in the morning. It's noon and you check your phone. It says 'the next alarm will ring in 20 hours'. You now hop onto a concorde at Schiphol Airport in Amsterdam, and fly west, to New York. The flight takes 3 hours. When you land, you check your phone. Should it say 'the next alarm will ring in 17 hours' (3 hours of flight time have passed), or should it say: 'the next alarm will ring in 23 hours' (you actually land at 9 in the morning in New York time because its 6 hours earlier there relative to Amsterdam, so it's 23 hours until 8 o' clock in the morning local time). The right answer is presumably: 23 hours. This requires the concept of 'local time': A point in time stated in terms of years, months, days, hours, minutes, and seconds - but no timezone, and not even 'please assume a timezone for me', but the concept '... where-ever you are now'.
Before you jumped in the plane, you made an appointment at a barber's in Amsterdam for when you return. You made it for March 8th, 2022, at 12:00. When you check your phone it reads: "365 days, 0 hours, 0 minutes, and 0 seconds" as you made the appointment. If you fly to new york, that should now read "364 days, 21 hours, 0 minutes, and 0 seconds": The fact that you're currently in new york has no bearing on the situation whatsoever. You'd think that time-as-millis-since-UTC should thus suffice, but no it does not: Imagine that the netherlands abolishes daylight savings time (crazy idea? No, quite likely actually). The very instant that the gavel hits the desk and the law is adopted that The Netherlands would switch to summer time in 2021, and then stay there forever more, that very moment? Your phone's indicator for 'time until barber appointment' should instantly increment by 1 hour (or it is decrement?). Because that barber appointment isn't going to reschedule itself to be at 11:00 or 13:00.
During your flight, you snapped a pic of the tulip fields before the plane crossed the atlantic. The timestamp of this picture is yet another concept of time: If, somehow, the netherlands decides to apply the timezone change retroactively, then the timestamp in terms of 'this picture was taken on 2021, march 8th, 12:05, Amsterdam time' should instantly pop and now read 13:05 or 11:05 instead. In this way it is unlike the barber appointment.
Before this question can be answered, you need to figure out which of the 3 different concepts of time your situation boils down to.
Only the java.time package fully covers this all. Respectively:
Alarm clock times are represented by LocalDate, LocalTime, and LocalDateTime.
Appointment times are represented by ZonedDateTime.
When did I make the picture times are represented by Instant.
The java.sql.Date type is most equivalent to Instant, which is bizarre, because you'd think that this is more java.sql.Timestamp's bailiwick.
Pragmatics, how to get the job done
Your first stop is to fully understand your DB engine's data types. For example, in postgres:
concept
java.time type
postgres type
alarm clocks
java.time.LocalTime
time without time zone
-
java.time.LocalDate
date
-
java.time.LocalDateTime
timestamp without time zone
appointments
java.time.ZonedDateTime
timestamp with time zone
when i took the picture
java.time.Instant
no appropriate type available
Kind of silly that the implementation of java.sql.Date and java.sql.Timestamp best matches the very concept postgres cannot express, huh.
Once you ensured that your DB is using the right type for your concept, the next thing to check is if your JDBC driver and other infrastructure is up to date. You can use the right types java-side (from the java.time packages): Use these types (LocalDate, Instant, ZonedDateTime, etc) in your infrastructre and if making raw JDBC calls, .setObject(x, instanceOfZDT) instead of .setDate when setting, and .getObject(col, LocalDateTime.class) to fetch, which works on many JDBC drivers.
If it doesn't work, you need to work around the issues, because the process is now that the DB is storing e.g. the year, month, day, hour, minute, second, and complete timezone description (not 'EST' there are way too many zones to cover with 3 letters, but something like 'Europe/Amsterdam'), will then convert this into millis-since-epoch to transfer it to the JDBC server, and then your java code will inject a timezone again, and thus you're going to run into issues. The best bet is to IMMEDIATELY convert ASAP, so that you have an old unwieldy type (java.sql.Date or java.sql.Timestamp) as short as possible and can test right at the source that you're 'undoing the damage' done when your ZDT/LDT type arrives as the Instant-esque java.sql type.

Using localDate with UTC

I'm encountering a problem using LocalDate in UTC. My server uses UTC, and my database uses UTC. I used LocalDate to store a billingDate for a subscription based application.
What happens is that we bill at midnight UTC (when doing comparisions like billingDate <= LocalDate.now()). We actually mean to bill sometime after midnight PST.
I really felt like using LocalDate was appropriate here, because we just want to bill at some point during that day. However, it doesn't seem practical when doing comparisons either directly in the code or in the database (billing_date <= CURRENT_DATE()). Did I make a mistake, should this be a ZonedDateTime in PST? Or should we be converting to ZonedDateTime for comparisons? It feels error prone, we need to remember to convert any time we do a comparision, but perhaps this is the correct solution?
Does anyone have experience with this situation and found a nice solution?
I've taken a look at this question, but it doesn't answer my question: Spring REST LocalDate UTC differs of one day
I suggest that this is just a matter of passing the desired time zone to LocalDate.now(ZoneId).
Use LocalDate.now(ZoneId.of("Asia/Manila")) for Philippine Standard Time. At the moment it yeilds 2019-07-09.
Use LocalDate.now(ZoneId.of("Pacific/Pitcairn")) for Pitcairn Standard Time. It just gave 2019-07-08.
I am assuming that you didn’t mean Pacific Standard Time since no time zone uses Pacific Standard Time as we speak (those that do in winter, are on Pacific Daylight Time now). In any case, mind you that three letter time zone abbreviations are often ambiguous.
The java.time classes that have a now method generally have three overloaded variants of it:
One that takes a ZoneId arguments that I recommend for general use.
One that takes a Clock argument that is great for testability. A Clock includes a time zone, so this one too gets you the current date and/or time in that specified time zone.
One that doesn’t take any arguments and uses the JVM’s default time zone. I recommend that you never use it. It’s nice for the reader to know that you have considered time zone and chosen which one you want. And the default time zone can be changed at any time by any program running in the same JVM, so is not stable enough to rely on for real work.
I feel like you should be using Instants.
I really felt like using LocalDate was appropriate here, because we just want to bill at some point during that day.
Well, no. You do care about the time you bill, because your database cares about the time. It stores the billing time as 00:00 UTC. Since that is an instant in time, I think Instant would be the most suitable choice here. You could use a ZonedDateTime as well, but considering that you are probably getting a java.sql.Date from your database, which has a toInstant method already, using Instants is more convenient.
You can get an instant from a year, month, day like this:
LocalDate ld = LocalDate.of(2019, 7, 8);
Instant i = ld.atStartOfDay(ZoneId.of("America/Los_Angeles")).toInstant();
America/Los_Angeles is PST.

Why is UTC (which is not a time zone) considered a time zone in Java (and not only there)?

Given that UTC is not a time zone, but a time standard (as stated, for example, here), why in my Java application I can use UTC as if it was a time zone (see the code snippet below)?
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
format.setTimeZone(TimeZone.getTimeZone("UTC"));
If UTC is not a time zone, why is TimeZone.getTimeZone("UTC") able to return the time zone object?
By the way, on my Windows machine UTC is in the list of time zones also (see the screenshot).
Is the statement "UTC is not a time zone" in reality wrong?
Because it makes life much, much simpler to regard UTC as a time zone than to treat it as something else, basically.
It's one of those "Yeah, strictly speaking it's not" scenarios. For everything except "Which region of the world is this observed?" you can think of UTC as a time zone and it works fine. So it's simpler to bend it slightly out of shape than to have a whole separate concept.
If you view a time zone as a mapping from "instant in time" to "UTC offset" (or equivalent, from "instant in time" to "locally observed time") then UTC is fine to think of as a time zone - and that's most of what we do within software.
If you view a time zone as a geographical region along with that mapping, then no, it doesn't work as well - but that's more rarely useful in software. (And you can always fake it by saying it's an empty region :)
Is the statement "UTC is not a time zone" in reality wrong?
Technically and strictly speaking, the statement is not wrong. UTC is a standard, not a timezone (as you already linked).
A timezone corresponds to some region in the world and has lots of different rules regarding that region:
What's the UTC offset (the difference from UTC) when it's in Daylight Saving time and when it's not
When DST starts and ends
All the changes in offsets and DST this region had during its history
Example: in 1985, the brazilian state of Acre had standard offset UTC-05:00 (and UTC-04:00 during DST), then in 1988 it was on UTC-05:00 without DST, then in 2008 the standard changed to UTC-04:00 (and no DST), and since 2013 it's back to UTC-05:00 and no DST.
While the timezone keeps track of all of these changes, UTC has no such rules. You can think of UTC in many different ways:
a "base" date/time, from where everybody else is relative to - this difference from UTC is called "offset". Today, São Paulo is in UTC-03:00 (the offset is minus 3 hours, or 3 hours behind UTC) while Tokyo is in UTC+09:00 (offset of plus 9 hours, or 9 hours ahead UTC).
a "special" timezone that never varies. It's always in the same offset (zero), it never changes, and never has DST shifts.
As the "offset of UTC" (not sure how technically accurate is this term) is always zero, it's common to write is as UTC+00:00 or just Z.
Another difference between UTC and timezone is that a timezone is defined by governments and laws and can change anytime/anywhere. All the changes in Acre described above were defined by politicians, for whatever reasons they thought at that time. (So, even if a region today follows UTC in their timezone, there's no guarantee that it'll stay the same in the future, and that's why even those regions have their own timezones, even if they look redundant).
But no matter how many times politicians change their regions offsets, they must be relative to UTC (until a new standard comes up, of course).
Now, when you see implementations like TimeZone.getTimeZone("UTC"), you can think of it in 2 different ways:
a design flaw, because it's mixing 2 different concepts and leading people to think they're the same thing
a shortcut/simplification/nice-trick/workaround, that makes things easier (as #JonSkeet explained in his answer).
For me, it's a mix of both (fifty/fifty).
The new java.time API, though, separates the concepts in 2 classes: ZoneRegion and ZoneOffset (actually both are subclasses of ZoneId, but ZoneRegion is not public so actually we use ZoneId and ZoneOffset):
if you use a ZoneId with IANA timezones names (always in the format Continent/City, like America/Sao_Paulo or Europe/Berlin), it will create a ZoneRegion object - a "real" timezone with all the DST rules and offset during its history. So, you can have different offsets depending on the dates you're working with in this ZoneId.
if you use a ZoneOffset (with Z, UTC, +03:00 and so on), it will return just an object that represents an offset: the difference from UTC, but without any DST rules. No matter what dates you use this object with, it'll always have the same difference from UTC.
So, the ZoneId (actually ZoneRegion) is consistent with the idea that offsets in some region (in some timezone) change over time (due to DST rules, politicians changing things because whatever, etc). And ZoneOffset represents the idea of difference from UTC, that has no DST rules and never changes.
And there's the special constant ZoneOffset.UTC, which represents a zero difference from UTC (which is UTC itself). Note that the new API took a different approach: instead of saying that everything is a timezone and UTC is special kind, it says that UTC is a ZoneOffset which has a value of zero for the offset.
You can still think it's a "wrong" design decision or a simplification that makes things easier (or a mix of both). IMO, this decision was a great improvement comparing to the old java.util.TimeZone, because it makes clear that UTC is not a timezone (in the sense that it has no DST rules and never changes), it's just a zero difference from the UTC standard (a very technical way of saying "it's UTC").
And it also separates the concepts of timezone and offset (that are not the same thing, although very related to each other). I see the fact that it defines UTC as a special offset as an "implementation detail". Creating another class just to handle UTC would be redundant and confusing, and keeping it as a ZoneOffset was a good decision that simplified things and didn't mess the API (for me, a fair tradeoff).
I believe that many other system's decide to take similar approaches (which explains why Windows have UTC in the timezone list).

How can I store and display dates in the timezone as they were originally given?

I have a server that is being fed data from clients in different timezones. The data feed contains people, their date of birth and other dates of events. For our purposes, it would be convenient if we could just store the dates as their given to us.
For example, if the client is in California and it tells us the person's date of birth is May 31st, we'd like to store it in the database as May 31st 1999, pacific time. This way, no matter what timezone you're in, you can see that the person was born on May 31st.
At the same time, we want to be able to query this data to be able to figure out things like, "Is this person a minor" or "did this event happen less than 24 hours ago?
The clients are sending us data over a http based rest API. The server is written in Java (using eclipselink). The database is postgresql. Is it possible to satisfy these requirements?
Typically, people say to store everything as UTC, but I feel like that would be a bad idea because we'd lose the timezone of the original data.
UTC is the way to go. For timestamptz (timestamp with time zone) the time zone of input values only serves as modifier for Postgres to calculate UTC internally. (For timestamp [without time zone] any appended time zone would be ignored!). The time zone is not saved. You need to save it additionally to know where in the world something happened.
If you do that, you might as well store local timestamps. Just don't get confused which is which. Either convert everything to UTC (happens automatically for timestamptz), or convert everything to local time (define "local": your local? local to the db server? local to the user?).
In particular, rather store the exact time zone name (or a reference to it) than just "pacific time". This is more exact for daylight saving time, leap seconds or other events.
Detailed explanation:
Ignoring timezones altogether in Rails and PostgreSQL
About time zone names and abbreviations:
Time zone names with identical properties yield different result when applied to timestamp
About time zone handling:
Accounting for DST in Postgres, when selecting scheduled items
Preserve timezone in PostgreSQL timestamptz type
The answer by Erwin Brandstetter is 100% correct.
Calculating Age
As for matters such as calculating age of a minor, that is a bit tricky because of time of day. Using the Joda-Time library, you can call the method withTimeAtStartOfDay to set a DateTime object to the first moment of the day. Usually that first moment is the time 00:00:00 but not always because of Daylight Saving Time or other anomalies. Ignore the "midnight"-related classes and methods as they have been supplanted by the above-mentioned method.
Furthermore, to be really accurate about age to cover yourself legally, you might want to calculate age as the first moment of the day or two after the birth date-time. Unless you know their time of birth and the time zone of that birth, you cannot know exactly their age.
Avoid j.u.Date/.Calendar
The java.util.Date and .Calendar classes bundled with Java are notoriously troublesome. Avoid them. Use either Joda-Time and/or the new java.time package bundled in Java 8 (inspired by Joda-Time but re-architected).
Unlike java.util.Date, the date-time objects in both the other libraries know their own assigned time zone. A j.u.Date is particularly confusing because, while it has no time zone assigned, its toString method applies the JVM’s current default time zone thereby creating the illusion of an assigned time zone.
Joda-Time | java.time
With Joda-Time and java.time, things are much clearer. You specify a time zone to each date-time object (otherwise the JVM default is assigned). You can easily convert from one time zone to other.
Both libraries use immutable objects, where a new object based on the original is created rather than changing (mutating) the original.
You can call getZone a Joda-Time DateTime object to obtain its time zone name (ID) and its offset from UTC for your records if you deem that important.
ISO 8601
Learn about the ISO 8601 standard for sensible String formats of date-time values. Consider using those in your text-based API. ISO 8601 is now the norm for all new Internet protocols. Ex: 2014-08-13T16:02:01Z or 2014-12-22T11:54:23+04:00.
And use proper time zone names. Avoid the 3 or 4 letter codes as they are neither standardized nor unique.

Categories