Recently I stumbled over the hint to use Years.between() as JODA best practice to calculate someones age. It does not work in general as the following example demonstrates.
DateTime y0000 = new DateTime(0000, 1, 1, 0, 0, 0, 0);
DateTime y2000 = new DateTime(2000, 1, 1, 0, 0, 0, 0);
assertEquals(2000, new Period(y0000, y2000).getYears());
assertEquals(1999, Years.yearsBetween(y0000.toInstant(), y2000.toInstant()).getYears());
assertEquals(1999, new Period(y0000.toInstant(), y2000.toInstant()).getYears());
assertEquals(2000, new Period(new DateTime(y0000),new DateTime(y2000)).getYears());
Does JODA work here as designed or is this a JODA defect?
UPDATE:
JODA works as designed. If you really want to use Years.between() then either work with LocalDateTime or make sure to work with DateTimeZone.UTC.
From my comment (now transferred into this answer):
The result of 1999 for instant-conversion can be explained by the fact that timezone data are not well defined in far past. Joda-Time uses estimated zone offsets with second parts (so called LMT entries in the TZDB-database). This can result in different offsets for year 0 and year 2000 and hence decremented year delta. It is also important to understand that the timezone idea is an idea of the 19th century! So your code is meaningless ;-) The dark side by design here is also that Joda-Time enables code without making the timezone effect visible and explicit.
I have now investigated the offsets in my local timezone using this code:
System.out.println(DateTimeZone.getDefault().getOffset(y0000.toInstant()) / 1000); // 3208 s
System.out.println(DateTimeZone.getDefault().getOffset(y2000.toInstant()) / 1000); // 3600 s
The consequence is: You have to subtract the first smaller offset from y0000 and to subtract the bigger offset from y2000 so the delta in seconds between both instants cannot be equal to 2000 years expressed in seconds. Therefore the year delta is decremented.
In order to avoid such unexpected deltas (where you do obviously not think about any timezone effects) I strongly recommend to use types like LocalDateTime. This type has no timezone in contrast to DateTime and will do what you expect (of course without conversion to Instant). Otherwise, if you care about "exact" physical time differences (either by different summer/winter-time or historical estimation of offsets) then the expected year difference is not really correct i.e. should take into account the timezone offsets.
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'm trying to calculate LDAP accountExpires.
The given value is LDAP date - nanoseconds since 01/01/1601 00:00.
What is the best way to test if it is indeed after new Date()?
The best way probably depends on your precision requirements. I suggest
private static final Instant ldapEpoch = LocalDateTime.of(1601, Month.JANUARY, 1, 0, 0)
.atOffset(ZoneOffset.UTC)
.toInstant();
and then
long ldapTime = 131_428_662_140_000_000L;
Instant convertedTime = ldapEpoch.plusMillis( ldapTime / 10_000L );
System.out.println(convertedTime.isAfter(Instant.now()));
With my example LDAP time value this produces an Instant of 2017-06-25T12:10:14Z and prints false because the time is not after the current time.
Since you mentioned new Date() in the question, I assumed that the precision of Date would suffice for you, that is, milliseconds. I would really have loved to do ldapEpoch.plusNanos(ldapTime * 100) to keep the full precision, but this overflows the Java long data type and therefore gives an incorrect result. If you need the full precision, … Edit: as suggested by Basil Bourque in a comment, slice off the fractional second, work in whole seconds, then add back your fractional second:
Instant convertedTime = ldapEpoch.plusSeconds( ldapTime / 10_000_000L )
.plusNanos( ldapTime % 10_000_000L * 100L );
(The way I had first presented works too, gives the same result; but the edited version may be more natural to readers who know the Java date & time API (and may also perform a slight bit better, but that’s hardly critical).)
Why I wanted to multiply by 100? The LDAP, Active Directory & Filetime Timestamp Converter I found says “The timestamp is the number of 100-nanoseconds intervals (1 nanosecond = one billionth of a second) since Jan 1, 1601 UTC.”
Beware that in 1601 not everyone agreed about calendars, so January 1 that year is ambiguous. Most computer software assumes the Gregorian calendar, so I guess the definition of LDAP time does too, it’s not something I know.
Is TimeZone.getDSTSavings() in java returns always positive value or Negative values also?
I have tried this way
long millisecondsForGivenDateTime = 0l;
if (TimeZone.getTimeZone(strTZID).inDaylightTime(
new Date(millisecondsForGivenDateTime))) {
millisecondsForGivenDateTime = (millisecondsForGivenDateTime + TimeZone
.getTimeZone(strTZID).getDSTSavings());
}
here strTZID is timezone id dynamically passed to this function and millisecondsForGivenDateTime contains the dynamic value.
I am always getting 3600000 or 0 won't this function return -3600000?
In theory it could return a negative value - if you were using a TimeZone subclass where daylight saving time caused clocks to go back rather than forward.
I very much doubt you'll see that in the wild, although I have seen something similar in the Windows time zone database to get around the fact that Windows couldn't cope with time zones changing their standard UTC offset... if a zone went from UTC+5 to UTC+6 (as standard time) for example - and stopped observing DST - the zone data indicated that "DST" was actually -1 hour, and just inverted when it was in effect from what humans would expect. I've seen this with the Russian time zone, although I believe the data is cleaner now.
The desktop JRE prevents you from constructing a SimpleTimeZone with negative DST savings, but I don't know whether the same (undocumented) limitation is present in Android. You could always create your own subclass of TimeZone which did return a negative offset though.
This class returns 3600000 (1 hour) for time zones that use daylight savings time and 0 for timezones that do not, leaving it to subclasses to override this method for other daylight savings offsets.
If it were returning both positive and negative values, that would cause a two hour difference between the seasons.
I'm trying to create a date with absolute values for the fields through JODA DateTime, for example (the example is in the epoch for easier examples):
new DateTime(1970, 1, 1, 1, 0, 0, 0, DateTimeZone.forId("GMT+0").toDate()
The JODA DateTime object correctly represents the hour as 1AM, if I inspect it all the absolute values are precise and correct (1 hour, 3600 seconds, 3600000 millis since the epoch, etc.) but as soon as toDate() and java.util.Date kicks in it becomes 2AM because he's adding the DST of my system's current time, even though I explicitly created the DateTime object with GMT+0 offset.
How can I ignore DST offset and just tell Java to create me a java.util.Date with the absolute values I want? Unfortunately I'm dealing with legacy code and I'm stuck with using java.util.Date so switching everything to Joda is not an option in the short term.
Thanks.
java.util.Date does not store any time zone or DST-offset itself because it just stores the elapsed milliseconds since UNIX epoch, not counting leap seconds.
What you observe is rather the output of the toString()-method of j.u.Date which indeed is dependent on your local (JVM) time zone! So you have no error, only that j.u.Date has confusing behaviour.
Added: If you want to have a "correct" display of your Date-object then you need a format tool like SimpleDateFormat which you can use for any formatted output you wish.
I would like to know exactly how many months and days(possibly years) some older date is from today. Is there a method to do that?
I know how to get the difference of the months, I know how to get the difference in days. But I am unable to get the months and the days.
Ex:
old = '2013-03-04'
now = '2013-04-17'
so the result im looking for is something like 1 month(s) and 13* day(s)
*maybe its 12 im not every sure.
This can be done by using Period in JodaTime.
For example,
LocalDate old = new LocalDate(2013, 3, 4);
LocalDate now = new LocalDate(2013, 4, 17);
Period p = new Period(old, now, PeriodType.yearMonthDay());
To get the months, use p.getMonths(), to get the days p.getDays().
The result of the example is 1 month, 13 days.
Yes, see the documentation of intervals:
Intervals
An interval in Joda-Time represents an interval of time from one
instant to another instant. Both instants are fully specified instants
in the datetime continuum, complete with time zone.
Intervals are implemented as half-open, which is to say that the start
instant is inclusive but the end instant is exclusive. The end is
always greater than or equal to the start. Both end-points are
restricted to having the same chronology and the same time zone.
Two implementations are provided, Interval and MutableInterval, both
are specializations of ReadableInterval.