为什么一个新的SimpleDateFormat对象包含错年的日历? [英] Why does a new SimpleDateFormat object contain calendar with the wrong year?

查看:124
本文介绍了为什么一个新的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]


(同样的事情发生,如果我提供一个有效的格式字符串,如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 在某种程度上实际上记录了这一点:


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


所以,在解析日期的年份,他们只是默认将内部日历设为80年。那部分没有记录在案,但是当你知道的时候,这些部分都合在一起。

解决方案

我是不知道为什么汤姆说这与序列化有关,但他有正确的一行:

  private void initializeDefaultCentury() {
calendar.setTime(new Date());
calendar.add(Calendar.YEAR,-80);
parseAmbiguousDatesAsAfter(calendar.getTime());
}

SimpleDateFormat.java中的行813是这个过程的后期。到那时为止,年份是正确的(与日期的其余部分一样),然后减少80。



Aha!



调用 parseAmbiguousDatesAsAfter()是与 set2DigitYearStart()调用相同的私有函数:

  / *定义使用
*两位数年份消除歧义日期的一个世纪的窗口。
* /
private void parseAmbiguousDatesAsAfter(Date startDate){
defaultCenturyStart = startDate;
calendar.setTime(startDate);
defaultCenturyStartYear = calendar.get(Calendar.YEAR);
}

/ **
*设置100年期间2位数年份将被解释为在
*开始于用户指定的日期。
*
* @param startDate在解析期间,两位数年份将被放置在
*< code> startDate< / code>范围内。到< code> startDate + 100年< / 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年期的开始,而不是当年!

$ b $谢谢,这很有趣 - 最后让我安装了jdk源码(我的$ code> / 分区总共有4GB的空间)。


I came upon a strange behavior that has left me curious and without a satisfactory explanation as yet.

For simplicity, I've reduced the symptoms I've noticed to the following code:

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

When I run this code, I get something very similar to the following output:

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]

(The same thing happens if I provide a valid format string like "yyyy-MM-dd" to SimpleDateFormat.)

Forgive the horrendous non-wrapping lines, but it's the easiest way to compare the two. If you scroll to about 2/3rds of the way over, you'll see that the calendars have YEAR values of 1929 and 2009, respectively. (There are a few other differences, such as week of year, day of week, and DST offset.) Both are obviously instances of GregorianCalendar, but the reason why they differ is puzzling.

From what I can tell the formatter produces accurate when formatting Date objects passed to it. Obviously, correct functionality is more important than the correct reference year, but the discrepancy is disconcerting nonetheless. I wouldn't think that I'd have to set the calendar on a brand-new date formatter just to get the current year...

I've tested this on Macs with Java 5 (OS X 10.4, PowerPC) and Java 6 (OS X 10.6, Intel) with the same results. Since this is a Java library API, I assume it behaves the same on all platforms. Any insight on what's afoot here?

(Note: This SO question is somewhat related, but not the same.)


Edit:

The answers below all helped explain this behavior. It turns out that the Javadocs for SimpleDateFormat actually document this to some degree:

"For parsing with the abbreviated year pattern ("y" or "yy"), SimpleDateFormat must interpret the abbreviated year relative to some century. It does this by adjusting dates to be within 80 years before and 20 years after the time the SimpleDateFormat instance is created."

So, instead of getting fancy with the year of the date being parsed, they just set the internal calendar back 80 years by default. That part isn't documented per se, but when you know about it, the pieces all fit together.

解决方案

I'm not sure why Tom says "it's something to do with serialization", but he has the right line:

private void initializeDefaultCentury() {
    calendar.setTime( new Date() );
    calendar.add( Calendar.YEAR, -80 );
    parseAmbiguousDatesAsAfter(calendar.getTime());
}

It's line 813 in SimpleDateFormat.java, which is very late in the process. Up to that point, the year is correct (as is the rest of the date part), then it's decremented by 80.

Aha!

The call to parseAmbiguousDatesAsAfter() is the same private function that set2DigitYearStart() calls:

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

Now I see what's going on. Peter, in his comment about "apples and oranges", was right! The year in SimpleDateFormat is the first year of the "default century", the range into which a two-digit year string (e.g, "1/12/14") is interpreted to be. See http://java.sun.com/j2se/1.4.2/docs/api/java/text/SimpleDateFormat.html#get2DigitYearStart%28%29 :

So in a triumph of "efficiency" over clarity, the year in the SimpleDateFormat is used to store "the start of the 100-year period into which two digit years are parsed", not the current year!

Thanks, this was fun -- and finally got me to install the jdk source (I only have 4GB total space on my / partition.)

这篇关于为什么一个新的SimpleDateFormat对象包含错年的日历?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆