SimpleDateFormat parse теряет часовой пояс

код:

 SimpleDateFormat sdf = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss z");
    sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
    System.out.println(new Date());
    try {
        String d = sdf.format(new Date());
        System.out.println(d);
        System.out.println(sdf.parse(d));
    } catch (Exception e) {
        e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
    }

выход:

Thu Aug 08 17:26:32 GMT+08:00 2013
2013.08.08 09:26:32 GMT
Thu Aug 08 17:26:32 GMT+08:00 2013

отметим, что format() форматирование Date правильно GMT, но parse() потерял детали GMT. Я знаю, что могу использовать substring() и обойти это, но какова причина, лежащая в основе этого явления?

вот дубликат вопроса который не имеет никаких ответов.

Edit: позвольте мне поставить вопрос по-другому, каков способ получить объект даты, чтобы его всегда GMT?

3 ответов


все, что мне было нужно, это:

SimpleDateFormat sdf = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss");
sdf.setTimeZone(TimeZone.getTimeZone("GMT"));

SimpleDateFormat sdfLocal = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss");

try {
    String d = sdf.format(new Date());
    System.out.println(d);
    System.out.println(sdfLocal.parse(d));
} catch (Exception e) {
    e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
}

вывод: немного сомнительно, но я хочу, чтобы только дата была последовательной

2013.08.08 11:01:08
Thu Aug 08 11:01:08 GMT+08:00 2013

решение ОП его проблемы, как он говорит, имеет сомнительный выход. Этот код все еще показывает путаницу в представлениях о времени. Чтобы прояснить эту путаницу и сделать код, который не приведет к неправильным временам, рассмотрим это расширение того, что он сделал:

public static void _testDateFormatting() {
    SimpleDateFormat sdfGMT1 = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss");
    sdfGMT1.setTimeZone(TimeZone.getTimeZone("GMT"));
    SimpleDateFormat sdfGMT2 = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss z");
    sdfGMT2.setTimeZone(TimeZone.getTimeZone("GMT"));

    SimpleDateFormat sdfLocal1 = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss");
    SimpleDateFormat sdfLocal2 = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss z");

    try {
        Date d = new Date();
        String s1 = d.toString();
        String s2 = sdfLocal1.format(d);
        // Store s3 or s4 in database.
        String s3 = sdfGMT1.format(d);
        String s4 = sdfGMT2.format(d);
        // Retrieve s3 or s4 from database, using LOCAL sdf.
        String s5 = sdfLocal1.parse(s3).toString();
        //EXCEPTION String s6 = sdfLocal2.parse(s3).toString();
        String s7 = sdfLocal1.parse(s4).toString();
        String s8 = sdfLocal2.parse(s4).toString();
        // Retrieve s3 from database, using GMT sdf.
        // Note that this is the SAME sdf that created s3.
        Date d2 = sdfGMT1.parse(s3);
        String s9 = d2.toString();
        String s10 = sdfGMT1.format(d2);
        String s11 = sdfLocal2.format(d2);
    } catch (Exception e) {
        e.printStackTrace();
    }       
}

изучение значений в отладчике:

s1  "Mon Sep 07 06:11:53 EDT 2015" (id=831698113128)    
s2  "2015.09.07 06:11:53" (id=831698114048) 
s3  "2015.09.07 10:11:53" (id=831698114968) 
s4  "2015.09.07 10:11:53 GMT+00:00" (id=831698116112)   
s5  "Mon Sep 07 10:11:53 EDT 2015" (id=831698116944)    
s6  -- omitted, gave parse exception    
s7  "Mon Sep 07 10:11:53 EDT 2015" (id=831698118680)    
s8  "Mon Sep 07 06:11:53 EDT 2015" (id=831698119584)    
s9  "Mon Sep 07 06:11:53 EDT 2015" (id=831698120392)    
s10 "2015.09.07 10:11:53" (id=831698121312) 
s11 "2015.09.07 06:11:53 EDT" (id=831698122256) 

sdf2 и sdfLocal2 включают часовой пояс, поэтому мы можем видеть, что на самом деле происходит. s1 & s2 находятся в 06: 11: 53 в зоне EDT. s3 & s4 находятся в 10: 11: 53 в зоне GMT -- эквивалентно исходному времени EDT. Представьте, что мы сохраняем s3 или s4 в базе данных, где мы используем GMT для согласованности, поэтому мы можем иметь время из любой точки мира, без хранения разных часовых поясов.

s5 анализирует время GMT, но рассматривает его как местное время. Поэтому он говорит "10: 11: 53" - время GMT - но думает, что это 10:11: 53 в местные времени. Не хороший.

s7 анализирует время GMT, но игнорирует GMT в строке, поэтому все еще рассматривает его как локальный время.

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

Теперь предположим, что вы не хотите хранить зону, вы хотите иметь возможность анализировать s3, но отображать ее как локальное время. Ответ:синтаксический анализ с использованием того же часового пояса, что и в -- поэтому используйте тот же sdf, что и в sdfGMT1. s9, s10, & s11-это все представления исходного времени. Они все "правильный." То есть d2 == d1. Тогда вопрос только в том, как вы хотите его отобразить. Если вы хотите отобразить то, что хранится в DB -- GMT time -- тогда вам нужно отформатировать его с помощью GMT sdf. THS является С10.

Итак, вот окончательное решение, если вы не хотите явно хранить с " GMT " в строке и хотите отображать в формате GMT:

public static void _testDateFormatting() {
    SimpleDateFormat sdfGMT1 = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss");
    sdfGMT1.setTimeZone(TimeZone.getTimeZone("GMT"));

    try {
        Date d = new Date();
        String s3 = sdfGMT1.format(d);
        // Store s3 in DB.
        // ...
        // Retrieve s3 from database, using GMT sdf.
        Date d2 = sdfGMT1.parse(s3);
        String s10 = sdfGMT1.format(d2);
    } catch (Exception e) {
        e.printStackTrace();
    }       
}

tl; dr

как получить объект даты, чтобы он всегда был в GMT?

Instant.now() 

подробности

вы используете хлопотные запутанные старые классы даты-времени, которые теперь заменены java.классы время.

Instant = UTC

на Instant класс представляет момент на временной шкале в UTC с разрешением наносекунд (вверх до девяти (9) цифр десятичной дроби).

Instant instant = Instant.now() ; // Current moment in UTC.

ISO 8601

для обмена данными в виде текста используйте стандарт ISO 8601 форматы исключительно. Эти форматы разумно разработаны, чтобы быть однозначными, легко обрабатываются машиной и легко читаются во многих культурах людьми.

java.классы времени используют стандартные форматы по умолчанию при анализе и генерации строк.

String output = instant.toString() ;  

2017-01-23T12:34: 56.123456789 Z

часовой пояс

если вы хотите увидеть тот же самый момент, что и в настенных часах определенного региона, примените ZoneId и ZonedDateTime.

указать правильное название часового пояса в формате continent/region, например America/Montreal, Africa/Casablanca или Pacific/Auckland. Никогда не используйте аббревиатуру из 3-4 букв, такую как EST или IST как они не истинные часовые пояса, не стандартизированные и даже не уникальные(!).

ZoneId z = ZoneId.of( "Asia/Singapore" ) ;
ZonedDateTime zdt = instant.atZone( z ) ;  // Same simultaneous moment, same point on the timeline.

посмотреть этот код жить на IdeOne.com.

обратите внимание на разницу в восемь часов, как часовой пояс Asia/Singapore в настоящее время имеет смещение от-UTC +08:00. Тот же момент, другое время настенных часов.

мгновенный.toString (): 2017-01-23T12:34: 56.123456789 Z

нмв.метод toString(): 2017-01-23T20:34:56.123456789+08:00[Азия/Сингапур]

преобразование

избегайте наследие java.util.Date класса. Но если нужно, ты можешь обратиться. Искать новые методы, добавленные к старым классам.

java.util.Date date = Date.fromInstant( instant ) ;

... в другую сторону...

Instant instant = myJavaUtilDate.toInstant() ;

Дата-только

только для даты используйте LocalDate.

LocalDate ld = zdt.toLocalDate() ;

о java.время

на java.время фреймворк встроен в Java 8 и потом. Эти классы вытесняют беспокойных старых наследие классы даты и времени, такие как java.util.Date, Calendar, & SimpleDateFormat.

на Joda-Time, теперь режим обслуживания, советует переход к java.время классы.

чтобы узнать больше, см. В Oracle Учебник. И Search Stack Overflow для многих примеров и объяснения. Спецификация JSR 310.

где получить java.время занятий?

  • Java SE 8, Java SE 9, и позже
    • встроенный.
    • часть стандартного API Java со встроенной реализацией.
    • Java 9 добавляет некоторые незначительные функции и зафиксировать.
  • Java SE 6 и Java SE 7
    • большая часть java.функциональность времени обратно портирована на Java 6 & 7 в ThreeTen-Backport.
  • Android

на ThreeTen-Extra проект расширяет java.время с дополнительными занятиями. Этот проект является испытательной площадкой для возможных будущих дополнений к java.время. Вы можете найти некоторые полезные классы, такие как Interval, YearWeek, YearQuarter и больше.