How does Java week year really work?
It's simple: December 27 2015 is day 1 of week 1 of week-year 2016 (and December 27 2026 is day 1 of week 1 of week-year 2027). This can be verified by adding these lines:
SimpleDateFormat odf = new SimpleDateFormat("YYYY-ww-u");
System.out.println(odf.format(d1));
System.out.println(odf.format(d2));
System.out.println(odf.format(d3));
If a SimpleDateFormat
outputs a date it can use all fields: year, month, day, day of week, week of month, week in year, week-year etc.
On parsing, SimpleDateFormat
expects a matching set of values: either day, month, year or day of week, week in year, week-year. Since you supplied a week-year but did not supply day of week and week in year, those to values have been assumed as 1.
The actual values depend on your locale:
- which week of a year is week 1
- which day is the first day of the week
(see https://docs.oracle.com/javase/7/docs/api/java/util/GregorianCalendar.html#week_and_year)
On my system (using de-ch locale, with "EEE MMM dd HH:mm:ss zzz yyyy - YYYY-ww-u" as format) I get
Mo Jan 04 00:00:00 MEZ 2016 - 2016-01-1
Mo Jan 04 00:00:00 MEZ 2016 - 2016-01-1
Mo Jan 04 00:00:00 MEZ 2027 - 2027-01-1
SimpleDateFormat week year
TL;DR: The formatter uses its own week numbering
The date formatter is using its own week numbering scheme, not that of the date (in your case that of the Calendar
object). I am demonstrating using java.time the modern Java date and time API. Specifically I am using a LocalDate
. A LocalDate
is always a date in the ISO calendar system.
LocalDate date = LocalDate.of(1998, Month.JANUARY, 1);
DateTimeFormatter baseFormatter = DateTimeFormatter.ofPattern("YYYY-MM-dd");
DateTimeFormatter chinaFormatter = baseFormatter.withLocale(Locale.CHINA);
System.out.println("China: " + date.format(chinaFormatter));
DateTimeFormatter franceFormatter = baseFormatter.withLocale(Locale.FRANCE);
System.out.println("France: " + date.format(franceFormatter));
DateTimeFormatter irelandFormatter = baseFormatter.withLocale(Locale.forLanguageTag("en-IE"));
System.out.println("Ireland: " + date.format(irelandFormatter));
Output from this snippet is:
China: 1998-01-01
France: 1998-01-01
Ireland: 1997-01-01
- If your default locale is China, your formatter was using China weeks. They begin on Sunday, and week 1 is the week that contains January 1 (like in the US, as another example). So January 1 of 1998 must belong to week year 1998.
- France uses ISO numbering: The week begins on Monday, and as you said, in this case week 1 of 1998 begins on December 29, 1997. So we get 1998 again.
- Ireland is one of the few countries using the mixed scheme that you were trying to obtain in your question: Minimal days in first week is 4 (as in ISO), and Sunday is the first day of the week (as in China and the US). So using a formatter with Ireland as locale country gives you the result you were trying to get, 1997.
A different way of obtaining a week year of 1997 from your date is to put your own WeekFields
object together. Then you don’t need to worry about locale.
WeekFields myWeekFields = WeekFields.of(DayOfWeek.SUNDAY, 4);
int weekYear = date.get(myWeekFields.weekBasedYear());
System.out.println("Week year: " + weekYear);
Week year: 1997
java.time??
The date and time classes that you were using, Calendar
and SimpleDateFormat
, are poorly designed and long outdated.
It seems from your document link that you are using Java 8, and if so, there’s absolutely no reason why you should struggle with the old classes. Use java.time as I do above. A backport for Java 6 and 7 exists too.
How come the code in the question didn’t work?
You were taking the time out of your Calendar
using calendar.getTime()
. This returns a Date
(another poorly designed and long outdated class). A Date
is a point in time, nothing more. It hasn’t got any notion of week number or week year. You passed this Date
to your SimpleDateFormat
, which had no way of detecting that the Date
had originally come from a Calendar
object with a certain setting of days in first week and first day of week. Instead the SimpleDateFormat
uses its own definition of weeks. It always does that. Since you had not explicitly set a locale or calendar for the SimpleDateFormat
, it used the default locale of your JVM. This produced a week year of 1998, as it would in the vast majority of locales.
SimpleDateFormat wrong week of year for last week
Don't use Calendar
. It's obsolete, and, more to the point, incredibly bad API.
There's a list as long as my leg as to what's wrong with it. The specific one of about 200 things that is relevant here is that, boneheadedly, its month value is 0 indexed. So, '12, 3'? That's the 3rd of Undecimber, or whatever you'd like to call the 13th month. That, or the calendar doesn't do 13th month in which case it leniently just assumes you meant to say 3rd of jan 2022 . Either way, that's week 1.
So why is The 2nd of undecimber (or if you prefer, via rollover, 2nd jan 2022) "Week 52"?
Because it is.
Week numbering is weird, but they have to be. A week starts on monday (or sunday, for my silly standards loving USian brethren), and can't start on any other day. That means unless Jan 1st so happens to fall on a monday, there's going to be weirdness; either days in 2021 counting as being in 'week 1 of 2022', or days in 2022 counting as 'week 52 of 2021'. In fact, from time to time there'll have to be a week 53. After all, 52*7 is 364, but there are 365.2475 days in a year, so unless you just want to make some days poof out of existence, every so often a week 53 has to exist for it all to add up.
Use java.time
instead.
LocalDate ld = LocalDate.of(2021, 12, 3);
WeekFields wf = WeekFields.of(Locale.ENGLISH);
int weekNumber = ld.get(wf.weekOfWeekBasedYear());
java.time does many things fantastically, and one of the things is does great is that it tends not to hide complex things. For example, 'when does a week start' is just not answerable, not unless you tell me where on the planet you're asking this question. Hence, 'which week is it' is also not actually an answerable question until you tell me exactly which week-counting system we're using, and there is no standard that is universally accepted enough. Hence, you have to go through the rigamarole of making a separate WeekFields
instance to capture that info. We do it here based on locale.
Understanding java.util.Calendar WEEK_OF_YEAR
From java.util.Calendar javadoc:
First Week
Calendar defines a locale-specific seven day week using two
parameters: the first day of the week and the minimal days in first
week (from 1 to 7). These numbers are taken from the locale resource
data when a Calendar is constructed. They may also be specified
explicitly through the methods for setting their values.When setting or getting the WEEK_OF_MONTH or WEEK_OF_YEAR fields,
Calendar must determine the first week of the month or year as a
reference point. The first week of a month or year is defined as the
earliest seven day period beginning on getFirstDayOfWeek() and
containing at least getMinimalDaysInFirstWeek() days of that month or
year. Weeks numbered ..., -1, 0 precede the first week; weeks numbered
2, 3,... follow it. Note that the normalized numbering returned by
get() may be different. For example, a specific Calendar subclass may
designate the week before week 1 of a year as week n of the previous
year.
So it's locale-specific. In your case, if the week contains days from new year, it is counted as week 1 from the new year.
You can change this behavior by using Calendar#setMinimalDaysInFirstWeek(int).
Date using 'YYYY'
For a concrete answer, the following table shows January 1 for each year from 2010 through 2020 with day-of-week, week-based year and week number in ISO (the international standard) and in the US.
Year DOW ISO US
2010 Fri 2009-53 2010-01
2011 Sat 2010-52 2011-01
2012 Sun 2011-52 2012-01
2013 Tue 2013-01 2013-01
2014 Wed 2014-01 2014-01
2015 Thu 2015-01 2015-01
2016 Fri 2015-53 2016-01
2017 Sun 2016-52 2017-01
2018 Mon 2018-01 2018-01
2019 Tue 2019-01 2019-01
2020 Wed 2020-01 2020-01
As you can see, internationally January 1 regularly falls in week 52 or 53 of the previous year, while in the US it always falls in week 1 of its own year.
International rule: Week 1 is the first one that contains at least 4 days of the new year. In other words, a week belongs in the year where most of its days are. In yet other words, week 1 is the one that holds the first Thursday of the year (since weeks begin on Mondays). This implies that when January 1 is a Friday, Saturday or Sunday, it belongs to the last week of the previous year.
US rule: Week 1 is the week that contains January 1. That January 1 always falls in week 1 of its own year follows from this definition (weeks begin on Sundays).
The table came out of this snippet:
System.out.println("Year DOW ISO US");
for (int year = 2010; year <= 2020; year++) {
LocalDate jan1 = LocalDate.of(year, Month.JANUARY , 1);
System.out.format(Locale.ROOT, "%4d %3s %4d-%02d %4d-%02d%n",
year, jan1.getDayOfWeek().getDisplayName(TextStyle.SHORT_STANDALONE, Locale.ROOT),
jan1.get(WeekFields.ISO.weekBasedYear()), jan1.get(WeekFields.ISO.weekOfWeekBasedYear()),
jan1.get(WeekFields.SUNDAY_START.weekBasedYear()), jan1.get(WeekFields.SUNDAY_START.weekOfWeekBasedYear()));
}
I am of course using java.time
, the modern Java date and time API. I warmly recommend it over the outdated Calendar
, SimpleDateFormat
and Date
.
Java Time's week-of-week-based-year pattern parsing with DateTimeFormatter
The documentation of DateTimeFormatterBuilder
specifies that "Y" appends the localized week-based year. Thus, the meaning of the week-based year and week fields will depend on the locale set in the formatter.
Related Topics
Why Isn't There a Java.Lang.Array Class? If a Java Array Is an Object, Shouldn't It Extend Object
How to Pass a Primitive Data Type by Reference
Valid Characters in a Java Class Name
Alert Handling in Selenium Webdriver (Selenium 2) with Java
Using Generics in Spring Data JPA Repositories
Getting Java Version at Runtime
How to Replace the Awt Eventqueue with Own Implementation
Scanner Nosuchelementexception
Why Won't This Generic Java Code Compile
How to Force Selenium Webdriver to Click on Element Which Is Not Currently Visible
Sorting Arraylist in Alphabetical Order (Case Insensitive)
Jackson and Generic Type Reference
Why Does Math.Round(0.49999999999999994) Return 1
Print an Integer in Binary Format in Java