Calendar.get(Calendar.DAY_OF_MONTH) 返回错误日期

Calendar.get(Calendar.DAY_OF_MONTH) returning wrong day

当我运行以下代码时:

int year = 2017;
int month = 7;
int dayOfMonth = 10;

Calendar dateOfBirth = new GregorianCalendar(year, month, dayOfMonth);
dateOfBirth.getTime(); // See http://bugs.java.com/bugdatabase/view_bug.do?bug_id=4827490
dateOfBirth.setTimeZone(TimeZone.getTimeZone("UTC"));

System.out.println("Original Day Of Month:\t\t" + dayOfMonth);
System.out.println("Calendar:\t\t\t" + dateOfBirth);
System.out.println("Calendar substring:\t\t" + dateOfBirth.toString().substring(dateOfBirth.toString().indexOf("DAY_OF_MONTH"), dateOfBirth.toString().indexOf("DAY_OF_MONTH") + 15));
System.out.println("Formatted date:\t\t\t" + new SimpleDateFormat("dd").format(dateOfBirth.getTime()));
System.out.println("get(Calendar.DAY_OF_MONTH):\t" + dateOfBirth.get(Calendar.DAY_OF_MONTH));

我得到这个输出:

Original Day Of Month:      10
Calendar:                   java.util.GregorianCalendar[time=1502312400000,areFieldsSet=false,areAllFieldsSet=false,lenient=true,zone=sun.util.calendar.ZoneInfo[id="UTC",offset=0,dstSavings=0,useDaylight=false,transitions=0,lastRule=null],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=?,YEAR=2017,MONTH=7,WEEK_OF_YEAR=?,WEEK_OF_MONTH=?,DAY_OF_MONTH=10,DAY_OF_YEAR=?,DAY_OF_WEEK=?,DAY_OF_WEEK_IN_MONTH=?,AM_PM=0,HOUR=0,HOUR_OF_DAY=0,MINUTE=0,SECOND=0,MILLISECOND=?,ZONE_OFFSET=?,DST_OFFSET=?]
Calendar substring:         DAY_OF_MONTH=10
Formatted date:             10
get(Calendar.DAY_OF_MONTH): 9

如您所见,dateOfBirth.get(Calendar.DAY_OF_MONTH)返回的值不正确。

我使用 UTC 是因为它是出生日期,我不希望它依赖于特定时区。为了正确执行此操作,我需要调用 dateOfBirth.getTime() - 请参阅 http://bugs.java.com/bugdatabase/view_bug.do?bug_id=4827490。 如果我取消那个电话,这会正常工作,但随后我会遇到其他错误。

知道我可以做些什么来准确地得到一个月中的正确日期吗?

我正在使用 Java v1.7。0_79,并且,以防万一,我目前使用的是 GMT+3。

设置时区后只需设置日期值:

int year = 2017;
int month = Calendar.JULY;
int dayOfMonth = 10;

Calendar calendar = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
calendar.set(year, month, dayOfMonth);

System.out.println("DAY_OF_MONTH: " + calendar.get(Calendar.DAY_OF_MONTH));

这会正确输出月中的第几天:

DAY_OF_MONTH: 10

很高兴 解决了您的问题。我想解释一下发生了什么以及为什么这个答案有帮助。

当您使用三参数 GregorianCalendar 构造函数时,它“构造一个 GregorianCalendar 并在默认时区 中设置给定日期 默认语言环境。”引用文档;斜体字是我的。在你的情况下,这是 GMT+3(现在;重要的是在八月它仍然是 GMT plus 东西)。

文档不太清楚的是将一天中的小时设置为 0,所以你确实得到了 2017 年 8 月 10 日 0:00 时区偏移 +[=63= 的时间点].你真的希望 GregorianCalendar 不用担心一天中的时间,但没有这样的事情。正如我在评论中提到的那样,这就是我推荐 LocalDate 的原因之一。

当您更改时区时,时间点保持不变,但由于您在自己的时区早于格林威治标准时间,因此您现在拥有 2017 年 8 月 9 日 21:00 UTC。所以现在 9 是正确的日期值。

我认为最大的惊喜是 toString 方法仍然产生 DAY_OF_MONTH=10。当 GregorianCalendar 计算哪些字段对我来说是模糊的,但显然此时仍记得旧值。

对于格式,您使用 new SimpleDateFormat("dd")。此格式化程序使用您的默认时区 GMT+3,以及来自日历对象的时间点,正确生成 10。如果您希望它从日历中生成日期,您可能已经设置了它的时区也到 UTC。

dateOfBirth.get(Calendar.DAY_OF_MONTH) 生成正确的值 9。

有趣的是,如果在这次通话后我重复你的话:

    System.out.println("Calendar substring:\t\t" 
            + dateOfBirth.toString().substring(dateOfBirth.toString().indexOf("DAY_OF_MONTH"), 
                    dateOfBirth.toString().indexOf("DAY_OF_MONTH") + 15));

这次它产生:

Calendar substring:     DAY_OF_MONTH=9,

现在已经计算了该字段。

在 Robby Cornelissen 的代码中,日历对象天生带有 UTC 时区,因此当您设置日期时,它实际上设置为 — 好吧,这次是 2017 年 7 月 10 日,但是 0:00 UTC。这对应于您所在时区的 3:00,但那里的日期也是 7 月 10 日,因此您的其余代码将产生您预期的 10。但是,格林威治以西时区的用户现在会遇到问题,因为他们的 SimpleDateFormat 将使用他们的时区,该时区晚于格林威治标准时间,因此他们将看到 09.

的格式化日期

为了完整起见,这里是 ThreeTen(向后移植)版本:

    LocalDate date = LocalDate.of(2017, Month.AUGUST, 10);
    System.out.println("Day of month:\t" + date.getDayOfMonth());

这会打印

Day of month:   10

A LocalDate 既没有一天中的任何时间也没有任何时区,因此混淆的空间要小得多。与 Calendar 相反,它还按照人类的方式对月份进行编号,因此 7 是七月,8 是八月。