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.
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 had a list of objects and want to map them by their week numbers.
I've looked at using something like
private static int getWeekOfYear(LocalDate date) {
WeekFields wf = WeekFields.of(Locale.getDefault());
return date.get(wf.weekOfYear());
}
The issue is that I don't want to do this by the week number in a year.
I want to have a Map<Integer, List<DTO>> where the Integers will be 1, 2, 3, ...
I had sorted the original List<DTO> by date and then I can get the first DTO.getDate() to determine when week 1 begins.
My problem is then iterating over the DTO's and determining which week number each one will be in. I feel like I'll have to find the day of the week, then work out how many days until the start of the next week e.g. if the first date is Wednesday, then week 2 will start in 5 days and then each subsequent week will be a further 7 days.
I'm really struggling to find a nice way of writing this. So am wondering if anyone can think of a good way of creating this Map?
Thanks
Basically you want to look into the new Java8 date/time APIs when doing such computations on date.
And then please note: are you sure you want to use a Map here?
If I get your input right, you say that the keys will run 1, 2, 3, ... anyway? So - when you know that the keys will just be a sequence of numbers, why not use a list then?
Another alternative would be to use a TreeSet and a custom compartor. If you use the comparator to sort your DTO objects based on their date, the TreeSet will automatically put them into the correct order!
WeekField has a overloaded method to specify the start day of the week and number of days in first week.
Since the DTO List is already sorted and the first day of week would be the starting day and 7 days from that day would be the next week.
List<DTO> dtos = ...// sorted
LocalDate firstDate = dtos.get(0).getDate();
WeekFields weekFields = WeekFields.of(firstDate.getDayOfWeek(), 7); // to override the default start day and no of days in first week
Map<Integer, List<DTO>> map = dtos.stream().collect(Collectors.groupingBy(dto -> dto.getDate().get(weekFields.weekOfYear())));
Try JodaTime's week of year format: http://joda-time.sourceforge.net/field.html#weekyear
A week based year is one where dates are expressed as a day of week,
week number and year (week based). The following description is of the
ISO8601 standard used by implementations of this method in this
library. Weeks run from 1 to 52-53 in a week based year. The first day
of the week is defined as Monday and given the value 1. The first week
of a year is defined as the first week that has at least four days in
the year. As a result of this definition, week 1 may extend into the
previous year, and week 52/53 may extend into the following year.
Hence the need for the year of weekyear field. For example, 2003-01-01
was a Wednesday. This means that five days, Wednesday to Sunday, of
that week are in 2003. Thus the whole week is considered to be the
first week of 2003. Since all weeks start on Monday, the first week of
2003 started on 2002-12-30, ie. in 2002. The week based year has a
specific text format.
2002-12-30 (Monday 30th December 2002) would be represented as 2003-W01-1
2003-01-01 (Wednesday 1st January 2003) would be represented as 2003-W01-3.
What you have to do, is find the first day of the week of the first date, and create the map using this date as reference :
private static Map<Long, List<DTO>> mapByWeek(final List<DTO> dtos) {
if (dtos.isEmpty())
return Collections.emptyMap();
// find the first day of first week
final LocalDate referenceDate = dtos.stream().sorted(Comparator.comparing(DTO::getDate)).findFirst()
.map(dto -> dto.getDate().with(WeekFields.of(Locale.getDefault()).dayOfWeek(), 1)).get();
// group by number of weeks of difference with the reference date
return dtos.stream().collect(Collectors.groupingBy(dto -> ChronoUnit.WEEKS.between(referenceDate, dto.getDate()) + 1));
}
Now I just count number of Thursdays in period from 1st to last date.
Is there one line solutions using Java 8 Date & Time API?
I've tried:
date.range(WeekFields.ISO.weekOfMonth()).getMaximum();
but it gives incorrect result, for example for March,5,2014 it returns 5 while March has only 4 weeks according to ISO8601.
Lets state the question as "how many Thursdays are there in a given month"?
(ISO-8601 does not describe an approach for weeks within months - it only describes an approach for weeks within years).
We can use the approach to find the 5th occurrence of Thursday, and see if it is in the same month:
LocalDate date = ...
LocalDate fifthThu = date.with(TemporalAdjusters.dayOfWeekInMonth(5, THURSDAY));
int thursdays = (fifthThu.getMonth() == date.getMonth() ? 5 : 4);
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