Lamma I/O

Advanced Date Generation

All code used in this tutorial can be found here: Scala / Java

Some of the below problems might occur to you after Basic Date Generation:

  • Sometimes you need to manually adjust start date or end date.
    For example, if we want to generate something like all Fridays for the next 3 months starting from today, then we will have to first figure out the first Friday (if not today), and then use that Friday as from date.
  • Not able to handle irregular recurring pattern. For example, if we want to generate last Monday of every month:
    • by month cannot be used because not every month's last Monday are on the same day of month
    • by (4 weeks) cannot be used because some months have 5 Mondays

To solve all the above, Lamma introduces a new concept called positioning. The syntax will be from_date to to_date by (frequency) on (position)

Following examples are demos of how to use the positioning concept to overcome these limitations.


Generate dates of a specific day of a week within a certain period

Generate dates on Tuesday for every third week from 2014-05-10 to 2014-07-01

(2014, 5, 10) to (2014, 7, 1) by (3 weeks) on Tuesday
Dates.from(2014, 5, 10).to(2014, 7, 1).byWeeks(3).on(DayOfWeek.TUESDAY).build();

Result: Lamma will position the first specified Tuesday first, and then recur for every 3 weeks.

List(2014-05-13, 2014-06-03, 2014-06-24)


Generate dates on the nth day of a month

Generate the tenth day of every month from 2014-05-01 to 2014-07-30

(2014, 5, 1) to (2014, 7, 30) by month on (10 th day)  // remember the () for the positioner
Dates.from(2014, 5, 1).to(2014, 7, 30).byMonth().on(Locators.nthDay(10)).build();

Result: Lamma will first position the 10th of May, and then recur every month.

List(2014-05-10, 2014-06-10, 2014-07-10)


Generate dates on the nth weekday of a month

Generate every second Friday of every two moths from 2014-05-01 to 2014-09-30

(2014, 5, 1) to (2014, 9, 30) by (2 months) on (2 nd Friday)
// Locators is used to build position
Dates.from(2014, 5, 1).to(2014, 9, 30).byMonths(2).on(Locators.nth(2, DayOfWeek.FRIDAY)).build();

Result: Lamma will position the first Second Friday of the Month on or after the starting date (in this case, May), then recur every two months.

List(2014-05-09, 2014-07-11, 2014-09-12)


Generate a date sequence by position of year

Generate the last Tuesday of each year from 2014-01-01 to 2016-12-31

(2014, 1, 1) to (2016, 12, 31) by year on lastTuesday
// Locators is used to build position
Dates.from(2014, 1, 1).to(2016, 12, 31).byYear().on(Locators.last(DayOfWeek.TUESDAY)).build();

Result: Lamma will position the next last Tuesday of the year and then recur every one year.

List(2014-12-30, 2015-12-29, 2016-12-27)


A more complicated example combining things together

Generate all the third Friday of February for every 3 years in 2010s

(2010, 1, 1) to (2019, 12, 31) by (3 years) on (3 rd Friday of February)
// Locators is used to build position
Dates.from(2010, 1, 1).to(2019, 12, 31).byYears(3).on(Locators.nth(3, DayOfWeek.FRIDAY).of(Month.FEBRUARY)).build();

Result: Again, Lamma will automatically find the first occurrence of the third Friday of Feb, and then recur every 3 years

List(2010-02-19, 2013-02-15, 2016-02-19, 2019-02-15)


Customize DayOfMonth and DayOfYear

By implementing io.lamma.DayOfMonth or io.lamma.DayOfYear, one can easily customize the positioning behavior.


A sample of customised DayOfMonth

Generate dates containing the first day in February and the third day for other months from 2014-01-01 to 2014-03-30

case object MyPositionOfMonth extends DayOfMonth {
 override def isValidDOM(d: Date) = {
   if (d.month == February) {
     d.dd == 1
   } else {
     d.dd == 3
   }
 }
}

(2014, 1, 1) to (2014, 3, 31) by month on MyPositionOfMonth
static class MyPositionOfMonth implements DayOfMonth {
 @Override
 public boolean isValidDOM(Date d) {
   return d.month() == Month.FEBRUARY ? d.dd() == 1 : d.dd() == 3;
 }
}

Dates.from(2014, 1, 1).to(2014, 3, 31).byMonth().on(new MyPositionOfMonth()).build();

Result: Customised DayOfMonth is now used to position different recurring dates of different months.

List(2014-01-03, 2014-02-01, 2014-03-03)


Shifting and Selecting generated result

You can further shift and select dates based on the generated results.


Generate a list of dates and then shift by calendar days

Select the third last day of each month from January to March 2014

(2014, 1, 1) to (2014, 3, 31) by month on lastDay shift -2
Dates.from(2014, 1, 1).to(2014, 3, 31).byMonth().on(Locators.lastDay()).shift(-2).build();

Result: last dates of each month will first be generated by Months(1, LastDayOfMonth), and then each of them will be shifted by -2 days

List(2014-01-29, 2014-02-26, 2014-03-29)


Shift by working days example

Generate the second last working day before the end of each month from January to March 2014

(2014, 1, 1) to (2014, 3, 31) by month on lastDay shift(-2, Weekends)
Dates.from(2014, 1, 1).to(2014, 3, 31).byMonth().on(Locators.lastDay()).shift(-2, HolidayRules.weekends()).build();

Result: last dates of each month will first be generated by Months(1, LastDayOfMonth), then each of them will be shifted by -2 working days. Note the last date generated here is different from the previous example.

List(2014-01-29, 2014-02-26, 2014-03-27)


Select shifted dates

Generate the third last day of each month from January to March 2014 and then select with Forward convention

(2014, 1, 1) to (2014, 3, 31) by month on lastDay shift -2 forward Weekends
Dates.from(2014, 1, 1).to(2014, 3, 31).byMonth().on(Locators.lastDay()).shift(-2).forward(HolidayRules.weekends()).build();

Result: dates are generated and shifted as previous examples, and then Forward convention will be applied to make sure the result dates are working days. Note the last date is different from previous two examples.

List(2014-01-29, 2014-02-26, 2014-03-31)


Edge cases on date sequence generation


Very long recurring period for forward generation pattern

Generate dates from 2014-01-01 to 2014-03-31 for every 6 months

(2014, 1, 1) to (2014, 3, 31) by (6 months)
Dates.from(2014, 1, 1).to(2014, 3, 31).byMonths(6).build();

Result: The first recurrence day 2014-07-01 is already after to date, in this case only from date is returned.

List(2014-01-01)


Very long recurring period for backward generation pattern

Generate dates from 2014-03-31 to 2014-01-01 for every 6 months in backward direction

(2014, 3, 31) to (2014, 1, 1) by (-6 months)
Dates.from(2014, 3, 31).to(2014, 1, 1).byMonths(-6).build();

Result: The first recurrence day 2013-09-31 is already before from date, so in this case only to date is returned.

List(2014-03-31)


From date equals to To date

Generate dates from 2014-01-01 to 2014-01-01 for every 6 months

(2014, 1, 1) to (2014, 1, 1) by (6 months)
Dates.from(2014, 1, 1).to(2014, 1, 1).byMonth().build();

Result: Simply return a single date list

List(2014-01-01)