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
Related
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..
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.
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.
I have a GregorianCalendar instance for Tuesday September 2nd. The valñue is checked in milliseconds and is OK. I want another calendar which is the next Sunday (7th) at 23:59:59. So:
GregorianCalendar currentCalendar = MyClock.INSTANCE.getCurrentCalendar();
GregorianCalendar nextSunday =
(GregorianCalendar)currentCalendar.clone();
// GregorianCalendar uses Sunday as first day of week, so we must
// advance one week
int currentWeek = nextSunday.get(GregorianCalendar.WEEK_OF_YEAR);
nextSunday.set(GregorianCalendar.WEEK_OF_YEAR,
currentWeek + this.THIS_WEEK);
nextSunday.set(GregorianCalendar.DAY_OF_WEEK, GregorianCalendar.SUNDAY);
nextSunday.set(GregorianCalendar.HOUR_OF_DAY, 23);
nextSunday.set(GregorianCalendar.MINUTE, 59);
nextSunday.set(GregorianCalendar.SECOND, 59);
nextSunday.set(GregorianCalendar.MILLISECOND, 0);
So, since sunday is day number 1 of the week for the GregorianCalendar, and I am at week of year number 36, I add one week and then set the day to sunday.
The real problems comes now: when I execute in my development machine, with OpenJDK 1.7.0_55, it works perfectly. If I go to my test machine with OpenJDK 1.7.0_51, it does it all wrong:
Adds one week until tuesday 9th, and then goes to sunday 14th instead of sunday 7th.
I don't know if I am doing it right or wrong: what is really killing me is that the result depends on the machine, and I haven't found any difference at GregorianCalendar at those OpenJDK versions. Any explanation for this behaviour?
PD: Please stick to GregorianCalendar. I know is a bit shitty, but I don't want to use Joda Calendar or any other at current stage of development.
EDIT: I found method setWeekDate(year, week_of_year, day_of_week). One would think that setting year, week and day of week into the same method will grant it will succeed. It does not: still going from 2nd to 14th. What monkey wrote this?
I've made slight alterations to your code:
SimpleDateFormat sdf = new SimpleDateFormat("dd MM yyyy - HH:mm:ss.SSSS Z");
GregorianCalendar currentCalendar = (GregorianCalendar) Calendar.getInstance();
currentCalendar.set(Calendar.DAY_OF_MONTH, 2);
System.out.println(sdf.format(currentCalendar.getTime()));
GregorianCalendar nextSunday = (GregorianCalendar) currentCalendar.clone();
// GregorianCalendar uses Sunday as first day of week, so we must
// advance one week
int currentWeek = nextSunday.get(GregorianCalendar.WEEK_OF_YEAR);
nextSunday.set(GregorianCalendar.WEEK_OF_YEAR, currentWeek + 1);
nextSunday.set(GregorianCalendar.DAY_OF_WEEK, GregorianCalendar.SUNDAY);
nextSunday.set(GregorianCalendar.HOUR_OF_DAY, 23);
nextSunday.set(GregorianCalendar.MINUTE, 59);
nextSunday.set(GregorianCalendar.SECOND, 59);
nextSunday.set(GregorianCalendar.MILLISECOND, 0);
System.out.println(sdf.format(nextSunday.getTime()));
This outputs:
02 12 2014 - 19:40:46.0250 +0200
07 12 2014 - 23:59:59.0000 +0200
Which is correct. However, I have two things to point out:
check the value of this.THIS_WEEK. I have substituted it for the value 1 and it works ok on my machine.
check the timezone on both machines (in my case GMT+2). Since both machines use the same code which both initialize the values and use them, there shouldn't really be problems. But if you use the values as milliseconds on a different machine (e.g. exposing the value through a webservice or something), you might hit problems.
I would try using java.util.Calendar method add(int field, int amount) instead.
nextSunday.add(Calendar.DAY_OF_MONTH,5). One line of code instead of three.
I'm newish to Java and am trying to do somethings with dates. First I started using the Date class, which I found out was mostly deprecated so I switched over to Calendar.
Now I am getting weird values. for example the Month value for December is 0, not 12. And on those Calendars where it is giving me 0 for December it is also moving the year ahead one year.
It's weird!
What am I missing?
Thanks for your help.
-GG
EDIT FOR AN EXAMPLE:
So I am reading some line sin from a file such as this:
Johnny Graham H F 12-2-1973 Black
I parse it, and then for the Calendar I set:
int year = Integer.parseInt(stringVersionOfYear); // this value is 1973
Then later when I go to get the year back with a line like this:
calendar.get(Calendar.YEAR)
the value is 1974... And the month is 0 for
cal.get(Calendar.MONTH)
EDIT 2:
I am creating the Calendar like this:
Calendar outputCalendar = Calendar.getInstance();
outputCalendar.set(year, month, day);
The java.util.Date and Calendar classes are poorly designed (eg, the first day in a month is day 1 but the first month in a year is month 0). Many projects use the Joda Time package instead.
Months values are 0 thru 11; set a month to 12 and the date gets "normalized", incrementing the year by one and setting the month to 0. This makes it easy to "add a month" without having to worry about handling the overflow at year end.
EDIT: January=0, February=1,... December=11. When you set the month value to 12 you were asking for the 13th month, which got normalized to the first month of the following year.
Note that this normalization process happens in general -- Try to set the date to December 32nd and you'll get back January 1 of the following year. This means it's important to be careful when modifying individual fields of a Calendar object. If you create a default Calendar on, say, January 31st and then want to modify it to contain, say February 5th, the order in which you set the fields is important. If you change the month first you'll be creating February 31st, which will get normalized to March 2nd or 3rd (depending on the leap year) and then when you set the day to 5 the result is March 5th, not February 5th. You have the opposite problem in other cases, such as starting from any date in February and modifying to the 30th or 31st of any other month. In that case doing the month first results in the same type of problem.
The only safe way to modify a date is to use a method that sets all three values simultaneously, such as the set(int,int,int) method.
See this line?
outputCalendar.set(year, month, day);
Just change it like this:
outputCalendar.set(year, month - 1, day);
and then when you want to get the month, don't use this:
cal.get(Calendar.MONTH)
instead, use this:
(1+(cal.get(Calendar.MONTH)))
It's a pain, but it will fix the problem (I think).
The main piece of advice I have for you is: read the API! Calendar is indeed not the pinnacle of intuitive API design, but it is definitely usable if you take the time to read the javadoc. Learn the difference between .add and .roll. Check out what happens when you set a year (1973) into a Calendar initialized with the current date (default Calendar.getInstance).
Whining about API is all fine (we all do it), but in the end to find solutions start off by reading what the authors provided to you before asking relatively obtuse questions on the Internet.