Dealing with Date Object and TimeZone in Java 7 - java

I am developing a library to store complex data in an Object. One of the field in this object is a date. When I set the date using setter method, it is assumed that the date object is in GMT timezone. Internally, the Date is stored as a long with the number of milliseconds from epoch. In my get() method, I am doing the following :
return new Date(storedDateinMilliseconds);
The problem is that if anyone is calling toString() on the returned object, it uses the default timezone to return the date. Therefore, the returned date does not always match with the date provided in GMT. Is there a way to fix this? so that the user of this implementation will always get the GMT date when they invoke toString()?
I tried the following :
TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
But this would modify the default timezone of the application using it.

As Sotirios Delimanolis’ comment said, you are giving a java.util.Date object to the calling programmer. What she does with it is up to her. And it is up to her to understand all the goofy problems that come with that object including its toString method applying the JVM’s current default time zone in generating a string representation of its date-time value.
If you want to return a date-time value with an assigned time zone, then return a different object.
Alternative Date-Time Objects
You have at least three alternatives to returning a java.util.Date object.
java.time
In Java 8 and later, the obvious choice is to use the new java.time framework (Tutorial). Give the calling programmer a ZonedDateTime object which is basically a Instant object plus a ZoneId object.
Tip: When specifying a time zone, use a proper time zone name. Never use the 3-4 letter codes like EST or IST.
Joda-Time
Joda-Time was the inspiration for java.time. This 3rd-party library is excellent, and well-worn from popular use. It supports multiple versions of Java and Android too.
The DateTime class is a moment on the timeline plus a time zone, similar to java.time’s ZonedDateTime.
ISO 8601
A third alternative is to give the calling programmer a string representation of a date-time value. The obvious choice of formats is to use those defined by the ISO 8601 standard. These formats are sensible, well thought-out, and unambiguous.
2015-09-16T18:06:14Z
…or…
2015-09-16T11:06:14-07:00
Both java.time and Joda-Time use these formats by default in parsing and generating strings. The formats are wisely extended by java.time to append the proper name of the time zone in square brackets.
2015-09-16T11:06:14-07:00[America/Los_Angeles]
Never Adjust Default Time Zone
You stated that setting the default time zone affected your entire app. Wrong. It affects all code of all apps in all threads running in that JVM. Worse, it does so immediately during runtime while that other code is running.
Set the default time zone only as a last resort when all other approaches to resolve a date-time problem have been exhausted. This is rare. The usual solution is to:
Use java.time or Joda-Time.
Always specify the desired/expected time zone rather than rely implicitly on the default.
Use UTC in most of your business logic, data storage, and data exchange.
Avoid whenever possible the mess that is java.util.Date/.Calendar.
Search StackOverflow
All of these topics have been discussed many times over on StackOverflow.com. Please search for more information and examples.

AFAIK, you have 2 options:
Option 1. This may sound like an overkill, but you could roll out your own Date object just for this complex class of yours, and overwrite the toString() method. Maybe something like
public class GMTDate extends java.util.Date {
#Override
public String toString() {
//return GMTDate
}
}
Option 2: Keep your date as java.util.Date, but don't expose a public getter for it. Expose instead a public getter that returns your date in GMT format, and maybe a public getter that returns your date as a long (with the number of milliseconds from epoch)
EDIT:
And a 3rd option: AspectJ. You can use aspect oriented programming to intercept calls to the toString() method and return a GMT string date
Relevant Stack Overflow Question: AspectJ: Intercept method execution/call and make it return

Related

Getting the UTC timestamp in Java

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]

How can I pass an empty String value to a Date field in Java

I have a scenario in a current java program where if the user enters a certain date (01/01/1900) in an excel Date field I'm supposed to flag that and clear the value out in the database. In this case since the field is a Date type, I need to pass it to an xml as "" i.e and empty String since I can't just pass null. I'm getting an error because it is expecting a format of "yyyy-MM-dd" to pass to the xml and not "". Of course when setting the Date field to a certain value, I can't just pass an empty String in the setVesselDate() method because it will complain saying it must be in so so format. I have a method that validates the date (handleValidateDate) that returns the sql Date value.
Any recommendation to actually set the date field as "" would be helpful. Below is the code I have:
if (vesselAssignmentObj.getVesselDate().length() > 0)
{
String clearOutDateFlag = "1900-01-01";
String clearField = "";
//below returns a dateformat of yyyy-MM-dd at i
vesselDate = handleValidateDate(vesselAssignmentObj.getVesselDate(), VESSEL_ASSIGNMENTS_VESSEL, i);
if (vesselDate.toString().equals(clearOutDateFlag))
{
vesselAssignmentObj.setVesselDate(String.valueOf(vesselDate.toString().equals(clearField)));
}
else
{
vesselAssignmentObj.setVesselDate(String.valueOf(vesselDate).toString());
}
}
No easy answer.
… I'm supposed to flag that and clear the value out in the database…
Whoever instructed you should have defined what "clear the value out" means.
NULL
Usually in a database "clear the value out" might mean using a NULL value. But nulls bring a bag of hurt, raising ambiguity as to the meaning, affecting sorting and queries in various ways, and carrying ramifications in your app(s) and libraries.
While I generally avoid NULL like the plague (taking Dr. Chris Date’s advice), this may in fact be the best fit for your needs.
Arbitrary date as flag
You could choose an arbitrary date to use as flag that means 'empty' or 'not specified' or 'unknown'. But what date? One commenter suggested 0000-00-00 but this value may exceed the limits of some database’s date data-types. Furthermore, dates around year zero can be problematic because of how different calendaring systems handle the meaning of that period. Furthermore, any date before the adoption of the Gregorian calendar in the West is problematic as we lost some days in transitioning from Julian calendar to Gregorian.
1901-01-01
In a comment you said that a business rules requires dates to be over the year 1900. In that case I would choose 1901-01-01 as my flag date. The trick is making this clear to the entire team and to posterity. Be sure to use well-named constant in your programming, and document well in both your apps and in the database.
1970-01-01
Another common choice of arbitrary date flag is the first of 1970, 1970-01-01. That date is a commonly used epoch reference date used in Unix time and in java.time. This makes that particular date recognizable to many folks in the information trade. But you still need to document thoroughly, as this epoch date is far from the only one: At least a couple dozen other epoch dates have been in widespread usage.
Millenium
Yet another choice could be the date of the new millennium. Unfortunately, that phrase means different dates to different people, in year 2000 or 2001.
The Postgres database uses 2000-01-01 as its epoch. The Apple Cocoa framework, possibly the most widely distributed software in the world, uses 2001-01-01.
Use objects, not strings
You should not be passing strings to/from your database. Use objects instead. The job of your JDBC driver is to mediate between your Java data types and your database types.
Drivers compliant with JDBC 4.2 and later should be able to use java.time types via PreparedStatement::setObject and ResultSet::getObject. If not, fall back to using the java.sql types briefly and immediately convert to/from the java.time types. To convert, look to new methods added to the old classes.
All this JDBC date-time stuff has been covered many times already, so search Stack Overflow for more information. Search for class names Instant, ZonedDateTime, and ZoneId.

What is the most accurate joda-time representation for "DateUTC"?

Up until now we have been using joda-time class DateTime to represent date-time data with UTC timezone, and this meets our requirements nicely.
Example: 2014-11-07T14:10:00Z
Now, we have a requirement to represent "date" data (in UTC timezone).
Example: 2014-11-07
There exists a LocalDate joda-time class, and a method to convert from DateTime via DateTime.toLocalDate().
I looked for a joda-time class which I thought would exist DateUTC - but it doesn't seem to exist!
Can someone please point me in the right direction, or in fact specify what is the best joda-time class for representing "date-UTC" data (without timestamp) ?
P.s. we are not ready to move to java 8 yet, since we have a lot of code using joda-time library
If your DateTime object is in UTC, converting it to LocalDate is fine if you don't care about time. If it's not already in UTC, you can convert it to UTC using DateTime.withZone(DateTimeZone.UTC).toLocalDate()
LocalDate represents only the date components, without timezone information. Therefore, your code can just use it and assume it's in UTC, as long as you're consistent. For a string representation, you can use LocalDate.toString().
However, if all you want is to format your existing DateTime object so that the string representation only shows the date components, then you shouldn't need to use LocalDate. Just format it to only show the dates: DateTime.toString("yyyy-MM-dd").

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.

Categories