如何找到 Java 日期时间 API 或 Joda 时间的第 n 个星期日?

How to find n'th previous Sunday with Java date time API or Joda-time?

我们如何计算前一个星期日或前一个星期日之前的星期日,或者一般来说如何找出 n 周之前的星期日?要注意的是,如果今天是星期天,那么它应该 return 今天是星期天而不是上周。

正在寻找 Joda-Time 或 Java 8 时间解决方案。

编辑: 我试过了

DateTime sunday = now
    .minusWeeks(1)
    .withDayOfWeek(DateTimeConstants.SUNDAY)
    .wi‌​thTimeAtStartOfDay()‌​;
DateTime previousWeekSunday = now
    .minusWeeks(2)
    .withDayOfWeek(DateTimeConstants.SATURDAY)
    .‌​withTime(23, 59, 59, 999); 

但是如果当前是星期天,那么这个逻辑就会失败,因为它没有给出今天的日期。

你基本上需要检查今天是否是星期天,如果不是,然后回头看上一个...(或者如果你需要前一个,则递归地向后移动日期...)

使用 java 8 您将需要:


LocalDate date = LocalDate.now();
DayOfWeek todayAsDayOfWeek = date.getDayOfWeek();
LocalDate prevSun = todayAsDayOfWeek == DayOfWeek.SUNDAY ? date : date.with(TemporalAdjusters.previous(DayOfWeek.SUNDAY));
System.out.println(prevSun);

编辑:previousOrSame 方法将跳过实际星期几的检查

    LocalDate date = LocalDate.now();
    LocalDate prevSun = date.with(TemporalAdjusters.previous(DayOfWeek.SUNDAY));

    prevSun = date.with(TemporalAdjusters.previousOrSame(DayOfWeek.SUNDAY));
    System.out.println(prevSun);

这个问题已经用 Java 8 的方法回答了,但是为了记录,这里有一个 Joda-Time 的解决方案(还有我的 Java 8 的 2 美分)。

如果我没理解错的话,获得第 n 前一个星期天的一般算法是:

  • 如果当前日期已经是星期天,则返回 n-1 周(因此对于 n=1,它 returns 是相同的日期)
  • 否则,找到那个日期
  • 的第 nth 个星期日

我创建了一个接收 n(周数)和 DateTime(开始日期)的方法。代码是:

// get the n'th previous Sunday, from the given DateTime
public DateTime nPreviousSunday(int n, DateTime dateTime) {
    // avoid zero or negative numbers (optional, see if it fits your use cases)
    if (n <= 0) {
        return dateTime; // return the same date
    }

    DateTime d = dateTime;

    // get first previous (or same) Sunday
    int dow = d.getDayOfWeek();
    if (dow != DateTimeConstants.SUNDAY) { // not a Sunday, adjust the day to the previous one
        int diff = DateTimeConstants.SUNDAY - dow;
        // DateTimeConstants.SUNDAY is 7, so diff is always positive
        // d is (7 - diff) days ahead of Sunday, adjusting
        d = d.minusDays(7 - diff);
    }

    // find the n'th previous (considering that the first was already found above)
    d = d.minusWeeks(n - 1);

    return d;
}

以下是 04/06/2017(周日)的测试。对于 n=1,它 returns 04/06/2017,对于 n >= 2,它找到该日期的第 nth 个星期日(考虑到 04/06/2017本身就是第一个):

System.out.println(nPreviousSunday(1, new DateTime(2017, 6, 4, 10, 0))); // 2017-06-04
System.out.println(nPreviousSunday(2, new DateTime(2017, 6, 4, 10, 0))); // 2017-05-28
System.out.println(nPreviousSunday(3, new DateTime(2017, 6, 4, 10, 0))); // 2017-05-21

测试 05/06/2017(不是星期日),它得到相同的结果(因为前一个星期日是 04/06/2017):

System.out.println(nPreviousSunday(1, new DateTime(2017, 6, 5, 10, 0))); // 2017-06-04
System.out.println(nPreviousSunday(2, new DateTime(2017, 6, 5, 10, 0))); // 2017-05-28
System.out.println(nPreviousSunday(3, new DateTime(2017, 6, 5, 10, 0))); // 2017-05-21

测试整个星期直到星期六 (10/06/2017):

System.out.println(nPreviousSunday(1, new DateTime(2017, 6, 6, 10, 0))); // 2017-06-04
System.out.println(nPreviousSunday(1, new DateTime(2017, 6, 7, 10, 0))); // 2017-06-04
System.out.println(nPreviousSunday(1, new DateTime(2017, 6, 8, 10, 0))); // 2017-06-04
System.out.println(nPreviousSunday(1, new DateTime(2017, 6, 9, 10, 0))); // 2017-06-04
System.out.println(nPreviousSunday(1, new DateTime(2017, 6, 10, 10, 0))); // 2017-06-04

System.out.println(nPreviousSunday(3, new DateTime(2017, 6, 6, 10, 0))); // 2017-05-21
System.out.println(nPreviousSunday(3, new DateTime(2017, 6, 7, 10, 0))); // 2017-05-21
System.out.println(nPreviousSunday(3, new DateTime(2017, 6, 8, 10, 0))); // 2017-05-21
System.out.println(nPreviousSunday(3, new DateTime(2017, 6, 9, 10, 0))); // 2017-05-21
System.out.println(nPreviousSunday(3, new DateTime(2017, 6, 10, 10, 0))); // 2017-05-21

PS:我使用的是 DateTime,但您也可以将此代码用于 org.joda.time.LocalDateorg.joda.time.LocalDateTime(算法是一样的,只是在方法中改变变量的类型)。


Java8进场(我的2美分)

在Java8中你可以使用TemporalAdjuster作为。但只是为了投入我的 2 美分,你可以创建一个 returns 一个 TemporalAdjuster 的方法,然后你可以将它与任何 java-time 类型一起使用:

// get the n'th previous dayOfWeek, from the given temporal
public TemporalAdjuster previous(int n, DayOfWeek dayOfWeek) {
    return (temporal) -> {
        // avoid zero or negative numbers (optional, see if it fits your use cases)
        if (n <= 0) {
            return temporal; // return the same temporal
        }

        // get first previous (or same) dayOfWeek
        Temporal t = temporal.with(TemporalAdjusters.previousOrSame(dayOfWeek));

        // find the n'th previous (considering that the first was already found above)
        t = t.minus(n - 1, ChronoUnit.WEEKS);

        return t;
    };
}

所以你可以这样使用它:

System.out.println(LocalDate.of(2017, 6, 4).with(previous(1, DayOfWeek.SUNDAY))); // 2017-06-04
System.out.println(LocalDate.of(2017, 6, 4).with(previous(2, DayOfWeek.SUNDAY))); // 2017-05-28
System.out.println(LocalDate.of(2017, 6, 4).with(previous(3, DayOfWeek.SUNDAY))); // 2017-05-21

System.out.println(LocalDate.of(2017, 6, 5).with(previous(1, DayOfWeek.SUNDAY))); // 2017-06-04
System.out.println(LocalDate.of(2017, 6, 5).with(previous(2, DayOfWeek.SUNDAY))); // 2017-05-28
System.out.println(LocalDate.of(2017, 6, 5).with(previous(3, DayOfWeek.SUNDAY))); // 2017-05-21

好处是它也适用于其他类型:

LocalDateTime dt = LocalDateTime.of(2017, 6, 4, 10, 0);
System.out.println(dt.with(previous(1, DayOfWeek.SUNDAY))); // 2017-06-04T10:00
System.out.println(dt.with(previous(2, DayOfWeek.SUNDAY))); // 2017-05-28T10:00

ZonedDateTime zdt = ZonedDateTime.of(dt, ZoneId.of("America/Sao_Paulo"));
System.out.println(zdt.with(previous(1, DayOfWeek.SUNDAY))); // 2017-06-04T10:00-03:00[America/Sao_Paulo]
System.out.println(zdt.with(previous(2, DayOfWeek.SUNDAY))); // 2017-05-28T10:00-03:00[America/Sao_Paulo]

OffsetDateTime odt = OffsetDateTime.of(dt, ZoneOffset.ofHours(2));
System.out.println(odt.with(previous(1, DayOfWeek.SUNDAY))); // 2017-06-04T10:00+02:00
System.out.println(odt.with(previous(2, DayOfWeek.SUNDAY))); // 2017-05-28T10:00+02:00

作为previous()方法returns一个TemporalAdjuster,不需要每次都调用它,只需将调整器存储在一个变量中并重新使用它:

TemporalAdjuster thirdPreviousSunday = previous(3, DayOfWeek.SUNDAY);
System.out.println(LocalDate.of(2017, 6, 4).with(thirdPreviousSunday)); // 2017-05-21
System.out.println(LocalDate.of(2017, 6, 5).with(thirdPreviousSunday)); // 2017-05-21

这种方法的另一个优点是:代码变得更加清晰和干净 (IMO),并且它适用于一周中的任何一天。


PS: 如果类型没有 DayOfWeek 字段(如 LocalTime,下面的代码将抛出异常只有 hour/minute/second/nanosecond):

// throws UnsupportedTemporalTypeException (because LocalTime doesn't have the DayOfWeek field)
LocalTime.now().with(previous(1, DayOfWeek.SUNDAY));

只是提醒一下,现有的调节器已经发生了:

// also throws exception (Unsupported field: DayOfWeek)
LocalTime.now().with(TemporalAdjusters.previous(DayOfWeek.SUNDAY));

这是有道理的,因为 LocalTime 没有日期字段并且不知道工作日。