1

I mentioned that one of the method in the production project work wrong with dates, but i can't just replace it, because it is in production for a long time. I've created a new method, that works correct, but i can't figure out why the first method work wrong.

Old method (that works wrong):

public static Integer getNumberOfDays(Date startDate, Date endDate) {
    TimeZone.setDefault((TimeZone.getTimeZone("Europe/Moscow")));

    startDate.setHours(00);
    startDate.setMinutes(00);
    startDate.setSeconds(00);

    endDate.setHours(23);
    endDate.setMinutes(59);
    endDate.setSeconds(59);

    Calendar cal1 = Calendar.getInstance();
    cal1.setTime(startDate);

    Calendar cal2 = Calendar.getInstance();
    cal2.setTime(endDate);

    Calendar date = (Calendar) cal1.clone();
    int daysBetween = 0;
    while (date.before(cal2)){
        date.add(Calendar.DAY_OF_MONTH, 1);
        daysBetween++;
    }
    return daysBetween;
}

New method:

public static Integer getNumberOfDaysSecondVersion(Date startDate, Date endDate) {
    long difference = startDate.getTime() - endDate.getTime();
    float daysBetween = (difference / (1000*60*60*24));
    return (int) daysBetween > 0 ? (int) daysBetween : 0;
}

Here is how i call both:

DateFormat formated = new SimpleDateFormat("yyyy-MM-dd");

    System.out.println(Calculation.getNumberOfDays(
            formated.parse("2018-06-14"),
            formated.parse("2018-06-06")
    ));

    System.out.println(Calculation.getNumberOfDaysSecondVersion(
            format.parse("2018-06-14"),
            format.parse("2018-06-06"))
    );

Output:

0
8

Please help.

Alexander
  • 51
  • 8
  • Calculation is a class with these methods – Alexander Jul 04 '18 at 07:49
  • Duplicate: see https://stackoverflow.com/questions/1555262/calculating-the-difference-between-two-java-date-instances for answers – Peter Walser Jul 04 '18 at 07:49
  • Possible duplicate of [Calculating the difference between two Java date instances](https://stackoverflow.com/questions/1555262/calculating-the-difference-between-two-java-date-instances) – Peter Walser Jul 04 '18 at 07:50
  • What is the output of both methods? – Devstr Jul 04 '18 at 07:50
  • 1
    What are the expected and actually observed results of each? I mean, *in which way* was the old method wrong? – Ole V.V. Jul 04 '18 at 07:51
  • It's not a duplicate. I already have a working method (second one). I just don't see what is wrong with the first method. As i mentioned, i can't just replace one with another. – Alexander Jul 04 '18 at 07:52
  • 1
    Same remark as Olve V.V. Moscow doesn't have DST anymore, but it did until recently, so in case you ever work with past dates... – kumesana Jul 04 '18 at 07:54
  • FYI, the terribly troublesome old date-time classes such as [`java.util.Date`](https://docs.oracle.com/javase/10/docs/api/java/util/Date.html), [`java.util.Calendar`](https://docs.oracle.com/javase/10/docs/api/java/util/Calendar.html), and `java.text.SimpleDateFormat` are now [legacy](https://en.wikipedia.org/wiki/Legacy_system), supplanted by the [*java.time*](https://docs.oracle.com/javase/10/docs/api/java/time/package-summary.html) classes built into Java 8 and later. See [*Tutorial* by Oracle](https://docs.oracle.com/javase/tutorial/datetime/TOC.html). – Basil Bourque Jul 04 '18 at 23:31

3 Answers3

3

Your old method is the correct one. When start date is after end date, it returns 0. This is the case in your call.

Your new method subtracts end date from start date, which is wrong, is should be the other way around. I also suspect that it will give surprises across transitions from and to summer time (DST). While Moscow currently doesn’t use summer time, it has done historically, at least until 2010, and may do again if politicians decide so.

That said, you should try if you can avoid the old and long outdated date and time classes DateFormat, SimpleDateFormat, Calendar, Date and TimeZone. Today we have so much better in java.time, the modern Java date and time API. Of course, in legacy code you have old-fashioned Date objects. When writing a new method, I recommend you convert those to the modern LocalDate and use ChronoUnit.DAYS.between().

ChronoUnit.DAYS.between(
    LocalDate.parse( "2018-06-14" ) ,
    LocalDate.parse( "2018-06-06" ) 
)

-8

Be aware that when the old method sets the default time zone, it affects all programs running in your JVM and may come as a nasty surprise to other parts of your program and to other programs.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
Ole V.V.
  • 81,772
  • 15
  • 137
  • 161
  • Alternatively, use [`java.time.Period`](https://docs.oracle.com/javase/10/docs/api/java/time/Period.html) class to represent the difference as a span-of-time unattached to the timeline: `Period.between( LocalDate.parse( "2018-06-14" ) , LocalDate.parse( "2018-06-06" ) ).toString()` → `P-8D`. Or, attached to the timeline: `org.threeten.extra.LocalDateRange.of( start , stop )` (but start cannot be later than stop). – Basil Bourque Jul 04 '18 at 23:38
2

Probably because startDate and endDate's timezones aren't affected by setting the default timezone, so that when you set Calendar times (in Moscow time) based on them, you're converting timezones, possibly turning 00:00:00 into the previous day 21:00:00 or something.

EDIT

Seeing your outputs, it became obvious... you're passing in a start date that is in the future compared to end date. The original method uses a loop that can only count up, while your new method takes the absolute value of the difference.

Andrew Cheong
  • 29,362
  • 15
  • 90
  • 145
  • I've just deleted this setting in the first method, but the result is the same. It is obvious that difference between 2018-06-14 and 2018-06-06 is 8 days, but the first method returnes 0 – Alexander Jul 04 '18 at 08:00
2

You used a very different algorithm for the two versions.

The old version keeps adding days to the start date until it is after the end date.

The new version subtracts the end date from the start date and divides it by the number of milliseconds there are in a day.

This means that for the first version to work, the start date must be before the end date, and for the second version to work, the start date must be after the end date. The parameters you gave the the first version has the start date after the end date, making it return 0.

To fix this, you can just reverse the two arguments:

System.out.println(getNumberOfDays(
        formated.parse("2018-06-06"),
        formated.parse("2018-06-14")
));

Or, check which date comes first before calculating the difference between them.

By the way, your first version seems to output one more than your second version. You seem to want a result of 8 days. This means that your first version has an off-by-1 error. You can fix this by subtracting 1 from the counted result.

Remember to always work with java.time whenever you can!

Sweeper
  • 213,210
  • 22
  • 193
  • 313