1

I have the following helper method

    private Date getDate(int year, int month, int day) {
        Calendar cal = Calendar.getInstance();
        cal.set(Calendar.YEAR, year);
        cal.set(Calendar.MONTH, month);
        cal.set(Calendar.DAY_OF_MONTH, day);
        cal.setTimeZone(TimeZone.getTimeZone("GMT"));
        return cal.getTime();
    }

Someone in my company implemented this method:

    public static int getDaysBetween(Date d1, Date d2) {
        return (int)((d2.getTime() - d1.getTime()) / (1000 * 60 * 60 * 24));
    }

Can someone explain to me why the answer to the following is 28?

        System.err.println(DateUtils.getDaysBetween(
                getDate(2023, 01, 01), getDate(2023, 02, 01)));
Tinker
  • 4,165
  • 6
  • 33
  • 72
  • 1
    1. Don't use `java.util.*` based time/date APIs; 2. Don't do "maths" based calculations on date/time values, as it's not as simple as that, instead, make use of the `java.time.*` APIs instead – MadProgrammer Apr 03 '23 at 00:44
  • 1
    For example, `Duration.between(LocalDateTime.of(2023, Month.JANUARY, 1, 0, 0), LocalDateTime.of(2023, Month.FEBRUARY, 1, 23, 59)).toDays()` prints `31` – MadProgrammer Apr 03 '23 at 00:48
  • 1
    You can also make use of `ChronoUnit.DAYS.between(LocalDate.of(2023, Month.JANUARY, 1), LocalDate.of(2023, Month.FEBRUARY, 1))` which is probably easier and closer to what you want – MadProgrammer Apr 03 '23 at 00:53
  • 1
    That `getDaysBetween` method looks like a rich source of confusing problems. – Dawood ibn Kareem Apr 03 '23 at 00:54
  • 1
    Your colleague’s `getDaysBetween` method is not only using the outdated `Date` class that no none should use, it is also using the wrong approach assuming that a day is always 24 hours. A day can sometimes be 23 hours, 25 hours or some other amounts. Because of summer time (DST) and other time anomalies. That method will occassionally give wrong results. – Ole V.V. Apr 03 '23 at 02:15
  • Does this answer your question? [Why is January month 0 in Java Calendar?](https://stackoverflow.com/questions/344380/why-is-january-month-0-in-java-calendar) – Ole V.V. Apr 03 '23 at 02:27
  • 1
    Minor points: 1. Your colleague should not cast `long` to `int` without checking for overflow. They should use `Math.toIntExact()`. 2. While using `01` and `02` for January and February may look nice (and seduce you into thinking that its right), you will be surprised when you try `08` for August or `09` for September and your code will not compile. A leading 0 means an octal number, so leave those zeroes out. – Ole V.V. Apr 03 '23 at 02:37

1 Answers1

2

tl;dr

If you really want a count of seconds.

Duration
.between(
    LocalDate
    .of( y1 , m1 , d1 )               // Returns a `LocalDate` object.
    .atStartOfDay( ZoneOffset.UTC )   // Returns a `ZonedDateTime` object.
    .toInstant() ,                    // Returns a `Instant` object.
    LocalDate
    .of( y2 , m2 , d2 ) 
    .atStartOfDay( ZoneOffset.UTC )
    .toInstant() ,
)                                     // Returns a `Duration` object.
.toSeconds()                          // Returns a `long` primitive value.

But if you want to count the number of calendar days between dates, you are working much too hard.

long daysElapsed = 
    ChronoUnit
    .DAYS
    .between(
        LocalDate.of( 2023 , 1 , 1 ) , 
        LocalDate.of( 2023 , 2 , 1 )
    )
;

Details

Avoid legacy date-time classes

The Answer by Zedki is technically correct but uses terribly flawed date-time classes that were years ago supplanted by the modern java.time classes defined by JSR 310.

One of the many flaws in the Calendar class is that it counts the month number by zero-based index counting. Silly, and confusing: January is 0, December is 11.

In contrast, the java.time classes use sane counting. Months run 1-12 for January-December.

java.time

The java.util.Date class was replaced by java.time.Instant, both representing a moment as seen in UTC, that is, with an offset from UTC of zero hours-minutes-seconds.

Your inputs of year-month-day can be replaced with a java.time.LocalDate object. That class represents a date-only value, without time-of-day, and without time zone or offset.

Your method name getDate is not very descriptive. Apparently you want to determine the first moment of the day on the specified date as seen in UTC (an offset of zero). So we can use the descriptive name firstMomentOfTheDayInUtc.

To determine the first moment of the day, always let java.time do the work. Never assume the day starts at 00:00. Some days on some dates in some time zones start at another time, such as 01:00. In UTC, we do know that every day starts at 00:00 by definition, but still we can let java.time figure that out. We end up with a ZonedDateTime object, representing a moment as seen through the wall-clock time of a particular time zone.

The Instant class is the basic building block of java.time. So from our ZonedDateTime object we extract an Instant, and return the object from our method.

private Instant firstMomentOfTheDayInUtc ( LocalDate localDate ) 
{
    ZonedDateTime zdt = localDate.atStartOfDay( ZoneOffset.UTC ) ;
    Instant instant = zdt.toInstant() ;
    return instant ;
}

Then you want to get the elapsed time between two of these Instant objects as a count of whole seconds. No need for your method getDaysBetween, as the code is so simple and brief in java.time.

The Duration class represents a span-of-time not attached to the timeline.

Duration duration = Duration.between( instantX , instantY ) ;

Then interrogate for a total number of seconds.

long seconds = duration.toSeconds() ;

But seconds is not needed to address your ultimate goal. If you merely want to know the number of elapsed calendar days, use the enum object ChronoUnit.DAYS and its between method.

long daysElapsed = 
    ChronoUnit.DAYS.between(
        LocalDate.of( 2023 , 1 , 1 ) , 
        LocalDate.of( 2023 , 2 , 1 )
    );

Octal literals

Your code:

getDate(2023, 01, 01)

As commented by Ole V.V., be careful with your numeric literals. Leading with a zero digit marks the literal as an octal (base 8) number rather than a decimal (base 10) number.

  • 01 through 07 you can get away with. Those octals are the same as decimals.
  • 08 & 09 are nonsense, and won’t compile.
  • 010 is 8, 011 is 9.
Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154