首页 文章

为什么新的SimpleDateFormat对象包含错误年份的日历?

提问于
浏览
7

我发现了一种奇怪的行为,这让我感到好奇,而且还没有令人满意的解释 .

为简单起见,我已将我注意到的症状减少到以下代码:

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 =真,过渡= 185,lastRule = java.util.SimpleTimeZone中[ID =美洲/洛杉矶,偏移= -28800000,dstSavings = 3600000,useDaylight =真,startYear = 0,STARTMODE = 3,startMonth = 2,朝九特派= 8,startDayOfWeek = 1,开始时间= 7200000,startTimeMode = 0,endMode = 3,endMonth = 10,endday指定= 1,一个endDayOfWeek = 1,结束时间= 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,微差= 245,ZONE_OFFSET = -28800000,DST_OFFSET = 0]
java.util.GregorianCalendar中[时间= 1249962944248,areFieldsSet =真,areAllFieldsSet =真,宽大=真,区= sun.util.calendar.ZoneInfo [ID = “美国/洛杉矶”,偏移= -28800000,dstSavings = 3600000,useDaylight =真,过渡= 185,lastRule = java.util.SimpleTimeZone中[ID =美洲/洛杉矶,偏移= -28800000,dstSavings = 3600000,useDaylight =真,startYear = 0,STARTMODE = 3,startMonth = 2,朝九特派= 8, startDayOfWeek = 1,开始时间= 7200000,startTimeMode = 0,endMode = 3,endMonth = 10,endday指定= 1,一个endDayOfWeek = 1,结束时间= 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,微差= 248,ZONE_OFFSET = -28800000,DST_OFFSET = 3600000]

(如果我向SimpleDateFormat提供像 "yyyy-MM-dd" 这样的有效格式字符串,也会发生同样的事情 . )

原谅可怕的非缠绕线,但这是比较两者的最简单方法 . 如果您滚动到大约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,我认为它在所有平台上的行为都相同 . 对这里正在发生的事情的任何见解?

(注意:This SO question有些相关,但不一样 . )


Edit:

以下答案都有助于解释这种行为 . 事实证明,SimpleDateFormat的Javadoc实际上在某种程度上证明了这一点:

“对于使用缩写年份模式(”y“或”yy“)进行解析,SimpleDateFormat必须解释相对于某个世纪的缩写年份 . 它通过将日期调整为在SimpleDateFormat之前的20年和之后20年来实现这一点 . 实例已创建 . “

因此,他们不会在解析日期的年份中看中,而是将内部日历默认设置为80年 . 这部分本身没有记录,但是当你知道它时,这些部分都是合在一起的 .

5 回答

  • 5

    我不确定为什么汤姆说“这与序列化有关”,但他有正确的路线:

    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);
    }
    

    现在我看到发生了什么 . 彼得在关于"apples and oranges"的评论中说得对! SimpleDateFormat中的年份是"default century"的第一年,即两位数年份字符串(例如"1/12/14")被解释为的范围 . 见http://java.sun.com/j2se/1.4.2/docs/api/java/text/SimpleDateFormat.html#get2DigitYearStart%28%29

    因此,在“效率”超过清晰度的胜利中,SimpleDateFormat中的年份用于存储“解析两位数年份的100年期间的开始”,而不是当前年份!

    谢谢,这很有趣 - 最后让我安装了jdk源代码(我的 / 分区上只有4GB的总空间 . )

  • 2

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

    除此之外,我相信1929年用于考虑何时将两位数年份解释为19xx而不是20XX年 .

  • 1

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

    这里有一些代码来说明:

    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());
        }
    }
    
  • 0

    通过SimpleDateFormat来看,它似乎与序列化有关:

    /* Initialize the fields we use to disambiguate ambiguous years. Separate
     * so we can call it from readObject().
     */
    private void initializeDefaultCentury() {
        calendar.setTime( new Date() );
        calendar.add( Calendar.YEAR, -80 );
        parseAmbiguousDatesAsAfter(calendar.getTime());
    }
    
  • 2
    System.out.println(new SimpleDateFormat().getCalendar());
    System.out.println(new GregorianCalendar());
    

    比较上面的代码是比较苹果和梨

    第一个提供了一个工具来解析字符串到日期,反之亦然第二个是DateUtility,它允许你操作日期

    应该提供类似的输出并不是真正的原因 .

    将其与以下内容进行比较

    System.out.println(new String() );
    System.out.println(new Date().toString() );
    

    两行都会输出一个字符串,但逻辑上你不会期望相同的结果

相关问题