Java Convert GMT/UTC по местному времени не работает должным образом

Чтобы показать воспроизводимый сценарий, я делаю следующее

  1. Получить текущее системное время (местное время)

  2. Конвертировать местное время в UTC // Работает отлично До здесь

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

    {

    long ts = System.currentTimeMillis();
    Date localTime = new Date(ts);
    String format = "yyyy/MM/dd HH:mm:ss";
    SimpleDateFormat sdf = new SimpleDateFormat (format);
    
    // Convert Local Time to UTC (Works Fine) 
    sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
    Date gmtTime = new Date(sdf.format(localTime));
    System.out.println("Local:" + localTime.toString() + "," + localTime.getTime() + " --> UTC time:" + gmtTime.toString() + "-" + gmtTime.getTime());
    
    // Reverse Convert UTC Time to Locale time (Doesn't work) Approach 1
    sdf.setTimeZone(TimeZone.getDefault());        
    localTime = new Date(sdf.format(gmtTime));
    System.out.println("Local:" + localTime.toString() + "," + localTime.getTime() + " --> UTC time:" + gmtTime.toString() + "-" + gmtTime.getTime());
    
    // Reverse Convert UTC Time to Locale time (Doesn't work) Approach 2 using DateFormat
    DateFormat df = new SimpleDateFormat (format);
    df.setTimeZone(TimeZone.getDefault());
    localTime = df.parse((df.format(gmtTime)));
    System.out.println("Local:" + localTime.toString() + "," + localTime.getTime() + " --> UTC time:" + gmtTime.toString() + "-" + gmtTime.getTime());
    
    // Approach 3
    Calendar c = new GregorianCalendar(TimeZone.getDefault());
    c.setTimeInMillis(gmtTime.getTime());
    System.out.println("Local Time " + c.toString());
    

    }

6 ответов

Решение

Я также рекомендую использовать Йоду, как упоминалось ранее.

Решение вашей проблемы с использованием стандартной Java Date Только объекты могут быть выполнены следующим образом:

    // **** YOUR CODE **** BEGIN ****
    long ts = System.currentTimeMillis();
    Date localTime = new Date(ts);
    String format = "yyyy/MM/dd HH:mm:ss";
    SimpleDateFormat sdf = new SimpleDateFormat(format);

    // Convert Local Time to UTC (Works Fine)
    sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
    Date gmtTime = new Date(sdf.format(localTime));
    System.out.println("Local:" + localTime.toString() + "," + localTime.getTime() + " --> UTC time:"
            + gmtTime.toString() + "," + gmtTime.getTime());

    // **** YOUR CODE **** END ****

    // Convert UTC to Local Time
    Date fromGmt = new Date(gmtTime.getTime() + TimeZone.getDefault().getOffset(localTime.getTime()));
    System.out.println("UTC time:" + gmtTime.toString() + "," + gmtTime.getTime() + " --> Local:"
            + fromGmt.toString() + "-" + fromGmt.getTime());

Выход:

Local:Tue Oct 15 12:19:40 CEST 2013,1381832380522 --> UTC time:Tue Oct 15 10:19:40 CEST 2013,1381825180000
UTC time:Tue Oct 15 10:19:40 CEST 2013,1381825180000 --> Local:Tue Oct 15 12:19:40 CEST 2013-1381832380000

Joda времени


ОБНОВЛЕНИЕ: проект Joda-Time сейчас находится в режиме обслуживания, и команда советует перейти на классы java.time. Смотрите Учебник по Oracle.

Посмотрите мой другой ответ, используя лучшие в отрасли классы java.time.


Обычно мы считаем плохой формой на Stackru.com отвечать на конкретный вопрос, предлагая альтернативную технологию. Но в случае классов даты, времени и календаря, связанных с Java 7 и более ранними версиями, эти классы настолько плохи как в дизайне, так и в исполнении, что я вынужден предложить вместо этого использовать стороннюю библиотеку: Joda-Time.

Joda-Time работает, создавая неизменные объекты. Поэтому вместо того, чтобы изменять часовой пояс объекта DateTime, мы просто создаем новый DateTime с другим назначенным часовым поясом.

Ваша главная задача использования местного и UTC-времени очень проста в Joda-Time, занимая всего 3 строки кода.

    org.joda.time.DateTime now = new org.joda.time.DateTime();
    System.out.println( "Local time in ISO 8601 format: " + now + " in zone: " + now.getZone() );
    System.out.println( "UTC (Zulu) time zone: " + now.toDateTime( org.joda.time.DateTimeZone.UTC ) );

Выход при запуске на западном побережье Северной Америки может быть:

Local time in ISO 8601 format: 2013-10-15T02:45:30.801-07:00

UTC (Zulu) time zone: 2013-10-15T09:45:30.801Z

Вот класс с несколькими примерами и дальнейшими комментариями. Использование Joda-Time 2.5.

/**
 * Created by Basil Bourque on 2013-10-15.
 * © Basil Bourque 2013
 * This source code may be used freely forever by anyone taking full responsibility for doing so.
 */
public class TimeExample {
    public static void main(String[] args) {
        // Joda-Time - The popular alternative to Sun/Oracle's notoriously bad date, time, and calendar classes bundled with Java 8 and earlier.
        // http://www.joda.org/joda-time/

        // Joda-Time will become outmoded by the JSR 310 Date and Time API introduced in Java 8.
        // JSR 310 was inspired by Joda-Time but is not directly based on it.
        // http://jcp.org/en/jsr/detail?id=310

        // By default, Joda-Time produces strings in the standard ISO 8601 format.
        // https://en.wikipedia.org/wiki/ISO_8601
        // You may output to strings in other formats.

        // Capture one moment in time, to be used in all the examples to follow.
        org.joda.time.DateTime now = new org.joda.time.DateTime();

        System.out.println( "Local time in ISO 8601 format: " + now + " in zone: " + now.getZone() );
        System.out.println( "UTC (Zulu) time zone: " + now.toDateTime( org.joda.time.DateTimeZone.UTC ) );

        // You may specify a time zone in either of two ways:
        // • Using identifiers bundled with Joda-Time
        // • Using identifiers bundled with Java via its TimeZone class

        // ----|  Joda-Time Zones  |---------------------------------

        // Time zone identifiers defined by Joda-Time…
        System.out.println( "Time zones defined in Joda-Time : " + java.util.Arrays.toString( org.joda.time.DateTimeZone.getAvailableIDs().toArray() ) );

        // Specify a time zone using DateTimeZone objects from Joda-Time.
        // http://joda-time.sourceforge.net/apidocs/org/joda/time/DateTimeZone.html
        org.joda.time.DateTimeZone parisDateTimeZone = org.joda.time.DateTimeZone.forID( "Europe/Paris" );
        System.out.println( "Paris France (Joda-Time zone): " + now.toDateTime( parisDateTimeZone ) );

        // ----|  Java Zones  |---------------------------------

        // Time zone identifiers defined by Java…
        System.out.println( "Time zones defined within Java : " + java.util.Arrays.toString( java.util.TimeZone.getAvailableIDs() ) );

        // Specify a time zone using TimeZone objects built into Java.
        // http://docs.oracle.com/javase/8/docs/api/java/util/TimeZone.html
        java.util.TimeZone parisTimeZone = java.util.TimeZone.getTimeZone( "Europe/Paris" );
        System.out.println( "Paris France (Java zone): " + now.toDateTime(org.joda.time.DateTimeZone.forTimeZone( parisTimeZone ) ) );

    }
}

У вас есть дата с известным часовым поясом (здесь Europe/Madrid) и целевой часовой пояс (UTC)

Вам просто нужно два SimpleDateFormats:

        long ts = System.currentTimeMillis (); Дата localTime = новая дата (тс);

        SimpleDateFormat sdfLocal = new SimpleDateFormat ("гггг / мм / дд чч: мм: сс"); sdfLocal.setTimeZone (TimeZone.getTimeZone ("Europe /Madrid"));

        SimpleDateFormat sdfUTC = новый SimpleDateFormat ("гггг / мм / дд чч: мм: сс");
        sdfUTC.setTimeZone(TimeZone.getTimeZone("UTC"));

        // Преобразовать местное время в UTC
        Date utcTime = sdfLocal.parse(sdfUTC.format(localTime));
        System.out.println("Local:" + localTime.toString() + "," + localTime.getTime() + "-> время UTC:" + utcTime.toString() + "-" + utcTime.getTime ()); // Обратное преобразование времени UTC в локаль time localTime = sdfUTC.parse (sdfLocal.format (utcTime)); System.out.println ("UTC:" + utcTime.toString() + "," + utcTime.getTime () + "-> Местное время:" + localTime.toString() + "-" + localTime.getTime());

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

    public Date convertDate (Date dateFrom, String fromTimeZone, String toTimeZone) выдает ParseException {
        Образец строки = "гггг / мм / дд чч: мм: сс";
        SimpleDateFormat sdfFrom = new SimpleDateFormat (шаблон);
        sdfFrom.setTimeZone(TimeZone.getTimeZone(fromTimeZone));

        SimpleDateFormat sdfTo = новый SimpleDateFormat (шаблон);
        sdfTo.setTimeZone(TimeZone.getTimeZone(toTimeZone));

        Date dateTo = sdfFrom.parse(sdfTo.format(dateFrom));
        вернуть dateTo;
    }

ТЛ; др

Instant.now()                           // Capture the current moment in UTC.
.atZone( ZoneId.systemDefault() )       // Adjust into the JVM's current default time zone. Same moment, different wall-clock time. Produces a `ZonedDateTime` object.
.toInstant()                            // Extract a `Instant` (always in UTC) object from the `ZonedDateTime` object.
.atZone( ZoneId.of( "Europe/Paris" ) )  // Adjust the `Instant` into a specific time zone. Renders a `ZonedDateTime` object. Same moment, different wall-clock time.
.toInstant()                            // And back to UTC again.

java.time

Современный подход использует классы java.time, которые вытеснили проблемные старые унаследованные классы даты и времени (Date, Calendar, так далее.).

Использование вами слова "local" противоречит использованию в классе java.time. В java.time "местный" означает любой населенный пункт или все населенные пункты, но не один конкретный населенный пункт. Во всех классах java.time с именами, начинающимися с "Local…", отсутствует понятие часового пояса или смещения от UTC. Таким образом, они не представляют конкретный момент, они не являются точкой на временной шкале, в то время как ваш Вопрос связан с моментами, точками на временной шкале, просматриваемыми в различные часы.

Получить текущее системное время (местное время)

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

Instant instant = Instant.now() ;  // Capture the current moment in UTC.

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

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

ZoneId z = ZoneId.of( "America/Montreal" ) ;
ZonedDateTime zdt = instant.atZone( z ) ;  // Same moment, different wall-clock time.

В качестве ярлыка, вы можете пропустить использование Instant чтобы получить ZonedDateTime,

ZoneId z = ZoneId.of( "America/Montreal" ) ;
ZonedDateTime zdt = ZonedDateTime.now( z ) ;

Конвертировать местное время в UTC // Работает отлично До здесь

Вы можете отрегулировать от зонированной даты-времени до UTC, извлекая Instant из ZonedDateTime,

ZoneId z = ZoneId.of( "America/Montreal" ) ;
ZonedDateTime zdt = ZonedDateTime.now( z ) ;
Instant instant = zdt.toInstant() ;

Обратное время UTC, назад к местному времени.

Как показано выше, примените ZoneId настроить тот же момент на другое время на настенных часах, используемое людьми определенного региона (часового пояса).

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

ZoneId zDefault = ZoneId.systemDefault() ;  // The JVM's current default time zone.
ZonedDateTime zdtDefault = instant.atZone( zDefault ) ;

ZoneId zTunis = ZoneId.of( "Africa/Tunis" ) ;  // The JVM's current default time zone.
ZonedDateTime zdtTunis = instant.atZone( zTunis ) ;

ZoneId zAuckland = ZoneId.of( "Pacific/Auckland" ) ;  // The JVM's current default time zone.
ZonedDateTime zdtAuckland = instant.atZone( zAuckland ) ;

Возвращаясь к UTC с зонированной даты и времени, звоните ZonedDateTime::toInstant, Думайте об этом концептуально как: ZonedDateTime = Instant + ZoneId.

Instant instant = zdtAuckland.toInstant() ;

Все эти объекты, Instant и три ZonedDateTime все объекты представляют один и тот же момент, одну и ту же точку истории.

Использовали 3 различных подхода (перечислены ниже), но все 3 подхода сохраняют время только в UTC.

Забудьте о попытке исправить код, используя эти ужасные Date, Calendar, а также GregorianCalendar классы. Это жалкий беспорядок плохого дизайна и недостатков. Тебе никогда больше не нужно трогать их Если вам нужно взаимодействовать со старым кодом, который еще не обновлен до java.time, вы можете конвертировать туда и обратно с помощью новых методов преобразования, добавленных к старым классам.


О java.time

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

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

Чтобы узнать больше, смотрите Oracle Tutorial. И поиск переполнения стека для многих примеров и объяснений. Спецификация JSR 310.

Вы можете обмениваться объектами java.time напрямую с вашей базой данных. Используйте драйвер JDBC, соответствующий JDBC 4.2 или более поздней версии. Нет необходимости в строках, нет необходимости в java.sql.* классы.

Где взять классы java.time?

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

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

Я присоединяюсь к хору и рекомендую пропустить давно устаревшие классы Date, Calendar, SimpleDateFormat и друзья. В частности, я бы предостерег от использования устаревших методов и конструкторов Date класс, как и Date(String) конструктор вы использовали. Они устарели, потому что они не работают надежно в разных часовых поясах, поэтому не используйте их. И да, большинство конструкторов и методов этого класса устарели.

В то время как вы задали вопрос, Joda-Time был (насколько я знаю) явно лучшей альтернативой, время снова пошло дальше. Сегодня Joda-Time - в значительной степени законченный проект, и его разработчики рекомендуют вам использовать java.time современный API даты и времени Java. Я покажу вам, как.

    ZonedDateTime localTime = ZonedDateTime.now(ZoneId.systemDefault());

    // Convert Local Time to UTC 
    OffsetDateTime gmtTime
            = localTime.toOffsetDateTime().withOffsetSameInstant(ZoneOffset.UTC);
    System.out.println("Local:" + localTime.toString() 
            + " --> UTC time:" + gmtTime.toString());

    // Reverse Convert UTC Time to Local time
    localTime = gmtTime.atZoneSameInstant(ZoneId.systemDefault());
    System.out.println("Local Time " + localTime.toString());

Для начала, обратите внимание, что код не только вдвое меньше вашего, но и понятнее.

На моем компьютере код печатает:

Local:2017-09-02T07:25:46.211+02:00[Europe/Berlin] --> UTC time:2017-09-02T05:25:46.211Z
Local Time 2017-09-02T07:25:46.211+02:00[Europe/Berlin]

Я пропустил миллисекунды из эпохи. Вы всегда можете получить их от System.currentTimeMillis(); как в вашем вопросе, и они не зависят от часового пояса, поэтому я не нашел их интересными здесь.

Я нерешительно сохранил ваше имя переменной localTime, Я думаю, что это хорошее имя. Современный API имеет класс под названием LocalTime поэтому, используя это имя, только без заглавной буквы, для объекта, который не имеет типа LocalTime может запутать некоторых (LocalTime не хранит информацию о часовом поясе, которую мы должны хранить здесь, чтобы сделать правильное преобразование; он также содержит только время суток, а не дату).

Ваше преобразование из местного времени в UTC было неправильным и невозможным

Устаревшие Date класс не содержит никакой информации о часовом поясе (вы можете сказать, что внутри он всегда использует UTC), поэтому нет такой вещи, как преобразование Date из одного часового пояса в другой. Когда я просто запустил ваш код на моем компьютере, первая напечатанная строка была:

Local:Sat Sep 02 07:25:45 CEST 2017,1504329945967 --> UTC time:Sat Sep 02 05:25:45 CEST 2017-1504322745000

07:25:45 CEST правильно, конечно. Правильное время UTC было бы 05:25:45 UTC, но это говорит CEST опять же, что неверно.

Теперь вам никогда не понадобится Date снова класс,:-), но если вы когда-нибудь собираетесь, обязательно прочитайте " Все о java.util.Date" в блоге Джона Скита по кодированию.

Вопрос: Могу ли я использовать современный API с моей версией Java?

Если вы используете хотя бы Java 6, вы можете.

  • В Java 8 и более поздних версиях новый API поставляется встроенным.
  • В Java 6 и 7 получают ThreeTen Backport, бэкпорт новых классов (это ThreeTen для JSR-310, где впервые был определен современный API).
  • На Android используйте Android-версию ThreeTen Backport. Он называется ThreeTenABP, и я думаю, что в этом вопросе есть замечательное объяснение : как использовать ThreeTenABP в Android Project.

Я настоятельно рекомендую использовать Joda Time http://joda-time.sourceforge.net/faq.html

Другие вопросы по тегам