Почему новый объект SimpleDateFormat содержит календарь с неправильным годом?

StackOverflow https://stackoverflow.com/questions/1258441

Вопрос

Я столкнулся со странным поведением, которое вызвало у меня любопытство и пока не дало удовлетворительного объяснения.

Для простоты я сократил замеченные мной симптомы до следующего кода:

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 пути, вы увидите, что в календарях значения ГОДА равны 1929 и 2009 соответственно.(Есть еще несколько отличий, например, неделя года, день недели и смещение летнего времени.) Оба, очевидно, являются экземплярами GregorianCalendar, но причина, по которой они различаются, озадачивает.

Насколько я могу судить, форматтер обеспечивает точность при форматировании переданных ему объектов Date.Очевидно, что правильная функциональность более важна, чем правильный базовый год, но, тем не менее, несоответствие вызывает беспокойство.Я бы не подумал, что мне придется устанавливать календарь на совершенно новый форматер даты, чтобы получить текущий год...

Я тестировал это на компьютерах Mac с Java 5 (OS X 10.4, PowerPC) и Java 6 (OS X 10.6, Intel) с теми же результатами.Поскольку это API библиотеки Java, я предполагаю, что он ведет себя одинаково на всех платформах.Есть понимание, что здесь происходит?

(Примечание: Этот ТАК вопрос в чем-то родственно, но не одно и то же.)


Редактировать:

Ответы ниже помогли объяснить такое поведение.Оказывается, Javadocs для SimpleDateFormat на самом деле документируйте это в некоторой степени:

«Для анализа сокращенного образца года («y» или «yy») SimpleDateFormat должен интерпретировать сокращенный год относительно некоторого столетия.Это достигается путем корректировки дат так, чтобы они находились в пределах 80 лет до и 20 лет после создания экземпляра SimpleDateFormat».

Таким образом, вместо того, чтобы придумывать год анализируемой даты, они просто по умолчанию устанавливают внутренний календарь на 80 лет назад.Эта часть сама по себе не документирована, но когда вы о ней знаете, все части складываются воедино.

Это было полезно?

Решение

Я не уверен, почему Том говорит: «Это как-то связано с сериализацией», но у него есть правильная фраза:

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

Это строка 813 в SimpleDateFormat.java, которая находится очень поздно.До этого момента год указан правильно (как и остальная часть даты), затем он уменьшается на 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 — это первый год «столетия по умолчанию», диапазона, в который интерпретируется двухзначная строка года (например, «12.01.14»).Видеть http://java.sun.com/j2se/1.4.2/docs/api/java/text/SimpleDateFormat.html#get2DigitYearStart%28%29 :

Таким образом, в триумфе «эффективности» над ясностью, год в SimpleDateFormat используется для хранения «начала 100-летнего периода, в который анализируются две цифры года», а не текущего года!

Спасибо, это было весело — и наконец-то позволило мне установить исходный код jdk (у меня всего 4 ГБ свободного места на моем компьютере). / раздел.)

Другие советы

Вы исследуете внутреннее поведение.Если это выходит за пределы опубликованного API, вы видите неопределенный материал, и вам не следует об этом беспокоиться.

Помимо этого, я считаю, что 1929 год используется для определения того, когда интерпретировать двузначный год как относящийся к 19xx, а не к 20xx.

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

Просматривая 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());
}
System.out.println(new SimpleDateFormat().getCalendar());
System.out.println(new GregorianCalendar());

сравнение приведенного выше кода сравнивает яблоки и груши

Первый предоставляет вам инструмент для разбора строки в даты, и наоборот, вторая - это дата, которая позволяет вам манипулировать датами

На самом деле нет причин, по которым должен обеспечивать аналогичный результат.

Сравните его со следующим

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

обе строки выведут строку, но логически вы не ожидаете одного и того же результата

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top