Convert LocalDateTime to Instant requires a ZoneOffset. Why? - java

I need to convert a LocalDateTime object to a new Instant object.
I've realized LocalDateTime has an toInstant method, but it's requesting me an ZoneOffset.
I don't quite figure out how to use it, or what does ZoneOffset meant for.

You can't convert a LocalDateTime directly to an Instant because a LocalDateTime can represent many instants. It does not represent one particular instant in time.
Here is a LocalDateTime:
23/10/2018 09:30:00
Can you figure out which instant of time exactly does the above refer to just by looking at it? No. Because that time in the UK is a different instant from that time in China.
To figure out what instant that time refers to, you also need to know how many hours is it offset from UTC, and that, is what ZoneOffset basically represents.
For example, for an offset of 8 hours, you would write this:
localDateTime.toInstant(ZoneOffset.ofHours(8))
Or, if you know you always want the zone offset in the current time zone for that local date time, you can replace ZoneOffset.ofHours(8) with:
ZoneId.systemDefault().getRules().getOffset(localDateTime)
You should think about what offset you want to use before converting it to an instant.

you can try this:
LocalDateTime dateTime = LocalDateTime.of(2018, Month.OCTOBER, 10, 31, 56);
Instant instant = dateTime.atZone(ZoneId.of("Europe/Rome")).toInstant();
System.out.println(instant);

Related

What Date time format can be used to handle DST in Java

Our client sends us a start and end date-time in a text file as a String in the below format
2019-10-07 11:07 AM
All date-time is in one timezone. We calculate the difference between the start and end date-time to calculate the hours worked. The hours worked calculation goes wrong when the transition of daylight savings time happens. They are not sending enough information for us to calculate correctly.
I am about to recommend that they send us more information so that we can address this issue. What is a good solution here? What date-time format should we recommend to them that will help us address the DST change and calculate hours worked correctly.
We use Java.
Getting it right is not obvious
They are telling you their local time, and you can infer the time zone (because "all date is in one time zone").
The basic calculation looks like this:
ZoneId pacific = ZoneId.of("America/Los_Angeles");
DateTimeFormatter local = DateTimeFormatter.ofPattern("uuuu-MM-dd hh:mm a").withZone(pacific);
ZonedDateTime start = ZonedDateTime.parse("2022-11-06 01:30 AM", local);
ZonedDateTime until = ZonedDateTime.parse("2022-11-07 01:30 AM", local);
long hours = start.until(until, ChronoUnit.HOURS);
System.out.printf("%d hours elapsed%n", hours);
This prints "25 hours elapsed." In the Pacific time zone, November 6, 2022, is 25 hours long, because when daylight saving ends in the autumn, the clock is set back one hour. If someone tells you it's 1:00 AM, you don't know if midnight was one hour ago or two.
The default offset heuristic
What you really need is the offset, and you have to rely on some heuristic for that. By default, ZonedDateTime chooses one instant from multiple ambiguous local date-times by selecting the earliest offset (the "summer" offset).
Specifying the offset
If that's not what you want, you can override the offset explicitly. For example, maybe you process these time stamps close to real-time, and you can guess what the offset should be based on the current time. Or maybe you know that these local time stamps are always processed in chronological order; by tracking the latest time you've seen, and noting if an earlier time stamp follows, you can detect the clock set back and change the offset.
The ZonedDateTime.ofLocal() and ZonedDateTime.ofStrict() functions can be used to explicitly control the offset.
OffsetDateTime
Alternatively, you might request that they include the offset in the timestamp string. Usually this would be indicated with a signed number of hours and minutes: "-07:00" or "-0800". This will provide unambiguous interpretation of times during DST transitions.
Here is an example using OffsetDateTime. First, if the offset uses a colon, as in "2019-10-07T11:07:00+01:00", it is a standard format, and can be parsed like this:
OffsetDateTime start = OffsetDateTime.parse("2019-10-07T11:07:00+01:00");
If the colon is missing, you need a formatter to handle the non-standard input:
DateTimeFormatter odt = new DateTimeFormatterBuilder()
.append(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
.appendOffsetId()
.toFormatter();
OffsetDateTime when = OffsetDateTime.parse("2019-10-07T11:07:00+01:00", odt);
From there, the calculation is the same as with ZonedDateTime:
OffsetDateTime start = OffsetDateTime.parse("2022-11-06T01:00:00-07:00", odt);
OffsetDateTime until = OffsetDateTime.parse("2022-11-07T01:54:00-08:00", odt);
long hours = start.until(until, ChronoUnit.HOURS);
System.out.printf("%d complete hours elapsed.%n", hours);
Duration duration = Duration.between(start, until);
System.out.println("Full duration: " + duration);
This is simple task. The DateTimeFormatter class gives you all the info you need. 2019-10-07 11:07 AM Your format would be 'yyyy-MM-dd hh:mm a' and you should use LocalDateTime class. But since you need to take into account daylight savings time then you might want to use classes ZonedDateTime or OffsetDateTime and provide your timezone. It might be an overkill, but I once worked on the project where I needed to parse Strings to Dates without knowing the format in advance. So, here is the article I wrote on how to do that: Java 8 java.time package: parsing any string to date

How to get a Date instance with time of a day at a certain time (like 12:00:00.000) in a specific time zone?

for example, now it is 2020-03-16 11:23:23.121 in Vietnam, but my program is running in the USA, how to get a Date instance which is 2020-03-16 12:00:00.000 in Vietnam, which mean, I keep the year, month, day as the same, but set hour as 12, minute, second and nanosecond as 0, can LocalDateTime play a role?
ZonedDateTime zdt = ZonedDateTime.of(2020, 3, 16, 12, 0, 0, 0, ZoneId.of("Asia/Ho_Chi_Minh"));
ZonedDateTime
From java-8 you can use ZonedDateTime to get the date time from any zone
ZonedDateTime dateTime = ZonedDateTime.now(ZoneId.of("Asia/Ho_Chi_Minh"))
And the you can modify the time to 12:00:00 using with method. Pass the time of day as a LocalTime object obtained by calling LocalTime.of. In the new LocalTime object, the second and the nanosecond default to zero, so no need to pass those arguments to the factory method.
dateTime.with( LocalTime.of( 12 , 0 ) ) //2020-03-16T12:00+07:00[Asia/Ho_Chi_Minh]
Java util Date will not store any time zone information and it just represents a specific instant in time (which is only UTC), with millisecond precision. I will suggest to avoid using legacy util.Date
No, do not use LocalDateTime here
can LocalDateTime play a role?
LocalDateTime cannot represent a moment, as it lacks the context of a time zone or offset-from-UTC. So that is exactly the wrong classs to use here on your Question.
To represent a moment, a specific point on the timeline, use:
Instant (always in UTC)
OffsetDateTime (carries an offset-from-UTC, a number of hours-minutes-seconds)
ZonedDateTime (carries an assigned time zone, named in Continent/Region)
See the correct Answer by Deadpool showing the proper use of ZonedDateTime to solve your problem.
For more info, see What's the difference between Instant and LocalDateTime?

ZonedDateTime with timezone added to print format

I'm using https://github.com/JakeWharton/ThreeTenABP in my project.
I have org.threeten.bp
ZonedDateTime: 2019-07-25T14:30:57+05:30[Asia/Calcutta]
How can I get this printed with addition of the timezone hours? ie the result should have 2019-07-25T20:00:57
Get the offset in the form of seconds from ZonedDateTime
ZonedDateTime time = ZonedDateTime.parse("2019-07-25T14:30:57+05:30");
long seconds = time.getOffset().getTotalSeconds();
Now get the LocalDateTime part from ZonedDateTime
LocalDateTime local = time.toLocalDateTime().plusSeconds(seconds); //2019-07-25T20:00:57
toLocalDateTime
Gets the LocalDateTime part of this date-time.
If you want to get the local date time in UTC use toInstant()
This returns an Instant representing the same point on the time-line as this date-time. The calculation combines the local date-time and offset.
Instant i = time.toInstant(); //2019-07-25T09:00:57Z
You misunderstood. The offset of +05:30 in your string means that 5 hours 30 minutes have already been added to the time compared to UTC. So adding them once more will not make any sense.
If you want to compensate for the offset, simply convert your date-time to UTC. For example:
ZonedDateTime zdt = ZonedDateTime.parse("2019-07-25T14:30:57+05:30[Asia/Calcutta]");
OffsetDateTime utcDateTime = zdt.toOffsetDateTime().withOffsetSameInstant(ZoneOffset.UTC);
System.out.println(utcDateTime);
Output:
2019-07-25T09:00:57Z

Date from ZonedDateTime instant not in utc

I am creating date like this:
ZonedDateTime now = ZonedDateTime.now(ZoneOffset.UTC);
Date.from(now.toInstant());
I need Date object have current time in utc, but when I print date it gives me my local time and not utc time.
I also tried with:
OffsetDateTime now = OffsetDateTime.now(ZoneOffset.UTC);
Date date = Date.from(now.toInstant());
But when I print Date again time is not in utc. Am I doing something wrong when creating Date object. Why above 2 approaches not give me Date that have current time in utc.
Two points:
Avoid the long outdated Date class, in particular when you are already using classes from java.time, the modern Java date and time API.
A Date object hasn’t got and cannot have a time zone in it.
To print offset or time zone
If you need your offset, you need to hold on to your OffsetDateTime (or ZonedDateTime) object:
OffsetDateTime now = OffsetDateTime.now(ZoneOffset.UTC);
System.out.println(now);
On my computer this just printed
2017-11-21T11:53:11.519Z
The Z in the end indicates Zulu time zone, another name for UTC (you may also informally think of it as Zero offset from UTC).
If you would like a more human-readable format, you are right, use a formatter:
DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.FULL);
ZonedDateTime now = ZonedDateTime.now(ZoneOffset.UTC);
System.out.println(now.format(formatter));
Depending on your locale and the time, this prints something like
Tuesday, November 21, 2017 11:53:11 AM Z
Again the Z means Zulu time zone, UTC.
Date is not going to help you
A Date is just a point in time. So is the Instant that you use for initializing the date. None of them has got a time zone or offset. The difference here is their toString methods: The Instant is always printed in UTC, the Date usually (always?) in the JVM’s default time zone. The latter confuses many into thinking the Date has a time zone when it hasn’t. See All about java.util.Date.
As I have demonstrated, a formatter may put a time zone or offset into a string when formatting the date-time. This does not in any way modify the date-time object, whether OffsetDateTime, ZonedDateTime, Instant or Date. The long outdated DateFormat class may do the same when formatting a Date. It cannot and will not set a time zone in the Date object since (and I repeat) a Date object cannot have a time zone in it.
Long story short, you have no need for the outdated Date class that I can see.

How to convert an Instant to a LocalTime?

I don't really understand TemporalAdjusters or Java's new time library even after reading numerous tutorials.
How can I convert an Instant object to a LocalTime object. I was thinking something along the lines of the following:
LocalTime time = LocalTime.of(
instantStart.get(ChronoField.HOUR_OF_DAY),
instantStart.get(ChronoField.MINUTE_OF_HOUR)
);
But it isn't working. How can I do this?
The way I understand it... Instant is a UTC style time, agnostic of zone always UTC. LocalTime is a time independent of given zone. So you'd expect the following would work given that Instant implements TemporalAccessor,
Instant instant = Instant.now();
LocalTime local = LocalTime.from(instant);
but you get "Unable to obtain LocalTime from TemporalAccessor" error. Instead you need to state where "local" is. There is no default - probably a good thing.
Instant instant = Instant.now();
LocalTime local = LocalTime.from(instant.atZone(ZoneId.of("GMT+3")));
System.out.println(String.format("%s => %s", instant, local));
Output
2014-12-07T07:52:43.900Z => 10:52:43.900
instantStart.get(ChronoField.HOUR_OF_DAY) throws an error because it does not conceptually support it, you can only access HOUR_OF_DAY etc. via a LocalTime instance.
yourInstant.atZone(yourZoneId).toLocalTime()
Instead of hardcoding the timezone you could use the configured timezone of your system:
LocalTime.ofInstant(instant, ZoneId.systemDefault())

Categories

Resources