我发现了一种奇怪的行为,这让我感到很好奇,而且还没有令人满意的解释.
为简单起见,我已将我注意到的症状减少到以下代码:
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]
(如果我提供类似于"yyyy-MM-dd"
SimpleDateFormat 的有效格式字符串,也会发生同样的事情.)
原谅可怕的非包裹线,但这是比较两者的最简单方法.如果您滚动到大约2/3的路径,您将看到日历的YEAR值分别为1929和2009.(还有一些其他差异,例如一周中的一周,一周中的某一天和DST偏移.)两者显然都是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实例之前的20年之前和20年之后来实现这一点.被建造."
因此,他们不会对解析日期的年份感到满意,而是默认将内部日历设置为80年.这部分本身没有记录,但是当你知道它时,这些部分都是合在一起的.
我不确定为什么汤姆说"这与序列化有关",但他有正确的路线:
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
tostartDate + 100 years
. * @see #get2DigitYearStart * @since 1.2 */ public void set2DigitYearStart(Date startDate) { parseAmbiguousDatesAsAfter(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的空间.)