【问题标题】:How to find nearest week day for an arbitrary date?如何为任意日期找到最近的工作日?
【发布时间】:2011-11-30 05:26:50
【问题描述】:

有没有一种优雅的方法可以使用 JodaTime 找到给定日期的一周中最近的一天?我最初认为setCopy() 会是这样,但这会将日期设置为同一周内的特定日期。因此,如果ld2011-11-27 并且day 是“星期一”,则以下函数将返回2011-11-21,而不是我想要的2011-11-28

    // Note that "day" can be _any_ day of the week, not just weekdays.
    LocalDate getNearestDayOfWeek(LocalDate ld, String day) {
        return ld.dayOfWeek().setCopy(day);
    }

各种输入的期望输出:

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

以下是我想出的一种解决方法,它适用于我当前情况下的特定限制,但我很想得到帮助,找到一个始终有效的完全通用的解决方案。

    LocalDate getNearestDayOfWeek(LocalDate ld, String day) {
        LocalDate target = ld.dayOfWeek().setCopy(day);
        if (ld.getDayOfWeek() > DateTimeConstants.SATURDAY) {
            target = target.plusWeeks(1);
        }
        return target;
    }

【问题讨论】:

  • 我不这么认为。这会找到下一个天,但我想找到最近的天。
  • 您需要定义“最近”和“工作日”的含义才能提供正确的答案。也许是一张预期输入到输出的表格?
  • 我很乐意,但这引出了一个问题:如何在 SO 的 Markdown 中制作表格?我可以使用 HTML,但我没有看到支持的 table 标记。我想我必须使用预先格式化的文本?
  • 添加了所需结果的表格。

标签: java jodatime


【解决方案1】:

Java 8 中有一个很好的 API 用于此目的,称为 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);

java.time.temporal.TemporalAdjusters 类包含工厂一些常见的用例:

firstDayOfMonth, lastDayOfMonth, firstDayOfNextMonth, firstDayOfYear, lastDayOfYear, firstDayOfNextYear, firstInMonth, lastInMonth, dayOfWeekInMonth, next, nextOrSame, previous, previousOrSame

如您所见,“最近”没有工厂,但您可以创建自己的工厂。在检查了这些方法的来源之后,自己制作非常简单:

    // 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);
        };
    }

使用它非常简单:

LocalDate date = LocalDate.now(); // some date to adjust, from your code
date.with(nearest(DayOfWeek.TUESDAY)); // the nearest Tuesday from the date

可以简化为单个表达式,将-6到+6的范围转换为-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);
        };
    }

完整示例:

这是一个完整的实现,包含对所有日期组合的测试。 您可以运行程序来验证结果,产生如下输出:

...
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
....

解决方案:

// 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();
    }
}

【讨论】:

    【解决方案2】:

    基于 Richard Povinelli 的回答,但更新为使用 Java Time(从 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;
        }
    }
    

    【讨论】:

    • 这很好,但我发布了一个替代解决方案,使用更简单、更低级别的 TemporalAdjuster API,它不需要创建新的 LocalDate 和进行日期比较。
    【解决方案3】:

    这是解决问题的方法。我对 JodaTime 有一点了解,但不是所有的类和方法。我假设给定一个日期,您可以获得星期几以及下一个或上一个日期。

    有三种情况。

    1. 特定日期的 dayOfTheWeek 是工作日。返回日期。
    2. dayOfTheWeek 是星期六。从您的日期中减去 1 天。返回日期 - 1 天。
    3. dayOfTheWeek是星期日。将 1 天添加到您的日期。返回日期 + 一天。

    如果 dayOfTheWeek 是枚举类型,那么 case 语句将直接处理任务。

    【讨论】:

    • 我想你误解了我的问题。您的算法似乎找到了最近的工作日,这不是我想要的。相反,对于任何给定的日期,我希望能够问“最近的星期一是什么?星期二?星期六?”我编辑了我的问题以明确这一点。
    • @stig-brautaset:是的,我做到了。我想我写答案的时候可能有,但我想一个错误的答案可能有助于得到一个正确的答案:-)我稍后再试。
    【解决方案4】:

    这通过定义一周中最近几天的间隔来查找一周中最近的一天。 Joda 将一周定义为从星期一开始。因此,如果今天是星期二并且一周中的某一天设置为星期日,则日期将是下一个星期日,而不是前一个星期日。如果将一周的第一天重新定义为星期日,则返回的日期将是上一个星期日的日期。以下代码不受一周第一天定义的影响。

    DateTime getNearestDayOfWeek(DateTime dateTime, String day) { //创建一个包含一周中最近几天的间隔。 DateTime 开始 = dateTime.minusHours(DateTimeConstants.HOURS_PER_WEEK/2).dayOfWeek().roundHalfCeilingCopy(); DateTime end = dateTime.plusHours(DateTimeConstants.HOURS_PER_WEEK/2).dayOfWeek().roundHalfCeilingCopy(); 间隔间隔 = 新间隔(开始,结束); //将最近的一天调整到间隔内。不取决于一周第一天的定义。 最近的日期时间 = dateTime.dayOfWeek().setCopy(day); if (interval.isAfter(nearest)) //最近在间隔之前 返回最近的.plusWeeks(1); else if (interval.isBefore(nearest)) //最近的是在间隔之后 返回最近的.minusWeeks(1); 别的 返回最近; }

    【讨论】:

    • 一个有趣的替代方法。
    【解决方案5】:

    在Jodatime中,这种事情应该是三四行就可以做到的:

       /** 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;
       }
    

    或者更紧凑、更高效:

    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;
    }
    

    如果您想将星期几作为字符串传递:

    public static LocalDate getNearestDayOfWeek(LocalDate t0, String dow) {
        return getNearestDayOfWeek(t0,t0.dayOfWeek().setCopy(dow).getDayOfWeek());
    }
    

    例子:

        // 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 ));
       }
    

    【讨论】:

    • 这很好。字符串在我的特定情况下是有意义的。 (我正在处理配置文件中的日期字符串名称。)我同意这在一般情况下不是最好的。
    • 下面发布的是使用 TemporalAdjuster 的简单 Java 8 方法,不需要外部库。
    【解决方案6】:

    类似的东西。对于 dayOfWeek 参数,使用 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);
    }
    

    用法:

    LocalDate nextSunday = foo.getNext(DateTimeConstants.SUNDAY);
    

    【讨论】:

      猜你喜欢
      • 2016-09-17
      • 1970-01-01
      • 1970-01-01
      • 2023-04-07
      • 2013-06-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-04-16
      相关资源
      最近更新 更多