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

查看:136
本文介绍了为什么一个新的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 = startDayOfWeek = 1,startTime = 7200000,startTimeMode = 0,endMode = 3,endMonth = 10,endDay = 1,endDayOfWeek = 1,endTime = 7200000,endTimeMode = 0]],firstDayOfWeek = 1,minimalDaysInFirstWeek = 1,ERA = 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年期间的开始,其中两位数年份被解析,而不是当前年份!


谢谢,这很有趣 - 最后让我安装jdk源(我只有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天全站免登陆