How to convert Date to ZonedDateTime? - java

I've got various java.util.Date objects with values of this format: 2014-01-21 10:28:57.122Z. I would like to convert them all to ZonedDateTime objects.
According to this SO question, and ZonedDateTime's ofInstant(), one way is like so:
ZonedDateTime z = ZonedDateTime.ofInstant(dateObject.toInstant(), ZoneId);
The problem is, how do I figure out what to use for the ZoneId parameter? I can't use my system time (ZoneId.systemDefault()) because my Date objects all have different timezones.

Your java.util.Date object does not have a time zone. Dates are always stored internally in UTC.
When you parse a text string into a Date object, the parser applies a time zone, either as given in the text, as a parameter to the parser, or by default.
When you display a Date object, the formatter will generate the text as requested, in the time zone requested, whether the time zone is displayed or not.
So, without time zone information in the Date object, you must specify the time zone you want when converting to ZonedDateTime.
Better yet, parse the text directly to a ZonedDateTime, so it can remember the original time zone from the text.

Related

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

Can you please explain when the zonedDateTime.withZoneSameInstant(ZoneId.of("UTC")).toInstant() and zonedDateTime.toInstant() give different outputs?

Can someone please state when these two types of ways to adjust a ZonedDateTime to UTC differ?? If possible provide some test event date times also.
String eventDate = "2016-11-28T10:56:28+11:00"; // my example date time
ZonedDateTime zonedDateTime = ZonedDateTime.parse(eventDate.trim(),
DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssXXX"));
// defaulting to UTC Zone
//1st way
System.out.println(zonedDateTime.withZoneSameInstant(ZoneId.of("UTC")).toInstant());
//2nd way
System.out.println(zonedDateTime.toInstant());
my question was are these two ways any different for other date time inputs that might not necessarily have the same formats or timezone or offsets.
There cannot be any difference. You will always get the same instant from both ways.
The reason is: A ZonedDateTime always uniquely defines a point in time, an instant. After converting to another time zone using withZoneSameInstant the new ZonedDateTime will always define the same point in time, the same instant.
BTW deHaar is correct in the comment: Your string contains an offset from UTC, +11:00, and no time zone like for example Asia/Shanghai, so OffsetDateTime is a more appropriate class than ZonedDateTime for your purpose.

What is the difference between ZonedDateTime.withZoneSameInstant and ZonedDateTime.withZoneSameLocal?

Let's say I have a ZonedDateTime:
ZonedDateTime zonedDateTime =
ZonedDateTime.of(LocalDateTime.now(), ZoneId.of("US/Pacific"));
I would like to know which date/time it is let's say in Berlin.
I have two methods :
zonedDateTime.withZoneSameInstant(ZoneId.of("Europe/Berlin")); // probably this is the right one to get the corresponding date/time in Berlin
zonedDateTime.withZoneSameLocal(ZoneId.of("Europe/Berlin"));
The docs for the withZoneSameLocal method say: "The local date-time is only changed if it is invalid for the new zone..." and it's not clear when this really can happen (any example ? =)).
Which date/time each of them represents and what is the difference?
If you want to convert a timestamp from one timezone to another, use withZoneSameInstant(). withZoneSameLocal() will change the zone but keep all the other fields the same. The exception is where it would be an invalid date in that timezone.
For example,
ZonedDateTime dtUTC = ZonedDateTime.parse("2019-03-10T02:30:00Z");
ZoneId pacific = ZoneId.of("US/Pacific");
System.out.println(dtUTC.withZoneSameInstant(pacific));
System.out.println(dtUTC.withZoneSameLocal(pacific));
prints
2019-03-09T18:30-08:00[US/Pacific]
2019-03-10T03:30-07:00[US/Pacific]
The first line is the original timestamp converted to another timezone. The second tries to preserve the date/time fields, but 2:30 is not a valid time on that date (because of the Daylight Savings jump), so it shifts it by an hour.

After Parsing timestamp contains timezone to Date object, does Date object contain timezone information

If we have timestamps that contain the timezone info, like 2017-07-03T17:30:00-04:00, and parse it into java.Date or joda.DateTime.
Does it contains timezone information ?
I am asking this because i want to compare two different date instance. So if it does not contain timezone information, the day difference will be wrong with different timezones
UPDATE:
I run a quick unit test to verify, first convert date instance to milliseconds and convert back to TimeUnit after subtract these two milliseconds. The hours are different for different timezone
Both java.util.Date and Joda-Time have been supplanted by the java.time classes.
Your input string 2017-07-03T17:30:00-04:00 is in standard ISO 8601 format and has an offset-from-UTC at the end. That -04:00 means the string represents a moment four hours behind UTC.
This offset is not a time zone. A time zone is a history of offsets for a particular region. For example, America/Barbados or America/New_York.
Parse your string as an java.time.OffsetDateTime object.
OffsetDateTime odt = OffsetDateTime.parse( "2017-07-03T17:30:00-04:00" );
odt.toString(): 2017-07-03T17:30:00-04:00
You may compare OffsetDateTime instances by calling the methods IsEqual, isBefore, and isAfter.
To see the same simultaneous moment in UTC, extract an Instant.
Instant instant = odt.toInstant() ;
instant.toString(): 2017-07-03T21:30:00Z
The Z on the end is short for Zulu and means UTC.
It is going to depend on what type of DateTime you use, as of Java 8 you have these options:
A LocalDate or LocalDateTime. It is going to discard time zone information, you will wind up with a value that is 'valid' only for the local timezone. This value is ambiguous without some context as to the specific timezone of the server process which generated the value.
A ZonedDate or ZonedDateTime. This one preserves the time zone. Comparison is still going to be ambiguous: you have issues like DST or calendaring changes to contend with (depending on the range of datetime which you need to be compatible with). For sorting/comparison purposes you would probably want to convert it to a reference timescale, which is why:
An Instant represents a particular moment in time, on the absolute timescale of UTC. Any Instant is directly comparable with any other Instant and any ambiguity in values is resolved by the definition of Instant. Input values will be converted to the matching counterparts in UTC, so the original timezone (if any) will be lost even if the absolute time value will be preserved correctly. Instant is therefore not a good choice if you rely on the timezone to make decisions about location or locale, for instance.

Retrieving a UTC datetime from a data source -- putting it into a Joda DateTime object and persisting it to MongoDB. What is the best way?

I am pulling data from an external source into my program and it has an ISO8601 Date attached to it but one of our requirements is that the hour/minutes/seconds get set to zero. This happens before I receive the date. So I get this from the data.
2013-05-17T00:00:00.000Z
for instance. I am then putting that value into a Joda DateTime object called "businessDay". I do some processing based off of this value but then I need to persist it to MongoDB.
Since a Joda DateTime object is not serializable I need to put the DateTime object into a Date object and persist it to Mongo (and reverse that when it comes out).
When I use Joda in this way
businessDay.toDate() -- I receive a Java Date object but it is
Sun May 19 20:00:00 EDT 2013
and businessDay printed out normally is
2013-05-20T00:00:00.000Z
It converts it to my local time zone, which is then making it the previous day.
What I want is to convert the DateTime object into a Date object that retains the values.
I've been trying a bunch of things with DateTimeFormatter but I can't get it to work at all. I've also been deleting all of my efforts otherwise I would paste them here but I've been doing this all day to try to figure this out.
Thank you for any assistance.
EDIT:
Showing method that converts a String Date into a Joda DateTime object.
private DateTime asDateTime(String value) {
// Was experiencing an issue converting DateTime to date, it would convert to localtime zone
// giving me the wrong date. I am splitting the value into its year/month/day values and using a dateFormatter
// to give me an appropriate format for the date. Timezone is based on UTC.
String[] splitValue = value.split("-");
String[] splitDay = splitValue[2].split("T");
int year = Integer.parseInt(splitValue[0]);
int month = Integer.parseInt(splitValue[1]);
int day = Integer.parseInt(splitDay[0]);
DateTime date = new DateTime(DateTimeZone.UTC).withDate(year, month, day).withTime(0, 0, 0, 0);
return date;
}
Firstly, if you've just got a date, I would suggest using LocalDate rather than DateTime. However, I think you've misunderstood what java.util.Date does:
It converts it to my local time zone, which is then making it the previous day.
No, it really doesn't. Your DateTime value is precisely 2013-05-20T00:00:00.000Z. Now a java.util.Date is just a number of milliseconds since the Unix epoch. It doesn't have the concept of a time zone at all. It's equivalent to a Joda Time Instant.
When you call toString() on a Date, that converts the instant in time into your local time zone - but that's not part of the state of the object.
So both your DateTime and your Date represent midnight on May 20th UTC. I don't know what MongoDB is then doing with the value, but just the conversion from Joda Time to java.util.Date has not performed any time zone conversion for you.
My apologies, I found out that it wasn't an issue of the Dates it was a completely different issue. MongoDB can accept a Java Date and will convert it to UTC format automatically.
My fault for creating this post before looking at this problem from different angles.
Do I accept the other answer and give the bounty? Just curious if that is the correct thing to do on Stack Overflow.

Categories

Resources