【问题标题】:Java Convert GMT/UTC to Local time doesn't work as expectedJava 将 GMT/UTC 转换为本地时间无法按预期工作
【发布时间】:2013-10-22 21:52:09
【问题描述】:

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

  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());
    

    }

【问题讨论】:

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


    【解决方案1】:

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

    【讨论】:

    • 您能否提供一个使用#joda 实现相同目的的示例?
    • 我不介意使用 joda,但想了解为什么代码没有按预期运行。
    【解决方案2】:

    乔达时间


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

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


    通常,我们认为在 StackOverflow.com 上通过建议替代技术来回答特定问题是一种不好的形式。但是对于与 Java 7 及更早版本捆绑在一起的日期、时间和日历类,这些类在设计和执行方面都非常糟糕,以至于我不得不建议使用 3rd-party 库: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

    这是一个包含几个示例和更多 cmets 的类。使用 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 ) ) );
    
        }
    }
    

    【讨论】:

      【解决方案3】:

      我也推荐使用前面提到的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
      

      【讨论】:

      • 一个很好的答案!但是,应该有 TimeZone.getDefault().getOffset(gmtTime.getTimeInMillis()),而不是 TimeZone.getDefault().getOffset(localTime.getTime())。否则,如果您的 UTC 时间在冬季,但本地时间在夏季,您可能会遇到夏令时问题。
      • new Date(String string) 已弃用 :(
      • 你是救命稻草 T_T 这是解决 GMT 与 UTC 的 java.util.date(@temporal) 和 timestamp(postgresql) 冲突的唯一方法
      【解决方案4】:

      您的日期具有已知时区(此处为 Europe/Madrid)和目标时区(UTC

      您只需要两个 SimpleDateFormat:

      长 ts = System.currentTimeMillis(); 日期本地时间 = 新日期(ts); SimpleDateFormat sdfLocal = new SimpleDateFormat ("yyyy/MM/dd HH:mm:ss"); sdfLocal.setTimeZone(TimeZone.getTimeZone("欧洲/马德里")); SimpleDateFormat sdfUTC = new SimpleDateFormat ("yyyy/MM/dd HH:mm:ss"); sdfUTC.setTimeZone(TimeZone.getTimeZone("UTC")); // 将本地时间转换为 UTC 日期 utcTime = sdfLocal.parse(sdfUTC.format(localTime)); System.out.println("Local:" + localTime.toString() + "," + localTime.getTime() + " --> UTC 时间:" + utcTime.toString() + "-" + utcTime.getTime( )); // 将 UTC 时间反向转换为语言环境时间 localTime = sdfUTC.parse(sdfLocal.format(utcTime)); System.out.println("UTC:" + utcTime.toString() + "," + utcTime.getTime() + " --> 当地时间:" + localTime.toString() + "-" + localTime.getTime( ));

      所以在看到它工作后,您可以将此方法添加到您的实用程序中:

          公共日期转换日期(日期 dateFrom,字符串 fromTimeZone,字符串 toTimeZone)抛出 ParseException {
              字符串模式 = "yyyy/MM/dd HH:mm:ss";
              SimpleDateFormat sdfFrom = new SimpleDateFormat(模式);
              sdfFrom.setTimeZone(TimeZone.getTimeZone(fromTimeZone));
      
              SimpleDateFormat sdfTo = new SimpleDateFormat(模式);
              sdfTo.setTimeZone(TimeZone.getTimeZone(toTimeZone));
      
              日期 dateTo = sdfFrom.parse(sdfTo.format(dateFrom));
              返回日期至;
          }
      

      【讨论】:

      【解决方案5】:

      我正在加入合唱团,建议您跳过现在早已过时的课程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,则可以。

      【讨论】:

        【解决方案6】:

        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 等)。

        您对“本地”一词的使用与 java.time 类中的用法相矛盾。在java.time 中,“本地”表示任何 地点或所有 地点,但不是任何一个特定地点。名称以“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。切勿使用 3-4 个字母的缩写,例如 ESTIST,因为它们不是真正的时区,没有标准化,甚至不是唯一的 (!)。

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

        您可以通过从ZonedDateTime 中提取Instant,将分区日期时间调整为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.DateCalendarSimpleDateFormat

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

        要了解更多信息,请参阅Oracle Tutorial。并在 Stack Overflow 上搜索许多示例和解释。规格为JSR 310

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

        从哪里获得 java.time 类?

        ThreeTen-Extra 项目通过附加类扩展了 java.time。该项目是未来可能添加到 java.time 的试验场。您可以在这里找到一些有用的类,例如IntervalYearWeekYearQuartermore

        【讨论】:

          猜你喜欢
          • 2023-03-26
          • 2010-09-15
          • 2011-08-29
          • 2015-11-09
          • 2018-09-17
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多