Is there an elegant way to find the nearest day of the week for a given date using JodaTime? I initially thought setCopy()
would be it, but this sets the day to the particular day in the same week. Thus, if ld
is 2011-11-27
and day
is "Monday" the following function returns 2011-11-21
, and not 2011-11-28
as I want.
// Note that "day" can be _any_ day of the week, not just weekdays.
LocalDate getNearestDayOfWeek(LocalDate ld, String day) {
return ld.dayOfWeek().setCopy(day);
}
Desired output for various inputs:
2011-12-04, Monday => 2011-12-05
2011-12-04, Tuesday => 2011-12-06
2011-12-04, Wednesday => 2011-12-07
2011-12-04, Thursday => 2011-12-01
2011-12-04, Friday => 2011-12-02
2011-12-04, Saturday => 2011-12-03
2011-12-04, Sunday => 2011-12-04
2011-12-05, Monday => 2011-12-05
2011-12-05, Tuesday => 2011-12-06
2011-12-05, Wednesday => 2011-12-07
2011-12-05, Thursday => 2011-12-08
2011-12-05, Friday => 2011-12-02
2011-12-05, Saturday => 2011-12-03
2011-12-05, Sunday => 2011-12-04
Below is a work-around I came up with that works for the particular constraints in my current situation, but I'd love to get help find a completely generic solution that works always.
LocalDate getNearestDayOfWeek(LocalDate ld, String day) {
LocalDate target = ld.dayOfWeek().setCopy(day);
if (ld.getDayOfWeek() > DateTimeConstants.SATURDAY) {
target = target.plusWeeks(1);
}
return target;
}
In Jodatime, this kind of thing should be doable with three or four lines:
/** Given a reference LocalDate and a day of week, eg DateTimeConstants.MONDAY
Returns the nearest date with that day of week */
public static LocalDate getNearestDayOfWeek(LocalDate t0,int dow) {
LocalDate t1 = t0.withDayOfWeek(dow);
LocalDate t2 = t1.isBefore(t0) ? t1.plusWeeks(1) : t1.minusWeeks(1);
return Math.abs(Days.daysBetween(t1, t0).getDays()) <
Math.abs(Days.daysBetween(t2, t0).getDays()) ? t1 : t2;
}
Or more compact and efficient:
public static LocalDate getNearestDayOfWeek(LocalDate t0, int dow) {
LocalDate t1 = t0.withDayOfWeek(dow);
if (t1.isBefore(t0.minusDays(3))) return t1.plusWeeks(1);
else if (t1.isAfter(t0.plusDays(3))) return t1.minusWeeks(1);
else return t1;
}
And if you want to pass the day-of-the-week as String:
public static LocalDate getNearestDayOfWeek(LocalDate t0, String dow) {
return getNearestDayOfWeek(t0,t0.dayOfWeek().setCopy(dow).getDayOfWeek());
}
Example:
// prints 2011-11-28
public static void main(String[] args) throws Exception {
LocalDate today = new LocalDate(2011,11,27);
int dow = DateTimeConstants.MONDAY;
System.out.println(getNearestDayOfWeek(today ,dow ));
}
This finds the nearest day of the week by defining an interval of closest days of the week. Joda defines a week as starting on Monday. So if today is Tuesday and the day of the week is set to Sunday, the date will be for the following Sunday, not the previous. If the first day of the week is redefined to be Sunday, the date returned will be for the previous Sunday. The following code isn't effected by the definition of the first day of the week.
DateTime getNearestDayOfWeek(DateTime dateTime, String day) { //Create an interval containing the nearest days of the week. DateTime begin = dateTime.minusHours(DateTimeConstants.HOURS_PER_WEEK/2).dayOfWeek().roundHalfCeilingCopy(); DateTime end = dateTime.plusHours(DateTimeConstants.HOURS_PER_WEEK/2).dayOfWeek().roundHalfCeilingCopy(); Interval interval = new Interval(begin, end); //Adjust nearest day to be within the interval. Doesn't depend on definition of first day of the week. DateTime nearest = dateTime.dayOfWeek().setCopy(day); if (interval.isAfter(nearest)) //nearest is before the interval return nearest.plusWeeks(1); else if (interval.isBefore(nearest)) //nearest is after the interval return nearest.minusWeeks(1); else return nearest; }
Something like this. For the dayOfWeek parameter, use the constants defined in org.joda.time.DateTimeConstants:
public LocalDate getNext(int dayOfWeek) {
LocalDate today = new LocalDate();
return getNext(dateOfWeek, today);
}
public LocalDate getNext(int dayOfWeek, LocalDate fromDate) {
int dayOffset = DateTimeConstants.DAYS_PER_WEEK - dayOfWeek + 1;
LocalDate weekContainingDay = fromDate.plusDays(dayOffset);
return weekContainingDay.withDayOfWeek(dayOfWeek);
}
Usage:
LocalDate nextSunday = foo.getNext(DateTimeConstants.SUNDAY);
There's a good API in Java 8 for this purpose called TemporalAdjuster
:
LocalDate today = LocalDate.now();
TemporalAdjuster adjustToNextWed = TemporalAdjusters.next(DayOfWeek.WEDNESDAY);
TemporalAdjuster adjustToNexOrSametWed = TemporalAdjusters.nextOrSame(DayOfWeek.WEDNESDAY);
LocalDate nextWed = today.with(adjustToNextWed);
LocalDate nextWedOrToday = today.with(adjustToNextOrSameWed);
The java.time.temporal.TemporalAdjusters
class contains factories some common use-cases:
firstDayOfMonth, lastDayOfMonth, firstDayOfNextMonth, firstDayOfYear, lastDayOfYear, firstDayOfNextYear, firstInMonth, lastInMonth, dayOfWeekInMonth, next, nextOrSame, previous, previousOrSame
As you can see, there is no factory for "nearest", but you can create your own. After checking the source of these methods, making your own is quite straightforward:
// This implementation is expanded and verbose for clarity,
// see simplified version below
public static TemporalAdjuster nearest(DayOfWeek dayOfWeek) {
int targetDay = dayOfWeek.getValue(); // range: +1..+7
return (temporal) -> {
int originalDay = temporal.get(DAY_OF_WEEK); // range: +1..+7
// difference between the target (1..7) and original (1..7) weekdays
int adjustDays = targetDay - originalDay; // range: -6..+6
if (adjustDays <= -4) {
// if the adjustment is 4 or more days ago,
// next week is closer:
adjustDays += 7;
}
if (adjustDays >= 4) {
// if the adjustment is 4 or more days in future,
// previous week is closer:
adjustDays -= 7;
}
return temporal.plus(adjustDays, DAYS);
};
}
Using this is then quite straightforward:
LocalDate date = LocalDate.now(); // some date to adjust, from your code
date.with(nearest(DayOfWeek.TUESDAY)); // the nearest Tuesday from the date
It can be simplified to a single expression to convert the range of -6 to +6 to -3..+3:
public static TemporalAdjuster nearest(DayOfWeek dayOfWeek) {
int targetDay = dayOfWeek.getValue();
return (temporal) -> {
int originalDay = temporal.get(DAY_OF_WEEK);
final int adjustDays = ((targetDay - originalDay + 10) % 7) - 3;
return temporal.plus(adjustDays, DAYS);
};
}
Here's a full implementation with tests for all combinations of days. You can run the program to verify results, yielding output like:
...
original day: MONDAY, target day THURSDAY, difference: -3
nearest THURSDAY to MONDAY 2021-05-03 is 2021-05-06
original day: MONDAY, target day FRIDAY, difference: -4
nearest FRIDAY to MONDAY 2021-05-03 is 2021-04-30
...
original day: MONDAY, target day FRIDAY, difference: 4
nearest FRIDAY to MONDAY 2021-05-03 is 2021-04-30
....
Solution:
// file TestNearest.java
class TestNearest {
public static LocalDate nearestDayOfWeek(LocalDate originalDate, DayOfWeek dayOfWeek) {
return originalDate.with(nearest(dayOfWeek));
}
public static TemporalAdjuster nearest(DayOfWeek dayOfWeek) {
int targetDay = dayOfWeek.getValue();
return (temporal) -> {
int originalDay = temporal.get(ChronoField.DAY_OF_WEEK);
final int adjustment = ((targetDay - originalDay + 10) % 7) - 3;
return temporal.plus(adjustment, ChronoUnit.DAYS);
};
}
public static void main(String[] args) {
for (int original = 1; original <= 7; original++) {
for (int target = 1; target <= 7; target++) {
final DayOfWeek originalDayOfWeek = DayOfWeek.of(original);
final DayOfWeek targetDayOfWeek = DayOfWeek.of(target);
// Create a test date:
final LocalDate testOriginalDate = LocalDate.now()
.with(TemporalAdjusters.dayOfWeekInMonth(1, originalDayOfWeek));
final LocalDate nearestDate = nearestDayOfWeek(testOriginalDate, targetDayOfWeek);
debug(testOriginalDate, targetDayOfWeek, nearestDate);
}
}
}
private static void debug(LocalDate original, DayOfWeek target, LocalDate result) {
System.out.println("original day: " + original.getDayOfWeek() +
", target day " + target +
", difference: " + (target.getValue() - original.getDayOfWeek().getValue()));
System.out.println(" nearest " + (result.getDayOfWeek()) + " to " +
(original.getDayOfWeek()) + " " +
original +
" is " +
result);
System.out.println();
}
}
Based on Richard Povinelli's answer, but updated to use Java Time (as of Java 8)
public static LocalDate getNearestDayOfWeek(LocalDate date, DayOfWeek dayOfWeek) {
LocalDate start = date.minusDays(3);
LocalDate end = date.plusDays(3);
LocalDate guessDate = date.with(dayOfWeek);
// the nearest day is between start and end, so we adjust our guess if required
if (guessDate.isAfter(end)) {
// guessed one week to late
return guessDate.minusWeeks(1);
} else if (guessDate.isBefore(start)) {
// guessed one week to early
return guessDate.plusWeeks(1);
} else {
// the guess was correct
return guessDate;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With