如何在Java中转换UTC和本地时区

时间:2014-01-13 00:47:51

标签: java timezone

我对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

我认为我可以在13/01/2014 00:12:00为c2因为UTC时间晚于我的11小时。日历不按我期望的方式工作吗?

我们将不胜感激。

修改

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

4 个答案:

答案 0 :(得分:11)

三字母代码

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

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

约达时间

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

请注意下面显示的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

答案 1 :(得分:6)

您可能打算在格式化程序上设置时区,而不是日历(或者除了日历之外,它不是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(格式化程序的时区)中显示的上午11点UTC是晚上10点! (以24小时的格式使用HH)

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

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中的一个陷阱。

答案 2 :(得分:0)

tl; dr

使用现代的 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年1月14日11:12:00 AEDT

java.time

您使用的是可怕的日期时间类,这些类早在几年前通过定义现代 java.time 类的JSR 310而过时了。

  

我对Java中的时区感到好奇。

仅供参考,与UTC的偏移量仅是数小时-数分钟-秒。当我们说“ UTC”或在字符串的末尾放置Z时,表示UTC本身的偏移量为零小时-分钟-秒。

时区远不止于此。时区是特定区域的人们过去,现在和将来对偏移量的更改的历史记录。世界各地的政客都对改变管辖范围产生了奇怪的喜好。

  

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

当前使用Instant。内部的Instant是自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 );
  

我系统中的时区是澳大利亚/悉尼(世界标准时间+ 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 );
  

output = 14/01/2014 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点之后的十二分钟与UTC午夜之后的十二分钟是同一时刻,因为该日期的Australia/Sydney比UTC早十一小时。

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

long millisecondsSinceEpoch = instant.toEpochMilli() ;

答案 3 :(得分:0)

这是您需要检查的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()));