23

I have three dates in Java: a, b, c. Any or all of these dates may be null. What's the most efficient way of determining the earliest date among a,b,c, without having a massive if-else block?

Ace
  • 821
  • 3
  • 16
  • 37

13 Answers13

32

There's no getting around null checking, but with some refactoring you can make it painless.

Create a method that safely compares two dates:

/**
 * Safely compare two dates, null being considered "greater" than a Date
 * @return the earliest of the two
 */
public static Date least(Date a, Date b) {
    return a == null ? b : (b == null ? a : (a.before(b) ? a : b));
}

then combine calls to that:

Date earliest = least(least(a, b), c);

Actually, you can make this a generic method for any Comparable:

public static <T extends Comparable<T>> T least(T a, T b) {
    return a == null ? b : (b == null ? a : (a.compareTo(b) < 0 ? a : b));
}
Bohemian
  • 412,405
  • 93
  • 575
  • 722
21

Java 8+ oneliner. To make it safe, null check is added. Pass any number of dates.

public static Date min(Date... dates) {
    return Arrays.stream(dates).filter(Objects::nonNull).min(Date::compareTo).orElse(null);
}

Not null safe, but much shorter:

public static Date min(Date... dates) {
    return Collections.min(Arrays.asList(dates));
}

Not null safe without a new method:

Collections.min(Arrays.asList(date1, date2));
pavelety
  • 746
  • 6
  • 8
  • 2
    If you are using Java or higher you should not use `Date`. That class is poorly designed and long outdated. Depending on exact requirements use `Instant` or another class from [java.time, the modern Java date and time API](https://docs.oracle.com/javase/tutorial/datetime/). Even on Java 6 or 7 you may consider the same using [ThreeTen Backport](https://www.threeten.org/threetenbp/). – Ole V.V. Dec 19 '19 at 19:21
  • 1
    * using Java **8** or higher – Ole V.V. Dec 21 '19 at 14:21
  • @OleV.V. I prefer to use `Date` in most cases, because there is no issues to use it in DTO with Spring MVC (`spring.jackson.serialization.write-dates-as-timestamps=true`), no timezone problems between JavaScript (Angular, React, etc) and Java using REST. In JS just use `new Date(timeFromServer)` without any parsers. You don't ever think about a timezone when `Date` comes from frontend and goes with Hibernate (also `Date`) to database and vice versa. Also it perfectly works with Swagger (OpenAPI). It's very well supported by all new and old frameworks. New time API is perfect for time math. – pavelety Apr 05 '21 at 07:51
  • `Date` has caused innumerable time zone errors over the years. Hibernate 5 supports java.time just fine. [jackson-modules-java8](https://github.com/FasterXML/jackson-modules-java8) too. Tastes differ. – Ole V.V. Apr 05 '21 at 10:02
8

Well, 'efficient' has some different meanings, but I don't think there will be an efficiency problem with comparing three dates. In fact, it's really cheap. You can try this approach:

SortedSet<Date> dates = new TreeSet<Date>();
dates.add(date1);
dates.add(date2);
// ...
dates.add(dateN);
Date earliest = dates.first();

Or, maybe more elegant:

for (Date date : someDates) {
   if (date != null) {
      dates.add(date);
   }
}
Date earliest = dates.first();
FoGh
  • 58
  • 10
Grzegorz Olszewski
  • 1,380
  • 10
  • 12
7

When Apache Commons is available, you might use ObjectUtils.min:

Date earliest = ObjectUtils.min(a, b, c);
n0nick
  • 1,067
  • 10
  • 21
6

Some Java 8 methods using streams. The first will filter nulls before comparing, the second will put them at the end of the list.

Date minDate = Arrays.asList(date1, date2, etc).stream()
     .filter(Objects::nonNull).min(Date::compareTo).get()

or

Date minDate = Arrays.asList(date1, date2, etc).stream()
    .sorted((a, b) -> {
        //some kind of custom sort.
        if(a == null && b == null) return 0;
        if(a == null) return 1;
        if(b == null) return -1;
        return a.compareTo(b);
    }).findFirst().get()
Danny
  • 7,368
  • 8
  • 46
  • 70
6

Meanwhile you are probably working with Java 8 LocalDate (instead of the old java.util.Date)

For Java 8 LocalDate use this method to get the earliest of a list of dates:

import java.time.LocalDate;

public static LocalDate earliestDate(LocalDate... dates) {
    return
        Arrays
        .stream(dates)
        .filter(Objects::nonNull)
        .min(LocalDate::compareTo)
        .orElse(null);
}
yglodt
  • 13,807
  • 14
  • 91
  • 127
  • 1
    My answer specifically covers Java 8 LocalDate which none of the others does. – yglodt Dec 28 '20 at 08:37
  • I hadn’t noticed. Very well, I changed my down-vote to an up-vote, and removed my Comment. I suggest you add some prose explaining your use of `LocalDate`, the benefits, and how your Answer is different and better. – Basil Bourque Dec 28 '20 at 18:19
2

Use the java Date object http://docs.oracle.com/javase/6/docs/api/java/util/Date.html

You can use the before() and after() functions of these objects then

Cristiano
  • 2,839
  • 8
  • 25
  • 35
2

using before and after :

  /**
     * find Min Dates
     * @param date1
     * @param date2
     * @return
     */
    public static Date minDate(Date date1, Date date2) {
        // if date1 before date2 then return date1 else return date2
        return date1.before(date2) ? date1 : date2;
    }
     /**
     * find Max Dates
     * @param date1
     * @param date2
     * @return
     */
    public static Date maxDate(Date date1, Date date2) {
        // if date1 after date2 then return date1 else return date2
        return date1.after(date2) ? date1 : date2;
    }
Mortada Jafar
  • 3,529
  • 1
  • 18
  • 33
2

Using stream:

Date min = Stream.of(date1, date2, etc)
  .filter(Objects::nonNull)
  .min(Date::compareTo)
  .orElse(null);

If your array does not contain NULL values you can use Ordering

Ordering.natural().min(date1, date2, etc);
michal.jakubeczy
  • 8,221
  • 1
  • 59
  • 63
1

Another way is to use java.util.Collections.min(collection):

Returns: the minimum element of the given collection, according to the natural ordering of its elements.

public static Date getEarliestDate(List<Date> dates) {

    if (dates == null || dates.isEmpty())
        return null;

    dates.removeIf(Objects::isNull);

    return dates.isEmpty() ? null : Collections.min(dates);
}
Justinas Jakavonis
  • 8,220
  • 10
  • 69
  • 114
0

You can use

date1.compareTo(anotherDate)

Returns:

the value 0 if the argument Date is equal to this Date; a value less than 0 if this Date is before the Date argument; and a value greater than 0 if this Date is after the Date argument.

Throws:

NullPointerException - 

if anotherDate is null.

NullPointerException
  • 3,732
  • 5
  • 28
  • 62
0

just another version as an idea and for fun

new Date(Math.min(a != null ? a.getTime() : Long.MAX_VALUE
, Math.min(b != null ? b.getTime() : Long.MAX_VALUE
, c != null ? c.getTime() : Long.MAX_VALUE)))
Vitaly
  • 2,760
  • 2
  • 19
  • 26
-1
List.of(date1, date2)
                .stream()
                .min(Date::compareTo)
                .get();
Maksim Sirotkin
  • 473
  • 2
  • 5
  • 14
  • sorry for my fault, really did not read a question. However, it is simple solution, which I wanted to share that could be used in private method when you sure all dates are not null – Maksim Sirotkin Jan 21 '22 at 10:43