1

I'm trying to resolve the expression "last Friday the 13th" in jsr310, though if you can do it in Joda Time or some other library, that would be fine too. I got this far:

val builder = new DateTimeBuilder()
  .addFieldValue(ChronoField.DAY_OF_MONTH, 13)
  .addFieldValue(ChronoField.DAY_OF_WEEK, DayOfWeek.FRIDAY.getValue)

That seems to specify "Friday the 13th" okay. But how do I go from this to "last Friday the 13th"?

Steve
  • 3,038
  • 2
  • 27
  • 46

2 Answers2

1

Here a monthly iterating solution (remember there cannot be more than 14 months between two such dates) which is probably better than a daily iterating solution. I write it on base of JSR-310 in pure Java - not tested, therefore no guarantee (and I don't know to write Scala so you have to adapt it to your needs):

public static final TemporalAdjuster LAST_FRIDAY_13 = (Temporal temporal) -> {
  LocalDate test = LocalDate.from(temporal);

  // move to last 13th of month before temporal
  if (test.getDayOfMonth() <= 13) {
    test = test.minus(1, ChronoUnit.MONTHS);
  }

  test = test.withDayOfMonth(13);

  // iterate monthly backwards until it is a friday
  while (test.getDayOfWeek() != DayOfWeek.FRIDAY) {
    test = test.minus(1, ChronoUnit.MONTHS);
  }

  return test;
}

Note that the adjuster is stored as static constant (what is also recommended by the spec lead Stephen Colebourne). Then you can go and use this adjuster this way:

System.out.println(LocalDate.of(2012, 12, 12).with(LAST_FRIDAY_13));
// Output: 2012-07-13

By the way, you asked for a solution also in other libraries. Well, if you can wait for some few weeks (3-4) then I will provide a very similar solution using my new time library which would only require Java 6+. And you can surely translate the shown code to JodaTime, too (should be more or less straight away).

Meno Hochschild
  • 42,708
  • 7
  • 104
  • 126
  • This is definitely more efficient for the specific problem of Friday the 13th. It doesn't generalize very well to any other fields though. For example, if I switched to `DAY_OF_YEAR` instead of `DAY_OF_MONTH`, or if I added another constraint, like that it also has to be in February, your code couldn't be used directly. – Steve Jan 07 '14 at 06:16
  • Yes, it is specific that way that a combination of DAY_OF_MONTH and DAY_OF_WEEK is expected to configure the adjuster. For other conditions another algorithm is required anyway - in final consequence due to the irregularities of our calendar. There are endless possibilities. No library can offer all, but you are free to write your own adjuster for your specific problem. – Meno Hochschild Jan 07 '14 at 17:10
  • Note that with the answer I gave to this question, you don't need to write a new adjuster for each problem - it will handle any type of unit and any number of constraints. But it may be pretty difficult to make it as efficient as a custom coded solution to each problem. – Steve Jan 09 '14 at 13:46
0

The only solution I could come up with was to just walk backwards through the days and manually check whether or not the day satisfies the constraint. Here's a general class for walking backwards through time to find a DateTime that meets some constraints:

class PreviousAdjuster(constraints: (DateTimeField, Int)*) extends WithAdjuster {
  val unit = constraints.map(_._1.getBaseUnit).minBy(_.getDuration)
  def doWithAdjustment(dateTime: DateTime): DateTime = {
    var curr = dateTime
    while (constraints.exists{case (field, value) => curr.get(field) != value}) {
      curr = curr.minus(1, unit)
    }
    curr
  }
}

And then I can use that adjuster in the with method of a DateTime:

val lastFridayThe13th = LocalDate.of(2012, 12, 12).`with`(new PreviousAdjuster(
    ChronoField.DAY_OF_MONTH -> 13,
    ChronoField.DAY_OF_WEEK -> DayOfWeek.FRIDAY.getValue))

println(lastFridayThe13th) // prints 2012-07-13    

It feels like there should be a more efficient way of doing this, since the constraints mean we don't have to walk through every single day, but I'm not sure how to implement that...

Steve
  • 3,038
  • 2
  • 27
  • 46