Java转换GMT / UTC到本地时间不能按预期工作

时间:2013-10-15 07:19:24

标签: java simpledateformat utc date-formatting timestamp-with-timezone

为了显示可重现的场景,我正在执行以下操作

  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 个答案:

答案 0 :(得分:56)

我还建议使用前面提到的 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

答案 1 :(得分:4)

约达时间


更新:Joda-Time项目现在位于maintenance mode,团队建议迁移到java.time课程。请参阅Tutorial by Oracle

使用行业领先的 java.time 类查看my other Answer


通常我们认为StackOverflow.com上的错误形式是通过建议替代技术来回答特定问题。但是对于与Java 7及更早版本捆绑在一起的日期,时间和日历类,这些类在设计和执行方面都非常糟糕,我不得不建议使用第三方库:Joda-Time

Joda-Time通过创建immutable objects来工作。因此,我们只是实例化一个分配了不同时区的新DateTime,而不是改变DateTime对象的时区。

在Joda-Time中,使用本地和UTC时间的中心问题非常简单,仅需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 ) ) );

    }
}

答案 2 :(得分:2)

您的日期包含已知时区(此处为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;
    }

答案 3 :(得分:2)

我正在加入合唱团,建议您跳过现在已久的过时课程DateCalendarSimpleDateFormat和朋友。特别是我会警告不要使用Date类的已弃用方法和构造函数,例如您使用的Date(String)构造函数。它们被弃用,因为它们不能跨时区可靠地工作,所以不要使用它们。是的,该类的大多数构造函数和方法都已弃用。

当你问这个问题时,Joda-Time(据我所知)是一个明显更好的选择,时间再次开始。今天Joda-Time是一个基本完成的项目,它的开发人员建议你使用现代Java日期和时间API java.time。我会告诉你如何。

    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课程,:-)但是如果你要去的话,那就必须在Jon Skeet的编码博客上阅读All about java.util.Date

问题:我可以在Java版本中使用现代API吗?

如果至少使用Java 6 ,则可以。

答案 4 :(得分:2)

TL;博士

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 类,这些类取代了麻烦的旧遗留日期时间类(DateCalendar等。)。

您对“local”一词的使用与 java.time 类中的用法相矛盾。在 java.time 中,“local”表示任何位置或所有位置,但不是任何一个特定位置。名称以“Local ...”开头的 java.time 类都缺少时区或从UTC偏移的概念。所以他们代表一个特定的时刻,他们时间轴上的一个点,而你的问题是关于时刻,通过各种挂钟查看时间轴上的点次。

  

获取当前系统时间(当地时间)

如果要捕获UTC中的当前时刻,请使用InstantInstant类代表UTC中时间轴上的一个时刻,分辨率为nanoseconds(小数部分最多九(9)位)。

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

通过应用ZoneId来调整时区以获得ZonedDateTime。同一时刻,时间轴上的同一点,不同的挂钟时间。

continent/region的格式指定proper time zone name,例如America/MontrealAfrica/CasablancaPacific/Auckland。切勿使用诸如ESTIST之类的3-4字母缩写,因为它们不是真正的时区,不是标准化的,甚至不是唯一的(!)。

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 //在这里工作正常

您可以从Instant中提取ZonedDateTime,从分区日期时间调整为UTC。

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时间。

忘记尝试使用那些糟糕的DateCalendarGregorianCalendar类来修复代码。他们是一个糟糕的设计和缺陷的可怜混乱。你永远不需要再碰他们了。如果必须与尚未更新为 java.time 的旧代码连接,则可以通过添加到旧类的新转换方法来回转换。

关于 java.time

java.time框架内置于Java 8及更高版本中。这些类取代了麻烦的旧legacy日期时间类,例如java.util.DateCalendar和& SimpleDateFormat

现在位于Joda-Timemaintenance mode项目建议迁移到java.time类。

要了解详情,请参阅Oracle Tutorial。并搜索Stack Overflow以获取许多示例和解释。规范是JSR 310

您可以直接与数据库交换 java.time 对象。使用符合JDBC driver或更高版本的JDBC 4.2。不需要字符串,不需要java.sql.*类。

从哪里获取java.time类?

ThreeTen-Extra项目使用其他类扩展java.time。该项目是未来可能添加到java.time的试验场。您可以在此处找到一些有用的课程,例如IntervalYearWeekYearQuartermore

答案 5 :(得分:1)

我强烈建议使用Joda Time http://joda-time.sourceforge.net/faq.html