Search code examples
javajodatimejava.util.date

Good way for Java Date comparison without time


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.


Solution

  • 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);