【问题标题】:How to convert UTC and local timezone in Java如何在 Java 中转换 UTC 和本地时区
【发布时间】:2014-01-31 16:04:25
【问题描述】:

我对 Java 中的时区很好奇。我想从设备获取 UTC 时间(以毫秒为单位)并发送到服务器。服务器在向用户显示时间时会将其转换为本地时区。我系统中的时区是澳大利亚/悉尼(UTC + 11:00),我在测试时区时得到以下结果:

int year = 2014;
int month = 0;
int date = 14;
int hourOfDay = 11;
int minute = 12;
int second = 0;
Calendar c1 = Calendar.getInstance();
c1.set(year, month, date, hourOfDay, minute, second);
    
SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss z");
System.out.println(sdf.format(c1.getTime()));
    
Calendar c2 = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
c2.set(year, month, date, hourOfDay, minute, second);
System.out.println(sdf.format(c2.getTime()));

输出:

14/01/2014 11:12:00 EST
14/01/2014 22:12:00 EST

我认为我可以为 c2 设置 13/01/2014 00:12:00,因为 UTC 时间比我的晚 11 小时。日历不是按我预期的方式工作吗?

您的帮助将不胜感激。

编辑

添加了 z 来显示时区。这让我更加困惑,因为 Mac 说它的时区是(AEDT)澳大利亚东部夏令时间,但 Java 是 EST。无论如何,结果仍然不同,因为 EST 是 UTC-5 小时。

【问题讨论】:

  • 这个new Date(c1.getTimeInMillis())) 是多余的。使用c1.getTime()
  • 还在格式末尾添加Z 以查看您的时区。
  • 仅供参考,诸如 java.util.Datejava.util.Calendarjava.text.SimpleDateFormat 等非常麻烦的日期时间类现在是 legacy,被 Java 8 及更高版本中内置的 java.time 类所取代.见Tutorial by Oracle

标签: java timezone


【解决方案1】:

这是您需要查看的output result

                final String time="UTC";
                int year = 2014;
                int month = 0;
                int date = 14;
                int hourOfDay = 11;
                int minute = 12;
                int second = 0;
        
               calendar.set(year, month, date, hourOfDay, minute, second);
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSz");   
                  System.out.println(sdf.format(calendar.getTime()));
        
               Calendar calendar1=Calendar.getInstance();
               Date dat= calendar.getTime();
             
                calendar1.set(year,month,date,hourOfDay,minute,second);
                sdf.setTimeZone(TimeZone.getTimeZone(time));
               System.out.println(sdf.format(calendar1.getTime()));

【讨论】:

    【解决方案2】:

    更新:此答案现已过时。 Joda-Time 库现在被 Java 8 及更高版本中内置的 java.time 框架所取代。见this new Answer

    三字母代码

    您应避免使用 3 或 4 个字母的时区代码,例如 ESTIST。它们既不标准也不独特。

    使用proper time zone names,主要是Continent/CityOrRegion,例如America/MontrealAsia/Kolkata

    乔达时间

    java.util.Date/Calendar 类是出了名的糟糕。避免使用它们。使用 Joda-Time 或在 Java 8 中使用由 JSR 310 定义并受 Joda-Time 启发的新 java.time.* classes

    请注意下面显示的 Joda-Time 代码是多么简单和明显。 Joda-Time 甚至知道如何计算 - 一月是 1,而不是 0!

    时区

    在 Joda-Time 中,DateTime 实例知道自己的时区。

    Sydney Australia 的标准时间比 UTC/GMT 早 10 小时,夏令时 (DST) 早 11 小时。 DST 适用于问题指定的日期。

    提示:不要这样想……

    UTC 时间比我的晚 11 小时

    这样想……

    悉尼 DST 比 UTC/GMT 早 11 小时。

    如果您以 UTC/GMT 进行思考、工作和存储,日期时间工作会变得更容易且不易出错。仅转换为本地化日期时间以在用户界面中显示。全局思考,局部展示。您的用户和服务器可以轻松移动到其他时区,因此忘记您自己的时区。始终指定时区,切勿假设或依赖默认值。

    示例代码

    这里是一些使用 Joda-Time 2.3 和 Java 8 的示例代码。

    // Better to specify a time zone explicitly than rely on default.
    // Use time zone names, not 3-letter codes. 
    // This list is not quite up-to-date (read page for details): http://joda-time.sourceforge.net/timezones.html
    DateTimeZone timeZone = DateTimeZone.forID("Australia/Sydney");
    DateTime dateTime = new DateTime(2014, 1, 14, 11, 12, 0, timeZone);
    DateTime dateTimeUtc = dateTime.toDateTime(DateTimeZone.UTC); // Built-in constant for UTC (no time zone offset).
    

    转储到控制台...

    System.out.println("dateTime: " + dateTime);
    System.out.println("dateTimeUtc: " + dateTimeUtc);
    

    运行时……

    dateTime: 2014-01-14T11:12:00.000+11:00
    dateTime in UTC: 2014-01-14T00:12:00.000Z
    

    【讨论】:

      【解决方案3】:

      tl;博士

      使用现代 java.time 类。

      ZonedDateTime
      .of( 2014 , 1 , 14 , 11 , 12 , 0 , 0 , ZoneId.of( "Australia/Sydney" ) )
      .toInstant()
      .toEpochMilli() 
      

      1389658320000

      往另一个方向发展。

      Instant
      .ofEpochMilli( 1_389_658_320_000L )  // .toString(): 2014-01-14T00:12:00Z
      .atZone( 
          ZoneId.of( "Australia/Sydney" ) 
      )                                    // .toString(): 2014-01-14T11:12+11:00[Australia/Sydney]
      .format(
          DateTimeFormatter
          .ofPattern ( 
              "dd/MM/uuuu HH:mm:ss z" , 
              new Locale( "en" , "AU" ) 
          )
      )
      

      2014 年 14 月 1 日 11:12:00 澳大利亚东部夏令时间

      java.time

      您正在使用糟糕的日期时间类,这些类在几年前因采用定义现代 java.time 类的 JSR 310 而过时。

      我对 Java 中的时区很好奇。

      仅供参考,与 UTC 的偏移量只是几个小时-分钟-秒。当我们说“UTC”或在字符串末尾添加Z 时,我们指的是 UTC 本身的零时分秒偏移量。

      时区要多得多。时区是特定地区的人们使用的偏移量的过去、现在和未来变化的历史。世界各地的政客对改变他们的管辖范围有一种奇怪的偏好。

      我想从设备获取 UTC 时间(以毫秒为单位)并发送到服务器。

      目前,请使用InstantInstant 内部是自 1970 UTC 第一时刻的纪元参考以来的整秒数,加上以纳秒为单位的小数秒。

      Instant now = Instant.now() ;  // Capture current moment in UTC.
      long millisecondsSinceEpoch = now.toEpochMilli() ;
      

      往另一个方向发展。

      Instant instant = Instant.ofEpochMilli( millisecondsSinceEpoch ) ;
      

      服务器会将其转换为本地时区……

      指定用户期望/期望的时区。

      如果没有指定时区,JVM 会隐式应用其当前的默认时区。该默认值可能在运行时(!)期间change at any moment,因此您的结果可能会有所不同。最好将您想要/预期的时区明确指定为参数。如果关键,请与您的用户确认该区域。

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

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

      …当它向用户显示时间时

      根据用户的语言和文化自动本地化。

      要本地化,请指定:

      • FormatStyle 确定字符串的长度或缩写。
      • Locale 确定:
        • 人类语言用于翻译日名、月名等。
        • 文化规范决定缩写、大写、标点、分隔符等问题。

      例子:

      Locale l = Locale.CANADA_FRENCH ;   // Or Locale.US, Locale.JAPAN, etc.
      DateTimeFormatter f = 
          DateTimeFormatter
          .ofLocalizedDateTime( FormatStyle.FULL )
          .withLocale( l )
      ;
      String output = zdt.format( f );
      

      我系统中的时区是澳大利亚/悉尼(UTC + 11:00)

      您的服务器当前的默认时区应该与您的程序无关。始终指定所需/预期的时区。坦率地说,将各种日期时间方法的时区(和Locale)参数设为可选是 java.time 框架中为数不多的设计缺陷之一。

      提示:通常最好将您的服务器设置为 UTC 作为其当前的默认时区。

      顺便说一句,要明确时区和语言环境没有任何关系。您可能需要日语来显示在Africa/Tunis 时区中看到的时刻。

      ZoneID zAuSydney = ZoneId.of( "Australia/Sydney" ) ;
      ZonedDateTime zdt = instant.atZone( zAuSydney ) ;
      String output = zdt.format(
          DateTimeFormatter
          .localizedDateTime( FormatStyle.LONG )
          .withLocale( new Locale( "en" , "AU" ) ;
      ) ;
      

      int 年 = 2014; …

      请注意,java.time 使用合理的编号,这与遗留类不同。 1 月至 12 月的月份为 1-12,周一至周日的工作日为 1-7。

      LocalDate ld = LocalDate.of( 2014 , 1 , 14 ) ;
      LocalTime lt = LocalTime.of( 11 , 12 ) ;
      ZoneId z = ZoneId.of( "Australia/Sydney" ) ;
      ZonedDateTime zdt = ZonedDateTime.of( ld , lt , z ) ;
      

      zdt.toString() = 2014-01-14T11:12+11:00[澳大利亚/悉尼]

      通常最好自动本地化显示,如上所示。但如果您坚持,您可以硬编码格式模式。

      Locale locale = new Locale ( "en" , "AU" );
      ZoneId z = ZoneId.of ( "Australia/Sydney" );
      ZonedDateTime zdt = ZonedDateTime.of ( 2014 , 1 , 14 , 11 , 12 , 0 , 0 , z );
      

      zdt.toString(): 2014-01-14T11:12+11:00[澳大利亚/悉尼]

      指定你的格式化模式。

      DateTimeFormatter f = DateTimeFormatter.ofPattern ( "dd/MM/uuuu HH:mm:ss z" , locale );
      String output = zdt.format ( f );
      

      输出 = 2014 年 1 月 14 日 11:12:00 AEDT

      您的问题对自 1970-01-01T00:00:00Z 纪元以来的毫秒数感兴趣。所以从澳大利亚时区调整为UTC。同一时刻,时间轴上的同一点,不同的挂钟时间。

      Instant instant = zdt.toInstant() ;  // Adjust from time zone to UTC.
      

      instant.toString(): 2014-01-14T00:12:00Z

      注意instantzdt 之间的小时差异。

      我认为我可以为 c2 设置 13/01/2014 00:12:00,因为 UTC 时间比我的晚 11 小时。

      ➥ 如您所愿,悉尼地区上午 11 点后 12 分钟与 UTC 午夜后 12 分钟是同一时刻,因为该日期的Australia/Sydney 比 UTC 早 11 小时。

      计算自纪元以来的毫秒数。

      long millisecondsSinceEpoch = instant.toEpochMilli() ;
      

      【讨论】:

        【解决方案4】:

        您可能打算在格式化程序上设置时区,而不是日历(或者除了日历,它不是 100% 清楚您要完成的任务)!用于创建人类表示的时区来自 SimpleDateFormat。当您通过调用 getTime() 将其转换回 java.util.Date 时,日历中的所有“时区”信息都会丢失。

        代码:

        Calendar c2 = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
        c2.set(year, month, date, hourOfDay, minute, second);
        System.out.println(sdf.format(c2.getTime()));
        

        正在打印14/01/2014 10:12:00,因为在 Syndey(格式化程序的时区)中显示的 11AM UTC 是晚上 10 点! (使用 HH 格式表示 24 小时时间)

        这会打印出你想要做的事情:

        SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy hh:mm:ss z");
        System.out.println(sdf.format(c1.getTime()));
        
        sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
        System.out.println(sdf.format(c1.getTime()));
        

        “UTC 毫秒”的概念毫无意义。毫秒数只是历史上的一个固定点,它没有与之关联的时区。我们向其添加时区以将其转换为人类可读的表示形式。

        编辑:是的,对于(美国)东部时间和(澳大利亚)东部时间都使用“EST”的歧义一直是 Java 中的一个陷阱。

        【讨论】:

        • 感谢您的意见。我更改了格式,但结果仍然不是我所期望的。
        • 当我传递将 EST 转换为 UTC 的 UTC 时区参数时,我只是希望 Calendar 会返回 UTC 日期时间,但它看起来不像那样。
        • 这是大多数人第一次使用时所期望的,是的。
        猜你喜欢
        • 1970-01-01
        • 2011-03-01
        • 1970-01-01
        • 2011-09-16
        • 1970-01-01
        • 1970-01-01
        • 2018-04-17
        • 2018-08-08
        • 2014-10-05
        相关资源
        最近更新 更多