I need to get week of year for a given date. Using SimpleDateFormat produces wrong results for the last week of the year.
Example:
This produces week 52 correctly:
SimpleDateFormat w = new SimpleDateFormat("w");
Calendar c = Calendar.getInstance();
c.set(2021, 11, 25);
System.out.println("Week: ${w.format(c.getTime())}");
produces: Week: 52
But the next day is already considered as 1st week of next year?
SimpleDateFormat w = new SimpleDateFormat("w");
Calendar c = Calendar.getInstance();
c.set(2021, 11, 26);
System.out.println("Week: ${w.format(c.getTime())}");
produces: Week: 1
This only happens in Java 7 and not in Java 8 and above!
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.
Actually this is not specific to Calendar as this would also show Week 1 if ran on the 29th of December for example:
System.out.println("Week: ${new SimpleDateFormat("w").format(new Date())}");
But it is specific to Java 7. It was fixed in Java 8.
And I found the explanation for this here (as #rzwitserloot also explained):
https://docs.oracle.com/javase/8/docs/api/java/util/GregorianCalendar.html
A week year is in sync with a WEEK_OF_YEAR cycle. All weeks between
the first and last weeks (inclusive) have the same week year value.
Therefore, the first and last days of a week year may have different
calendar year values.
For example, January 1, 1998 is a Thursday. If getFirstDayOfWeek() is
MONDAY and getMinimalDaysInFirstWeek() is 4 (ISO 8601 standard
compatible setting), then week 1 of 1998 starts on December 29, 1997,
and ends on January 4, 1998. The week year is 1998 for the last three
days of calendar year 1997. If, however, getFirstDayOfWeek() is
SUNDAY, then week 1 of 1998 starts on January 4, 1998, and ends on
January 10, 1998; the first three days of 1998 then are part of week
53 of 1997 and their week year is 1997.
This is really interesting..
Related
I'm sure I'm just missing something here, but it continues to elude me.
Why does SUNDAY_START produce a week number of 1 for 2020-12-29 instead of 53?
Is it because I'm misinterpreting what the Oracle docs say? Or am I missing something further?
Details
Going to https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/temporal/WeekFields.html#SUNDAY_START shows that SUNDAY_START appears to follow the same rules as above. If I use the following code...
ZonedDateTime zdt = ZonedDateTime.ofInstant(/* representation of Dec. 29, 2020 */, ZoneId.of("UTC"));
TemporalField wn = WeekFields.SUNDAY_START.weekOfWeekBasedYear();
int weekNumber = zdt.get(wn);
Then "weekNumber" is 1, when I would expect it to be 53.
If I use WeekFields.ISO instead, I get 53; however, the definition of ISO is that it uses Monday as the start of the week, which isn't true in the case I'm dealing with and I'm unsure of future ramifications of just using ISO instead of SUNDAY_START.
What am I missing?
The Answer
Based on the comments below (thank you to Basil and Andreas), I was indeed misreading the Oracle documentation, which states:
Defined as starting on Sunday and with a minimum of 1 day in the month.
The "minimum of 1 day in the month" was the part that threw me for a loop within the context of a new year.
With the clarifications below, it's clear that since Jan. 1, 2021 occurs on a Friday, and the SUNDAY_START week starts on a Sunday, that this week would, in fact, be week number 1 in this case.
Motivation
I had originally asked this question as part of an attempt to better understand SUNDAY_START. The motivation for this was to find a way to recreate MySQL's WEEK() "mode 6" (that is, a Sunday start to the week with week 1 being the first week with 4 or more days this year).
I had forgotten about the ability to create custom week fields with a specified start day and the minimum number of days in the month for it to contain.
Still, I am grateful to have cleared up my misunderstanding of the Javadocs for SUNDAY_START, and I hope this post might be useful to someone in the future.
Thank you for everyone's comments and answers and I apologize for any initial confusion.
https://savvytime.com/week-number is wrong.
When you show Country: United States, Year: 2020, it shows:
Week 53 December 27, 2020 January 2, 2021 Current Week
When you show Country: United States, Year: 2021, it shows:
Week 1 December 27, 2020 January 2, 2021 Current Week
How can the same date range be both week 53 and week 1? It can't. The site is broken.
UPDATE: From comment:
needing a Java datetime "equivalent" of MySQL's WEEK() function using mode 6
That's easy enough. Mode 6 is defined as:
First day of week: Sunday
Week 1 is the first week... with 4 or more days this year
So you get a WeekFields for that:
WeekFields weekMode6 = WeekFields.of(/*firstDayOfWeek=*/DayOfWeek.SUNDAY,
/*minimalDaysInFirstWeek=*/4);
For reference, the two predefined WeekFields instances are defined as:
ISO = new WeekFields(DayOfWeek.MONDAY, 4);
SUNDAY_START = WeekFields.of(DayOfWeek.SUNDAY, 1);
Not so savvy SavvyTime.com
Your linked web site uses undisclosed rules for defining a week:
Weeks are according United States calendar rules, Sunday first day and weeks are Sunday to Saturday
The phrase “United States calendar rules” has no meaning. There are many sets of calendar rules in the US: Various accounting organizations make rules, IRS has rules, industries have their own rules such as a two-season year in some retail, and private companies define their own rules, sometimes quite innovative.
And the rest of that definition is unclear.
Furthermore, that site seems to be inexplicably self-contradictory, as seen in the Answer by Andreas.
I would not consider that web site to be authoritative.
ISO 8601
In contrast, the ISO 8601 calendar is very clearly defined, including the definition of a week and week-based-year:
Weeks start on a Monday, end on Sunday.
Week-based years have either 52 or 53 whole weeks.
Week number 1 contains the first Thursday in the calendar year.
This definition means that a week-based year may contain a few days from the previous calendar year in week # 1, and may contain a few days from the following calendar-year in the last week of the week-based year.
Am not sure exactly what your goal is, as your Question is unclear. But if the ISO 8601 rules match those of your business, then use the implementation in java.time.
ThreeTen-Extra and YearWeek
Even better, add the ThreeTen-Extra library to your project. This library is led by the same man who led the java.time JSR 310 project, Stephen Colebourne. This library brings additional functionality to java.time. In particular, see the YearWeek class for ISO 8601 weeks.
I only found a solution for Joda Time.
My solution works only if the last day is not in the first week:
LocalDate.now() // or any other LocalDate
.withDayOfMonth(31)
.withMonth(12)
.get(weekFields.weekOfWeekBasedYear())
So what is the correct way in Java Time (like in Joda Time)?
This information is available directly using the java.time.* API.
The key method is rangeRefinedBy(Temporal) on TemporalField. It allows you to obtain a ValueRange object that provides the minimum and maximum values for the field, refined by the temporal object passed in.
To find out how many ISO weeks there are in the year, do the following:
LocalDate date = LocalDate.of(2015, 6, 1);
long weeksInYear = IsoFields.WEEK_OF_WEEK_BASED_YEAR.rangeRefinedBy(date).getMaximum();
System.out.println(weeksInYear);
Note that the date you pass in is used to determine the answer. So when passing in dates in early January or late December ensure you understand how the ISO week-based calendar works, and the difference between the calendar year and the week-based year.
If one wants to get the week number based on 7 days no matter when the week starts and how many days the first partial week of the year has, ChronoField.ALIGNED_WEEK_OF_YEAR might be helpful.
For example, the 1st of January 2016 based on the ISO-8601 definition (where a week starts on Monday and the first week has a minimum of 4 days) falls into week number 0, but in the aligned it is week number 1.
LocalDate date = LocalDate.of(2016, 1, 1);
int iso8601 = date.get(WeekFields.ISO.weekOfYear()); // result is 0
int aligned = date.get(ChronoField.ALIGNED_WEEK_OF_YEAR); // result is 1
It seems that when the last day is in the first week, you don't want to get 1 as an answer but 52/3/4, in which case you may be looking for:
LocalDate.of(2017, 12, 31).get(WeekFields.ISO.weekOfYear());
There are several ways to define week numbers - if that doesn't do what you want you need to clarify which method you want to use.
The correct and best solution is given by #JodaStephen. Here are some alternatives anyways.
December, 28th is always in the last week of a year, because the remaining three days after can not form a major part of another week:
int weeks = LocalDate.of(2017, 12, 28).get(WeekFields.ISO.weekOfYear());
A year has 53 weeks if it starts or ends with a thursday:
Year year = Year.of(2017);
DayOfWeek firstDay = year.atDay(1).getDayOfWeek();
DayOfWeek lastDay = year.atDay(year.length()).getDayOfWeek();
int weeks = firstDay == DayOfWeek.THURSDAY || lastDay == DayOfWeek.THURSDAY ? 53 : 52;
And finally this will give you the "week number" of the last day of year. It's 53 also in cases where the last week's number is 52 iff the major part of the last day's week lies in the next year (the week is claimed by the next year).
// This will not give the correct number of weeks for a given year
Year year = Year.of(2018);
year.atDay(year.length()).get(WeekFields.ISO.weekOfYear()); // 53
That's what you actually did.
By the following way I set date object to calendar instance.
But when I get the week of year , wrongly it returns week of the year as 1.
Same as week of the year, week of the month value also wrong. It return as 6. But it should be 5.
Following is output of calendar instance value
java.util.GregorianCalendar[time=1514678400000,
areFieldsSet=true,
areAllFieldsSet=true,
lenient=true,
zone=sun.util.calendar.ZoneInfo[id="UTC",
offset=0, dstSavings=0, useDaylight=false, transitions=0, lastRule=null],
firstDayOfWeek=1, minimalDaysInFirstWeek=1, ERA=1, YEAR=2017, MONTH=11, WEEK_OF_YEAR=1, WEEK_OF_MONTH=6, DAY_OF_MONTH=31, DAY_OF_YEAR=365, DAY_OF_WEEK=1, DAY_OF_WEEK_IN_MONTH=5, AM_PM=0, HOUR=0, HOUR_OF_DAY=0, MINUTE=0, SECOND=0, MILLISECOND=0, ZONE_OFFSET=0, DST_OFFSET=0]
firstDayOfWeek=1 means that Sunday is the first day of the week. So Sunday December 31, 2017 belonged to week 1 of 2018.
It’s not well documented, but it turns out that the week of month follows the month, so on Dec 31, it gives you the number of the week in December. Since December 1 and 2 were Friday and Saturday, they formed week 1 of December. Week 2 began on December 3. So week 6 began on December 31 and lasted only that day. This explains why week of month is 6 in your case.
Whether we use IST time zone (I am assuming it means Asia/Kolkata) or UTC doesn’t make a difference. At 5:30 AM in India, it was 00:00 in UTC, the date was still December 31, and therefore the week numbers are also unchanged.
Your Calendar’s settings of first day of week and minimal days in first week follow the standard used in USA and some other countries. If instead you wanted the international standard where Monday is the first day of the week and there is a minimum of 4 days in week one, you may instantiate it with a locale of a country that uses the international standard, for example:
Calendar c = Calendar.getInstance(Locale.FRANCE);
In this case week of year will be 52 and week of month will be 4. Not 5. This is because the first three days of December are not enough to form a week, so week 1 of December begins on December 4. Then the first three days of December are in week 0.
Prefer to use java.time
All of that said, the Calendar class is long outmoded and poorly designed. I recommend you use java.time, the modern Java date and time API, instead. Specifically, the LocalDate & WeekFields classes.
For example:
LocalDate date = LocalDate.of(2017, Month.DECEMBER, 31);
System.out.println("ISO week of year " + date.get(WeekFields.ISO.weekOfWeekBasedYear()));
System.out.println("ISO week of month " + date.get(WeekFields.ISO.weekOfMonth()));
System.out.println("US week of year " + date.get(WeekFields.SUNDAY_START.weekOfWeekBasedYear()));
System.out.println("US week of month " + date.get(WeekFields.SUNDAY_START.weekOfMonth()));
Output:
ISO week of year 52
ISO week of month 4
US week of year 1
US week of month 6
The results agree with those we got from Calendar.
Link: Oracle tutorial: Date Time explaining how to use java.time.
in my android app I'm working on to get the week number of a provided date. but it doesn't give me the correct week number I'm using following code to get the week number.
String format = "dd-MM-yyyy";
SimpleDateFormat df = new SimpleDateFormat(format);
Date date = df.parse(date);
Calendar cal = Calendar.getInstance();
cal.setTime(date);
int week = cal.get(Calendar.WEEK_OF_YEAR);
but this not give me what I'm expecting. for instance lets take January 2016.
As of January 2016 as seen from the above calendar, days belongs to the week number 1 are only 1st,2nd and 3rd. and for the week number 2, relevant days are 4th, 5th, 6th, 7th,8th, 9th and 10th. then for the week number 3, relevant days are 11th, 12th, 13th, 14th, 15th, 16th and 17th and so on. but for the date 12/01/2016 above code gives me week number 2 instead of week number 3. am I doing something wrong or is this the way the function works normally. how can I meet my goal to get the week number as I mention above.
1st, 2nd and 3rd of January are actually week 53 of year 2015. The code is working correctly, your assumptions about week numbering are wrong. The default behaviour of GregorianCalendar is the same as ISO definition for the first week in Wikipedia:
The ISO 8601 definition for week 01 is the week with the year's first
Thursday 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.
Its first day is the Monday nearest to 1 January.
It has 4 January in it. Hence the earliest possible dates are 29 December through 4 January, the latest 4 through 10 January.
It has the year's first working day in it, if Saturdays, Sundays and 1 January are not working days.
If 1 January is on a Monday, Tuesday, Wednesday or Thursday, it is in
week 01. If 1 January is on a Friday, it is part of week 53 of the
previous year; if on a Saturday, it is part of week 52 (or 53 if the
previous year was a leap year); if on a Sunday, it is part of week 52
of the previous year.
Found at https://docs.oracle.com/javase/7/docs/api/java/util/GregorianCalendar.html :
Values calculated for the WEEK_OF_YEAR field range from 1 to 53. The first week of a calendar year is the earliest seven day period starting on getFirstDayOfWeek() that contains at least getMinimalDaysInFirstWeek() days from that year. It thus depends on the values of getMinimalDaysInFirstWeek(), getFirstDayOfWeek(), and the day of the week of January 1.
So your first week will be week 0 because it starts counting from your first full 7 days week. You can change this by setting getMinimalDaysInFirstWeek() to a lower value
I'd like to have a function which returns the next week and year given a week and year. It looks something like:
public static int[] getNextWeek(int year, int week) {
Calendar c = Calendar.getInstance(); // I'm Locale.US and TimeZone "America/New_York"
c.clear();
c.set(Calendar.YEAR, year);
c.set(Calendar.WEEK_OF_YEAR, week);
c.add(Calendar.WEEK_OF_YEAR, 1);
return new int[] {c.get(Calendar.YEAR), c.get(Calendar.WEEK_OF_YEAR)}
}
This sometimes does not work around year boundaries. It seems to depend on what day you invoke it and for what parameters you use! For example, if I invoke it with year 2012 and week 52 then I expect the result to be year 2013 and week 1. If you invoke it today (Tuesday July 17 2012) it works. If you set the day of week to yesterday it does not; and oddly results in year 2012 week 1. Weird. What is going on? It appears to relate to the day of the week because it doesn't work if invoked with SUNDAY or MONDAY, which are the last two days of 2012! If I set the day of the week to the last day of the week the function seems to work; before calling Calendar.add() I do:
// Must set to last day of week; initially set to first day due to API
c.set(Calendar.DAY_OF_WEEK, c.getFirstDayOfWeek()); // SUNDAY
c.add(Calendar.DAY_OF_WEEK, 6); // Must roll forward not backwards
There doesn't seem to be any weirdness like this if I create a getPreviousWeek method. Is this a java.util.Calendar bug? Next time I guess I'll use Joda time!
Have you considered the fact that ISO8601 week algorithm considers the first week of the year to be the one that has at least 4 days fall within that year?
So if the Jan 1 is on thurdsay, that week is actually considered week 52 of the previous year, not week one of the current year.
You might want to at least consider joda-time for this kind of calculation, as it has proper handling of the ISO standard.
LocalDate ld = new LocalDate();
ld = ld.withWeekOfWeekyear(29);
ld = ld.withWeekyear(2012);
System.out.println(ld.getWeekOfWeekyear());
System.out.println(ld.getWeekyear());
// 29
// 2012
System.out.println(ld.plusWeeks(1).getWeekOfWeekyear());
System.out.println(ld.plusWeeks(1).getWeekyear());
// 30
// 2012
And this will work across year boundaries.
Just do an explicit check to see if there's a rollover.
return new int[] {c.get(Calendar.WEEK_OF_YEAR) == 1 ? c.get(Calendar.YEAR) + 1 :
c.get(Calendar.YEAR), c.get(Calendar.WEEK_OF_YEAR)};
As far as I can tell, the reason why WEEK_OF_YEAR doesn't add correctly is because it's not an actual property of Calendar like SECOND, MINUTE, MONTH, DAY, or YEAR. It's a derived property like WEEK_OF_MONTH. One way to get around this is to use c.add(Calendar.DAY, 7) to add exactly 7 days instead of incrementing WEEK_OF_YEAR.
At this point I'm going to assume this really is a bug. I've filled a bug report with Oracle here:
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7197761