Calendar.get(Calendar.DAY_OF_MONTH)返回错误的一天

时间:2017-06-20 08:20:56

标签: java calendar java-7

当我运行以下代码时:

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。

2 个答案:

答案 0 :(得分:3)

很高兴the answer by Robby Cornelissen解决了你的问题。我想解释一下发生了什么以及为什么答案会有所帮助。

当您使用三参数GregorianCalendar构造函数时,它“使用默认区域设置在默认时区中构造一个具有给定日期集GregorianCalendar。”引用文档;斜体是我的。在你的情况下,这是格林尼治标准时间+3(现在;重要的是在8月它仍将是GMT 加上的东西)。

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

当您更改时区时,时间点保持不变,但由于您在自己的时区中领先GMT,因此您现在有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将使用他们的时区,这是GMT背后的时区,因此他们会看到格式化日期为09

为了完整起见,这里是ThreeTen(Backport)版本:

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

打印

Day of month:   10

LocalDate既没有任何时间也没有任何时区,从而减少了混淆的空间。与Calendar相反,它也与人类的方式相同,因此7月为7月,8月为8月。

答案 1 :(得分:2)

设置时区后设置日期值:

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