The behaviour that I see is very strange - sometimes LocalDateTime would be equal to ZonedDateTime, other times it will differ by 1 hour or 2 and sometimes it's 30 minutes. All these strange differences depend on the year that I subtract. Can someone explain what's happening? Tried jdk1.8.0_65 and jdk1.8.0_91, MacOS 10.11.5. I work with UTC:
ZoneOffset offset = ZoneOffset.UTC;
Here are some experiments. For 1919 values may differ by nano or milliseconds, which is expected:
assertEquals(
LocalDateTime.now(offset).minusYears(85).toInstant(offset),
ZonedDateTime.now().minusYears(85).withZoneSameInstant(offset).toInstant());
For 1919 it's 1 hour difference:
assertEquals(
LocalDateTime.now(offset).minusYears(86).toInstant(offset),
ZonedDateTime.now().minusYears(86).withZoneSameInstant(offset).toInstant());
Expected :<1930-05-28T20:19:10.383Z>
Actual :<1930-05-28T21:19:10.383Z>
For 1920 it's 2 hours difference:
assertEquals(
LocalDateTime.now(offset).minusYears(95).toInstant(offset),
ZonedDateTime.now().minusYears(95).withZoneSameInstant(offset).toInstant());
Expected :<1921-05-28T20:21:45.094Z>
Actual :<1921-05-28T18:21:45.094Z>
For 1921 again milli or nano seconds difference:
assertEquals(
LocalDateTime.now(offset).minusYears(96).toInstant(offset),
ZonedDateTime.now().minusYears(96).withZoneSameInstant(offset).toInstant());
And the weirdest of all - 1930 year with 30 mins difference:
assertEquals(
LocalDateTime.now(offset).minusYears(97).toInstant(offset),
ZonedDateTime.now().minusYears(97).withZoneSameInstant(offset).toInstant());
Expected :<1919-05-28T20:24:27.345Z>
Actual :<1919-05-28T19:53:08.346Z>
Update
As #Tunaki pointed I had to specify the offset for the ZonedDateTime:
assertEquals(
LocalDateTime.now(offset).minusYears(95).toInstant(offset),
ZonedDateTime.now(offset).minusYears(95).withZoneSameInstant(offset).toInstant());
The problem is that this doesn't know about Time Zones: LocalDateTime.now(offset).minusYears(97).toInstant(offset). There is only offset present. But this knows about the time zone: ZonedDateTime.now().minusYears(97).toInstant()
ZoneId contains information about the place and the time difference at that place. It knows that N years ago in that particular time zone the offset was 2 hours, not 3 as it is now.
ZoneOffset keeps track only about the hours/minutes shift. It doesn't know the history of time changes in a particular country. It just "adds hours".
The suggested (and a little bit incorrect) solution is to let ZonedDateTime to forget about zones and use offset instead: ZonedDateTime.now(offset).minusYears(97). This now would agree with LocalDateTime with the same offset - both would show same incorrect information. But they would agree since both "just add hours" instead of understanding the time difference at that point of history for that place:
ZoneOffset offset = ZoneId.of("Europe/Moscow").getRules().getOffset(Instant.now());
assertEquals(
LocalDateTime.now(offset).minusYears(97).toInstant(offset),
ZonedDateTime.now(offset).minusYears(97).toInstant());
Alternatively we can set the LocalDateTime to show the correct value for that time for that place:
ZonedDateTime zoned = ZonedDateTime.now(ZoneId.of("Europe/Moscow")).minusYears(97);
ZoneOffset offset = zoned.getOffset();//agrees with history
assertEquals(
LocalDateTime.now().minusYears(97).toInstant(offset),
zoned.toInstant());
PS: This all shows that ZonedDateTime can work differently in different situations - sometimes it knows about time zones, other times it just "adds hours" as you would do with LocalDateTime by setting the offset manually. To me this is a weird implementation. JodaTime probably is still the best Java implementation. At least you don't have to learn it for several days to understand it.
Since no one is answering this...
You have to send in offset when creating the ZonedDateTime as well. Otherwise it will use Clock.systemDefaultZone() and you get a difference in timezones.
ZonedDateTime.now(offset).minusYears(95).withZoneSameInstant(offset).toInstant()
Related
This question already has answers here:
What Date time format can be used to handle DST in Java
(2 answers)
Closed 12 months ago.
We are sent two dates from an external source as Strings. We then calculate the difference between the dates to see how many hours they worked.
Two times a year due to the time change - Our calculations get wrong. We use Java.
How do you solve a problem like this?
The two dates come in a file as String and have the following format.
"2019-10-07 11:07 AM"
It really depends on what your input data is and precisely what you're trying to determine.
If you're just receiving the dates, represent them as LocalDate values - that's what LocalDate represents; a date without any associated time zone. You can then use Period.between to find out the difference between LocalDate values, for example.
However, if you want to actually work out an elapsed time between two instants in time, then you do need to take time zones into account - in which case you might parse into an OffsetDateTime or a ZonedDateTime (or perhaps directly Instant - it depends on your input data). You can find the elapsed Duration between any two Temporal values (such as OffsetDateTime and ZonedDateTime, or Instant) using Duration.between.
It's worth noting that if your input data specifies a time zone (e.g. Europe/London) and a local date/time, you will need to consider ambiguous local date/time values. For example, suppose the file is something like:
Time zone: Europe/London
Shift 1 start: 2022-10-31T01:30
Shift 1 end: 2022-10-31T02:30
Should that be one hour, or two? It could be either, because 1:30am happened twice in the Europe/London time zone on that day, as the clocks went back from 2am to 1am. You could even end up with data that seems implausible at first glance:
Time zone: Europe/London
Shift 1 start: 2022-10-31T01:30
Shift 1 end: 2022-10-31T01:15
That's entirely valid as a 45 minute shift, if it started at the first occurrence of 1:30, and ended at the second occurrence of 1:15.
A good way to represent correct points in time that, once constructed, allow calculations independent of any assumed time zone is ZonedDateTime. Whats nice about this class is that you can create instances of it easily using your own idea of local time. You just need to tell it what time zone you're in, and it will do the right thing for daylight savings time as defined for your time zone. Once constructed, you can do date/time arithmetic on these objects without concern for time zone or DST state.
Here are the two simple cases for creating and subtracting two ZonedDateTime objects representing an 8 hour timespan, one that spans a DST change, and one that doesn't. There is likely a way to do this with Java's date functions such that you never have to see and deal with the individual numbers.
Case 1: Worker works 8 hours that don't cross a DST point:
ZonedDateTime zdt = ZonedDateTime.of(
2022, 1, 13, 1, 0, 0, 0, ZoneId.of("America/Los_Angeles"));
ZonedDateTime zdt2 = ZonedDateTime.of(
2022, 1, 13, 9, 0, 0, 0, ZoneId.of("America/Los_Angeles"));
long hours = ChronoUnit.HOURS.between(zdt, zdt2);
System.out.println(hours);
Result:
8
Case 2: Worker works 8 hours that cross a DST point:
ZonedDateTime zdt = ZonedDateTime.of(
2022, 3, 13, 1, 0, 0, 0, ZoneId.of("America/Los_Angeles"));
ZonedDateTime zdt2 = ZonedDateTime.of(
2022, 3, 13, 10, 0, 0, 0, ZoneId.of("America/Los_Angeles"));
long hours = ChronoUnit.HOURS.between(zdt, zdt2);
System.out.println(hours);
Result:
8
Notice that if you forgot about DST, you'd think that for the second shift, the worker worked an extra hour. But given DST, the two shifts were the same number of hours.
NOTE: I started from numeric representations of date/times because the question does not give examples of or otherwise specify the formats of the input strings involved. A "correct" example that involved string parsing would need to assume a format that might be different than what the OP is starting with. The primary question here would be if and how time zone is represented in those strings. Parsing a string to come up with a set of numbers and a time zone is a separate problem that has nothing to do with DST, so I leave that for the OP to figure out. It is likely that you could use date/time parsing logic in the Java date/time library to directly arrive at a ZonedDateTime, again, dependent on the format of the strings.
I want to get a difference in hours between a current time in a specific timezone and UTC time. I tried this:
LocalTime time = LocalTime.now();
System.out.println(time); //21:05:42:764
LocalTime utcTime = LocalTime.now(ZoneId.of("UTC"));
System.out.println(utcTime); //18:05:42:769
System.out.println(Duration.between(utcTime, time).getSeconds()/3600); //2
System.out.println(Duration.between(time, utcTime).getSeconds()/3600); //-3
Why is the difference between the last two results and are there better ways to do it? I need to get the number 3.
Why is the difference between the last two results
The reason that you're getting different results for the two computed durations is a combination of the fact that there is some tiny amount of time elapsed between the two recordings and the fact that the duration start time is included in the range but the duration end time is not.
Consider these times instead: 6:00:00:001 vs 8:00:00:000. Here it is very obvious that we're only exactly one millisecond off of two hours, but when we think about seconds we're either going to get 7199 or -7200. When we then do integer math (i.e. divide by 3600), we're going to get 1 or -2.
If it weren't for the one extra millisecond on the first timestamp, the absolute value of the two would be identical.
Duration is the wrong class. There is zero duration between "now" in one time zone and "now" in another. For a fun but memorable way to think about this, see here.
You appear to be seeking to know the current offset from UTC for a given time zone. You can use the ZonedDateTime class for that:
ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("Asia/Kolkata"));
ZoneOffset offset = zdt.getOffset();
int offsetMinutes = offset.getTotalSeconds() / 60;
double offsetHours = ((double) offsetMinutes) / 60;
System.out.println(offsetHours); // 5.5
You could also just use ZonedDateTime.now() on the first line, if you want to use the computer's current time zone.
With regard to LocalTime - that is just the time portion (hours, minutes, seconds, and smaller). Since there is no date associated, you can't necessarily determine which time zone offset it belongs to. There is more than one date that "today" going on at any given moment. Time zone offsets range from UTC-12 to UTC+14, so there are indeed values where the same time of day is happening on two different dates somewhere on the planet.
As an example, 08:00:00 in Hawaii (Pacific/Honolulu) on 2019-01-01 is also 08:00:00 in Kiribati (Pacific/Kiritimati), but on 2019-01-02 - the following date! (Reference here.) Thus, if you had a LocalTime object with 08:00:00 and it was 08:00:00 in one of those two zones, you'd not be able to tell which one it was, or what the corresponding UTC offset should be.
Also, keep in mind that time zone offsets are not limited to whole hours. There are present-day time zones with half-hour and 45-minute offset. Historically, there have been others.
Lastly, keep in mind that an offset is not necessarily enough to identify a time zone. Many time zones share offsets at some points in time, but differ in others. See "Time Zone != Offset" in the timezone tag wiki.
Oh, and about your results getting 2 in one direction and -3 in the other - this is a rounding error due to your integer division. If you print out the seconds value, you'll notice they are one second apart (10799, vs -10800). Dig closer and you'll find that "now" included fractional seconds that were truncated with the getSeconds call. (You called .now() twice, so they were at slightly different times.)
just to verify this: I have this lame and brain dead method to calculate the time zone offset for my current location. I wonder if I need to adjust it when Day Light Saving time comes into question (currently we have Winter Time at my location, CET time zone, so it's hard to verify).
// The local time zone's offset
private int getLocalOffset() {
DateTimeZone defaultZone = DateTimeZone.getDefault();
return defaultZone.getOffset(null) / 1000 / 60 / 60;
}
Thanks for any hint.
Time zones and Daylight Saving Time are a nightmare. You certainly shouldn't take on this task yourself. Let Joda-Time do the heavy lifting.
See this answer to similar question, Using Joda time to get UTC offset for a given date and timezone. The class DateTimeZone offers a getOffset() method.
Example source code in Joda-Time 2.3 in Java 7…
// © 2013 Basil Bourque. This source code may be used freely forever by anyone taking full responsibility for doing so.
org.joda.time.DateTimeZone californiaTimeZone = org.joda.time.DateTimeZone.forID("America/Los_Angeles");
org.joda.time.DateTime now = new org.joda.time.DateTime(californiaTimeZone);
int millisecondOffsetToAddToUtcToGetLocalTime = californiaTimeZone.getOffset( now );
System.out.println( "millisecondOffsetToAddToUtcToGetLocalTime: " + millisecondOffsetToAddToUtcToGetLocalTime );
// Note the casting to doubles to avoid integer truncation. Time zone offsets are NOT always whole hours.
System.out.println( "Offset in decimal hours: " + (double)millisecondOffsetToAddToUtcToGetLocalTime / 1000d / 60d / 60d );
When run at 2013-11-20T01:03:56.464-08:00…
millisecondOffsetToAddToUtcToGetLocalTime: -28800000
millisecondOffsetToAddToUtcToGetLocalTime in hours: -8.0
IMPORTANT That number format -8.0 is incorrect for an offset. Must be either:
-08:00 with the colon and double digits (padded with leading zero).
-08 with leading zero.
Normally, Joda time will take care of DST by itself, so you don't have to worry about it. However, I notice that you are passing null to getOffset(). Given that the time zone offset depends on the date, you really should be passing the date/time at which you are calculating the offset, or you're going to get wrong results.
Also as mentionned in my previous comment: Be aware that some timezones have an offset that isn't a whole number of hours. India for example is at GMT +5:30
Yes, that's fine. To verify that it is correct - instead of passing null pass in a DateTime object to DateTimeZone.getOffset - set the datetime to sometime in summer when you know DST is in effect - you should see the offset value change.
How to get maximum time value of the current date in miliseconds in Java ?
E.g. 18th July 23.59 in epoch miliseconds where current time would be anything of 18th July
Half-Open
Seems like your Question involves determining the last moment of the day. Such a goal is ill-advised.
The last moment of the day is infinitely divisible as a fraction of a second, such as 2017-01-23T23:59:59.999Z. But different software uses different granularities when resolving such date-time values. The old legacy date-time classes is Java use milliseconds, or 3 decimal places. The new java.time classes use nanoseconds, or 9 decimal places. Other software such as the Postgres database use microseconds, for 6 decimal places. Other software uses other resolutions such as 5 decimal places. So you will be getting different values in different scenarios for the same date.
A better approach to defining spans of time is commonly used in date-time work, and avoids this ambiguity of last-moment-of-the-day: Half-Open. In this approach the beginning is inclusive while the ending is exclusive. So a single full day starts at the first moment of one date and runs up to, but does not include, the first moment of the following day. For UTC, that would be 2017-01-23T00:00:00Z to 2017-01-24T00:00:00Z.
ZoneId z = ZoneId.of( "America/Montreal" ) ;
LocalDate today = LocalDate.now( z ) ;
ZonedDateTime todayStart = today.atStartOfDay( z ) ;
long secondsSinceEpoch = todayStart.toEpochSecond() ; // Whole seconds since epoch of 1970-01-01T00:00:00Z.
long millisecondsSinceEpoch = todayStart.toInstant().toEpochMilli() ; // Milliseconds since epoch of 1970-01-01T00:00:00Z.
See this code run live at IdeOne.com.
todayStart: 2017-07-19T00:00-04:00[America/Montreal]
millisecondsSinceEpoch: 1500436800000
For the following day (tomorrow), add one day to the LocalDate.
LocalDate tomorrow = today.plusDays( 1 ) ;
ZonedDateTime tomorrowStart = tomorrow.atStartOfDay( z ) ;
There’s room for a bit of interpretation in your question. I suggest:
ZoneId zone = ZoneId.systemDefault();
long endOfDay = LocalDate.now(zone)
.plusDays(1)
.atStartOfDay(zone)
.toInstant()
.minusMillis(1)
.toEpochMilli();
System.out.println(endOfDay);
On my computer it just printed
1500501599999
This corresponds to a ZonedDateTime of 2017-07-19T23:59:59.999+02:00[Europe/Berlin].
Please note that the end of the day is time zone dependent, so you need to decide a time zone for the operation. Please fill in your desired time zone instead of ZoneId.systemDefault().
Since Java doesn’t include an “end of the day” operation I opted for adding one day, taking the beginning of the day in the time zone in question and then subtracting 1 millisecond (because you asked for milliseconds since the epoch). If you want, you may instead subtract a nanosecond, a second or a minute, for example. If you want to subtract a whole minute, the simplest is to use .minusMinutes(1) before toInstant().
The code should be safe even on a day and in a time zone where a summer time transition (DST changeover) happens at midnight.
I'm not entirely sure how the specific date plays into this, but if you want to find the last millisecond of the day, I would use LocalTime.MAX.toNanoOfDay()/1000000. This takes the maximum nanosecond of the day, then divides by 1000000 to convert to milliseconds. If you wanted to combine this with a date, I would combine this value with a LocalDate or LocalDateTime value.
i am trying to calculate seconds between two LocalDates.
Now this is actually giving me a hard time as this code snippet is
not working that well:
long seconds = lastRefreshPoint.until(systemTime, ChronoUnit.SECONDS);
This gives me the Exception:
java.time.temporal.UnsupportedTemporalTypeException: Unsupported unit: Minutes
I cant really find any other way to do this on the Internet.
It would be nice if you could help me!
Thanks in advance
Okay, here my comment as answer together with some details about the reasons of the behaviour observed.
Summarizing:
This does not make any sense since a calendar date does not know anything about hours, minutes or seconds. The lowest supported unit must be the day. See also the API of Java-8-class LocalDate:
If the unit is a ChronoUnit then the query is implemented here. The
supported units are: •DAYS •WEEKS •MONTHS •YEARS •DECADES
•CENTURIES •MILLENNIA •ERAS All other ChronoUnit instances will
return false.
Reasons:
While someone can imagine to implement the difference in seconds between two connecting days as 86400 seconds (ignoring zone effects or leap seconds), it would not be a good idea. If the API designers had decided to support the between-arithmetic then they should also have decided to support adding of seconds to a date. But here the problem starts:
date + 86400 secs = next date
But what is date + 123 secs???
The support for units more precise than a day in the class LocalDate would cause inconsistencies which cannot be cured.
The Answer by Hochschild is correct and should be accepted.
LocalDate::atStartOfDay
To make a date-time value from your date-only value, go with the first moment of the day. Doing that requires a time zone. For any given moment, the date can vary around the globe by zone.
Do not assume the day starts at time 00:00:00. Because of anomalies such as Daylight Saving Time, the day may begin at a time such as 01:00:00.
ZoneId z = ZoneId.of( "America/Montreal" );
ZonedDateTime zdt = myLocalDate.atStartOfDay( z );
Then proceed with calculating elapsed time.
Duration d = Duration.of( zdtStart , zdtStop );
long seconds = d.getSeconds();
I am posting this myself as this was posted as a comment and not as an answer:
This does not make any sense since a calendar date does not know
anything about hours, minutes or seconds. The lowest supported unit
must be the day.
-- Meno Hochschild
This pretty much hits the point.
Thanks a lot, Meno Hochschild