sentimental
sentimental

Reputation: 3

Last week number of year in default Java is not correct

Example of code:

LocalDate date = LocalDate.of(2000, 12, 31);
System.out.println("Default Java week #" + date.format(DateTimeFormatter.ofPattern("ww")));
System.out.println("Iso week #" + date.get(WeekFields.ISO.weekOfYear()));
System.out.println("Usa week #" + date.get(WeekFields.SUNDAY_START.weekOfYear()));
---------------------
Default Java week #01
Iso week #52
Usa week #54
---------------------

The ISO 8601 definition for week 01 is the week with the first Thursday of the Gregorian year (i.e. of January) in it. The following definitions based on properties of this week are mutually equivalent, since the ISO week starts with Monday: It is the first week with a majority (4 or more) of its days in January.

USA format: Method of calculation - the first week is the one in which January 1 happened, and the next week starts from the next Sunday.

2000 December 31 - Sunday. Referring to the standards of the week, December 31(Sunday) is the week of the last year in any standard(54 or 52). Why does Java say it is the 01 week?

P.S. Locale.getDefault() --> en_US

Upvotes: 0

Views: 691

Answers (1)

Anonymous
Anonymous

Reputation: 86276

You are comparing apples and bananas.

Format pattern letter w gives you week-of-week-based-year. In all calendar systems I know this is in the range 1 through 53. It uses the week numbering scheme of the locale of the formatter. Which in your case is the default locale of your JVM since you didn’t set locale explicitly. In many (virtually all?) week numbering systems, week 1 may well begin before New Year sometimes. In this case week 1 is correct for December 31.

weekOfYear() gives you week of year, which is 0 through 54. It differs from week-of-week-based-year around New Year (and only then). Contrary to week-of-week-based-year, week-of-year always gives you the week number in relation to the calendar year, year 2000 in your example, so this could never be 1 for December 31.

How to get your expected value from a formatter

A formatter too may use weekOfYear() from a WeekFields object so that you get consistent results. For example:

    LocalDate date = LocalDate.of(2000, Month.DECEMBER, 31);
    
    DateTimeFormatter formatter = new DateTimeFormatterBuilder()
            .appendValue(WeekFields.ISO.weekOfYear())
            .toFormatter();

    System.out.println("Iso week # from formatter: " + date.format(formatter));

Output:

Iso week # from formatter: 52

Is Java incorrect?

On www.timeanddate.com I constructed this 2000 calendar for the United States. Under More Options -> Advanced customization -> Weeks I chose “Yes – January 1 is always in week 1” as appropriate for the US. As you can see in the link, December 31 is now in week 1.

You are correct that in ISO December 31 2000 is in week 52. In ISO too in other years December 31 may fall in week 1 of the following year.

Simulating IBM’s WEEKNUM function in Java

Edit: It seems pretty clear that IBM and Java do not completely agree on a definitions of week numbers. Instead of discussing who’s right and who’s wrong I should like to simulate the IBM way in Java since I understand that this is what you really need.

Empirically

IBM neither consequently follows what Java calls week-of-year nor what Java calls week-of-week-based-year. Let’s see Java’s opinion in the sample dates from your link:

    LocalDate[] sampleDates = {
            LocalDate.of(2000, Month.JANUARY, 1),
            LocalDate.of(2000, Month.JANUARY, 2),
            LocalDate.of(2000, Month.JANUARY, 3),
            LocalDate.of(2000, Month.DECEMBER, 31),
            LocalDate.of(2014, Month.JANUARY, 2),
            LocalDate.of(2014, Month.JUNE, 9),
            LocalDate.of(2014, Month.DECEMBER, 31) };
    
    System.out.println("              US          US       ISO         ISO");
    System.out.println("Input date  week based  of year  week based  of year");
    for (LocalDate date : sampleDates) {
        System.out.format("%s    %02d          %02d        %02d          %02d%n", date,
                date.get(WeekFields.SUNDAY_START.weekOfWeekBasedYear()), 
                date.get(WeekFields.SUNDAY_START.weekOfYear()), 
                date.get(WeekFields.ISO.weekOfWeekBasedYear()), 
                date.get(WeekFields.ISO.weekOfYear()));
    }

Output:

              US          US       ISO         ISO
Input date  week based  of year  week based  of year
2000-01-01    01          01        52          00
2000-01-02    02          02        52          00
2000-01-03    02          02        01          01
2000-12-31    01          54        52          52
2014-01-02    01          01        01          01
2014-06-09    24          24        24          24
2014-12-31    01          53        01          53

Comparing to your table it seems that for USA IBM is producing what Java calls week-of-year. And for ISO IBM produces what Java calls week-of-week-based-year.

Documentation

I believe that I have found a description of IBM’s WEEKNUM function that agrees with yours. It goes:

  • WEEKNUM function converts a given Julian/Gregorian date to number of week. There are 2 versions of this function, the standard USA format and the ISO format.
  • WEEKNUM=USA function returns an integer in the range of 1 to 54 that represents the week of the year. The week starts with Sunday, and January 1 is always in the first week.
  • WEEKNUM=ISO function returns an integer in the range of 1 to 53 that represents the week of the year. The week starts with Monday and includes 7 days. Week 1 is the first week of the year to contain a Thursday, which is equivalent to the first week containing January 4.

While this isn’t horribly precise, it agrees with my interpretation from before. Nowhere does week-of-week-based-year go up to 54, so for that to happen in USA, we need the week-of-year interpretation, where the week number may change from 54 to 1 at New Year. Conversely ISO week-of-year may be 0 if week 1 begins later than January 1, but IBM does not allow 0, so here they are probably using week-of-week-based-year.

It’s not too bad simulating all of this in Java:

public static enum IBM_WEEKNUM_VERSION { USA, ISO };

public static int ibmWeeknum(IBM_WEEKNUM_VERSION version, LocalDate date) {
    switch (version) {
    case USA:
        return date.get(WeekFields.SUNDAY_START.weekOfYear());
    case ISO:
        return date.get(WeekFields.ISO.weekOfWeekBasedYear());
    default:
        throw new IllegalArgumentException(String.valueOf(version));
    }
}

Try it out on the same sample dates:

    for (LocalDate date : sampleDates) {
        System.out.format("%s    %02d          %02d%n", date,
                ibmWeeknum(IBM_WEEKNUM_VERSION.USA, date),
                ibmWeeknum(IBM_WEEKNUM_VERSION.ISO, date));
    }
2000-01-01    01          52
2000-01-02    02          52
2000-01-03    02          01
2000-12-31    54          52
2014-01-02    01          01
2014-06-09    24          24
2014-12-31    53          01

On one hand it agrees 100 % with your examples. On the other hand we can’t be 100 % sure about the working of the IBM function from what we know. I suggest that you test the method on the dates of the first and the last two weeks of each year of a 20 years period, for example.

Links

Upvotes: 3

Related Questions