为什么新的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年。 (还有一些其他的不同点,比如一周中的某一周,一周中的某一天,以及DST抵消)。这两个显然都是GregorianCalendar的实例,但它们之所以不同的原因令人费解。

从我可以告诉格式化程序产生准确时格式化日期对象传递给它。 显然,正确的功能比正确的参考年份更重要,但是这种差异仍然令人不安。 我不认为我必须将日历设置为全新的日期格式化程序才能获得当前年份...

我已经在具有Java 5(OS X 10.4,PowerPC)和Java 6(OS X 10.6,Intel)的Mac上测试了这一结果,结果相同。 由于这是一个Java库API,因此我认为它在所有平台上表现相同。 任何有关这里正在发生的事情的见解?

(注意:这个问题有点相关,但不一样。)


编辑:

下面的答案都有助于解释这种行为。 事实证明,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
 * <code>startDate</code> to <code>startDate + 100 years</code>.
 * @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总空间/分区)。


你正在调查内部行为。 如果这超出了已发布的API,那么您将看到未定义的内容,而您不应该关心它。

除此之外,我相信1929年是用来考虑何时解释一个两位数的年份,而不是20xx年的19xx年。


SimpleDateFormat具有可变的内部状态。 这就是为什么我避免像瘟疫一样(我推荐乔达时间)。 这个内部日历可能在解析日期的过程中使用,但是没有理由在分析日期之前将其初始化为任何特定的内容。

这里有一些代码来说明:

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.GregorianCalendar;

public class DateTest {
    public static void main(String[] args) {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat();
        System.out.println("sdf cal: " + simpleDateFormat.getCalendar());
        System.out.println("new cal: " + new GregorianCalendar());
        System.out.println("new date: " + simpleDateFormat.format(new Date()));
        System.out.println("sdf cal: " + simpleDateFormat.getCalendar());
    }
}
链接地址: http://www.djcxy.com/p/941.html

上一篇: Why does a new SimpleDateFormat object contain calendar with the wrong year?

下一篇: What is the purpose of the "role" attribute in HTML?