【问题标题】:Compute Week of Year issues with ZonedDateTime and Calendar API使用 ZonedDateTime 和 Calendar API 计算一年中的一周问题
【发布时间】:2017-05-31 02:53:01
【问题描述】:

我试图从 ISO-8601 日期格式字符串输入计算一年中的星期。最初我用 java.time.ZonedDateTime 尝试了这个,但它给出了输入日期的错误结果 - 2049 年 1 月 2 日。然后我尝试使用日历 API,它也给出了 2049 年 12 月 31 日的错误响应。

我已附上示例测试代码

public class ZonedDateTimeTest {        
    public static void main(String[] args) throws InterruptedException {
        System.out.println("======================================");
        String instantStr1 = "2049-01-02T03:48:00Z";
        printYearAndWeekOfYear(instantStr1);
        System.out.println("======================================");
        String instantStr2 = "2049-12-31T03:48:00Z";
        printYearAndWeekOfYear(instantStr2);
        System.out.println("======================================");
    }

    public static void printYearAndWeekOfYear(String ISODate) {
        System.out.println("Date provided -> " + ISODate);

        ZonedDateTime utcTimestamp = parseToInstant(ISODate).atZone(ZoneOffset.UTC);
        int year = utcTimestamp.getYear();
        int weekOfYear = utcTimestamp.get(IsoFields.WEEK_OF_WEEK_BASED_YEAR);
        System.out.println("Using ZonedDateTime API:: Year " + year + " weekOfYear " + weekOfYear);


        Date d1 = Date.from(parseToInstant(ISODate));
        Calendar cl = Calendar.getInstance();
        cl.setTime(d1);
        int year1 = cl.get(Calendar.YEAR);
        int weekOfYear1 = cl.get(Calendar.WEEK_OF_YEAR);
        System.out.println("Using Calendar API:: Year " + year1 + " weekOfYear " + weekOfYear1);
    }

    public static Instant parseToInstant(String ISODate) {
        return DateTimeFormatter.ISO_OFFSET_DATE_TIME.parse(ISODate, Instant::from);
    }
}

以上代码的输出

======================================
Date provided  2049-01-02T03:48:00Z
Using ZonedDateTime API: Year 2049 weekOfYear 53
Using Calendar API: Year 2049 weekOfYear 1    
======================================    
Date provided 2049-12-31T03:48:00Z
Using ZonedDateTime API: Year 2049 weekOfYear 52
Using Calendar API: Year 2049 weekOfYear 1
======================================

【问题讨论】:

  • 请将该评论放入问题中 - 从逻辑上讲,它是问题的一部分。很遗憾,您没有在 Calendar 部分的任何位置指定时区,这很容易影响结果。
  • 请停止破坏格式。我现在已经两次修复了您的代码格式,两次您都撤消了它。然后我对结果进行了重新格式化,使其更易于阅读,而您再次撤消了它。

标签: java date calendar iso8601 zoneddatetime


【解决方案1】:

你的代码有四个问题:

  • 当您使用Calendar 时,您使用的是系统默认时区,这很可能会改变Instant 所在的日期。如果您将日历设置为使用 UTC,则会使其更加一致。
  • 您使用的是Calendar.YEAR,它将为您提供日历年而不是年。您需要改用Calendar.getWeekYear()
  • 您正在使用ZonedDateTime.getYear(),这又是日历年。你应该使用utcTimestamp.get(IsoFields.WEEK_BASED_YEAR)
  • 您正在使用 Calendar.getInstance(),这可能会给您一个非公历,或者它可能为您想要执行的计算设置了不恰当的星期几

我们最终解决了这些问题(和命名约定):

import java.util.*;
import java.time.*;
import java.time.format.*;
import java.time.chrono.*;
import java.time.temporal.*;

public class ZonedDateTimeTest {

    public static void main(String[] args) {
        printYearAndWeekOfYear("2049-01-02T03:48:00Z");
        String instantStr2 = "2049-12-31T03:48:00Z";
        printYearAndWeekOfYear("2049-12-31T03:48:00Z");
    }

    public static void printYearAndWeekOfYear(String isoDate) {
        System.out.println("Date provided -> " + isoDate);

        Instant instant = DateTimeFormatter.ISO_OFFSET_DATE_TIME.parse(isoDate, Instant::from);
        ZonedDateTime utcTimestamp = instant.atZone(ZoneOffset.UTC);
        int year = utcTimestamp.get(IsoFields.WEEK_BASED_YEAR);
        int weekOfYear = utcTimestamp.get(IsoFields.WEEK_OF_WEEK_BASED_YEAR);
        System.out.println("ZonedDateTime: Year " + year + " weekOfYear " + weekOfYear);

        // Force the Gregorian calendar with ISO rules and using UTC
        Calendar calendar = new GregorianCalendar();
        calendar.setFirstDayOfWeek(Calendar.MONDAY);
        calendar.setMinimalDaysInFirstWeek(4);
        calendar.setTimeZone(TimeZone.getTimeZone("UTC"));
        calendar.setTime(Date.from(instant));

        int calYear = calendar.getWeekYear();
        int calWeekOfYear = calendar.get(Calendar.WEEK_OF_YEAR);
        System.out.println("Calendar: Year " + calYear + " weekOfYear " + calWeekOfYear);
        System.out.println();
    }
}

输出:

Date provided -> 2049-01-02T03:48:00Z
ZonedDateTime: Year 2048 weekOfYear 53
Calendar: Year 2048 weekOfYear 53

Date provided -> 2049-12-31T03:48:00Z
ZonedDateTime: Year 2049 weekOfYear 52
Calendar: Year 2049 weekOfYear 52

我觉得这两个都不错。

【讨论】:

  • 我有兴趣了解 weekOfYear 计算。 ZonedDateTime 或 Calendar 都可能给出不正确的响应。
  • @kanchan:不,他们没有。请参阅我的编辑 - 他们都给出了正确的 ISO 周年结果,但 Calendar 不会让你得到周年。
  • GregorianCalendar 类已在 Java-7 中更新,为该问题启用等效解决方案,请参阅我的回答。
  • @MenoHochschild:啊,我确实没有在Calendar 中发现getWeekYear(),习惯于向get 提供字段编号...
【解决方案2】:

旧的Calendar-stuff 确实启用了自 Java-7 以来的解决方案,因此我将其展示为 Jon Skeet 的 Java-8 相关答案的补充:

String instantStr1 = "2049-01-02T03:48:00Z";
String instantStr2 = "2049-12-31T03:48:00Z";

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");
Date d1 = sdf.parse(instantStr1);
Date d2 = sdf.parse(instantStr2);

GregorianCalendar gcal = new GregorianCalendar();
gcal.setFirstDayOfWeek(Calendar.MONDAY);
gcal.setMinimalDaysInFirstWeek(4);

gcal.setTime(d1);
System.out.println(
    "Using Calendar API: Year " + gcal.getWeekYear() + " weekOfYear "
    + gcal.get(Calendar.WEEK_OF_YEAR)
); // Using Calendar API: Year 2048 weekOfYear 53

gcal.setTime(d2);
System.out.println(
    "Using Calendar API: Year " + gcal.getWeekYear() + " weekOfYear "
    + gcal.get(Calendar.WEEK_OF_YEAR)
); // Using Calendar API: Year 2049 weekOfYear 52

对于此 API 为标准的 Android 用户:方法 getWeekYear() 自 API 级别 24 起可用。

【讨论】:

  • 非常感谢 Meno Hochschild 和 Jon Skeet 的帮助。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-09-04
  • 1970-01-01
  • 2020-05-23
  • 2021-10-31
  • 1970-01-01
  • 2014-07-05
  • 1970-01-01
相关资源
最近更新 更多