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

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

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

  • Преобразование локального времени в UTC//Works Fine До настоящего времени

  • Отмените время UTC, вернитесь к местному времени. Далее следуют 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());
    

    }

Ответ 1

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

Решение вашей проблемы с использованием стандартных объектов 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

Ответ 2

Обычно мы рассматриваем плохую форму на StackOverflow.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 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 ) ) );

    }
}

Ответ 3

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

В то время, когда вы задавали вопрос, Joda-Time была (из всего, что я знаю) явно лучшей альтернативой, время снова перешло. Сегодня Joda-Time - это в значительной степени законченный проект, и его разработчики рекомендуют использовать java.time, современный 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, поэтому использование этого имени, но не заглавное, для объекта, который hasnt получил тип localTime, может смутить некоторых (a 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 в блоге кодирования Jon Skeets.

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

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

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

Ответ 5

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

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

        long ts = System.currentTimeMillis();
        Date localTime = new Date(ts);

        SimpleDateFormat sdfLocal = new SimpleDateFormat ("yyyy/MM/dd HH:mm:ss");
        sdfLocal.setTimeZone(TimeZone.getTimeZone("Europe/Madrid"));

        SimpleDateFormat sdfUTC = new SimpleDateFormat ("yyyy/MM/dd HH:mm:ss");
        sdfUTC.setTimeZone(TimeZone.getTimeZone("UTC"));

        // Convert Local Time to UTC
        Date utcTime = sdfLocal.parse(sdfUTC.format(localTime));
        System.out.println("Local:" + localTime.toString() + "," + localTime.getTime() + " --> UTC time:" + utcTime.toString() + "-" + utcTime.getTime());

        // Reverse Convert UTC Time to Locale time
        localTime = sdfUTC.parse(sdfLocal.format(utcTime));
        System.out.println("UTC:" + utcTime.toString() + "," + utcTime.getTime() + " --> Local time:" + localTime.toString() + "-" + localTime.getTime());

Итак, после работы с ним вы можете добавить этот метод к своим utils:

    public Date convertDate(Date dateFrom, String fromTimeZone, String toTimeZone) throws ParseException {
        String pattern = "yyyy/MM/dd HH:mm:ss";
        SimpleDateFormat sdfFrom = new SimpleDateFormat (pattern);
        sdfFrom.setTimeZone(TimeZone.getTimeZone(fromTimeZone));

        SimpleDateFormat sdfTo = new SimpleDateFormat (pattern);
        sdfTo.setTimeZone(TimeZone.getTimeZone(toTimeZone));

        Date dateTo = sdfFrom.parse(sdfTo.format(dateFrom));
        return dateTo;
    }