1. Overview
Determining all years that start on a Sunday might seem like a simple requirement. However, it can be quite relevant in various real-world scenarios or business use cases. Specific dates and calendar structures often influence operations, events, or schedules. For instance, such a requirement might arise in a holiday or religious event scheduling, or payroll and work schedule planning.
In this tutorial, we’ll look at three approaches to find all the years that start on a Sunday within a given range. We’ll first look at a solution using Date and Calendar, and then see how to use the modern java.time API. Finally, we’ll include an optimized example using Spliterator<LocalDate>.
2. Legacy Solution
To start with, we’ll use the legacy Calendar class to check whether January 1st of each year, for a given range, falls on a Sunday:
public class FindSundayStartYearsLegacy {
public static List<Integer> getYearsStartingOnSunday(int startYear, int endYear) {
List<Integer> years = new ArrayList<>();
for (int year = startYear; year <= endYear; year++) {
Calendar calendar = new GregorianCalendar(year, Calendar.JANUARY, 1);
if (calendar.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY) {
years.add(year);
}
}
return years;
}
}
First, we create a Calendar object for each year with the date set to January 1st. We then use the calendar.get(Calendar.DAY_OF_WEEK) method, to check if the day is a Sunday. We’ll test our FindSundayStartYearsLegacy implementation using the year range from 2000 to 2025. We expect a result which includes the four years that start on a Sunday: 2006, 2012, 2017, and 2023:
@Test
public void givenYearRange_whenCheckingStartDayLegacy_thenReturnYearsStartingOnSunday() {
List<Integer> expected = List.of(2006, 2012, 2017, 2023);
List<Integer> result = FindSundayStartYearsLegacy.getYearsStartingOnSunday(2000, 2025);
assertEquals(expected, result);
}
3. New API Solution
Let’s solve the same problem using the java.time package introduced in Java 8. We’ll use LocalDate to check if January 1st of each year starts on a Sunday:
public class FindSundayStartYearsTimeApi {
public static List<Integer> getYearsStartingOnSunday(int startYear, int endYear) {
List<Integer> years = new ArrayList<>();
for (int year = startYear; year <= endYear; year++) {
LocalDate firstDayOfYear = LocalDate.of(year, 1, 1);
if (firstDayOfYear.getDayOfWeek() == DayOfWeek.SUNDAY) {
years.add(year);
}
}
return years;
}
}
We use here LocalDate.of(year, 1, 1) to represent January 1st of each year. Then, we use the getDayOfWeek() method, to check if the day is a Sunday. Next, we validate the solution using the java.time API in the FindSundayStartYearsTimeApi class, with the same input and expected result as in the previous test:
@Test
public void givenYearRange_whenCheckingStartDayTimeApi_thenReturnYearsStartingOnSunday() {
List<Integer> expected = List.of(2006, 2012, 2017, 2023);
List<Integer> result = FindSundayStartYearsTimeApi.getYearsStartingOnSunday(2000, 2025);
assertEquals(expected, result);
}
4. Enhancements Using Streams
Let’s look at how we can use the Spliterator<LocalDate> to iterate through the years and filter out the ones that start on a Sunday. The Spliterator is useful for efficiently traversing large data ranges:
public class FindSundayStartYearsSpliterator {
public static List<Integer> getYearsStartingOnSunday(int startYear, int endYear) {
List<Integer> years = new ArrayList<>();
Spliterator<LocalDate> spliterator = Spliterators.spliteratorUnknownSize(
Stream.iterate(LocalDate.of(startYear, 1, 1), date -> date.plus(1, ChronoUnit.YEARS))
.limit(endYear - startYear + 1)
.iterator(), Spliterator.ORDERED);
Stream<LocalDate> stream = StreamSupport.stream(spliterator, false);
stream.filter(date -> date.getDayOfWeek() == DayOfWeek.SUNDAY)
.forEach(date -> years.add(date.getYear()));
return years;
}
}
Here we create a Spliterator<LocalDate> using Stream.iterator() to iterate over the first day of each year in the given range. Next, the StreamSupport.stream() method converts the Spliterator into a Stream<LocalDate>. Additionally, we use filter() to check if the first day of each year is a Sunday. Finally, we populate the array with the valid entries, which we return at the end. Now we’ll test the Streams-based implementation in the FindSundayStartYearsSpliterator class:
@Test
public void givenYearRange_whenCheckingStartDaySpliterator_thenReturnYearsStartingOnSunday() {
List<Integer> expected = List.of(2006, 2012, 2017, 2023);
List<Integer> result = FindSundayStartYearsSpliterator.getYearsStartingOnSunday(2000, 2025);
assertEquals(expected, result);
}
5. Conclusion
In this article, we looked at three options to find the years that start on Sunday, for a given range. Although Date and Calendar are usable, they come with issues such as mutability, clunky APIs, and deprecated methods. This legacy solution is straightforward but not the best practice for new development. In general, the java.time package provides a cleaner and more robust API for handling dates and times. It’s type-safe, immutable, and easier to work with than Date and Calendar. Consequently, using Spliterator in combination with Stream is a functional and efficient way to process large data ranges, allowing parallelization and lazy evaluation. The java.time approach is the recommended method for handling dates and times in Java. The Spliterator solution adds flexibility and performance benefits. The complete source code for this article can be found over on GitHub.