简体   繁体   中英

Java Calendar DAY_OF_WEEK SET to zero

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?

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 eg 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);

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".

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM