patternjavaMinor
Let's have a meetup
Viewed 0 times
lethavemeetup
Problem
Problem statement:
Calculate the date of meetups.
Typically meetups happen on the same day of the week.
Examples are
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:
Test Suite:
```
import org.joda.time.DateTime;
import org.joda.time.DateTimeConstants;
import org.junit
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:
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:
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
Fortunately, the Java Time API already has some of those adjusters built-in:
What's missing is the one for the teenth case, but it is easy to build it:
This adjuster works by setting a given date to the first given day of week in the same month (
Putting this into code, you get:
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.
- Instead of
plus(Period.days(7)), consider usingplusWeeks(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 interfaceFortunately, 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.