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

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

Related

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

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.

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.

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.

Java SE 8 TemporalAccessor.from issues when used with a java.time.Instant object

java.time has an Instant class which encapsulates a position (or 'moment') on the timeline. While I understand that this is a seconds/nanoseconds value so not directly related to time zones or times offsets, its toString returns a date and time formatted as a UTC date/time, eg 2014-05-13T20:05:08.556Z. Also anInstant.atZone(zone) and anInstant.atOffset(offset) both produce a value that is consistent with treating the Instant as having an implied UTC time-zone/'zero' offset.
I would have expected therefore:
ZoneOffset.from(anInstant) to produce a 'zero' ZoneOffset
OffsetDateTime.from(anInstant) to produce a date/time with a 'zero' offset
ZoneId.from(anInstant) (probably) to produce a UTC ZoneId
ZonedDateTime.from(anInstant) (probably) to produce a ZonedDateTime with a UTC ZoneId
The documentation for ZonedDateTime.from, as I read it, appears to endorse this.
In fact ZoneOffset.from(anInstant) fails with DateTimeException, and I suppose for that reason OffsetDateTime.from(anInstant) also fails, as do the other two.
Is this the expected behaviour?
Short answer:
The JSR-310-designers don't want people to do conversions between machine time and human time via static from()-methods in types like ZoneId, ZoneOffset, OffsetDateTime, ZonedDateTime etc. This is explicitly specified if you carefully study the javadoc. Instead use:
OffsetDateTime#toInstant():Instant
ZonedDateTime#toInstant():Instant
Instant#atOffset(ZoneOffset):OffsetDateTime
Instant#atZone(ZoneId):ZonedDateTime
The problem with the static from()-methods is that otherwise people are able to do conversions between an Instant and for example a LocalDateTime without thinking about the timezone.
Long answer:
Whether to consider an Instant as counter or as field tuple, the answer given by JSR-310-team was a strict separation between so-called machine time and human time. Indeed they intend to have a strict separation - see their guidelines. So finally they want Instant to be only interpreted as a machine time counter. So they intentionally have a design where you cannot ask an Instant for fields like year, hour etc.
But indeed, the JSR-310-team is not consistent at all. They have implemented the method Instant.toString() as a field tuple view including year, ..., hour, ... and offset-symbol Z (for UTC-timezone) (footnote: Outside of JSR-310 this is quite common to have a field-based look on such machine times - see for example in Wikipedia or on other sites about TAI and UTC). Once the spec lead S. Colebourne said in a comment on a threeten-github-issue:
"If we were really hard line, the toString of an Instant would simply be the number of seconds from 1970-01-01Z. We chose not to do that, and output a more friendly toString to aid developers, But it doesn't change the basic fact that an Instant is just a count of seconds, and cannot be converted to a year/month/day without a time-zone of some kind."
People can like this design decision or not (like me), but the consequence is that you cannot ask an Instant for year, ..., hour, ... and offset. See also the documentation of supported fields:
NANO_OF_SECOND
MICRO_OF_SECOND
MILLI_OF_SECOND
INSTANT_SECONDS
Here it is interesting what is missing, above all a zone-related field is missing. As a reason, we often hear the statement that objects like Instant or java.util.Date have no timezone. In my opinion this is a too simplistic view. While it is true that these objects have no timezone state internally (and there is also no need for having such an internal value), those objects MUST be related to UTC timezone because this is the basis of every timezone offset calculation and conversion to local types. So the correct answer would be: An Instant is a machine counter counting the seconds and nanoseconds since UNIX epoch in timezone UTC (per spec). The last part - relationship to UTC zone - is not well specified by JSR-310-team but they cannot deny it. The designers want to abolish the timezone aspect from Instant because it looks human-time-related. However, they can't completely abolish it because that is a fundamental part of any internal offset calculation. So your observation regarding
"Also an Instant.atZone(zone) and an Instant.atOffset(offset) both produce a value that is consistent with treating the Instant as having an implied UTC time-zone/'zero' offset."
is right.
While it might be very intuitive that ZoneOffset.from(anInstant) might produce ZoneOffset.UTC, it throws an exception because its from()-method searches for a non-existent OFFSET_SECONDS-field. The designers of JSR-310 have decided to do that in the specification for the same reason, namely to make people think that an Instant has officially nothing to do with UTC timezone i.e. "has no timezone" (but internally they must accept this basic fact in all internal calculations!).
For the same reason, OffsetDateTime.from(anInstant) and ZoneId.from(anInstant) fail, too.
About ZonedDateTime.from(anInstant) we read:
"The conversion will first obtain a ZoneId from the temporal object, falling back to a ZoneOffset if necessary. It will then try to obtain an Instant, falling back to a LocalDateTime if necessary. The result will be either the combination of ZoneId or ZoneOffset with Instant or LocalDateTime."
So this conversion will fail again due to the same reasons because neither ZoneId nor ZoneOffset can be obtained from an Instant. The exception message reads as:
"Unable to obtain ZoneId from TemporalAccessor: 1970-01-01T00:00:00Z of type java.time.Instant"
Finally we see that all static from()-methods suffer from being unable to do a conversion between human time and machine time even if this looks intuitive. In some cases a conversion between let's say LocalDate and Instant is questionable. This behaviour is specified, but I predict that your question is not the last question of this kind and many users will continue to be confused.
The real design problem in my opinion is that:
a) There should not be a sharp separation between human time and machine time. Temporal objects like Instant should better behave like both. An analogy in quantum mechanics: You can view an electron both as a particle and a wave.
b) All static from()-methods are too public. Ihat is too easily accessible in my opinion and should better have been removed from public API or use more specific arguments than TemporalAccessor. The weakness of these methods is that people can forget to think about related timezones in such conversions because they start the query with a local type. Consider for example: LocalDate.from(anInstant) (in which timezone???). However, if you directly ask an Instant for its date like instant.getDate(), personally I would consider the date in UTC-timezone as valid answer because here the query starts from an UTC perspective.
c) In conclusion: I absolutely share with the JSR-310-team the good idea to avoid conversions between local types and global types like Instant without specifying a timezone. I just differ when it comes to the API-design to prevent users from doing such a timezone-less conversion. My preferred way would have been to restrict the from()-methods rather than saying that global types should not have any relation to human-time-formats like calendar-date or wall-time or UTC-timezone-offset.
Anyway, this (inconsequent) design of separation between machine time and human time is now set in stone due to preserving backward compatibility, and everyone who wants to use the new java.time-API has to live with it.
Sorry for a long answer, but it is pretty tough to explain the chosen design of JSR-310.
The Instant class does not have a time zone. It gets printed like it's in the UTC zone because it has to be printed in some zone(you wouldn't want it to be printed in ticks, would you?), but that's not it's time zone - as demonstrated in the following example:
Instant instant=Instant.now();
System.out.println(instant);//prints 2014-05-14T06:18:48.649Z
System.out.println(instant.atZone(ZoneId.of("UTC")));//prints 2014-05-14T06:18:48.649Z[UTC]
The signatures of ZoneOffset.from and OffsetDateTime.from accept any temporal object, but they fail for some types. java.time does not seem to have an interface for temporals that have a timezone or offset. Such an interface could have declare getOffset and getZone. Since we don't have such interface, these methods are declared separately in multiple places.
If you had a ZonedDateTime you could call it's getOffset. If you had an OffsetDateTime you could also call it's offset - but these are two different getOffset methods, as the two classes don't get that method from a common interface. That means that if you have a Temporal object that could be either you would have to test if it's an instanceof both of them to get the offset.
OffsetDateTime.from simplifies the process by doing that for you - but since it also can't rely on a common interface, it has to accept any Temporal object and throw an exception for those that don't have an offset.
Just an example w.r.t conversions, i believe some folks will get below exception
(java.time.DateTimeException: Unable to obtain LocalDateTime from
TemporalAccessor: 2014-10-24T18:22:09.800Z of type java.time.Instant)
if they try -
LocalDateTime localDateTime = LocalDateTime.from(new Date().toInstant());
to resolve the issue, please pass in zone -
LocalDateTime localDateTime = LocalDateTime.from(new Date().toInstant().atZone(ZoneId.of("UTC")));
The key to to get a LocalDateTime from an Instant is to provide the system's TimeZone:
LocalDateTime.ofInstant(myDate.toInstant(), ZoneId.systemDefault())
As a side note, beware of multi-zone cloud servers as the timezone will surely change.

Understanding the Etc/GMT time zone

What is the rationale behind Apple using Etc/GMT timezone when they return the receipt from the App Store for auto-renewable subscriptions.
What exactly is the Etc/GMT time zone? Does the Java SDK understand this time zone? Or do I have to use other third-party libraries like Joda-Time?
Etc/GMT is not strictly the same thing as UTC or GMT. They represent the same instant in time only when the offset is 0. In all other cases, they are quite different.
Apple explains the designation here.
A quote directly from the link gives an example:
We use POSIX-style signs in the Zone names and the output abbreviations,
even though this is the opposite of what many people expect.
POSIX has positive signs west of Greenwich, but many people expect
positive signs east of Greenwich. For example, TZ='Etc/GMT+4' uses
the abbreviation "GMT+4" and corresponds to 4 hours behind UTC
(i.e. west of Greenwich) even though many people would expect it to
mean 4 hours ahead of UTC (i.e. east of Greenwich).
Offset versus zone
Understand:
An offset-from-UTC is simply a number of hours-minutes-seconds, ahead of the baseline of UTC, or 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.
Positive versus negative numbering
Different protocols in various industries have varied in their numbering, with some considering offsets ahead of UTC to be positive numbers while others used negative. Symmetrically, some considered offsets behind UTC to be negative while others used positive.
In most modern protocols I’ve seen, such as the ISO 8601, offsets ahead of UTC (towards the east) are positive, while offsets behind UTC (towards the west) are negative. So the offsets used by zones in the Americas have negative numbers such as America/Los_Angeles having an offset of -07:00 or -08:00 nowadays (varies during the year because of Daylight Saving Time (DST)).
I suggest you learn to think of this manner (right of UTC is positive, left of UTC is negative) as mainstream, and the opposite as a minor annoying variation.
Time zone names are generally in the format Continent/Region, such as America/Edmonton, Europe/Paris, Africa/Tunis, Asia/Kolkata, and Pacific/Auckland. See this list on Wikipedia (may not be up-to-date). There are some exceptions. The Etc/GMT… names carry the opposite plus/minus convention:
Etc/GMT+1 = -01:00 offset = One hour behind UTC
Etc/GMT+12 = -12:00 offset = Twelve hours behind UTC
…and…
Etc/GMT-1 = +01:00 offset = One hour ahead of UTC
Etc/GMT-12 = +12:00 offset = Twelve hours ahead of UTC
Confusing? Welcome to the wacky world of date-time handling. It only gets weirder from here.
Key points:
Understand the meaning and intentions of those people publishing data. Never assume the meaning of an input string.
Use java.time classes only for all your date-time work. Never use the terrible legacy classes java.util.Date, Calendar, SimpleDateFormat, and such.
Fortunately the java.time classes can help you through this muddle. See the correct Answer by Ole V.V. using the ZoneId class.
Your questions
rationale behind Apple using Etc/GMT timezone
They mean an offset of zero, UTC itself. The string Etc/GMT is one canonical label for an offset-from-UTC of zero hours-minutes-seconds.
The letter Z (pronounced “Zulu”) seen commonly at the end of date-time strings means the same thing, an offset of zero.
What exactly is the Etc/GMT time zone?
The string Etc/GMT is a name for a time zone which has had only one offset-from-UTC ever, an offset of zero hours-minutes-seconds.
Most other time zones such as Europe/Berlin or Africa/Casablanca have varied in their offset over history. For example, in that Africa/Casablanca zone in Morocco, the politicians have decided last year that rather than switching an hour twice a year for standard time & DST, they will now stay permanently on DST year-round. I say “permanently” with a chuckle, as that really means “until the politicians change their mind again”. Politicians around the world have shown a penchant for redefining their time zones with surprising frequency.
Does the Java SDK understand this time zone?
Yes. See the Answer by Ole V.V.: ZoneId.of( "Etc/GMT" )
Or do I have to use other third-party libraries like Joda-Time?
FYI, the Joda-Time project is now in maintenance mode, advising migration to the java.time classes. See Tutorial by Oracle.
You should be using java.time classes for all your date-time handling.
Etc/GMT is just a standard way of saying UTC, GMT, GMT0 or GMT+00:00.
The Java JDK understands all of the formats. You can easily see this in action by doing the following:
import java.util.TimeZone;
public class Playground {
public static void main(String... args) {
for (String s : TimeZone.getAvailableIDs()) {
System.out.println(s);
}
}
}
This will print out all the different TimeZone formats that your Java JDK can parse:
...
Etc/GMT
Etc/GMT+0
Etc/GMT-0
Etc/GMT0
Etc/Greenwich
Etc/UCT
Etc/UTC
Etc/Universal
...
When conveying a point in time across time zones it is a recommended standard to use either UTC or GMT (the two are roughly equivalent, for most purposes we don’t distinguish). It appears that Apple does exactly this.
The JDK understands Etc/GMT just fine.
ZoneId etcGmt = ZoneId.of("Etc/GMT");
JDK uses the tz database (formerly known as the Olson database; link at the bottom). The list of time zone names in the database are in the other link at the bottom. Etc/GMT is listed there. You will notice that it is given as the canonical name for GMT (there are also some aliases, some current and some deprecated).
As an aside, my code line of course uses ZoneId from java.time, the modern Java date and time API. This is the JDK class that you will want to use (there is also an old and poorly designed class for time zones that you don’t want to use).
I don’t think you meant to ask, but for anyone interested: JDK also understands Etc/GMT+0, Etc/GMT+1, Etc/GMT0, Etc/GMT-0, Etc/GMT-1, etc. (no pun intended) since they are in the database too. And it correctly handles the reversed sign mentioned in the quote in the accepted answer by David Peden.
Links
Wikipedia article: tz database
List of tz database time zones
Documentation of ZoneId
I suggest you to use a Java library for App Store receipts and stop thinking about the date format.
Add the artifact (by.dev.madhead.utils.appstore_receipts_validator:model:2.0.0).
Use any HTTP client to call the App Store and get the response back (here I use Ktor):
suspend fun verify(receipt: String, password: String): VerifyReceiptResponse {
val rawResponse = client.post<String> {
url("https://buy.itunes.apple.com/verifyReceipt")
contentType(ContentType.Application.Json)
accept(ContentType.Application.Json)
body = VerifyReceiptRequest(
receipt,
password,
true
)
}
}
Parse the response with Jackson:
val response = objectMapper.readValue(rawResponse)
Now you can use plain old Java API to work with the response.

Categories