为什么一个新的SimpleDateFormat对象包含错误年份的日历?
为什么一个新的SimpleDateFormat对象包含错误年份的日历?
我发现了一种奇怪的行为,令我感到好奇,但目前没有令人满意的解释。
为简单起见,我将我注意到的症状简化为以下代码:
import java.text.SimpleDateFormat; import java.util.GregorianCalendar; public class CalendarTest { public static void main(String[] args) { System.out.println(new SimpleDateFormat().getCalendar()); System.out.println(new GregorianCalendar()); } }
运行此代码时,我会得到与以下输出非常相似的结果:
java.util.GregorianCalendar[time=-1274641455755,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id=\"America/Los_Angeles\",offset=-28800000,dstSavings=3600000,useDaylight=true,transitions=185,lastRule=java.util.SimpleTimeZone[id=America/Los_Angeles,offset=-28800000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=3,startMonth=2,startDay=8,startDayOfWeek=1,startTime=7200000,startTimeMode=0,endMode=3,endMonth=10,endDay=1,endDayOfWeek=1,endTime=7200000,endTimeMode=0]],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=1929,MONTH=7,WEEK_OF_YEAR=32,WEEK_OF_MONTH=2,DAY_OF_MONTH=10,DAY_OF_YEAR=222,DAY_OF_WEEK=7,DAY_OF_WEEK_IN_MONTH=2,AM_PM=1,HOUR=8,HOUR_OF_DAY=20,MINUTE=55,SECOND=44,MILLISECOND=245,ZONE_OFFSET=-28800000,DST_OFFSET=0]java.util.GregorianCalendar[time=1249962944248,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id=\"America/Los_Angeles\",offset=-28800000,dstSavings=3600000,useDaylight=true,transitions=185,lastRule=java.util.SimpleTimeZone[id=America/Los_Angeles,offset=-28800000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=3,startMonth=2,startDay=8,startDayOfWeek=1,startTime=7200000,startTimeMode=0,endMode=3,endMonth=10,endDay=1,endDayOfWeek=1,endTime=7200000,endTimeMode=0]],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2009,MONTH=7,WEEK_OF_YEAR=33,WEEK_OF_MONTH=3,DAY_OF_MONTH=10,DAY_OF_YEAR=222,DAY_OF_WEEK=2,DAY_OF_WEEK_IN_MONTH=2,AM_PM=1,HOUR=8,HOUR_OF_DAY=20,MINUTE=55,SECOND=44,MILLISECOND=248,ZONE_OFFSET=-28800000,DST_OFFSET=3600000]
(如果我向SimpleDateFormat提供有效的格式字符串,例如\"yyyy-MM-dd\"
,也会发生同样的情况。)
请原谅可怕的非自动换行行,但这是比较它们的最简单的方法。如果你滚动到大约2/3的位置,你会看到这些日历分别具有1929年和2009年的年值。(还有一些其他的差异,例如周年、周日和夏令时偏移量。)两者都显然是GregorianCalendar的实例,但它们之所以不同是令人困惑的。
据我所知,当格式化传递给它的Date对象时,该格式化程序会产生准确的结果。显然,正确的功能比正确的参考年份更重要,但是这种差异仍然令人不安。我不认为我必须在全新的日期格式化程序上设置日历以获取当前年份...
我已在搭载Java 5 (OS X 10.4,PowerPC)和Java 6 (OS X 10.6,Intel)的Mac上进行了测试并得到了相同的结果。由于这是一个Java库API,我假设它在所有平台上的行为都相同。对于这里发生的事情,你有什么见解吗?
(注:这个SO问题与本问题有一定的相关性,但不是完全相同的问题。)
编辑:
以下的答案都有助于解释这个行为。事实证明,《SimpleDateFormat》的Javadocs在某种程度上记录了这一点:
“对于使用缩写年份模式(\"y\"或\"yy\")进行解析,SimpleDateFormat必须相对于某个世纪来解释缩写年份。它通过将日期调整为在创建SimpleDateFormat实例前80年和20年内来实现这一点。”
因此,他们不会在解析日期的年份上花费太多心思,而是默认将内部日历设置为80年前。虽然这部分没有被正式文档化,但如果你了解了这一点,所有的拼图就会自然而然地拼到一起。
我不确定为什么Tom说:“这与序列化有关”,但他说对了:
private void initializeDefaultCentury() { calendar.setTime( new Date() ); calendar.add( Calendar.YEAR, -80 ); parseAmbiguousDatesAsAfter(calendar.getTime()); }
这是在SimpleDateFormat.java的第813行,这是在整个过程中非常晚的时候。在这一点上,年份是正确的(日期部分也是如此),然后将其减去80年。
啊哈!
调用parseAmbiguousDatesAsAfter()
是相同的私有函数,set2DigitYearStart()
也调用了这个函数:
/* Define one-century window into which to disambiguate dates using
* two-digit years.
*/
private void parseAmbiguousDatesAsAfter(Date startDate) {
defaultCenturyStart = startDate;
calendar.setTime(startDate);
defaultCenturyStartYear = calendar.get(Calendar.YEAR);
}
/**
* Sets the 100-year period 2-digit years will be interpreted as being in
* to begin on the date the user specifies.
*
* @param startDate During parsing, two digit years will be placed in the range
* startDate
现在我明白发生了什么事了。彼得在他关于“苹果和橙子”的评论中是正确的!SimpleDateFormat中的年份是“默认世纪”的第一年,即将被解释为两位数字年份的字符串(例如,“1/12/14”)的范围。参见http://java.sun.com/j2se/1.4.2/docs/api/java/text/SimpleDateFormat.html#get2DigitYearStart%28%29 :
因此,在“效率”胜过清晰度的胜利中,SimpleDateFormat中的年份用于存储“将两位数字年份解析为100年周期的起始时间”,而不是当前年份!
谢谢,这很有趣——最后让我安装了jdk源代码(我只有4GB的总空间在我的/
分区上。)