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, вы можете.
Ответ 4
Я настоятельно рекомендую использовать Joda Time http://joda-time.sourceforge.net/faq.html
Ответ 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;
}