How do I determine if a date falls between two dates?

Java provides different ways to determine if a certain date falls within a specified range. In this article, we’ll look at examples using the old java.util.Date and java.util.Calendar classes, as well as the newer Java Date Time API.

Using java.util.Date and java.util.Calendar

Before Java 8, you’d have to use Date or Calendar to work with dates:

package org.kodejava.datetime;

import java.util.Calendar;

public class CheckDateRange {
    public static void main(String[] args) {
        Calendar start = Calendar.getInstance();
        start.set(2024, Calendar.JANUARY, 1);
        Calendar end = Calendar.getInstance();
        end.set(2024, Calendar.DECEMBER, 31);
        Calendar target = Calendar.getInstance();
        target.set(2024, Calendar.JUNE, 15);

        if ((target.after(start) || target.equals(start)) &&
            (target.before(end) || target.equals(end))) {
            System.out.println("The date is within the range.");
        } else {
            System.out.println("The date is not within the range.");
        }
    }
}

The disadvantage with this approach is the excessive verbosity and error-prone copy-pasting necessary for setting up the Calendar instances.

The Java 8 Way – Using java.time.LocalDate

Java 8 introduced the new Java Date Time API, which replaced the inconsistent Date and Calendar classes with the more intuitive LocalDate, LocalTime, LocalDateTime, and ZonedDateTime. Here’s the same task performed using LocalDate:

package org.kodejava.datetime;

import java.time.LocalDate;

public class AnotherCheckDateRange {
    public static void main(String[] args) {
        LocalDate startDate = LocalDate.of(2024, 1, 1);
        LocalDate endDate = LocalDate.of(2024, 12, 31);
        LocalDate targetDate = LocalDate.of(2024, 6, 15);

        if ((!targetDate.isBefore(startDate)) && (!targetDate.isAfter(endDate))) {
            System.out.println("The date is within the range.");
        } else {
            System.out.println("The date is not within the range.");
        }
    }
}

In this code, startDate and endDate define the range of dates. The targetDate is the date you want to check.

The isBefore() method returns true if the targetDate is before the startDate, and the isAfter() method returns true if the targetDate is after the endDate. So, if targetDate is not before the startDate and not after the endDate, it means that the targetDate is between startDate and endDate (inclusive). If the targetDate is exactly the same as startDate or endDate, this condition will also return true.

This simplified API requires significantly less code and eliminates a number of potential bugs and inconsistencies.

Conclusion

The older java.util.Date and java.util.Calendar facilities for working with dates are widely considered difficult to use and error-prone. While they work for simple tasks, the newer Java Date Time API is recommended for all new applications due to its simplicity, consistency, and flexibility. It aligns with ISO standards and covers a comprehensive range of use-cases needed for date-time calculations. Migrating from older APIs to Java 8 Date Time API is likely advantageous for most projects.

How do I convert LocalDate to ZonedDateTime?

You can use the atStartOfDay() method from LocalDate class to convert a LocalDate into a LocalDateTime. Then, you need to convert LocalDateTime to a ZonedDateTime using the atZone() method.

Here is an example:

package org.kodejava.datetime;

import java.time.*;

public class LocalDateToZonedDateTimeExample {
    public static void main(String[] args) {
        // Create a LocalDate
        LocalDate date = LocalDate.of(2023, Month.JULY, 9);
        System.out.println("LocalDate: " + date);

        // Convert LocalDate to LocalDateTime
        LocalDateTime dateTime = date.atStartOfDay();
        System.out.println("LocalDateTime: " + dateTime);

        // Convert LocalDateTime to ZonedDateTime
        ZonedDateTime zonedDateTime = dateTime.atZone(ZoneId.systemDefault());
        System.out.println("ZonedDateTime: " + zonedDateTime);
    }
}

Output:

LocalDate: 2023-07-09
LocalDateTime: 2023-07-09T00:00
ZonedDateTime: 2023-07-09T00:00+08:00[Asia/Makassar]

In this example, we’re creating a LocalDate for July 9, 2023. Then we’re converting it to a LocalDateTime, and then to a ZonedDateTime. The atStartOfDay() method returns a LocalDateTime set to the start of the day (00:00) on the date of this LocalDate. The atZone() method then takes the ZoneId and returns a ZonedDateTime representing the start of the day in that timezone.

The ZoneId.systemDefault() returns the system default time zone. If you want to convert it to a specific time zone, you can specify the timezone as a string, like this: ZoneId.of("America/New_York").

How do I convert datetime between time zones?

The ZonedDateTime class is part of the Java Date-Time Package (java.time.*), released in Java 8 to address the shortcomings of the old date-time classes such as java.util.Date, java.util.Calendar, and java.util.SimpleDateFormat.

Some of the key features are:

  • It represents a date-time with a timezone in the ISO-8601 calendar system, such as ‘2007-12-03T10:15:30+01:00 Europe/Paris’.
  • It provides a lot of methods to play with year, month, day, hour, minute, second and nanosecond fields of the datetime.
  • It’s an immutable class, which is good for multithreaded environments.
  • It provides a fluent interface, which allows method calls to be chained.

Using Java java.time package (which is part of Java 8 and later), you can convert dates between time zones like this:

package org.kodejava.datetime;

import java.time.*;

public class ZonedDateTimeExample {
    public static void main(String[] args) {

        // Create a ZonedDateTime instance for the current date/time
        // in the current timezone
        ZonedDateTime now = ZonedDateTime.now();

        // Create a ZonedDateTime instance for the current date/time
        // in a different timezone
        ZonedDateTime nowInJakarta = now.withZoneSameInstant(ZoneId.of("Asia/Jakarta"));

        // Print the current date/time in the current timezone
        System.out.println("Current date/time: " + now);

        // Print the current date/time in the different timezone
        System.out.println("Current date/time in Jakarta: " + nowInJakarta);
    }
}

Output:

Current date/time: 2024-01-20T21:33:31.236022700+08:00[Asia/Makassar]
Current date/time in Jakarta: 2024-01-20T20:33:31.236022700+07:00[Asia/Jakarta]

The withZoneSameInstant method is used to adjust the date and time based on the timezone. It can be used to convert a datetime value to the datetime in another timezone.

This program will create a ZonedDateTime object representing the current date and time, and then create another ZonedDateTime object that represents the current date and time in Jakarta. Finally, it will print both dates to the console.

What is ZoneRules class of Java Date-Time API?

The ZoneRules class in Java’s Date-Time API is used to encapsulate the set of rules defining how the zone offset varies for a single time zone.

The information in this class is typically derived from the IANA Time Zone Database (TZDB). The rules model the data traditionally contained in the ‘zic’ compiled data files of information from the TZDB.

An instance of ZoneRules is obtained from a ZoneId using the ZoneId.getRules() method.

Here is a simple example of how to use the ZoneRules class:

package org.kodejava.datetime;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.zone.ZoneRules;

public class ZoneRulesExample {
    public static void main(String[] args) {
        // Get ZoneId for "Europe/Paris"
        ZoneId zoneId = ZoneId.of("Europe/Paris");
        System.out.println("ZoneId : " + zoneId);

        // Get ZoneRules associated with the ZoneId
        ZoneRules zoneRules = zoneId.getRules();
        System.out.println("ZoneRules : " + zoneRules);

        // Get the standard offset
        LocalDateTime localDateTime = LocalDateTime.now();
        ZoneOffset offset = zoneRules.getOffset(localDateTime);
        System.out.println("Offset for " + localDateTime + " is: " + offset);
    }
}

Here we are using LocalDateTime.now() to get the current time and the getOffset(LocalDateTime) method on ZoneRules to find the offset for that particular time. The API guarantees immutability and thread-safety of ZoneRules class.

This example will output:

  • The ZoneId which will be “Europe/Paris”
  • The ZoneRules for the “Europe/Paris” time zone
  • The ZoneOffset for the current LocalDateTime. This offset is the difference in time between the “Europe/Paris” time zone and UTC at the time provided.

Output:

ZoneId : Europe/Paris
ZoneRules : ZoneRules[currentStandardOffset=+01:00]
Offset for 2024-01-19T15:55:14.156977 is: +01:00

How do I use java.time.ZoneId class?

java.time.ZoneId is a class in Java’s Date-Time API used to represent a time zone identifier. This identifier is used to get a ZoneRules, which then can be used to convert between an Instant and a LocalDateTime.

Here is how you can use the ZoneId class in a simple way:

package org.kodejava.datetime;

import java.time.ZoneId;
import java.time.ZonedDateTime;

public class ZoneIdExample {
    public static void main(String[] args) {
        // Get the system default ZoneId
        ZoneId defaultZoneId = ZoneId.systemDefault();
        System.out.println("System Default TimeZone : " + defaultZoneId);

        // Get ZoneId instance using the specified zone ID as a string
        ZoneId londonZoneId = ZoneId.of("Europe/London");
        System.out.println("London ZoneId : " + londonZoneId);

        // Get ZonedDateTime using ZoneId
        ZonedDateTime zonedDateTimeInLondon = ZonedDateTime.now(londonZoneId);
        System.out.println("Current date and time in London: " + zonedDateTimeInLondon);
    }
}

Output:

System Default TimeZone : Asia/Makassar
London ZoneId : Europe/London
Current date and time in London: 2024-01-19T06:43:23.076855Z[Europe/London]

In the above code:

  • ZoneId.systemDefault() is used to get the system default ZoneId.
  • ZoneId.of(String zoneId) is used to get a ZoneId instance using the specified zone ID as a string. You can get all available zone IDs by calling ZoneId.getAvailableZoneIds().
  • ZonedDateTime.now(ZoneId zoneId) is used to get the current date and time in the specified time zone.

Please note that the ZoneId is immutable and thread-safe, it ensures the class can be used safely in multithreaded systems.