Correct java.time representation of XMLGregorianCalendar - java

I have a XML element with the following content:
<lastModified>2019-10-09T19:20:45.677+02:00</lastModified>
This is mapped to Java's XMLGregorianCalendar.
I need to convert this value in an appropriate java.time instance.
I am a little confused about which java.time class is the "correct" (i.e. lossless) representation of this XMLGregorianCalendar value.
I suppose it should be ZonedDateTime or is OffsetDateTime the better choice?

The String you have ("2019-10-09T19:20:45.677+02:00") is in an ISO format that doesn't even need an extra formatter in order to parse it. The main reason for the use of an OffsetDateTime are the last 6 characters: +02:00, which denote an offset of 2 hours from UTC (more than just one time zone may actually have this offset at the same time).
You can convert this value into the proper java.time instance like this, for example:
public static void main(String[] args) throws DatatypeConfigurationException {
// your example datetime
String lastModified = "2019-10-09T19:20:45.677+02:00";
// create an XMLGregorianCalendar for example purpose
XMLGregorianCalendar xmlGC = DatatypeFactory.newInstance()
.newXMLGregorianCalendar(lastModified);
// print it once in order to see the values
System.out.println("XMLGregorianCalendar: " + xmlGC.toString());
// parse its toString() method to an OffsetDateTime
OffsetDateTime lastModOdt = OffsetDateTime.parse(xmlGC.toString());
// format the content of the OffsetDateTime in ISO standard
System.out.println("OffsetDateTime: "
+ lastModOdt.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME));
}
Output:
XMLGregorianCalendar: 2019-10-09T19:20:45.677+02:00
OffsetDateTime: 2019-10-09T19:20:45.677+02:00
This should be correct (lossless enough by losing no information).

I've been struggling with a similar problem all morning and Ole's comments were more insightful than the accepted answer so thought I should post the conclusion I came to.
Technically yes, the exact equivalent to XMLGregorianCalendar is OffsetDateTime (assuming your example, where you have y-m-d h:m:s+z fields populated) as the time zone offset is known but which actual time zone it is is unknown (as per other comments, there are many time zones all with offset +2:00). You can convert it easily like so, without going via an intermediary string:
xmlGC.toGregorianCalendar ().toZonedDateTime ().toOffsetDateTime ()
But I think the real answer is it depends what you want to do with it. In my case, I want to display the date/time to the user. If that is true, do you really care what time zone the date/time happened to be stored as in the XML? The following are both effectively identical, i.e. they both represent the same point in time.
<lastModified>2019-10-09T19:20:45.677+02:00</lastModified>
<lastModified>2019-10-09T20:20:45.677+01:00</lastModified>
If you don't care about the time zone from the XML and just the point in time it reperesents, then the correct answer is Instant, which you can get like
xmlGC.toGregorianCalendar ().toInstant ()
and from there apply whatever time zone the user is in, rather than the time zone from the XML. So for me the right answer was to do:
xmlGC.toGregorianCalendar ().toInstant ().atZone (ZoneId.systemDefault ())

Related

Is there any reason to use ZoneId.of("UTC") instead of ZoneOffset.UTC?

Is there any reason to use ZoneId.of("UTC") instead of ZoneOffset.UTC?
We know the difference between the two as provided in What is the difference between ZoneOffset.UTC and ZoneId.of("UTC")?.
<tl;dr> version:
ZoneOffset.UTC returns a mere ZoneOffset with ID "Z", offset of 0 and default zone rules.
ZoneId.of("UTC") returns a ZoneRegion with ID "UTC" and ZoneOffset.UTC included.
</tl;dr>
For this question, I assume UTC-usage merely for easier date and time handling and not, because something might actually be located in the UTC region or some other business reason to have this as the actual ZoneRegion.
For example, when dealing with ZonedDateTime. The only difference I could find was that it prints differently.
2021-06-10T15:28:25.000000111Z
2021-06-10T15:28:25.000000111Z[UTC]
We are having code review discussions back and forth about this, so I guess this conflict is not uncommon.
Here are my thoughts so far
ZoneOffset.UTC
It is a constant (and further, it's Offset value (0) is even cached).
It has (a tiny bit) less overhead, due to the missing region information.
At UTC, there are no daylight saving times or historical shifts to consider, like in any other timezone.
Thus, an Offset of 0 is in general enough for all use cases I encountered so far (like converting to and from a certain Zone Region with a particular daylight saving status).
ZoneId.of("UTC")
In general, I'd say ZoneRegions are preferred to ZoneOffsets, due to a range of additional location-specific data like a particular daylight saving time or time shifts in history.
However, in case of UTC, ZoneId.of("UTC") is just a region wrap around ZoneOffset.UTC and I could not find any benefit so far. As far as I know, in UTC no region-relevant data exists apart from it's inherited ZoneOffset.UTC rules.
Needs to be parsed every time.
So my _guess_ is:
Unless you really need the UTC time zone/region for some (e.g. business) reasons, you should prefer ZoneOffset.UTC. It has a (minor) advantage in its performance footprint and the UTC ZoneRegion does not seem provide any benefit I can see or think of.
However, because of the complexity of the Java Date Time API it is hard to tell if I am missing something in this discussion.
Is there any reason why someone should ever use ZoneId.of("UTC")?
I can think of one situation where ZoneId.of("UTC") might be preferable over ZoneOffset.UTC. If you use jackson-modules-java8 to deserialize ZonedDataTime from JSON, you get dates with ZoneId.
For example, let's say we have this POJO (getters/setters omitted for brevity):
class Example {
private ZonedDateTime date = ZonedDateTime.now(ZoneOffset.UTC).truncatedTo(ChronoUnit.DAYS);
}
If you print an instance of the class you get:
Example example1 = new Example();
System.out.println(example1);
// output:
// Example(date=2022-06-17T00:00Z) - as expected
What happens when you deserialize JSON containing the above string 2022-06-17T00:00Z?
String json = "{\"date\":\"2022-06-17T00:00Z\"}";
Example example2 = mapper.readValue(json, Example.class);
System.out.println(example2);
// output:
// Example(date=2022-06-17T00:00Z[UTC]) // now with ZoneId(!)
So now we have two variants, one with ZoneOffset and one with ZoneId:
2022-06-17T00:00Z
2022-06-17T00:00Z[UTC]
And these are not equal to each other. For example:
Instant instant = Instant.now();
ZonedDateTime z1 = ZonedDateTime.ofInstant(instant, ZoneOffset.UTC);
ZonedDateTime z2 = ZonedDateTime.ofInstant(instant, ZoneId.of("UTC"));
System.out.println(z1.equals(z2)); // => false
As a result example1.equals(example2) will also be false. This could be a source of subtle bugs.
Note: you get same result if your serialize new Example() to a JSON string and deseralize the string back to an object. You will get a date with ZoneId.
Tested with Jackson 2.13.1.
Edit: I ended up filing a bug for this issue: https://github.com/FasterXML/jackson-modules-java8/issues/244
Is there any reason why someone should ever use ZoneId.of("UTC")?
I cannot (right now) think of any good reasons.
However, it is not egregiously bad to do that. There would most likely be a small runtime cost in doing the lookup, but it is unlikely to matter. And the two forms are (IMO) equally readable.
However, if the zone name is parameter you could find code like this:
String zoneName = "UTC"; // my default
// Try to get a non-default zone
// ...
id = ZoneId.of(zoneName);
which would (IMO) be better than this:
String zoneName = null;
// Try to get a non-default zone
// ...
id = zoneName == null ? ZoneId.UTC : ZoneId.of(zoneName);

Java: Fix incorrect timezone in date object

An external API returns an object with a date.
According to their API specification, all dates are always reported in GMT.
However, the generated client classes (which I can't edit) doesn't set the timezone correctly. Instead, it uses the local timezone without converting the date to that timezone.
So, long story short, I have an object with a date that I know to be GMT but it says CET. How can I adjust for this mistake withouth changing my local timezone on the computer or doing something like this:
LocalDateTime.ofInstant(someObject.getDate().toInstant().plus(1, ChronoUnit.HOURS),
ZoneId.of("CET"));
Thank you.
tl;dr ⇒ use ZonedDateTime for conversion
public static void main(String[] args) {
// use your date here, this is just "now"
Date date = new Date();
// parse it to an object that is aware of the (currently wrong) time zone
ZonedDateTime wrongZoneZdt = ZonedDateTime.ofInstant(date.toInstant(), ZoneId.of("CET"));
// print it to see the result
System.out.println(wrongZoneZdt.format(DateTimeFormatter.ISO_ZONED_DATE_TIME));
// extract the information that should stay (only date and time, NOT zone or offset)
LocalDateTime ldt = wrongZoneZdt.toLocalDateTime();
// print it, too
System.out.println(ldt.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
// then take the object without zone information and simply add a zone
ZonedDateTime correctZoneZdt = ldt.atZone(ZoneId.of("GMT"));
// print the result
System.out.println(correctZoneZdt.format(DateTimeFormatter.ISO_ZONED_DATE_TIME));
}
Output:
2020-01-24T09:21:37.167+01:00[CET]
2020-01-24T09:21:37.167
2020-01-24T09:21:37.167Z[GMT]
Explanation:
The reason why your approach did not just correct the zone but also adjusted the time accordingly (which is good when desired) is your use of a LocalDateTime created from an Instant. An Instant represents a moment in time which could have different representations in different zones but it stays the same moment. If you create a LocalDateTime from it and put another zone, the date and time are getting converted to the target zone's. This is not just replacing the zone while keeping the date and time as they are.
If you use a LocalDateTime from a ZonedDateTime, you extract the date and time representation ignoring the zone, which enables you to add a different zone afterwards and keep the date and time as it was.
Edit: If the code is running in the same JVM as the faulty code, you can use ZoneId.systemDefault() to get the same time zone as the faulty code is using. And depending on taste you may use ZoneOffset.UTC instead of ZoneId.of("GMT").
I am afraid you will not get around some calculations here. I'd strongly suggest to follow an approach based on java.time classes, but alternatively you might use the java.util.Calendar class and myCalendar.get(Calendar.ZONE_OFFSET) for those calculations:
https://docs.oracle.com/javase/8/docs/api/java/util/Calendar.html#ZONE_OFFSET

Convert Time with offset to UTC and return localdatetime

I have a Method which parses a String according to Iso8601 and returns a LocalDateTime.
Now I accept possible offsets.
The Problem now is I have to convert the offset to UTC and return it as LocalDateTime.
So far I have tried working with Instant, OffsetDateTime, ZonedDateTime.
I always get the opposite. Instead of +06:00 my result will be shown as -06:00.
return LocalDateTime.ofInstant(Instant.from(OffsetDateTime.parse(date, buildIso8601Formatter())), ZoneOffset.UTC);
This is the solution which works the same as my other tried ones I mentioned above.
I know this is not the common way to do it and I did a lot of research because of that but the current architecture only allows me to do it that way.
EDIT example:
With an implementation like this:
OffsetDateTime offsetDateTime = OffsetDateTime.parse(date, buildIso8601Formatter());
Instant instant = offsetDateTime.toInstant();
return LocalDateTime.ofInstant(instant, ZoneOffset.UTC);
Let's say I get "2020-01-12T08:30+06:00" as input for my method. I HAVE to return LocalDateTime.
As a result I want to have "2020-01-12T14:30" instead my best solution was to get it the opposite way: "2020-01-12T02:30".
The behavior of java.time is correct. The string 2020-01-12T08:30+06:00 means that the datetime part of this string is a datetime local to some region, which exists in an area with an offset of +06:00 from UTC.
Your interpretation is different from the abovementioned case. In your case, you interpret 08:30 as a time in sync with UTC, and then concatenate the timezone offset string for the desired region.
So if you really want to do this – think again.
One way to achieve this, is simply by parsing the datetime as an offset datetime and negate the offset.

Is there a simple way to change a timestamp value containing seconds and ms to a timestamp value having hours and minutes?

So I have an object ('Task') that has an attribute 'Start Date' which is basically a Timestamp object. So this date is in this format 'YYYY/MM/dd hh:mm:ss:ms'. But for a test case I am authoring, I need this date to be in this format 'YYYY/MM/dd hh:mm'. Also it needs to be a timestamp object as I have to set this value back to the 'Task' object.
I have tried several approaches including the snippet shown below:
SimpleDateFormat formatter = new SimpleDateFormat("YYYY-MM-dd hh:mm");
if (task.getStartDate() != null) {
String newDate = formatter.format(task.getStartDate());
Date date = formatter.parse(newDate);
task.setStartDate(new Timestamp(date.getTime()));
}
I expected the value of the timestamp to be in the format '2018-12-30 09:54' but it resulted in '2018-12-30 09:54:00.0'. So the questions that I have in mind is:
Is there a way to not consider the seconds and millis in the Timestamp object?
If no, then, is the snippet provided an efficient way to update the Timestamp object?
TL;DR
Avoid the Timestamp class if you can. It’s poorly designed and long outdated.
To answer your questions, no, a Timestamp hasn’t got, as in cannot have a format (the same holds true for its modern replacement, Instant (or LocalDateTime)).
Under all circumstances avoid SimpleDateFormat and Date. The former in particular is notoriously troublesome, and both are long outdated too.
Don’t put a format into your model class
You should not want an Instant nor a Timestamp with a specific format. Good practice in all but the simplest throw-away programs is to keep your user interface apart from your model and your business logic. The value of the Instant object belongs in your model, so keep your Instant or Timestamp there and never let the user see it directly. I hope that it’s clear to you that 2018-12-30 09:54 and 2018-12-30 09:54:00.0 represent the same value, the same Timestamp. Just like 17, 0017 and 0x11 represent the same integer value. When you adhere to what I said, it will never matter which format the Instant has got.
Whenever the user should see the date and time, this happens in the UI, not in the model. Format it into a String and show the string to the user. Similarly if you need a specific format for persistence or exchange with another system, format the Instant into a string for that purpose.
java.time and JDBC 4.2
Also for exchange with your database over JDBC, provided that you’ve got a JDBC 4.2 compliant driver, prefer to use a type from java.time over Timestamp. If the datatype on the database side is timestamp with time zone, very clearly recommended for a timestamp, pass an OffsetDateTime like
OffsetDateTime dateTime = yourInstant.atOffset(ZoneOffset.UTC);
yourPreparedStatement.setObject(4, dateTime);
Use setObject, not setTimestamp. Some drivers accept the Instant directly, without conversion to OffsetDateTime. If on the database side you need a mere timestamp (without time zone), use LocalDateTime in Java instead and pass one to setObject in the same way as above.
PS There are errors in your format pattern string
In a format pattern string, uppercase YYYY is for week based year and only useful with a week number. For year use either uuuu or lowercase yyyy. Similarly lowercase hh is for hour within AM or PM from 01 through 12 and only useful with an AM or PM marker. For hour of day from 00 through 23 you need uppercase HH. These errors will give you incorrect dates and times in most cases. Using the wrong case of format pattern letters is a very common mistake. SimpleDateFormat generally doesn’t mind, it just gives incorrect results. The modern DateTimeFormatter does a somewhat better job of notifying you of such errors.
Links
Oracle tutorial: Date Time explaining how to use java.time.
Related questions
Formatting timestamp in Java about getting rid of the .0 decimal on the second of a Timestamp.
timestamp formatting in scala [duplicate] about getting a Timestamp with only date and hour (no minute, second or fraction of second).
java parsing string to date about uppercase Y for year in a format pattern string.
Comparing two times in android about lowercase h for hour of day in a format pattern string.

in java I need define date in this format 1999-05-31T13:20:00-05:00 [duplicate]

This question already has an answer here:
Java how to set 2011-11-06T14:34:16.679+02:00 into XMLGregorianCalendar
(1 answer)
Closed 5 years ago.
I need to define date in this format 1999-05-31T13:20:00-05:00
I am using below code
SimpleDateFormat sdf = new SimpleDateFormat("YYYY-MM-DD'T'hh:mm:ssZ");
sdf.setTimeZone(TimeZone.getTimeZone("EST"));
String date = sdf.format(new Date());
System.out.println(date);
but it is generating date in format 2017-04-117T08:28:46-0500 (missing semicolon in timezone)
After defining the date I have to create an instance of XMLGregorianCalendar with same date.
I was surprised you didn’t find the answer when searching for it. Anyway, it’s easy. Two words of caution first, though:
Skip the three-letter time zone abbreviations. Many are ambiguous. While I think EST only means Eastern Standard Time, this isn’t a full time zone, since (most of?) that zone is on EDT now, which does not make it very clear what result you want.
If there’s any way you can, skip the old-fashioned classes SimpleDateFormat, TimeZone and Date. The new classes in java.time are generally much nicer to work with.
The format you are asking for is exactly what DateTimeFormatter.ISO_OFFSET_DATE_TIME is giving you. If you meant to have the time formatted suitably for a user in the Eastern time zone, use:
ZonedDateTime now = ZonedDateTime.now(ZoneId.of("America/New_York"))
.truncatedTo(ChronoUnit.SECONDS);
System.out.println(now.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME));
Note the use of the unambiguous time zone ID and of the mentioned formatter. The format will print milliseconds (and even smaller) if there are any, which you didn’t ask for. So I have cheated a bit: I am truncating the time to whole seconds. Now this prints something like:
2017-04-27T10:11:33-04:00
If instead you wanted the offset -05:00, that’s even easier:
OffsetDateTime now = OffsetDateTime.now(ZoneOffset.ofHours(-5))
.truncatedTo(ChronoUnit.SECONDS);
System.out.println(now);
This prints something in the same format, but with the desired offset:
2017-04-27T09:11:33-05:00
The toString method of OffsetDateTime gives you the format you want. Of course, if you prefer, you can use the same formatter as before.
If truncating to seconds is a bit too much cheating for your taste, you may of course use a formatter without milliseconds:
DateTimeFormatter isoNoSeconds = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ssXXX");
System.out.println(now.format(isoNoSeconds));
This will work with both a ZonedDateTime and an OffsetDateTime, that is, with both of the above snippets.

Categories

Resources