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.
Related
There has been changes in Java Date & Time API Since Java 9.
LocalDateTime now has microseconds precision.
Java 9 has a fresh implementation of java.time.Clock capable of capturing the current moment in resolution finer than milliseconds (three digits of decimal fraction).
We get the time in microseconds from our backend service.
System.currentTimeMillis > 1565245051795 > 2019-08-08T06:17:31.795
Service.getTime > 1565245051795306 > 2019-08-08T06:17:31.795306
In order to construct a LocalDateTime to be used in our application, we do
long timeMicros = service.getTime();
long timeMillis = timeMicros / 1000;
LocalDateTime ldt = Instant.ofEpochMilli(timeMillis).atZone(ZoneId.systemDefault()).toLocalDateTime();
For querying the service we need time microseconds again, then we do
long timeMillis = dateTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
long timeMicros = timeMillis * 1000;
The problem is we do not get back the time microseconds precision.
Is it possible to create an Instant with microsecond precision?
We are now using Java 11. I noticed this change when one of our JUnit tests failed because of the increased microsecond precision.
For the JUnit test I found a workaround:
private static final LocalDateTime START = LocalDateTime.now().truncatedTo(ChronoUnit.MILLIS);
I'm not sure if this is a workaround or an actual solution, but adding the last three microseconds digits from the timestamp seems to work.
long micros = 306L; //TODO get the last three digits from the timeMicros
ldt.plus(micros, ChronoUnit.MICROS));
long timeMicros = 1_565_245_051_795_306L;
Instant i = Instant.EPOCH.plus(timeMicros, ChronoUnit.MICROS);
System.out.println(i);
Output is:
2019-08-08T06:17:31.795306Z
Edit: Rather than dividing and multiplying to convert microseconds to milliseconds and/or seconds I preferred to use the built-in support for microseconds. Also when explicitly adding them to the epoch feels a little hand-held.
You already know how to convert Instant to LocalDateTime, you’ve shown it in the question, so I am not repeating that.
Edit:
Do you have a solution to get the timeMicros back from the Instant?
There are a couple of options. This way the calculation is not so complicated, so I might do:
long microsBack = TimeUnit.SECONDS.toMicros(i.getEpochSecond())
+ TimeUnit.NANOSECONDS.toMicros(i.getNano());
System.out.println(microsBack);
1565245051795306
To be more in style with the first conversion you may prefer the slightly shorter:
long microsBack = ChronoUnit.MICROS.between(Instant.EPOCH, i);
Edit: Possibly nit-picking, but also to avoid anyone misunderstanding: LocalDateTime has had nanosecond precision always. Only the now method had millisecond precision on Java 8. I read somewhere that from Java 9 the precision varies with the platform, but you are right, microsecond precision seems typical.
There has been changes in Java Date & Time API Since Java 9.
LocalDateTime now has microseconds precision.
Java 9 has a fresh implementation of java.time.Clock capable of capturing the current moment in resolution finer than milliseconds (three digits of decimal fraction).
We get the time in microseconds from our backend service.
System.currentTimeMillis > 1565245051795 > 2019-08-08T06:17:31.795
Service.getTime > 1565245051795306 > 2019-08-08T06:17:31.795306
In order to construct a LocalDateTime to be used in our application, we do
long timeMicros = service.getTime();
long timeMillis = timeMicros / 1000;
LocalDateTime ldt = Instant.ofEpochMilli(timeMillis).atZone(ZoneId.systemDefault()).toLocalDateTime();
For querying the service we need time microseconds again, then we do
long timeMillis = dateTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
long timeMicros = timeMillis * 1000;
The problem is we do not get back the time microseconds precision.
Is it possible to create an Instant with microsecond precision?
We are now using Java 11. I noticed this change when one of our JUnit tests failed because of the increased microsecond precision.
For the JUnit test I found a workaround:
private static final LocalDateTime START = LocalDateTime.now().truncatedTo(ChronoUnit.MILLIS);
I'm not sure if this is a workaround or an actual solution, but adding the last three microseconds digits from the timestamp seems to work.
long micros = 306L; //TODO get the last three digits from the timeMicros
ldt.plus(micros, ChronoUnit.MICROS));
long timeMicros = 1_565_245_051_795_306L;
Instant i = Instant.EPOCH.plus(timeMicros, ChronoUnit.MICROS);
System.out.println(i);
Output is:
2019-08-08T06:17:31.795306Z
Edit: Rather than dividing and multiplying to convert microseconds to milliseconds and/or seconds I preferred to use the built-in support for microseconds. Also when explicitly adding them to the epoch feels a little hand-held.
You already know how to convert Instant to LocalDateTime, you’ve shown it in the question, so I am not repeating that.
Edit:
Do you have a solution to get the timeMicros back from the Instant?
There are a couple of options. This way the calculation is not so complicated, so I might do:
long microsBack = TimeUnit.SECONDS.toMicros(i.getEpochSecond())
+ TimeUnit.NANOSECONDS.toMicros(i.getNano());
System.out.println(microsBack);
1565245051795306
To be more in style with the first conversion you may prefer the slightly shorter:
long microsBack = ChronoUnit.MICROS.between(Instant.EPOCH, i);
Edit: Possibly nit-picking, but also to avoid anyone misunderstanding: LocalDateTime has had nanosecond precision always. Only the now method had millisecond precision on Java 8. I read somewhere that from Java 9 the precision varies with the platform, but you are right, microsecond precision seems typical.
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.
What is the fastest way to write LocalDateTime instance to file and then read it from file and convert it back to LocalDateTime object?
I used to save milliseconds and then convert it to Date object. It looked pretty fast but now I am dealing with Java 8's LocalDateTime and it's not clear what would be most efficient way to save and retrieve it from file.
I don't think using DateTimeFormater is a good idea as it requires more resources to convert it to String and then parse the String.
Time zone is not relevant.
If you want to save the milliseconds and timezone is not important, you can use the java.time.Instant class - with only the LocalDateTime there's no way to get the milliseconds, because this class has no timezone/offset information.
// get the current date
Instant instant = Instant.now();
// get milliseconds (equivalent to java.util.Date.getTime())
long millis = instant.toEpochMilli();
// get Instant from milliseconds
Instant instant = Instant.ofEpochMilli(millis);
If you have a LocalDateTime, though, you can easily convert it to an Instant:
LocalDateTime d = LocalDateTime.now();
Instant instant = d.atOffset(ZoneOffset.UTC).toInstant();
This code obviously assumes that the values in the LocalDateTime correspond to an UTC date and time. To convert the Instant back to a LocalDateTime:
LocalDateTime d = LocalDateTime.ofInstant(instant, ZoneOffset.UTC);
PS: have you measured your system's performance to know if "fast" is really an issue? Anyway, I'm doing things the "standard" way (the most straightforward way based on what the API offers), is that what you want?
Maybe you can think that creating an Instant as an "intermediary" object makes things less "fast" (but you'll need to measure that anyway). If that's the case, you can get the millis from the LocalDateTime directly (assuming that it corresponds to a date and time in UTC):
// get the current date
LocalDateTime d = LocalDateTime.now();
// get milliseconds value
long millis = d.toEpochSecond(ZoneOffset.UTC) * 1000 + d.get(ChronoField.MILLI_OF_SECOND);
// get LocalDateTime from millis
LocalDateTime d = LocalDateTime.ofEpochSecond(millis / 1000, (int) (millis % 1000) * 1000000, ZoneOffset.UTC);
It's important to note that java.time classes have nanoseconds precision, so getting the milliseconds makes you lose this precision.
If you don't want to lose the nanoseconds precision and don't necessarily need to work with the millis value, you can store 2 different numbers (epoch day and nano of day):
// get the current date
LocalDateTime d = LocalDateTime.now();
// get values from LocalDateTime
long epochDay = d.toLocalDate().toEpochDay();
long nanoOfDay = d.toLocalTime().toNanoOfDay();
// save both values to file
// retrieve the LocalDateTime from the values
LocalDateTime d = LocalDateTime.of(LocalDate.ofEpochDay(epochDay), LocalTime.ofNanoOfDay(nanoOfDay));
This doesn't require the conversion to UTC, but it requires 2 numbers instead of one. You might think that creating a LocalDate and a LocalTime makes things slower, but these 2 objects are always created internally by LocalDateTime (in all cases).
Not sure, though, if all this math is "faster" enough than using an Instant. It's a matter of testing to see which one is best for your case.
But for me, the most "efficient" in terms of clarity and code-easy-to-maintain is using an Instant (or using the last approach of epoch day and nano of day). And unless you're dealing with millions of records, I'm not sure if that'll really be a performance issue.
I've made a simple test (run each case above 10 million times), and the last approach (using epoch day and nano of day) seems to be the fastest. But the difference is less than 1 second. Only by running 2 billion times I've got a 20-seconds difference, so if you're dealing with this many records, maybe it's worth it.
Regarding other resources (memory usage, CPU, I/O), I didn't check. But anyway, performance issues are very specific to each environment: depending on how the system is designed, how the system's parts/modules/components interact with each other, and many other factors, you can have different bottlenecks in each situation.
In the end, you'll have to test each approach and see which one performs best in your system. Or you can conclude that it doesn't make a significant difference (and for cases with less than a couple of millions records, maybe it doesn't - but you'll only know after benchmarking it).
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.