3

I have this very old code block from PROD (>7 years) to be debugged. There's one point I couldnt understand. A section in the code does a calculation of next time a task will run and for tasks who need to run specifically on sunday, monday it uses Calendar.SUNDAY. But there's one statement whose behaviour I cannot interpret even after reading the docs multiple times

Calendar cal = Calendar.getInstance(); cal.set(Calendar.DAY_OF_WEEK, 0);

since the days are numbered from 1-7 (Calendar.SUNDAY to Calendar.SATURDAY) that can be interpreted, but how does zero work here and why there is no exception?

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
dora
  • 33
  • 5
  • 3
    The doc says “The value is not interpreted by this method regardless of the leniency mode.” and also says an exception is thrown if value is *below* zero. I wonder if that means that no data validation is being done, and no exception is thrown for a zero value. These legacy date-time classes are such a mess. Be aware that you can write your code using their replacements in the *java.time* package, while converting easily back and forth by calling new `to`/`from` methods added to the old classes. – Basil Bourque Jun 03 '21 at 19:37
  • 1
    I recommend you don’t use `Calendar`. That class is poorly designed and long outdated. Instead use a class from [java.time, the modern Java date and time API](https://docs.oracle.com/javase/tutorial/datetime/), for example `LocalDate` or `ZonedDateTime`. – Ole V.V. Jun 05 '21 at 08:57

3 Answers3

2

why there is no exception?

It is because you haven't set the lenient mode to false and it is true by default.

Demo:

import java.util.Calendar;

public class Main {
    public static void main(String[] args) {
        Calendar cal = Calendar.getInstance();
        cal.setLenient(false);
        cal.set(Calendar.DAY_OF_WEEK, 0);
        System.out.println(cal.getTime());
    }
}

Output:

Exception in thread "main" java.lang.IllegalArgumentException: DAY_OF_WEEK

The documentation says:

Any out of range values are either normalized in lenient mode or detected as an invalid value in non-lenient mode

As part of the normalization, the value are rolled over e.g. the following code sets the value equivalent to cal.set(Calendar.DAY_OF_WEEK, Calendar.SUNDAY - 1):

cal.set(Calendar.DAY_OF_WEEK, 0);

Similarly, the following code sets the value equivalent to cal.set(Calendar.DAY_OF_WEEK, Calendar.SUNDAY - 2):

cal.set(Calendar.DAY_OF_WEEK, -1);
Arvind Kumar Avinash
  • 71,965
  • 6
  • 74
  • 110
  • interesting to know this. strange how it allows to set the value 0 even when Lenient mode is set to false without any exception until getTime() is called. – dora Jun 04 '21 at 11:45
  • 1
    @dora - There is no validation performed at the time of setting a value. It's only when you retrieve the value, you will get the exception if an invalid value is detected in non-lenient mode. – Arvind Kumar Avinash Jun 04 '21 at 11:58
2

By trying it out in a "test bench", I found this:

It looks the "calendar set" adjust value/integer when number 1-7 is "overflown". I can see this pattern:

       Day Of Week:  1  2  3  4  5  6  7  | 1 2 3 4 5 6 7 | 1  2  3   4   5   6   7  | ... 
 Value of calendar: -6 -5 -4 -3 -2 -1  0  | 1 2 3 4 5 6 7 | 8  9  10  11  12  13  14 | ...

Test bench:

public static void main(String[] args) {
    Calendar cal = Calendar.getInstance();
    cal.set(Calendar.DAY_OF_WEEK, -6);
    System.out.println("Calendar value -6 returns: " + cal.get(Calendar.DAY_OF_WEEK));

    cal.set(Calendar.DAY_OF_WEEK, 0);
    System.out.println("Calendar value 0 returns: " + cal.get(Calendar.DAY_OF_WEEK));

    cal.set(Calendar.DAY_OF_WEEK, 1);
    System.out.println("Calendar value 1 returns: " + cal.get(Calendar.DAY_OF_WEEK));

    cal.set(Calendar.DAY_OF_WEEK, 7);
    System.out.println("Calendar value 7 returns: " + cal.get(Calendar.DAY_OF_WEEK));

    cal.set(Calendar.DAY_OF_WEEK, 8);
    System.out.println("Calendar value 8 returns: " + cal.get(Calendar.DAY_OF_WEEK));

    cal.set(Calendar.DAY_OF_WEEK, 14);
    System.out.println("Calendar value 14 returns: " + cal.get(Calendar.DAY_OF_WEEK));
}

Output:

Calendar value -6 returns: 1
Calendar value 0 returns: 7
Calendar value 1 returns: 1
Calendar value 7 returns: 7
Calendar value 8 returns: 1
Calendar value 14 returns: 7

Output is according to "pattern".

DigitShifter
  • 801
  • 5
  • 12
0

java.time

I recommend that you use java.time, the modern Java date and time API, for your date and time work. For example:

    LocalDate ld = LocalDate.now(ZoneId.systemDefault())
                            .with(DayOfWeek.TUESDAY);
    System.out.println(ld);

When I ran this code today, Saturday, June 5, the output was:

2021-06-01

And yes, June 1 was Tuesday. Since we are passing an enum constant to with(), there is really no possibility of passing an out-of-range value. DayOfWeek is an enum holding 7 values for the 7 days of the week. Only trouble we can get ourselves into is by passing null, which will throw a NullPointerException, which I think you wanted.

If we do insist on passing the day of week as a number, that is possible, though. java.time numbers the days of the week from Monday = 1 through Sunday = 7.

    LocalDate ld = LocalDate.now(ZoneId.systemDefault())
                            .with(ChronoField.DAY_OF_WEEK, 2);

So far the output is 2021-06-01 as before. But what if we pass 0?

                            .with(ChronoField.DAY_OF_WEEK, 0);

Exception in thread "main" java.time.DateTimeException: Invalid value for DayOfWeek (valid values 1 - 7): 0

Not only do we get the exception you asked for, we are also getting a clear and helpful exception message, IMHO.

How does day of week zero work here?

With Calendar day of week 0 works the same as 7 = Saturday. It seems that at least a lenient old-fashioned GregorianCalendar performs a kind of modulo 7 operation on the day of week to bring it inside the interval 1 through 7. I did not find this documented. GregorianCalendar is probably the concrete subclass of Calendar that you are dealing with. I tried with different numbers that are all equivalent to 7 modulo 7:

    int[] dows = { 0, 7, -7, 14, -14, -98 };
    for (int dow : dows) {
        Calendar cal = new GregorianCalendar(2021, Calendar.JUNE, 2);               
        Date dateBefore = cal.getTime();
        cal.set(Calendar.DAY_OF_WEEK, dow);
        System.out.format("%s and day of week %3d yields %s%n", dateBefore, dow, cal.getTime());
    }

Output:

Wed Jun 02 00:00:00 CEST 2021 and day of week   0 yields Sat Jun 05 00:00:00 CEST 2021
Wed Jun 02 00:00:00 CEST 2021 and day of week   7 yields Sat Jun 05 00:00:00 CEST 2021
Wed Jun 02 00:00:00 CEST 2021 and day of week  -7 yields Sat Jun 05 00:00:00 CEST 2021
Wed Jun 02 00:00:00 CEST 2021 and day of week  14 yields Sat Jun 05 00:00:00 CEST 2021
Wed Jun 02 00:00:00 CEST 2021 and day of week -14 yields Sat Jun 05 00:00:00 CEST 2021
Wed Jun 02 00:00:00 CEST 2021 and day of week -98 yields Sat Jun 05 00:00:00 CEST 2021

Tutorial link

Oracle tutorial: Date Time explaining how to use java.time.

Ole V.V.
  • 81,772
  • 15
  • 137
  • 161