计算 ZonedDateTime 和日历的年度周问题 API
Compute Week of Year issues with ZonedDateTime and Calendar API
我试图从 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
时,您使用的是系统默认时区,这很可能会改变 Instant
所在的日期。如果您将日历设置为使用 UTC,您将使其更加一致。
- 您正在使用
Calendar.YEAR
,这将为您提供 日历 年而不是 周 年。您需要改用 Calendar.getWeekYear()
。
- 您使用的
ZonedDateTime.getYear()
又是日历年。你应该使用 utcTimestamp.get(IsoFields.WEEK_BASED_YEAR)
- 您使用的
Calendar.getInstance()
可能会给您一个 non-Gregorian 日历,或者它可能 first-day-of-week 设置不适合您要执行的计算
解决这些问题(和命名约定)我们最终得到:
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
我觉得这两个都不错。
旧的 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
对于 Android-users,其中 API 是标准的:方法 getWeekYear() 自 API-level 24 起可用。
我试图从 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
时,您使用的是系统默认时区,这很可能会改变Instant
所在的日期。如果您将日历设置为使用 UTC,您将使其更加一致。 - 您正在使用
Calendar.YEAR
,这将为您提供 日历 年而不是 周 年。您需要改用Calendar.getWeekYear()
。 - 您使用的
ZonedDateTime.getYear()
又是日历年。你应该使用utcTimestamp.get(IsoFields.WEEK_BASED_YEAR)
- 您使用的
Calendar.getInstance()
可能会给您一个 non-Gregorian 日历,或者它可能 first-day-of-week 设置不适合您要执行的计算
解决这些问题(和命名约定)我们最终得到:
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
我觉得这两个都不错。
旧的 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
对于 Android-users,其中 API 是标准的:方法 getWeekYear() 自 API-level 24 起可用。