HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavaMinor

Let's have a meetup

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
lethavemeetup

Problem

Problem statement:


Calculate the date of meetups.


Typically meetups happen on the same day of the week.


Examples are



  • the first Monday



  • the third Tuesday



  • the Wednesteenth



  • the last Thursday





Note that "Monteenth", "Tuesteenth", etc are all made up words. There
was a meetup whose members realised that there are exactly 7 days that
end in '-teenth'. Therefore, one is guaranteed that each day of the
week (Monday, Tuesday, ...) will have exactly one date that is named
with '-teenth' in every month.

Code:

import org.joda.time.DateTime;
import org.joda.time.Weeks;
import org.joda.time.Period;
import static org.joda.time.DateTimeConstants.*;

enum MeetupSchedule {
    FIRST,
    SECOND,
    THIRD,
    FOURTH,
    LAST,
    TEENTH
}

public class Meetup {
  private final int month;
  private final int year;
  private final DateTime firstOfMonth;

  public Meetup(int month, int year) {
    this.month = month;
    this.year = year;
    firstOfMonth = new DateTime(year, month, 1, 0, 0);
  }

  public DateTime day(int dayOfWeek, MeetupSchedule schedule) {
    switch (schedule) {
      case FIRST:
        return getNthWeekDayOfMonth(dayOfWeek, 1);
      case SECOND:
        return getNthWeekDayOfMonth(dayOfWeek, 2);
      case THIRD:
        return getNthWeekDayOfMonth(dayOfWeek, 3);
      case FOURTH:
        return getNthWeekDayOfMonth(dayOfWeek, 4);
      case LAST:
        return getNthWeekDayOfMonth(dayOfWeek, 6);
      case TEENTH:
        return getTeenthWeekDayOfMonth(dayOfWeek);
      default:
        return new DateTime(year, month, 31, 0, 0);
    }
  }

  private DateTime getNthWeekDayOfMonth(int dayOfWeek, int nthWeekDay) {
    DateTime current = getFirstDayOfWeekForMonth(firstOfMonth, dayOfWeek);
    int weekCount = 1;
    while (current.getMonthOfYear() == month && weekCount  12 && day < 20;
  }
}


Test Suite:

```
import org.joda.time.DateTime;
import org.joda.time.DateTimeConstants;
import org.junit

Solution

Your current code is good and I only have minor comments:

  • Instead of plus(Period.days(7)), consider using plusWeeks(1), which is clearer.



  • To get the last day of week in a month, you're saying that you want to get the 6th day of week. This will work since there will always be less than 6 weeks in a month but it is a bit weird.



But... this is a perfect challenge for it:
Use the Java Time API!

Starting with Java 8, you don't need to use JodaTime. The Java Time API will greatly simplify your code.

For that, we need to consider what your code wants to do:

  • When the schedule is FIRST, you want to adjust the date into the 1st given day of week in the same month.



  • When the schedule is SECOND, you want to adjust the date into the 2nd given day of week in the same month.



  • When the schedule is THIRD, you want to adjust the date into the 3rd given day of week in the same month.



  • When the schedule is FOURTH, you want to adjust the date into the 4th given day of week in the same month.



  • When the schedule is LAST, you want to adjust the date into the last given day of week in the same month.



  • When the schedule is TEENTH, you want to adjust the date into the given day of week in the same month with a day between 13 and 19.



What this really means is that each of your enum should have a single property, which is function taking the day of week and returning the TemporalAdjuster to apply to it. This interface

Fortunately, the Java Time API already has some of those adjusters built-in:

  • dayOfWeekInMonth(ordinal, dayOfWeek) will adjust any temporal objects into a date in the same month having the given day of week.



  • lastInMonth(dayOfWeek) will adjust any temporal objects into a date with the last given day of week in the same month.



  • firstInMonth(dayOfWeek) will adjust any temporal objects into a date with the first given day of week in the same month.



What's missing is the one for the teenth case, but it is easy to build it:

private static TemporalAdjuster teenthDayOfWeekInMonth(DayOfWeek dayOfWeek) {
    return temporal -> {
        Temporal temp = temporal.with(TemporalAdjusters.firstInMonth(dayOfWeek));
        int weeksDiff = (19 - temp.get(ChronoField.DAY_OF_MONTH)) / 7;
        return temp.plus(weeksDiff, ChronoUnit.WEEKS);
    };
}


This adjuster works by setting a given date to the first given day of week in the same month (firstInMonth(dayOfWeek)), then it calculates how many weeks is there to add. We're looking to adjust into a date having a day of month between 13 and 19, so the number of weeks to add is simply the number of weeks between the 19 of that month and the current day of month (you may want to extract that 19 into a constant also).

Putting this into code, you get:

enum MeetupSchedule {
    
    FIRST(dayOfWeek -> TemporalAdjusters.firstInMonth(dayOfWeek)),
    SECOND(dayOfWeek -> TemporalAdjusters.dayOfWeekInMonth(2, dayOfWeek)),
    THIRD(dayOfWeek -> TemporalAdjusters.dayOfWeekInMonth(3, dayOfWeek)),
    FOURTH(dayOfWeek -> TemporalAdjusters.dayOfWeekInMonth(4, dayOfWeek)),
    LAST(dayOfWeek -> TemporalAdjusters.lastInMonth(dayOfWeek)),
    TEENTH(dayOfWeek -> teenthDayOfWeekInMonth(dayOfWeek));
    
    private static TemporalAdjuster teenthDayOfWeekInMonth(DayOfWeek dayOfWeek) {
        return temporal -> {
            Temporal temp = temporal.with(TemporalAdjusters.firstInMonth(dayOfWeek));
            int weeksDiff = (19 - temp.get(ChronoField.DAY_OF_MONTH)) / 7;
            return temp.plus(weeksDiff, ChronoUnit.WEEKS);
        };
    }
    
    private final Function adjuster;
    
    private MeetupSchedule(Function adjuster) {
        this.adjuster = adjuster;
    }

    public TemporalAdjuster schedule(DayOfWeek dayOfWeek) {
        return adjuster.apply(dayOfWeek);
    }
    
}

public class Meetup {

    private final LocalDate firstOfMonth;

    public Meetup(int month, int year) {
        firstOfMonth = LocalDate.of(year, month, 1);
    }

    public DateTime day(int dayOfWeek, MeetupSchedule schedule) {
        LocalDate result = firstOfMonth.with(schedule.schedule(DayOfWeek.of(dayOfWeek)));
        return new DateTime(result.getYear(), result.getMonthValue(), result.getDayOfMonth(), 0, 0);
    }
    
}


With such an implementation, all of the tests still pass. Of course, it is now a bit awkward since the test are written with JodaTime. It would be best to rewrite them to also use the Java Time API; in the above code, I adapted the implementation to return Joda classes so that the current tests still pass.

Code Snippets

private static TemporalAdjuster teenthDayOfWeekInMonth(DayOfWeek dayOfWeek) {
    return temporal -> {
        Temporal temp = temporal.with(TemporalAdjusters.firstInMonth(dayOfWeek));
        int weeksDiff = (19 - temp.get(ChronoField.DAY_OF_MONTH)) / 7;
        return temp.plus(weeksDiff, ChronoUnit.WEEKS);
    };
}
enum MeetupSchedule {
    
    FIRST(dayOfWeek -> TemporalAdjusters.firstInMonth(dayOfWeek)),
    SECOND(dayOfWeek -> TemporalAdjusters.dayOfWeekInMonth(2, dayOfWeek)),
    THIRD(dayOfWeek -> TemporalAdjusters.dayOfWeekInMonth(3, dayOfWeek)),
    FOURTH(dayOfWeek -> TemporalAdjusters.dayOfWeekInMonth(4, dayOfWeek)),
    LAST(dayOfWeek -> TemporalAdjusters.lastInMonth(dayOfWeek)),
    TEENTH(dayOfWeek -> teenthDayOfWeekInMonth(dayOfWeek));
    
    private static TemporalAdjuster teenthDayOfWeekInMonth(DayOfWeek dayOfWeek) {
        return temporal -> {
            Temporal temp = temporal.with(TemporalAdjusters.firstInMonth(dayOfWeek));
            int weeksDiff = (19 - temp.get(ChronoField.DAY_OF_MONTH)) / 7;
            return temp.plus(weeksDiff, ChronoUnit.WEEKS);
        };
    }
    
    private final Function<DayOfWeek, TemporalAdjuster> adjuster;
    
    private MeetupSchedule(Function<DayOfWeek, TemporalAdjuster> adjuster) {
        this.adjuster = adjuster;
    }

    public TemporalAdjuster schedule(DayOfWeek dayOfWeek) {
        return adjuster.apply(dayOfWeek);
    }
    
}

public class Meetup {

    private final LocalDate firstOfMonth;

    public Meetup(int month, int year) {
        firstOfMonth = LocalDate.of(year, month, 1);
    }

    public DateTime day(int dayOfWeek, MeetupSchedule schedule) {
        LocalDate result = firstOfMonth.with(schedule.schedule(DayOfWeek.of(dayOfWeek)));
        return new DateTime(result.getYear(), result.getMonthValue(), result.getDayOfMonth(), 0, 0);
    }
    
}

Context

StackExchange Code Review Q#127619, answer score: 3

Revisions (0)

No revisions yet.