当我运行以下代码时:
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。
答案 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