1

There is requirement to see if some date (ex: expiry date) is greater than or equal to today. Presently JODA time library has been used to achieve this simple comparison. Even some post are recommending that like this.

But recently found some problem with timezones. Date exists in PST and when converted to LocalDate following conversion comes false at 5:00 pm PST, when it should be true -

LocalDate now = LocalDate.fromDateFields(new Date()); // Current date in PST
LocalDate expiryDate = LocalDate.fromDateFields(expiresOn); // expiresOn is java.util.Date
boolean notExpired = expiryDate.isEqual(now) || expiryDate.isAfter(now);

When looked closely, LocalDate expiryDate was using UTC chronology. So at 5:00pm PST, when variable expiryDate contains contains "2021-01-16", variable now becomes "2021-01-17"

enter image description here

Please recommend, what is the better-way to deal with this problem. I am trying to understand, what special advantages I might achieve by using joda time, because the same compassion can be done using SimpleDateFormatter.

Anuja Paradkar
  • 157
  • 1
  • 2
  • 13
  • 1
    Always treat times in UTC and adjust to the user's timezone. – maio290 Jan 17 '21 at 17:43
  • 1
    You have to decide what "today" means in your context. If "today" should be interpreted in the time zone that your code is running in, then you want to convert both times to the local time zone and then do the comparison of the date portion of that time. But if you want a comparison that that always gives the same answer no matter what time zone you're in, then you'd want to convert both times to whatever timezone you want to use to define "today". In any case, you need to convert both time values to a common time zone before comparing just their dates will make any sense... – CryptoFool Jan 17 '21 at 17:58
  • Once you understand the necessary logic and have decide how to define "today", then you should be able to code up the right solution using just about any time/date API. If you can assume a fairly recent (Java 8+) version of Java, then I would recommend that you use the newer date/time API that is built into Java rather than using JodaTime. I would stay away from the older Java API in any case. – CryptoFool Jan 17 '21 at 18:00
  • So the expiration should happen later in the Americas because, say, January 16 ends later there than in Asia and Europe, for example? – Ole V.V. Jan 17 '21 at 18:39
  • What do you mean by PST? Did you mean Pacific Time (PST or PDT depending on the time of year)? – Ole V.V. Jan 17 '21 at 19:42

2 Answers2

3

The date-time API of java.util and their formatting API, SimpleDateFormat are outdated and error-prone. It is recommended to stop using them completely and switch to the modern date-time API.

Learn about the modern date-time API from Trail: Date Time.

LocalDate uses JVM's timezone by default

Whenever timezone is involved, make sure to specify the same while creating an instance of LocalDate. A LocalDate uses JVM's timezone by default and you should never compare a LocalDate from one timezone to that of another without converting both of them in the same timezone (the recommended one is UTC). Same is the case with LocalDateTime. Instead of using LocalDate, you should do all processing with objects which have both date and time (e.g. LocalDateTime) and if required you can derive the LocalDate from them.

Also, the java.util.Date object simply represents the number of milliseconds since the standard base time known as "the epoch", namely January 1, 1970, 00:00:00 GMT (or UTC). When you print an object of java.util.Date, its toString method returns the date-time in the JVM's timezone, calculated from this milliseconds value.

Therefore, if you are deriving expiryDate from a java.util.Date object, it is essentially date-time in UTC.

You can convert now-in-PST and expiryDate into java.time.Instant and compare them. A java.time.Instant is an instantaneous point on the UTC time-line.

Demo using the modern date-time API:

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Calendar;
import java.util.Date;

public class Main {
    public static void main(String[] args) {
        LocalDateTime nowInPST = LocalDateTime.now(ZoneId.of("America/Los_Angeles"));
        System.out.println(nowInPST);

        // Convert it to date in UTC
        Instant nowInPSTConvertedToInstant = nowInPST.atZone(ZoneId.of("America/Los_Angeles"))
                                                .withZoneSameInstant(ZoneId.of("Etc/UTC"))
                                                .toInstant();

        // Some java.util.Date
        Calendar calendar = Calendar.getInstance();
        calendar.set(2020, 0, 10, 10, 10, 10);
        Date date = calendar.getTime();
        Instant expiry = date.toInstant();
        
        System.out.println(nowInPSTConvertedToInstant.isBefore(expiry));
    }
}

Output:

2021-01-17T10:58:38.490041
false

Note: Check the following notice at the Home Page of Joda-Time

Joda-Time is the de facto standard date and time library for Java prior to Java SE 8. Users are now asked to migrate to java.time (JSR-310).

Simplify your expression

The following statement

boolean notExpired = expiryDate.isEqual(now) || expiryDate.isAfter(now);

can be simplified as

boolean notExpired = !expiryDate.isBefore(now);
Arvind Kumar Avinash
  • 71,965
  • 6
  • 74
  • 110
1

You should consider two APIs:

  1. Joda-Time that you have been using until now is a good library, but in maintenance mode.
  2. The chief developer of Joda-Time, Stephen Colebourne, went on to develop java.time, the modern Java date and time API, drawing on lessons from good and not so good experiences from Joda-Time.

It’s not perfectly clear from your question. I am assuming that expiration has been recorded in UTC and appears to be one day early because it is looked at in Pacific Time. So I am showing you how to keep everything in UTC so comparisons make sense and are accurate.

Joda-Time

    System.setProperty("user.timezone", "America/Vancouver");
    
    Date expiresOn = new Date(1_610_841_600_000L); // Jan 17 UTC
    System.out.println(expiresOn);
    
    LocalDate now = LocalDate.now(DateTimeZone.UTC);
    System.out.println(now);
    LocalDate expiryDate = new DateTime(expiresOn, DateTimeZone.UTC).toLocalDate();
    System.out.println(expiryDate);
    
    boolean notExpired = expiryDate.isEqual(now) || expiryDate.isAfter(now);
    System.out.println("Expired? " + (notExpired ? "No" : "Yes"));

Output when running now:

Sat Jan 16 16:00:00 PST 2021
2021-01-17
2021-01-17
Expired? No

The Joda-Time home page says:

Note that Joda-Time is considered to be a largely “finished” project. No major enhancements are planned. If using Java SE 8, please migrate to java.time (JSR-310).

java.time

    LocalDate now = LocalDate.now(ZoneOffset.UTC);
    System.out.println(now);
    LocalDate expiryDate = expiresOn.toInstant()
            .atOffset(ZoneOffset.UTC)
            .toLocalDate();
    System.out.println(expiryDate);
    
    boolean notExpired = expiryDate.isEqual(now) || expiryDate.isAfter(now);
    System.out.println("Expired? " + (notExpired ? "No" : "Yes"));
2021-01-17
2021-01-17
Expired? No

A note on taste

My taste is for avoiding unnecessary negations in variable names (and elsewhere). I’d find it simpler to do:

    boolean expired = expiryDate.isBefore(now);
    System.out.println("Expired? " + expired);

Expired? false

Links

Ole V.V.
  • 81,772
  • 15
  • 137
  • 161
  • I applied your suggestion of java.time, but still wondering, why joda time might be giving wrong result. Thought to check with you, if you know. "LocalDate now = LocalDate.fromDateFields(new Date()); " the value of now is nextday in UTC and not in PST if I try at 4:00 PM PST. – Anuja Paradkar Jan 18 '21 at 18:03