2016-01-05 1 views
4

Я пытаюсь разобрать строки datetime и создать объекты Joda DateTime.Обработка сдвига смещения часового пояса и переход на летнее время с помощью Joda

Мои данные получены из устаревшей базы данных, в которой хранятся строки времени и времени, не указав часовой пояс/смещение. Хотя временная шкала/смещение строк даты и времени не сохраняется, это бизнес-правило унаследованной системы, в которой все данные хранятся в Восточном времени. К сожалению, у меня нет полномочий обновлять то, как устаревший DB хранит строки времени.

Таким образом, я разбираю строки datetime, используя часовой пояс JODA «США/Восток».

Этот подход вызывает исключение незаконногоInstance, когда строка dateTime попадает в течение часа, который «исчезает», когда включена летняя экономия.

Я создал следующий примерный код, чтобы продемонстрировать это поведение и показать предлагаемое обходное решение.

public class FooBar { 
public static final DateTimeZone EST = DateTimeZone.forID("EST"); 
public static final DateTimeZone EASTERN = DateTimeZone.forID("US/Eastern"); 

public static final DateTimeFormatter EST_FORMATTER = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss.SSS").withZone(EST); 
public static final DateTimeFormatter EASTERN_FORMATTER = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss.SSS").withZone(EASTERN); 


public static void main(String[] args) { 
    final String[] listOfDateTimeStrings = {"2014-03-09 02:00:00.000", "2014-03-08 02:00:00.000"}; 

    System.out.println(" *********** 1st attempt *********** "); 
    for (String dateTimeString: listOfDateTimeStrings){ 
     try{ 
      final DateTime dateTime = DateTime.parse(dateTimeString, EASTERN_FORMATTER); 
      System.out.println(dateTime);  
     } 
     catch(Exception e){ 
      System.out.println(e.getMessage()); 
     } 
    } 

    System.out.println(" *********** 2nd attempt *********** "); 
    for (String dateTimeString: listOfDateTimeStrings){ 
     try{ 
      final DateTime dateTime = DateTime.parse(dateTimeString, EST_FORMATTER); 
      System.out.println(dateTime);  
     } 
     catch(Exception e){ 
      System.out.println(e.getMessage()); 
     } 
    } 

    System.out.println(" *********** 3rd attempt *********** "); 
    for (String dateTimeString: listOfDateTimeStrings){ 
     try{ 
      DateTime dateTime = DateTime.parse(dateTimeString, EST_FORMATTER); 
      dateTime = dateTime.withZone(EASTERN); 
      System.out.println(dateTime);  
     } 
     catch(Exception e){ 
      System.out.println(e.getMessage()); 
     } 
    }  

} 

}

Выход производства:

 
*********** 1st attempt *********** 
Cannot parse "2014-03-09 02:00:00.000": Illegal instant due to time zone offset transition (America/New_York) 
2014-03-08T02:00:00.000-05:00 
*********** 2nd attempt *********** 
2014-03-09T02:00:00.000-05:00 
2014-03-08T02:00:00.000-05:00 
*********** 3rd attempt *********** 
2014-03-09T03:00:00.000-04:00 
2014-03-08T02:00:00.000-05:00 

В "3-й попытки" я получить ожидаемый результат: первый DateTime имеет смещение -04: 00. так как он попадает в первый час DST на 2015 год. Вторая временная метка имеет смещение -05: 00, поскольку оно выходит за пределы DST.

Is безопасно сделать это:

DateTime dateTime = DateTime.parse(dateTimeString, A_FORMATTER_WITH_TIME_ZONE_A); 
dateTime = dateTime.withZone(TIME_ZONE_B); 

Я тестировал этот код с несколькими различными комбинациями даты и времени строк и часовых поясов (и до сих пор он работает для всех тестов), но я задавался вопросом, может ли кто-нибудь, у кого больше опыта Джоды, увидеть что-то неправильное/опасное в этом подходе.

Или, альтернативно: есть ли лучший способ справиться с переходом с часовым поясом с Джодой?

+1

+1 Замечательный письменный вопрос с одним незначительным исключением: _ «Является ли код ... безопасным в использовании?» _ Что вы подразумеваете под «безопасным»? Если вы проверите его с достаточными случаями краев, приведет ли оно к ожидаемому и желаемому поведению? Вам нужно будет более подробно описать то, что вы спрашиваете конкретно. –

+0

@JimGarrison спасибо за указатели; Я обновил конец вопроса, надеюсь, теперь он более конкретный. –

+2

Когда вы говорите: «все данные хранятся в EST», вы буквально означаете, что все они хранятся в UTC-5 - Eastern * Standard * Time, или вы имеете в виду, что они хранятся в Восточном времени, что является UTC-5 (EST) для части года и UTC-4 (EDT) для другой части? Потому что вы также сказали: «Когда переход на летнее время включен в часовой пояс EST», я думаю, вы имеете в виду «Восточное время», а не «EST». (Переход на летнее время * не применяется * в стандартное время.) –

ответ

3

Будьте осторожны. Поведение метода withZone(...) документировано следующим образом:

Возвращает копию этого DateTime с другим часовым поясом, сохраняя миллисекунду мгновенного.

Помня об этом, вы должны понимать, что EST и «America/New_York» (лучше, чем устаревший идентификатор «US/Eastern») не совпадают. Первый (EST) имеет фиксированное смещение (без DST), но второй имеет DST, включая возможные пробелы. Вы должны применять EST только в качестве замены для Восточной, если вы уверены, что

a) вы уже находитесь в режиме исключения (обычно повторяющиеся даты в восточной зоне должны приниматься без повторного анализа, иначе применение EST исказило бы разобранный момент)

b) вы понимаете, что выбор EST во второй (и третьей) попытке заключается в выборе момента после перехода на летнее время.

Что касается этих ограничений/ограничений, обходной путь будет работать (но только для специальной пары EST по сравнению с America/New_York). Лично мне кажется пугающим использовать логику на основе исключений, чтобы обойти серьезное ограничение Joda-Time. В качестве встречного примера новый JSR-310 не использует стратегию исключения при обработке пробелов, а стратегию выбора более позднего момента после зазора, продвигаемого вперед по размеру разрыва (например, старый java.util.Calendar).

Я рекомендую вам сначала следовать рекомендациям @Jim Garrison, чтобы посмотреть, могут ли исправленные данные быть исправлены до того, как вы примените такое обходное решение (мой ответ на его ответ).

Update после прочтения оригинала требования спецификации (устаревшее - см ниже):

Если спецификация унаследованной системы говорит, что все время хранится в EST, то вы должны разобрать его таким образом и НЕ используйте «America/New_York» для разбора вообще. Вместо этого вы можете преобразовать проанализированные EST-моменты в New-York-time во второй фазе (используя withZone(EASTERN). Таким образом, у вас не будет никакой логики исключения, потому что (разобранный) момент всегда можно преобразовать в локальное представление времени однозначным образом (разобранная мгновенный типа DateTime, преобразованный результат содержит местное время) пример кода:.

public static final DateTimeZone EST = DateTimeZone.forID("EST"); 
public static final DateTimeZone EASTERN = DateTimeZone.forID("US/Eastern"); 
public static final DateTimeFormatter EST_FORMATTER = 
    DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss.SSS").withZone(EST); 

// in your parsing method... 
String input = "2014-03-09 02:00:00.000"; 
DateTime dt = EST_FORMATTER.parseDateTime(input); 
System.out.println(dt); // 2014-03-09T02:00:00.000-05:00 
System.out.println(dt.withZone(EASTERN)); // 2014-03-09T03:00:00.000-04:00 

Update после комментариев и разъяснений ОП:

Теперь подтверждается, что наследие системы не сохраняет метки времени в EST (с фиксированным смещением UTC-05, но в зоне EASTERN («America/New_York» с переменным смещением EST или EDT). Первое действие должно заключаться в том, чтобы связаться с поставщиком недопустимых временных меток, чтобы проверить, могут ли они исправить данные. Кроме того, вы можете использовать следующее обходное решение:

Что касается того, что ваш ввод содержит временные метки без какой-либо информации о смещении или зоне, я рекомендую сначала разобрать как LocalDateTime.

=> статическая инициализация часть

// Joda-Time cannot parse "EDT" so we use hard-coded offsets 
public static final DateTimeZone EST = DateTimeZone.forOffsetHours(-5); 
public static final DateTimeZone EDT = DateTimeZone.forOffsetHours(-4); 

public static final DateTimeZone EASTERN = DateTimeZone.forID("America/New_York"); 
public static final org.joda.time.format.DateTimeFormatter FORMATTER = 
    org.joda.time.format.DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss.SSS"); 

=> в методе синтаксического анализа

String input = "2014-03-09 02:00:00.000"; 
LocalDateTime ldt = FORMATTER.parseLocalDateTime(input); // always working 
System.out.println(ldt); // 2014-03-09T02:00:00.000 
DateTime result; 

try { 
    result = ldt.toDateTime(EASTERN); 
} catch (IllegalInstantException ex) { 
    result = ldt.plusHours(1).toDateTime(EDT); // simulates a PUSH-FORWARD-strategy at gap 
    // result = ldt.toDateTime(EST); // the same instant but finally display with EST offset 
} 
System.out.println(result); // 2014-03-09T03:00:00.000-04:00 
// if you had chosen <<<ldt.toDateTime(EST)>>> then: 2014-03-09T02:00:00.000-05:00 

Еще одно осветления из-за последний комментарий ОП:

Метод toDateTime(DateTimeZone) производящего DateTime документируется следующим образом:

При перекрытии летнего времени, когда одно и то же местное время происходит дважды, этот метод возвращает первое вхождение местного времени.

Иными словами, он выбирает более раннее смещение в случае перекрытия (осенью). Таким образом, нет необходимости вызывать

result = ldt.toDateTime(EASTERN).withEarlierOffsetAtOverlap(); 

Однако, это не причинит никакого вреда здесь, и вы могли бы предпочесть это ради документации. С другой стороны: Это не имеет никакого смысла заезжать за исключением обработки (пробелы)

result = ldt.toDateTime(EDT).withEarlierOffsetAtOverlap(); 

потому EDT (и EST тоже) является фиксированным смещением, где перекрывается никогда не может произойти. Итак, здесь метод withEarlierOffsetAtOverlap() ничего не делает. Кроме того: Оставив коррекцию ldt.plusHours(1) в случае EDT, не получится и даст еще одно мгновение. Уже тестировался мной, прежде чем писать это дополнительное объяснение, но, конечно, вы можете использовать альтернативу ldt.toDateTime(EST) для достижения того, чего вы хотите (EDT! = EST, но с исправлением plusHours(1) вы получаете одинаковые мгновения). Я только что заметил пример EDT, чтобы продемонстрировать, как вы можете точно моделировать стандартное поведение JDK. Это зависит от вас, который компенсирует вам предпочтение в случае устранения пробелов (EDT или EST), но получение таких же мгновений жизненно важно здесь (ldt.plusHours(1).toDateTime(EDT) против result = ldt.toDateTime(EST)).

+2

В то время как технические средства «EST» означают UTC-5, неясно, понимает ли это OP.Возможно, он действительно означает, что данные хранятся в «Восточном времени», включая EST и EDT. Было бы хорошо получить разъяснения. –

+0

Иными словами, хотя имеет смысл для '2014-03-09T02: 00: 00.000', который будет преобразован как' 2014-03-09T03: 00: 00.000-04: 00', маловероятно, что другие * действительные * отметки времени намерены быть перенесенными. Было бы странно, если бы '2014-07-01T00: 00: 00.000' пришел как' 2014-07-01T01: 00: 00.000-04: 00'. –

+0

FYI, я считаю, что правильное решение предполагает синтаксический разбор строки на «LocalDateTime» (используя 'parseLocalDateTime' на форматировании, я думаю), а затем отдельно преобразуя его в' DateTime' в 'America/New_York' (или' US/Восточный, если хотите) часовой пояс. Однако я не могу вспомнить, как вы говорите Джоде не бросать исключение во время преобразования значений в пробел. (Я знаю, что у него есть «сEarlierOffsetAtOverlap» для осени, но я не уверен, что он может сделать весной.) (Не стесняйтесь красть все это для обновления) Cheers! –

3

Ваш вопрос сводится к

Я должен справиться со значениями временных меток, которые не являются технически обоснованными и интерпретировать их последовательно

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

Предлагаю вам вернуться к владельцу/менеджеру проекта, сообщить им о проблеме (ввод содержит метки даты/времени, которые на самом деле не существуют) и заставить их принять решение о том, как с ними обращаться.

+0

Это самый правильный ответ. Однако я не могу это принять. К сожалению, мне нужно найти обходное решение, которое может интерпретировать недопустимые значения, поскольку мое ПО не имеет желания взять на себя монументальную задачу по устранению проблемы с корнем. –

+0

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

Смежные вопросы