Fluency of Java 8 Date-Time API

Here are a few quick examples of how API calls can be chained to answer advanced queries, such as "Which day is the last Friday of the next month". Or imagine you need match a weekly or monthly schedule. These and similar queries can be resolved without much of an effort.


Simple queries can be solved by a mere call chaining.

Example #1 - Last Friday in February 2015
 LocalDate date = LocalDate.now()  
         .withYear(2015)  
         .withMonth(2)  
         .with(TemporalAdjusters.lastInMonth(DayOfWeek.FRIDAY));  

 // Output: 2015-02-27
 System.out.println(date.format(DateTimeFormatter.ISO_LOCAL_DATE));

LocalDate and DayOfWeek come from the standard java.time package. As you can see, the API allows for a range of date adjustments. TemporalAdjusters is a utility class which comes in handy when you need to apply additional filtering.

More involved queries can still be resolved with a fairly low level of complexity. Let me introduce a custom class called DateTimeWizard. The class encapsulates all the logic needed for the examples below.

Example #2 - Work days (disregards public holidays) in the given month
1:  public class DateTimeWizard {  
2:    public List<LocalDate> workDays(int year, int month) {  
3:      LocalDate baseDate = LocalDate.now()  
4:        .withYear(year).withMonth(month);  
5:      LocalDate startDate = baseDate  
6:        .with(TemporalAdjusters.firstDayOfMonth());  
7:      LocalDate endDate = baseDate  
8:        .with(TemporalAdjusters.lastDayOfMonth());  
9:      List<LocalDate> workDays = new ArrayList<>();  
10:      while (!startDate.isAfter(endDate)) {  
11:        if (startDate.query(WorkDays::isWorkDay)) {  
12:          workDays.add(startDate);  
13:        }  
14:        startDate = startDate.plusDays(1);  
15:      }  
16:      return workDays;  
17:    }  
18:    static class WorkDays {  
19:      static boolean isWorkDay(TemporalAccessor date) {  
20:        int day = date.get(ChronoField.DAY_OF_WEEK);  
21:        return day >= 1 && day <= 5;  
22:      }  
23:    }  
24:  }  

Here is how we go about the implementation. First of all, we need to find the right point in time and milestones, which is represented by lines 3 - 8. Notice the use of TemporalAdjusters on lines 6 and 8.

Once we know the ranges we iterate within the found time span on a daily basis and determine if the given day is indeed a work day or not (lines 10 - 15). Since the WorkDays class exposes a static method, we can invoke it via a lambda expression (line 11).

Let's move to the last example dealing with a weekly schedule.

Example #3 - All days falling into a weekly schedule for the whole month
1:  public class DateTimeWizard {  
2:    public List<LocalDate> schedule(int year, int month, DayOfWeek... daysOfWeek) {  
3:      LocalDate baseDate = LocalDate.now()  
4:        .withYear(year).withMonth(month);  
5:      LocalDate startDate = baseDate  
6:        .with(TemporalAdjusters.firstDayOfMonth());  
7:      LocalDate endDate = baseDate  
8:        .with(TemporalAdjusters.lastDayOfMonth());  
9:      WeeklySchedule schedule = new WeeklySchedule(daysOfWeek);  
10:      List<LocalDate> scheduledDays = new ArrayList<>();  
11:      while (!startDate.isAfter(endDate)) {  
12:        if (startDate.query(schedule)) scheduledDays.add(startDate);  
13:        startDate = startDate.plusDays(1);  
14:      }  
15:      return scheduledDays;  
16:    }  
17:    private class WeeklySchedule implements TemporalQuery<Boolean> {  
18:      private DayOfWeek[] daysOfWeek;  
19:      WeeklySchedule(DayOfWeek[] daysOfWeek) {  
20:        this.daysOfWeek = daysOfWeek;  
21:      }  
22:      @Override  
23:      public Boolean queryFrom(TemporalAccessor date) {  
24:        boolean scheduled = false;  
25:        int day = date.get(ChronoField.DAY_OF_WEEK);  
26:        for (DayOfWeek dayOfWeek : daysOfWeek) {  
27:          if (dayOfWeek.get(ChronoField.DAY_OF_WEEK) == day) {  
28:            scheduled = true;  
29:            break;  
30:          }  
31:        }  
32:        return scheduled;  
33:      }  
34:    }  
35:  }  

Even though the approach is pretty much identical with the previous example (Example #2),
there is one subtle difference. The WeeklySchedule class encapsulating the query logic doesn't expose a static method anymore. Also note that it implements a TemporalQuery interface. The reason for that is the state (a range of scheduled week days) which makes us instantiate the query in advance and provide the intended schedule to it (line 9).

Full source code, including a bunch of unit tests, is available on github. There is one more case, I haven't described in here, which you might find interesting and that's a bi-weekly schedule. Check the oddEvenWeekSchedule method of the class DateTimeWizard.

That's about it. I hope I gave you an idea of how to use the API to your own advantage when it comes to dealing with various time constraints.