2016-11-11 5 views
4

Я успешно использовал SimpleDateFormat последние пару лет. Используя это, я создал кучу классов полезности.Проблемы при переходе с SimpleDateFormat на DateTimeFormatter

Как я столкнулся с проблемами, когда SimpleDateFormat (SDF) не был потокобезопасным, я потратил последние пару дней на реорганизацию этих служебных классов, чтобы внутренне использовать DateTimeFormatter (DTF). Поскольку временные диаграммы обоих классов почти идентичны, этот переход казался хорошей идеей в то время.

У меня возникли проблемы с получением EpochMillis (миллисекунды с 1970-01-01T00:00:00Z): хотя SDF будет, например, интерпретировать 10:30 с использованием HH:mm как 1970-01-01T10:30:00Z, DTF не делает то же самое. DTF может использовать 10:30 для разбора LocalTime, но не ZonedDateTime, который необходим для получения EpochMillis.

Я понимаю, что объекты java.time следуют другой философии; Date, Time и Zoned объекты хранятся отдельно. Однако для того, чтобы мой класс утилиты интерпретировал все строки, как это было раньше, мне нужно иметь возможность определять синтаксический анализ по умолчанию для всех отсутствующих объектов динамически. Я попытался использовать

DateTimeFormatterBuilder builder = new DateTimeFormatterBuilder(); 
builder.parseDefaulting(ChronoField.YEAR, 1970); 
builder.parseDefaulting(ChronoField.MONTH_OF_YEAR, 1); 
builder.parseDefaulting(ChronoField.DAY_OF_MONTH, 1); 
builder.parseDefaulting(ChronoField.HOUR_OF_DAY, 0); 
builder.parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0); 
builder.parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0); 
builder.append(DateTimeFormatter.ofPattern(pattern)); 

но это не работает для всех моделей. Кажется, что он разрешает только значения по умолчанию для параметров, которые не определены в pattern. Есть ли способ проверить, какие ChronoField s определены в pattern, чтобы затем выборочно добавлять значения по умолчанию?

Кроме того, я попытался

TemporalAccessor temporal = formatter.parseBest(time, 
     ZonedDateTime::from, 
     LocalDateTime::from, 
     LocalDate::from, 
     LocalTime::from, 
     YearMonth::from, 
     Year::from, 
     Month::from); 
if (temporal instanceof ZonedDateTime) 
    return (ZonedDateTime)temporal; 
if (temporal instanceof LocalDateTime) 
    return ((LocalDateTime)temporal).atZone(formatter.getZone()); 
if (temporal instanceof LocalDate) 
    return ((LocalDate)temporal).atStartOfDay().atZone(formatter.getZone()); 
if (temporal instanceof LocalTime) 
    return ((LocalTime)temporal).atDate(LocalDate.of(1970, 1, 1)).atZone(formatter.getZone()); 
if (temporal instanceof YearMonth) 
    return ((YearMonth)temporal).atDay(1).atStartOfDay().atZone(formatter.getZone()); 
if (temporal instanceof Year) 
    return ((Year)temporal).atMonth(1).atDay(1).atStartOfDay().atZone(formatter.getZone()); 
if (temporal instanceof Month) 
    return Year.of(1970).atMonth((Month)temporal).atDay(1).atStartOfDay().atZone(formatter.getZone()); 

, которая не покрывает все случаи либо.

Какова наилучшая стратегия для динамического анализа даты/времени/даты-времени/зоны-даты-времени?

+0

Если проблема была просто защитой от потоков, почему решение не защищало доступ к формату с помощью 'synchronized' блоков? – BadZen

+0

Да, оригинальная проблема была действительно просто потокобезопасной; но теперь я потратил немало времени и сил на то, чтобы переместить вещи, и я бы просто хотел, чтобы это работало ... Я не могу поверить, что установка общих параметров разбора по умолчанию невозможна в 'java.time'. – dotwin

+0

Похоже на ошибочное падение стоимости на данный момент, честно ... Конечно, вы можете применить зону к своему местному времени, а затем измерить миллионы с эпохи: 'ZonedDateTime zdt = local.atZone (ZoneId.of (« Америка/New_York "));' Но похоже, что вы добавляете сложность без причины. – BadZen

ответ

3

Java-8-решение:

Изменить порядок ваших инструкций разбора внутри строителя, так что виновные инструкции все произойдут после команды шаблона.

Например, используя этот статический код (ну, ваш подход будет использовать комбинацию экземпляра на основе различных моделей, а не производительным вообще):

private static final DateTimeFormatter FLEXIBLE_FORMATTER; 

static { 
    DateTimeFormatterBuilder builder = new DateTimeFormatterBuilder(); 
    builder.appendPattern("MM/dd"); 
    builder.parseDefaulting(ChronoField.YEAR_OF_ERA, 1970); 
    builder.parseDefaulting(ChronoField.MONTH_OF_YEAR, 1); 
    builder.parseDefaulting(ChronoField.DAY_OF_MONTH, 1); 
    builder.parseDefaulting(ChronoField.HOUR_OF_DAY, 0); 
    builder.parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0); 
    builder.parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0); 
    FLEXIBLE_FORMATTER = builder.toFormatter(); 
} 

Причина:

Метод parseDefaulting(...) работает смешно, а именно как встроенный парсер. Это означает, что этот метод будет вводить значение по умолчанию для определенного поля, если это поле еще не было проанализировано. И более поздняя команда шаблона пытается проанализировать одно и то же поле (здесь: MONTH_OF_YEAR для шаблона «MM/dd» и ввод «07/13»), но с возможным разным значением. Если это так, то составной парсер будет прерван, потому что он обнаружил амбивалентные значения для одного и того же поля и не может разрешить конфликт (проанализированное значение 7, но значение по умолчанию 1).

official API содержит следующее уведомление:

В процессе синтаксического анализа, текущее состояние синтаксического анализа проверяется. Если заданное поле не имеет связанного значения, так как оно не было успешно обработано в этой точке, то указанное значение равно , введенному в результат анализа. Инъекция немедленно, поэтому пара значений полей будет видна для любых последующих элементов в форматировании . Таким образом, этот метод обычно вызывается в конце строителя .

Мы должны читать как:

dont't вызова parseDefaulting(...) до любой команды синтаксического анализа для одной и той же области.

Примечание стороны 1:

Ваш альтернативный подход, основанный на parseBest(...) еще хуже, потому что

  • он не охватывает все комбинации с отсутствующими минуты или хватает только год (MONTHDAY?) И т.д. Решение по умолчанию более гибкое.

  • это не стоит обсуждать.

Примечание стороны 2:

Я предпочел бы сделал все исполнения заказа без учета регистра, поскольку эта деталь как ловушка для многих пользователей. И можно избежать этой ловушки, выбирая реализацию на основе карты для значений по умолчанию, как это сделано в моей собственной библиотеке времени Time4J, где порядок инструкций по умолчанию не имеет значения, потому что ввод значений по умолчанию происходит только после того, как все поля имеют был проанализирован. Time4J также предлагает специальный ответ на вопрос «Что является лучшей стратегией для динамического анализа даты/времени/даты-времени/времени зоны-даты?» предлагая MultiFormatParser.

UPDATE:

В Java-8: Использование ChronoField.YEAR_OF_ERA вместо ChronoField.YEAR, потому что образец содержит букву "у" (= год из эпохи, не то же самое, как предваряющий григорианского года). В противном случае синтаксический анализатор будет вводить год дефолт по умолчанию в дополнение к раздельному периоду времени и обнаружит конфликт. Настоящая ловушка. Только вчера я установил similar pitfall в моей библиотеке времени для поля месяца, которое существует в двух немного разных вариантах.

+0

Удивительный, полный ответ. +1 и спасибо тонну. – dotwin

+0

Я только что проверил много строк времени и все еще нашел некоторые шаблоны, которые не работают. Например, я выполнил настройки по умолчанию точно так же, как и вы, но «2015.11» разобрал как «yyyy.MM» выбрасывает «Исключение в потоке» main «java.time.format.DateTimeParseException: текст« 2015.11 »не может быть проанализирован: Конфликт найден: Год 1970 отличается от Year 2015' – dotwin

+1

@dotwin Пожалуйста, смотрите мое обновление, используя YEAR_OF_ERA вместо YEAR. –

0

Я использовал новый пакет java.time, и для этого требуется время. Но после кривой обучения я должен сказать, что это определенно очень всеобъемлющее и надежное решение, вероятно, заменяющее библиотеку времени Joda и другие предыдущие решения. Я написал свои собственные утилиты для работы с разбором строк. Я написал обобщающую статью, в которой объясняется, как я реализовал функцию, которая анализировала String неизвестного формата на Date. Это может быть полезно. Вот ссылка на статью: Java 8 java.time package: parsing any string to date