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

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

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

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, я предполагаю, что он ведет себя одинаково на всех платформах. Любое понимание того, что здесь происходит?

(Примечание: Этот вопрос SO несколько связан, но не совпадает.)


Edit:

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

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

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

Ответы [ 5 ]

5 голосов
/ 11 августа 2009

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

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

Это строка 813 в SimpleDateFormat.java, которая очень запаздывает в процессе. До этого момента год является правильным (как и остальные части даты), а затем уменьшается на 80.

Aha!

Вызов 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 (у меня всего 4 ГБ на моем разделе /.)

2 голосов
/ 11 августа 2009

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());
    }
}
2 голосов
/ 11 августа 2009

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

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

1 голос
/ 11 августа 2009

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

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

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

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

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

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

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

...