Annual calendar of weeks
To expand on the correct Answer by Arvind Kumar Avinash, let’s get the weekly calendar for a year. And for our week, let’s use a third-party class designed to represent a span-of-time of days. We collect a map of month as key and list of weeks as value. From that map we can retrieve any particular week.
ThreeTen-Extra library, LocalDateRange
class
Add the ThreeTen-Extra library to your project. This gives us the LocalDateRange
class to represent a span of time as a pair of LocalDate
objects. This class provides many convenient methods such as abuts
, contains
, and so on, likely to be helpful when you are working with weeks.
Specify a Year
and a Locale
. From the locale we determine the first day-of-week as shown in Answer by Avinash.
Loop each month, then each week
We loop every month of the year, using an array of Month
objects returned by the Month.values
method on this enum. We combine each month with the year to get a YearMonth
object.
For each YearMonth
, we get the first day of the month. Then we move back in time to get the first day-of-week (DayOfWeek
), or use the same date if it is indeed our desired day-of-week. A TemporalAdjuster
moves us along the timeline. You can write your own TemporalAdjuster
implementation, but we can find one already written for our needs here: TemporalAdjusters.previousOrSame( DayOfWeek )
.
Then we loop to calculate each week of the month, making a LocalDateRange
from the pair of LocalDate
objects for start and end of each week. We collect those LocalDateRange
objects into a NavigableSet
, a TreeSet
.
We report our results by adding each YearMonth
as the key to a NavigableMap
(TreeMap
), with the value being our NavigableSet
of LocalDateRange
.
Example code
package work.basil.datetime;
import org.threeten.extra.LocalDateRange;
import java.time.*;
import java.time.temporal.TemporalAdjuster;
import java.time.temporal.TemporalAdjusters;
import java.time.temporal.WeekFields;
import java.util.*;
public final class WeeklyCalendar {
public static final NavigableMap < YearMonth, List < LocalDateRange > > calculate ( final Year year , final Locale locale ) {
Objects.requireNonNull( year );
DayOfWeek startOfWeek = WeekFields.of( Objects.requireNonNull( locale ) ).getFirstDayOfWeek();
NavigableMap < YearMonth, List < LocalDateRange > > navMap = new TreeMap <>();
TemporalAdjuster adjuster = TemporalAdjusters.previousOrSame( startOfWeek );
for ( Month month : Month.values() ) {
List < LocalDateRange > weeks = new ArrayList <>();
YearMonth yearMonth = year.atMonth( month );
LocalDate start = yearMonth.atDay( 1 ).with( adjuster );
LocalDate end = start.plusWeeks( 1 );
do {
LocalDateRange week = LocalDateRange.of( start , end );
weeks.add( week );
// Set up next loop.
start = start.plusWeeks( 1 );
end = start.plusWeeks( 1 );
} while ( ! YearMonth.from( end ).isAfter( yearMonth ) );
navMap.put( yearMonth , weeks );
}
return Collections.unmodifiableNavigableMap( navMap ); // Generally best to return immutable values.
}
}
Usage:
Year year = Year.of( 2021 );
Locale locale = new Locale( "pt" , "BR" );
NavigableMap < YearMonth, List < LocalDateRange > > map = WeeklyCalendar.calculate( year , locale );
LocalDateRange weekOneOfMarch2021 = map.get( YearMonth.of( 2021 , Month.MARCH ) ).get( 0 ); // Lists use annoying zero-based counting, index number.
When run.
map = {2021-01=[2020-12-27/2021-01-03, 2021-01-03/2021-01-10, 2021-01-10/2021-01-17, 2021-01-17/2021-01-24, 2021-01-24/2021-01-31], 2021-02=[2021-01-31/2021-02-07, 2021-02-07/2021-02-14, 2021-02-14/2021-02-21, 2021-02-21/2021-02-28], 2021-03=[2021-02-28/2021-03-07, 2021-03-07/2021-03-14, 2021-03-14/2021-03-21, 2021-03-21/2021-03-28], 2021-04=[2021-03-28/2021-04-04, 2021-04-04/2021-04-11, 2021-04-11/2021-04-18, 2021-04-18/2021-04-25], 2021-05=[2021-04-25/2021-05-02, 2021-05-02/2021-05-09, 2021-05-09/2021-05-16, 2021-05-16/2021-05-23, 2021-05-23/2021-05-30], 2021-06=[2021-05-30/2021-06-06, 2021-06-06/2021-06-13, 2021-06-13/2021-06-20, 2021-06-20/2021-06-27], 2021-07=[2021-06-27/2021-07-04, 2021-07-04/2021-07-11, 2021-07-11/2021-07-18, 2021-07-18/2021-07-25], 2021-08=[2021-08-01/2021-08-08, 2021-08-08/2021-08-15, 2021-08-15/2021-08-22, 2021-08-22/2021-08-29], 2021-09=[2021-08-29/2021-09-05, 2021-09-05/2021-09-12, 2021-09-12/2021-09-19, 2021-09-19/2021-09-26], 2021-10=[2021-09-26/2021-10-03, 2021-10-03/2021-10-10, 2021-10-10/2021-10-17, 2021-10-17/2021-10-24, 2021-10-24/2021-10-31], 2021-11=[2021-10-31/2021-11-07, 2021-11-07/2021-11-14, 2021-11-14/2021-11-21, 2021-11-21/2021-11-28], 2021-12=[2021-11-28/2021-12-05, 2021-12-05/2021-12-12, 2021-12-12/2021-12-19, 2021-12-19/2021-12-26]}
weekOneOfMarch2021 = 2021-02-28/2021-03-07
Notice how LocalDateRange#toString
uses the standard format of start date, then SOLIDUS /
, then end date.
Annual overlap
Notice that we start with days in the previous year. And we do not include some of the last days of the calendar-year.
Half-Open
Notice that the weeks here are defined using Half-Open approach where the beginning is inclusive while the ending is exclusive. This is usually the best approach in defining a span-of-time. I recommend using this approach consistently throughout your codebase to keep the logic consistent and thereby avoid errors and confusion.
Fully-Closed
However, if you insist on fully-closed approach, the LocalDateRange
class can support that. See the LocalDateRange.ofClosed
method.
Use at your own risk
I quickly whipped up this code just now. So no guarantees. Verify the logic, and perform your own tests.
ISO 8601 weeks
Be aware that there are several ways to define a week. Some industries commonly use a particular definition.
There is an international standard that is growing in popularity: ISO 8601. That standard defines weeks as Monday being the first day of the week, and week # 1 containing the first Thursday of the calendar year. Therefore a year is made of either 52 or 53 complete weeks of 7 days each.
The ThreeTen-Extra library offers a class to represent ISO 8601 weeks: YearWeek
.