1

I am encountering some issues right now when I compare two dates in Java.

I want to check that a date is the same day on the next week that today.

First, I am doing this:

        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());

        Calendar todayCalendar = Calendar.getInstance();
        Date today = sdf.parse(sdf.format(todayCalendar.getTime()));

        Step step = actualStep.getStep();
        Date plannedDate = sdf.parse(sdf.format(actualStep.getPlannedDate()));

        long diffInMillies = Math.abs(today.getTime() - plannedDate.getTime());
        long deltaDays = TimeUnit.DAYS.convert(diffInMillies, TimeUnit.MILLISECONDS) +1L;

When I check the different values, I get this:

Mon Mar 27 00:00:00 CEST 2023
Mon Mar 20 00:00:00 CET 2023
deltaDays: 7

So I do get a difference of 7 days this way. The thing is I do not get the same when I'm trying to do some unit tests:

        Calendar todayCalendar = Calendar.getInstance();
        Date today = todayCalendar.getTime();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        Integer day = 7;
        todayCalendar.add(Calendar.DAY_OF_WEEK, +day);
        Date expectedReminderDay = sdf.parse(sdf.format(todayCalendar.getTime()));

This time, I get:

Mon Mar 27 00:00:00 CEST 2023
Mon Apr 03 00:00:00 CEST 2023
deltaDays: 8

Does anyone know why, in one case, I get 8 days of difference and 7 in another while having a week apart ?

Thanks for reading !

  • 4
    FYI, those classes have been outdated for arguably more than 10 years. Use `java.time` – Michael Mar 27 '23 at 10:16
  • By the way, regarding `sdf.parse(sdf.format(actualStep.getPlannedDate()))`, first letting `SimpleDateFormat` format a `Date` and then parse its result, is pretty cumbersome. – MC Emperor Mar 27 '23 at 10:57
  • I strongly recommend that you don’t use `SimpleDateFormat`, `Calendar` and `Date`. Those classes were poorly designed, the first in particular notoriously troublesome, and all are long outdated. Use `LocalDate` and `ChronoUnit.DAYS`, both from [java.time, the modern Java date and time API](https://docs.oracle.com/javase/tutorial/datetime/index.html). – Ole V.V. Mar 27 '23 at 17:14
  • `TimeUnit` falsely assumes that a day is always 24 hours. You may have observed that summer time (DST) went into effect in great parts of Europe yesterday, Sunday 26 March (also observable from the different time zone abbreviations `CET` and `CEST` in your output). So that day was only 23 hours. So when measuring 7 days across that day you don’t get milliseconds enough for 7 * 24 hours, and `TimeUnit` truncates to 6 days instead of 7. When you add `1L`, you get 7 instead of the 8 you would normally get from the same operation. – Ole V.V. Mar 27 '23 at 17:20
  • Today, March 28, I ran this code in Europe/Copenhagen time zone (one of the zones that had the spring forward this Sunday): `ChronoUnit.DAYS.between(LocalDate.parse("2023-03-21"), LocalDate.now(ZoneId.systemDefault()))`. I got the correct count; `7`. Because `LocalDate` is independent of time zone and thus cannot be affected by summer time and such time anomalies. – Ole V.V. Mar 28 '23 at 03:13

5 Answers5

2

tl;dr

I want to check that a date is the same day on the next week that today.

LocalDate            // Represent a date-only value. No time of day. No offset or time zone. 
.now()               // Capture the current date as seen in the JVM’s current default time zone. 
.getDayOfWeek()      // Get the `DayOfWeek` enum object for this date’s day of week: Monday though Sunday. 
.equals( 
    someOtherDate
    .getDayOfWeek()
)

Details

You are using terribly flawed date-time classes that are now legacy. These were years ago supplanted by the modern java.time classes defined in JSR 310.

Furthermore, you are using a date-with-time class to represent a date-only value. So your choice of type is a misfit. And your unexpected results may be coming from related time zone issues such as Daylight Saving Time.

For a date-only value, use java.time.LocalDate. No time of day. No offset from UTC, nor time zone. No Daylight Saving Time.

To capture the current date, a time zone is required. For any given moment, the date varies around the globe by time zone.

You may want to use the JVM’s current time zone. Or you may want to query the user.

ZoneId z = ZoneId.systemDefault() ;

Get the current date.

LocalDate today = LocalDate.now( z ) ;

Add a week.

LocalDate nextWeek = today.plusWeeks( 1 ) ;

Subtract a week.

LocalDate weekAgo = today.minusWeeks( 1 ) ;

Testing if same day of week. You can get the DayOfWeek enum object for any given date.

boolean sameDayOfWeek = today.getDayOfWeek().equals( someOtherDate.getDayOfWeek() ) ;
Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
1

It's the +1L in

long deltaDays = TimeUnit.DAYS.convert(diffInMillies, TimeUnit.MILLISECONDS) +1L;

That makes 7 into 8. Actually not the current reading is the one you should look at, but the prior one you tried to fix: originally you had 6 because of the CET-CEST change. As clock jumped an hour, there're 6 days and 23 hours between 20th of March 00:00 CET and 27th of March 00:00 CEST, then the difference got rounded downwards, to 6 days.

tevemadar
  • 12,389
  • 3
  • 21
  • 49
  • The explanation is correct, which is why I upvoted. (1) You are still using the `TimeUnit` enum, and that enum is still falsely assuming that a day is always 24 hours. (2) So how do we handle the change from CET to CEST? – Ole V.V. Mar 28 '23 at 14:53
  • 1
    @OleV.V. I just answered the *Does anyone know why, in one case, I get 8 days of difference and 7 in another while having a week apart ?* question, because that time only the nonsense posts were around with the `+day`. But I don't know the API-s at all, for an actual solution I'd check these possible dupes: https://stackoverflow.com/questions/44617846/duration-with-daylight-saving, https://stackoverflow.com/questions/72909759/adding-minutes-to-localdatetime-gives-error-on-daylight-saving-days, or https://stackoverflow.com/questions/8827420/daylight-savings-time-cst-cdt-adding-minutes-issue – tevemadar Mar 28 '23 at 16:01
  • 1
    @Cucumberbatch you may want to check the listed topics if they help. – tevemadar Mar 28 '23 at 16:03
  • Yeah, sure, sorry if I sounded too critical. Every answer has its limitations, and I stand by my upvote. – Ole V.V. Mar 28 '23 at 16:10
0

I think your problem is in "+day" in line:

todayCalendar.add(Calendar.DAY_OF_WEEK, +day);

Try to change it to just "day". I suppose '+' may have worked as "++" incrementing your day from 7 to 8 before you added it to current day.
However, note that classes Date and SimpleDateFormat are deprecated and STRONGLY not recommended to be ever used except when you are working with legacy code. Please see java.time package available since java 8. Your relevant classes for your case would be DateTimeFormatter, LocalDate and Duration

Michael Gantman
  • 7,315
  • 2
  • 19
  • 36
0

Don’t use Strings for comparing numeric values. Do not use SimpleDateFormat at all.

Calendar by itself can do this:

Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DATE, 7);
int nextWeekYear = calendar.get(Calendar.YEAR);
int nextWeekMonth = calendar.get(Calendar.MONTH);
int nextWeekDay = calendar.get(Calendar.DATE);

calendar.Time(actualStep.getPlannedDate());
int plannedYear = calendar.get(Calendar.YEAR);
int plannedMonth = calendar.get(Calendar.MONTH);
int plannedDay = calendar.get(Calendar.DATE);

if (plannedYear == nextWeekYear &&
    plannedMonth == nextWeekMonth &&
    plannedDay == nextWeekDay) {

    // ....
}

As others have pointed out, you are much better off using java.time.LocalDate instead of the old Date class. Date is Java’s equivalent of C’s time_t type, which is just a count of milliseconds since the start of 1970. The problem with using it for calendar dates is that timezones greatly complicate it. For instance, consider this code:

Date date = new Date();
System.out.println(date.getTime());
System.out.println(date);

If I run it on my computer late in the evening, I will see:

1679972400000
Mon Mar 27 23:00:00 EDT 2023

But if I run the same code in the UK, I will see:

1679972400000
Tue Mar 28 04:00:00 BST 2023

The exact same Date value can be interpreted as a different calendar date, depending on where it’s run.

But if you use LocalDate, which is not affected by timezones, all of those problems cease to be a concern:

public class Step {
    private LocalDate plannedDate = LocalDate.now();

    public LocalDate getPlannedDate() {
        return this.plannedDate;
    }
}

// ...

if (actualStep.getPlannedDate().equals(LocalDate.now().plusDays(7)) {
    // ...
}
VGR
  • 40,506
  • 4
  • 48
  • 63
-1

You add 8 days in your unit test. day = 7, but you add +day, thats mean day+1. Remove + from todayCalendar.add(Calendar.DAY_OF_WEEK, +day); and it should be ok

Istis
  • 196
  • 2
  • 7