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.
Related
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.
An old Stack Overflow posting suggests that the way to get the UTC timestamp in Java is the following:
Instant.now() // Capture the current moment in UTC.
Unfortunately this does not work for me. I have a very simple program (reproduced below) which demonstrates different behavior.
On Windows: the time is the local time and it is labeled with the offset with GMT
On Linux: the time is again the local time, and it is labeled correctly for the local timezone
Question: How do we display the UTC timestamp in a Java program?
My sample source code is as follows:
import java.time.Instant;
import java.util.Date;
public class UTCTimeDisplayer {
public static void main(String[] args) {
System.out.println(System.getProperty("os.name"));
Date currentUtcTime = Date.from(Instant.now());
System.out.println("Current UTC time is " + currentUtcTime);
}
}
Windows Output:
C:\tmp>java UTCTimeDisplayer
Windows 10
Current UTC time is Fri Jan 22 14:28:59 GMT-06:00 2021
Linux Output:
/tmp> java UTCTimeDisplayer
Linux
Current UTC time is Fri Jan 22 14:31:10 MST 2021
Your code:
Date.from(Instant.now())
You are mixing the terrible legacy classes with their replacement, the modern java.time classes.
Don’t.
Never use Date. Certainly no need to mix with java.time.Instant.
To explain your particular example, understand that among the Date class’ many poor design choices is the anti-feature of its Date#toString method implicitly applying the JVM’s current default time zone while generating its text.
You ran your code on two different JVMs that had different current default time zones. So you got different outputs.
Sun, Oracle, and the JCP gave up on the legacy date-time classes. So should we all. I recommend you not spend time trying understand Date, Calendar, SimpleDateFormat, and such.
You asked:
Question: How do we display the UTC timestamp in a Java program?
Instant.now().toString()
See that code run live at IdeOne.com.
2021-01-22T21:50:18.887335Z
You said:
On Windows: …
On Linux: …
You’ll get the same consistent results from Instant.now().toString() across Windows, Linux, BSD, macOS, iOS, Android, AIX, and so on.
Here is a table I made to guide you in transitioning from the legacy classes.
The java.util.Date object is not a real date-time object like the modern date-time types; rather, it represents the number of milliseconds since the standard base time known as "the epoch", namely January 1, 1970, 00:00:00 GMT (or UTC). When you print an object of java.util.Date, its toString method returns the date-time in the JVM's timezone, calculated from this milliseconds value. If you need to print the date-time in a different timezone, you will need to set the timezone to SimpleDateFormat and obtain the formatted string from it.
I would suggest you simply use Instant.now() which you can convert to other java.time type.
The date-time API of java.util and their formatting API, SimpleDateFormat are outdated and error-prone. It is recommended to stop using them completely and switch to the modern date-time API.
For any reason, if you have to stick to Java 6 or Java 7, you can use ThreeTen-Backport which backports most of the java.time functionality to Java 6 & 7.
If you are working for an Android project and your Android API level is still not compliant with Java-8, check Java 8+ APIs available through desugaring and How to use ThreeTenABP in Android Project.
However, if you still want to use java.util.Date, use SimpleDateFormat as mentioned above.
Demo:
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.util.Date;
import java.util.TimeZone;
public class Main {
public static void main(String[] args) {
Date currentUtcTime = Date.from(Instant.now());
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z");
sdf.setTimeZone(TimeZone.getTimeZone("Etc/UTC"));
System.out.println("Current UTC time is " + sdf.format(currentUtcTime));
}
}
Output:
Current UTC time is 2021-01-22 21:53:07 UTC
suggests that the way to get the UTC timestamp in Java is the following:
Instant.now() // Capture the current moment in UTC.
This, and most answers in this thread, are misleading.
Instant represents an instant in time. It's 'solarflares' time: Absolutely not one iota about it represents anything that is invented by human brains, and UTC is a timezone: A human invention. The cosmos, the sun, astronomy - they have no idea what UTC is, and don't care - and that's what Instant is all about. Instants are devoid of such human concepts as 'hours' or 'days' or 'timezones'. It makes no sense to ask an instant what day it happened on. It cannot tell you; some event occurred: If I ask a russian from the 19th century when that happened, they'll likely give a completely different answer vs. if I ask someone living a mere 100 miles west, for example. Instant doesn't know which localization to apply and thus doesn't let you ask it this question - that's a good thing, objects should not expose methods to which any answer it gives is either gobbledygook or at least requires knowing about all sorts of surprising caveats.
Crucially, if you tell me '... in UTC', you surely can tell with exacting detail which month, which day, etcetera. And Instant does not do this, which is why it is misleading to say that a java.time.Instant represents a moment of time in UTC. It doesn't. It represents a moment in time (not in any particular timezone).
Yeah, internally Instant, just like Date, is just a light wrapper around what System.currentTimeMillis() returns: "millis since epoch", but the crucial thing to understand about it, is that 'UTC' is not part of what it means, and therefore, when you give an Instant instance to some other method (such as System.out.println, to a database via JDBC, etc), that method is under absolutely no obligation to assume that UTC is semantically relevant.
When you want to mix human notions of time keeping (years, days, months, hours, minutes, milliseconds, and, yeah, timezones) with the notion of a more or less absolute* time, the right answer is java.time.ZonedDateTime. Note that any representation of time in something that isn't java.time.* based is by definition broken, as it is in most programming languages - turns out time is a lot more complex than most takes on a library to represent it realize. The fact that java.time is in effect the 4th attempt at writing a time library should be ample indication that it's hard to get it right.
ZonedDateTime zdt = ZonedDateTime.now(ZoneOffset.UTC);
THAT is what you want - that isn't just implementation-detail-wise what you want, but it is code that exactly describes what you mean: Right now, at the UTC time zone, stored in an object that semantically doesn't just store the right time but also stores, and tightly entangles into its very identity, that it is specifically in UTC and is not to be re-interpreted, moved to the local zone, or any other such shenanigans - at least, not unless you explicitly ask it to do so.
Date currentUtcTime = Date.from(Instant.now());
Note that Date is the old API and therefore necessarily broken. In this case, Date is a lying liar who lies - it doesn't represent a date, it represents an instant; it is badly named. (The second API is Calendar, also broken. For example, that is also a lying liar who lies: It doesn't represent a Calendar whatsoever. It represents some bizarre amalgamation of a zoned datetime and an instant and is fit to represent neither as a consequence). Any time you go to the Date API weirdness ensues, and something as simple as 'I just want the concept of the time, at some specific moment, in UTC' isn't possible in these APIs. You are now dependent on barely defined behaviour of all the various libraries up and down the chain - you're effectively stuck praying that they do the right thing, or delving into exotic settings to try to cajole these libraries into doing what you want.
TL;DR: Use java.time.
*) Note that ZonedDateTime is not absolute. For example, if you have the time January 20th, 2023, 8 in the morning, at Europe/Amsterdam, in the form of a ZonedDateTime object, then the amount of seconds that will pass between now and that moment sure seems like it does not change and will not change when e.g. amsterdam goes through an hour change due daylight savings. However, if the dutch government decrees that henceforth The Netherlands will no longer move the clocks at all and will stay in summer time forever (which is likely - EU directive is already in place, it's now just a matter of when), then at the moment the gavel lands, your appointment shifts by 1 hour exactly.
That hopefully provides crucial insight in the difference: Instant, representing events (hence why I like to call it 'solarflares time', to disentangle it from human time keeping concepts as much as possible), doesn't even understand the very concept of such a decision having an effect on things. ZonedDateTime on the other hand is inherently bound up in it - hence the Zone in ZonedDateTime.
If you want to store barber appointments and use Instant to do it, you WILL be an hour late or early sooner rather than later.
An Instant object and also a Date object by themselves
only contain a point in time, but no timezone information.
Furthermore, the toString() method of the Date class
implicitly chooses the timezone provided by the system environment,
which is not what you want.
Therefore you need to chose the timezone (in your case UTC) explicitly.
For example like this:
Instant instant = Instant.now();
OffsetDateTime offsetDateTime = instant.atOffset(ZoneOffset.UTC);
System.out.println("Current UTC time is " + offsetDateTime);
This will (independently from the operation system) print
Current UTC time is 2021-01-22T22:37:21.950354100Z
where the trailing Z denotes the zero timezone offset (i.e. UTC).
A simple method that could work!
My requirement was date time with milliseconds
2021-11-25 19:55:00.743
private String getUTCTimestamp() {
ZonedDateTime utc = ZonedDateTime.now(ZoneOffset.UTC);
return utc.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"));
}
Instant.now() is essentially the period of time since the epoch, (midnight 1 January 1970 in UTC), but you are using a Date to present that instant. Date is a reflection of the instant with millisecond precision, but as explained in the documentation at https://docs.oracle.com/javase/8/docs/api/java/util/Date.html, presenting a date should be done using a Calendar, as the presentation of a Date depends on the host. Essentially Date wraps the instant but is displayed according to other factors.
The simplest approach now if you want to output the instant is to use OffsetDateTime so that you can elect to present the instant in your desired timezone - UTC in your case. Use either OffsetDateTime.now() or OffsetDateTime.ofInstant() but if you are using the instant within your application logic then just stick with Instant.
Sometimes your program has to work with older java versions, so here is an example for 1.5:
java.text.SimpleDateFormat tfGMT = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
java.util.Calendar cUTC = java.util.Calendar.getInstance (java.util.TimeZone.getTimeZone ("GMT+0"));
tfGMT.setCalendar (cUTC);
java.util.Date d= new java.util.Date ();
String s= tfGMT.format (d);
System.out.printf ("now=%s [unix ts=%d.%03d]\n", s, d.getTime()/1000, d.getTime()%1000);
Mind you, the first three lines don't have to be repeat at every call, but keep in mind that SimpleDateFormat is not thread-safe. (Simple solution: create one for each thread.)
Example usage (it shows that setting TZ doesn't affect UTC-timestamp):
$ TZ=GMT+3 java5 now_utc; TZ=GMT-3 java5 now_utc
now=2021-01-24 12:56:14 [unix ts=1611492974.264]
now=2021-01-24 12:56:14 [unix ts=1611492974.726]
We are storing time in like '22-NOV-17 05.33.51.937000000 PM' format with server default timezone CST. We have half an our time comparison in many places. So CST to CDT and CDT to CST are facing issues because on retrieval time for database we can not identify the time zone. So it is breaking our time comparison on CST to CDT and CDT to CST time changes.
We can not change our storing logic like store with timezone and store in UTC timezone because it will breaking our existing logic in many places.
So is there any way to identity date timezone like CST or CDT, stored in database with '22-NOV-17 05.33.51.937000000 PM' format.
We are storing time in like '22-NOV-17 05.33.51.937000000 PM' format
does not make sense with your comment
We are storing as a timestamp in database
In Oracle databases, a TIMESTAMP does not have a format - it is stored in the database as 11 bytes representing year (2 bytes), month, day, hours, minutes, seconds (1 byte each) and fractional seconds (4 bytes). It is only when whatever interface you are using (SQL/Plus, SQL Developer, Toad, Java, PHP, etc.) to talk to the database decides to show it to you, the user, that that interface will format it as a string (but the database will just keep it as bytes without any format).
Assuming you are using SQL/Plus or SQL Developer then you can find the default format using:
SELECT value FROM NLS_SESSION_PARAMETERS WHERE parameter = 'NLS_TIMESTAMP_FORMAT';
And change the default format using:
ALTER SESSION SET NLS_TIMESTAMP_FORMAT = 'YYYY-MM-DD HH24:MI:SSXFF9';
Or for TIMESTAMP WITH TIME ZONE
ALTER SESSION SET NLS_TIMESTAMP_TZ_FORMAT = 'YYYY-MM-DD HH24:MI:SSXFF9 TZR';
So is there any way to identity date timezone like CST or CDT, stored in database with '22-NOV-17 05.33.51.937000000 PM' format.
No, without any other meta-data that could identify the source of the timestamp and indicate which location it came from (i.e. is there another column that links to the user who entered the data that could be mapped to a physical location and so a time zone) then it is impossible to determine which time zone it is from.
You will either need to:
change your database column to TIMESTAMP WITH TIME ZONE and store the time zone; or
convert all the values to the same time zone when you are storing them.
I am assuming by CST and CDT you mean North American Central Standard Time and Central Daylight Time such as observed in Rainy River, Chicago and Mexico (the city) among other places. More on this ambiguity later.
For 99.977 % of all times it is fairly easy to know whether they are standard time or daylight saving time. Only times from the two hours around the transition from DST to standard time are ambiguous, and as said in the comments, there is no way to know from the time stamp which is the right way to resolve this ambiguity.
java.time
This answer will take you as far into the future as possible without taking you away from Java 7. You can still use java.time, the modern Java date and time API also known as JSR-310. It has been backported to Java 6 and 7 in the ThreeTen Backport, so it’s a matter of getting this and adding it to your project (just until one day you upgrade to Java 8 or later).
I am taking your word for your date-time string format. What we can do with it:
DateTimeFormatter storedFormatter = new DateTimeFormatterBuilder()
.parseCaseInsensitive()
.appendPattern("d-MMM-uu hh.mm.ss.SSSSSSSSS a")
.toFormatter(Locale.US);
ZoneId zone = ZoneId.of("America/Mexico_City");
String storedTime = "22-NOV-17 05.33.51.937000000 PM";
LocalDateTime dateTime = LocalDateTime.parse(storedTime, storedFormatter);
// First shot -- will usually be correct
ZonedDateTime firstShot = ZonedDateTime.of(dateTime, zone);
System.out.println(firstShot);
This prints:
2017-11-22T17:33:51.937-06:00[America/Mexico_City]
You can see that it picked an offset of -06:00, which means that the time is in standard time (CDT is -05:00).
Since your month abbreviation is in all uppercase, I needed to tell the formatter to parse case insensitively. If America/Mexico_City time zone is not appropriate for you, pick a better one, for example America/Rainy_River or America/Chicago.
Ambiguous times in fall
I once had to parse a log file containing date-times without indication of standard time and summer time (DST). Since we assumed time would always move forward, we failed at the transition to standard time, and one hour of the log file was lost. In this case we might have solved it using the information that times were in summer time until the leap backward by an hour, from there they were in standard time. You may want to think about whether something similar will be possible for you.
Other options include just taking DST time every time — this is what the above code will do — or taking an average and living with the error thus introduced.
We can at least detect the ambiguous times:
ZoneOffset standardOffset = ZoneOffset.ofHours(-6);
ZoneOffset dstOffset = ZoneOffset.ofHours(-5);
// check if in fall overlap
ZonedDateTime standardDateTime
= ZonedDateTime.ofLocal(dateTime, zone, standardOffset);
ZonedDateTime dstDateTime
= ZonedDateTime.ofLocal(dateTime, zone, dstOffset);
if (! standardDateTime.equals(dstDateTime)) {
System.out.println("Ambiguous, this could be in CST or CDT: " + dateTime);
}
Now if the string was 29-OCT-17 01.30.00.000000000 AM, I get the message
Ambiguous, this could be in CST or CDT: 2017-10-29T01:30
ZonedDateTime.ofLocal() will use the provided offset for resolving the ambiguity if it is a valid offset for the date-time and zone.
Non-existing times in the spring
Similarly we can detect if your date-time falls in the gap where the clock is moved forward in the transition to DST:
// Check if in spring gap
if (! firstShot.toLocalDateTime().equals(dateTime)) {
System.out.println("Not a valid date-time, in spring gap: " + dateTime);
}
This can give a message like
Not a valid date-time, in spring gap: 2018-04-01T02:01
I suggest you can safely reject such values. They cannot be correct.
Avoid the three letter time zone abbreviations
CST may refer to Central Standard Time (in North and Central America), Australian Central Standard Time, Cuba Standard Time and China Standard Time. CDT may mean Central Daylight Time or Cuba Daylight Time. The three and four letter abbreviations are not standardized and are very often ambiguous. Prefer time zone IDs in the region/city format, for example America/Winnipeg.
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).
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.