使用 java.time 包从给定的星期和年份获取星期一日期

getting Monday date from given week and year using java.time package

我想使用 Java 8 包 java.time 从给定的周和年中获取星期一日期。 但在某些时候我遇到了问题,因为它没有返回正确的日期。

private LocalDate getDateFromWeekAndYear(final String week,final String year){
    LocalDate date = LocalDate.now();
    date = date.with(WeekFields.ISO.dayOfWeek(), 1);
    date = date.with(WeekFields.ISO.weekOfWeekBasedYear(), Long.parseLong(week));
    date = date.with(WeekFields.ISO.weekBasedYear(), Long.parseLong(year));

    return date;
}

例如:

我是否犯了任何逻辑错误或其他问题?

首先,您需要计算一年中第一周的第一个星期一, 然后简单地加上 7 的倍数到日期。

public static LocalDate firstMonday(int week, int year) {

    LocalDate firstMonOfFirstWeek = LocalDate.now()
            .with(IsoFields.WEEK_BASED_YEAR, year) // year
            .with(IsoFields.WEEK_OF_WEEK_BASED_YEAR, 1) // First week of the year
            .with(ChronoField.DAY_OF_WEEK, 1); // Monday

    // Plus multi of 7
    return firstMonOfFirstWeek.plusDays( (week - 1) * 7);
}

public static void main(String[] args) {
    System.out.println(firstMonday(1, 2013)); // 2012-12-31
    System.out.println(firstMonday(53 ,2015 )); // 2015-12-28
}

tl;博士

YearWeek.of ( 2013 , 1 ).atDay ( DayOfWeek.MONDAY )

不正确的期望

您的期望不正确。 2015-W53 的星期一是 2015-12-28,而不是 2014-12-28,是 2015 年而不是 2014 年。没有理由期待 2014 年。如果您需要更多解释,请编辑您的问题以解释您的想法.

您可能会对 calendar-year 年与 week-based 年感到困惑。在 ISO 8601 definition of a week based year 中,第一个 周包含 calendar-year 的第一个星期四。这意味着我们在这些年之间有一些重叠。 calendar-year 的最后几天可能在接下来的 week-based 年。而 vice-versa,calendar-year 的前几天可能在前 week-based 年。

例如,您可以在下面的屏幕截图中看到,2012 年日历的最后一天(12 月 31 日)恰好在以下 week-based-2013 年第 1 周的第一周。并且在另一个屏幕截图,我们有相反的情况,2016 年日历的前三天(1 月 1 日、2 日和 3 日)落在 2015 年 week-based 年的第 53 周。

  • 2013-W01 的星期一是 2012-12-31
  • 2015-W53 的星期一是 2015-12-28

YearWeek

我建议添加 ThreeTen-Extra library to your project to make use of of the YearWeek class。与其传递年和周的纯整数,不如传递这个 class 的对象。这样做会使您的代码更 self-documenting,提供 type-safety,并确保有效值。

// Pass ( week-based-year-number, week-number ). *Not* calendar year! See the ISO 8601 standard.
YearWeek yw = YearWeek.of( 2013 , 1 ); 

您可以从该周的任何一天提取。

LocalDate ld = yw.atDay( DayOfWeek.MONDAY );

让我们试试这种代码。

YearWeek yw1 = YearWeek.of ( 2013 , 1 );
LocalDate ld1 = yw1.atDay ( DayOfWeek.MONDAY );

YearWeek yw2 = YearWeek.of ( 2015 , 53 );
LocalDate ld2 = yw2.atDay ( DayOfWeek.MONDAY );

System.out.println ( "yw1: " + yw1 + " Monday: " + ld1 );
System.out.println ( "yw2: " + yw2 + " Monday: " + ld2 );

yw1: 2013-W01 Monday: 2012-12-31

yw2: 2015-W53 Monday: 2015-12-28


提示:要在 Calendar.app 中查看 Mac 上的那些 ISO 8601 standard week 数字,请设置 System Preferences > Language & Region > Calendar > ISO 8601。然后在Calendar.app中设置Preferences > Advanced > Show week numbers

这比 OP 和大多数答案显示的部分无效期望要困难得多。

先说:定义week-based-manipulations的正确顺序很重要。OP先申请了day-manipulation,然后[=47] =] 操纵。正确的做法是反过来!我将展示正确的辅助方法实现:

public static void main(String... args) {
    System.out.println(
      getDateFromWeekAndYear("53", "2015")); // 2015-12-28, NOT 2014-12-28
    System.out.println(
      getDateFromWeekAndYear("53", "2015").get(WeekFields.ISO.weekOfWeekBasedYear())); // 53
    System.out.println(
      getDateFromWeekAndYear("53", "2014")); // 2014-12-29
    System.out.println(
      getDateFromWeekAndYear("53", "2014").get(WeekFields.ISO.weekOfWeekBasedYear())); // 1
}

private static LocalDate getDateFromWeekAndYear(final String week,final String year) {
    int y = Integer.parseInt(year);
    LocalDate date = LocalDate.of(y, 7, 1); // safer than choosing current date
    // date = date.with(WeekFields.ISO.weekBasedYear(), y); // no longer necessary
    date = date.with(WeekFields.ISO.weekOfWeekBasedYear(), Long.parseLong(week));
    date = date.with(WeekFields.ISO.dayOfWeek(), 1);

    return date;
}

如果您不遵守此特定顺序,那么您有时确实会得到输入 2015-W53 的 2014 年日期(取决于当前日期)。

第二个问题:我也避免从当前日期开始,以免接近日历年的开始或结束(日历年!= week-based-year)而是选择年中作为起点。

第三个问题是2014(week-based)年第53周的宽处理。不存在因为2014只有52周!!!严格的算法应该识别并拒绝这样的输入。因此,我建议不要在 2015 年的第一周使用 YearWeek.of(2014, 53)(在外部库 Threeten-Extra 中),另请参阅其 javadoc。比这样宽松的处理更好

YearWeek yw = YearWeek.of(2014, 52);
if (yw.is53WeekYear()) {
  yw = YearWeek.of(2014, 53);
}

或使用我自己的时间库中的代码 Time4J (whose class CalendarWeekYearWeek 相比,具有额外的国际化功能和额外的周算术):

CalendarWeek.of(2014, 53); // throws an exception
System.out.println(CalendarWeek.of(2014, 1).withLastWeekOfYear()); // 2014-W52

仅使用 java.time-package:

使用这样的外部库至少可以帮助以透明的方式解决第一个问题。如果您不愿意添加额外的依赖项,那么如果无效,您可以这样做来处理第 53 周:

如果应用于辅助方法结果的表达式 WeekFields.ISO.weekOfWeekBasedYear() 产生值 1,则您知道第 53 周无效。然后您可以决定是接受宽松处理还是抛出异常。但是静默调整这样一个无效的输入是恕我直言的糟糕设计。

我想要大约 6 个月前的一周的星期一 - 具体来说是 26 周前.. 下面的代码给了我所需的日期:

LocalDate.now().minusWeeks(26).with(WeekFields.ISO.dayOfWeek(), DayOfWeek.MONDAY.getValue())