I'm using openjdk version 1.8.0_112-release for development but will need to support previous JDK versions too (pre-Java-8) - so can't use java.time
.
I am writing a utitily class to calculate the date to see if a saved date is before the current date which means its expired.
However, I am not sure I have done this the correct way. I am using LocalDate
class to calculate the days. The expiration is counted starting from the date and time the user clicked save. That date will be saved and a check will be done against this saved date and time and the current date and time i.e. when the user logs in.
Is this the best way to do it? I would like to keep to the LocalDate
class.
import org.threeten.bp.LocalDate;
public final class Utilities {
private Utilities() {}
public static boolean hasDateExpired(int days, LocalDate savedDate, LocalDate currentDate) {
boolean hasExpired = false;
if(savedDate != null && currentDate != null) {
/* has expired if the saved date plus the specified days is still before the current date (today) */
if(savedDate.plusDays(days).isBefore(currentDate)) {
hasExpired = true;
}
}
return hasExpired;
}
}
I'm using the class like this:
private void showDialogToIndicateLicenseHasExpired() {
final LocalDate currentDate = LocalDate.now();
final int DAYS_TO_EXPIRE = 3;
final LocalDate savedDate = user.getSavedDate();
if(hasDateExpired(DAYS_TO_EXPIRE, savedDate, currentDate)) {
/* License has expired, warn the user */
}
}
I am looking a solution that will take in account time zones. If a license was set to expire in 3 days, and the user was to travel to a different time zone. i.e. they could be ahead or behind based on hours. The license should still expire.
You can use ChronoUnit.DAYS
(in org.threeten.bp.temporal
package, or in java.time.temporal
if you use java 8 native classes) to calculate the number of days between the 2 LocalDate
objects:
if (savedDate != null && currentDate != null) {
if (ChronoUnit.DAYS.between(savedDate, currentDate) > days) {
hasExpired = true;
}
}
For this test, I'm using threetenbp version 1.3.4
As you want a solution that works even if the user is in a different timezone, you shouldn't use LocalDate
, because this class doesn't handle timezone issues.
I think the best solution is to use the Instant
class. It represents a single point in time, no matter in what timezone you are (at this moment, everybody in the world are in the same instant, although the local date and time might be different depending on where you are).
Actually Instant
is always in UTC Time - a standard indepedent of timezone, so very suitable to your case (as you want a calculation independent of what timezone the user is in).
So both your savedDate
and currentDate
must be Instant
's, and you should calculate the difference between them.
Now, a subtle detail. You want the expiration to happen after 3 days. For the code I did, I'm making the following assumptions:
The second assumption is important for the way I implemented the solution. I'm considering the following cases:
currentDate
is less than 72 hours after savedDate
- not expiredcurrentDate
is exactly 72 hours after savedDate
- not expired (or expired? see comments below)currentDate
is more than 72 hours after savedDate
(even by a fraction of a second) - expiredThe Instant
class has nanosecond precision, so in case 3 I'm considering that it's expired even if it's 1 nanosecond after 72 hours:
import org.threeten.bp.Instant;
import org.threeten.bp.temporal.ChronoUnit;
public static boolean hasDateExpired(int days, Instant savedDate, Instant currentDate) {
boolean hasExpired = false;
if (savedDate != null && currentDate != null) {
// nanoseconds between savedDate and currentDate > number of nanoseconds in the specified number of days
if (ChronoUnit.NANOS.between(savedDate, currentDate) > days * ChronoUnit.DAYS.getDuration().toNanos()) {
hasExpired = true;
}
}
return hasExpired;
}
Note that I used ChronoUnit.DAYS.getDuration().toNanos()
to get the number of nanoseconds in a day. It's better to rely on the API instead of having hardcoded big error-prone numbers.
I've made some tests, using dates in the same timezone and in different ones.
I used ZonedDateTime.toInstant()
method to convert the dates to Instant
:
import org.threeten.bp.ZoneId;
import org.threeten.bp.ZonedDateTime;
// testing in the same timezone
ZoneId sp = ZoneId.of("America/Sao_Paulo");
// savedDate: 22/05/2017 10:00 in Sao Paulo timezone
Instant savedDate = ZonedDateTime.of(2017, 5, 22, 10, 0, 0, 0, sp).toInstant();
// 1 nanosecond before expires (returns false - not expired)
System.out.println(hasDateExpired(3, savedDate, ZonedDateTime.of(2017, 5, 25, 9, 59, 59, 999999999, sp).toInstant()));
// exactly 3 days (72 hours) after saved date (returns false - not expired)
System.out.println(hasDateExpired(3, savedDate, ZonedDateTime.of(2017, 5, 25, 10, 0, 0, 0, sp).toInstant()));
// 1 nanosecond after 3 days (72 hours) (returns true - expired)
System.out.println(hasDateExpired(3, savedDate, ZonedDateTime.of(2017, 5, 25, 10, 0, 0, 1, sp).toInstant()));
// testing in different timezones (savedDate in Sao Paulo, currentDate in London)
ZoneId london = ZoneId.of("Europe/London");
// In 22/05/2017, London will be in summer time, so 10h in Sao Paulo = 14h in London
// 1 nanosecond before expires (returns false - not expired)
System.out.println(hasDateExpired(3, savedDate, ZonedDateTime.of(2017, 5, 25, 13, 59, 59, 999999999, london).toInstant()));
// exactly 3 days (72 hours) after saved date (returns false - not expired)
System.out.println(hasDateExpired(3, savedDate, ZonedDateTime.of(2017, 5, 25, 14, 0, 0, 0, london).toInstant()));
// 1 nanosecond after 3 days (72 hours) (returns true - expired)
System.out.println(hasDateExpired(3, savedDate, ZonedDateTime.of(2017, 5, 25, 14, 0, 0, 1, london).toInstant()));
PS: for case 2 (currentDate
is exactly 72 hours after savedDate - not expired) - if you want this to be expired, just change the if
above to use >=
instead of >
:
if (ChronoUnit.NANOS.between(savedDate, currentDate) >= days * ChronoUnit.DAYS.getDuration().toNanos()) {
... // it returns "true" for case 2
}
If you don't want nanosecond precision and just want to compare the days between the dates, you can do as in @Ole V.V's answer. I believe our answers are very similar (and I suspect that the codes are equivalent, although I'm not sure), but I haven't tested enough cases to check if they differ in any particular situation.