【问题标题】:getting Monday date from given week and year using java.time package使用 java.time 包从给定的星期和年份获取星期一日期
【发布时间】:2017-02-02 11:48:30
【问题描述】:

我想使用 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;
}

例如:

  • 如果我通过 week=1 和 year=2013 那么日期是:2012-12-31。
  • 但如果我通过 week=53 和 year=2015 那么日期是:2014-12-29。我预计是 2014 年 12 月 28 日。

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

【问题讨论】:

  • 您的意思是使用 YearMonth.of(int year, int month) 获取当前日期?
  • 2014 年 12 月 29 日是星期一 - 为什么您预计 2014 年 12 月 28 日?另请注意,使用DayOfWeek.MONDAY.getValue() 而不是1 会更清晰。
  • 你不需要当前日期的任何信息,不需要从那个开始。您可以使用YearMonth.of(int year, int month) 设置星期和年份,然后设置星期几
  • @AbhinayPandey 你对周数的定义是什么?我能想到至少四个定义。你想要ISO 8601 standard definition吗?
  • @AbhinayPandey 不,您的问题仍未解决。您在辅助方法中应用了错误的操作顺序,请参阅我的回答。

标签: java date java-8 java-time


【解决方案1】:

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

首先要说:定义基于周的操作的正确顺序非常重要。 OP 首先应用了日操作,然后是基于年的操作。正确的做法是反过来!我将展示正确的辅助方法实现:

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 日期(取决于当前日期)。

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

第三个问题是 2014 年(以周为单位)第 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 中的这段代码(与 YearWeek 相比,其类 CalendarWeek 具有额外的 i18n 功能和额外的周算术):

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 周是无效的。然后您可以决定是否要接受宽松的处理或抛出异常。但是静默调整这种无效输入是恕我直言的糟糕设计。

【讨论】:

  • 感谢您的回答。真的解开了我的疑惑。我申请了错误的订单来获取日期。
【解决方案2】:

首先,您需要计算一年中第一周的第一个星期一, 然后简单地加上 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
}

【讨论】:

    【解决方案3】:

    tl;博士

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

    错误的期望

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

    您可能会对日历年与基于周的年感到困惑。在ISO 8601 definition of a week based year 中,第一周包含日历年的第一个星期四。这意味着我们在这些年之间有一些重叠。一个日历年的最后几天可能位于下一个基于周的年份。反之亦然,一个日历年的前几天可能位于前一周的年份。

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

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

    YearWeek

    我建议将ThreeTen-Extra 库添加到您的项目中,以利用YearWeek 类。不要只传递年份和星期的整数,而是传递此类的对象。这样做可以使您的代码更具自记录性,提供类型安全性并确保有效值。

    // 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 星期一:2012-12-31

    yw2:2015-W53 星期一:2015-12-28


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

    【讨论】:

    • @MenoHochschild 我只是将问题中的“2014”误读为“2015”,同时关注匹配的第 12 个月和第 28 天。我修正了我的答案,颠倒了我关于“正确预期”的陈述。感谢您的批评。
    • 现在撤回我的反对票。我同意你的观点,使用带有特殊类的外部库来组合基于周的年份和一年中的周可以减少像 OP 所做的那样制作这样一个辅助方法的麻烦。
    【解决方案4】:

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

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

    【讨论】:

      猜你喜欢
      • 2012-05-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-06-12
      • 1970-01-01
      • 1970-01-01
      • 2017-07-19
      • 1970-01-01
      相关资源
      最近更新 更多