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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/21082325/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 06:27:30  来源:igfitidea点击:

How to convert UTC and local timezone in Java

javatimezone

提问by sunghun

I am curious about timezone in Java. I want to get UTC time in milliseconds from a device and send to server. Server will convert it to local timezone when it displays time to users. Timezone in my system is Australia/Sydney( UTC + 11:00), and I have got the result below when I tested 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()));

output:

输出:

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

I thought I could have 13/01/2014 00:12:00 for c2 because UTC time is 11 hours later than mine. Does not Calendar work the way I expect?

我以为我可以在 13/01/2014 00:12:00 获得 c2,因为 UTC 时间比我晚 11 小时。日历的工作方式不符合我的预期吗?

Your help would be appreciated.

您的帮助将不胜感激。

EDIT

编辑

Added z to display timezone. This makes me more confused because Mac says its timezone is (AEDT) Australian Eastern Daylight Time but Java is EST. Anyway still result is different because EST is UTC-5 hours.

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

采纳答案by Affe

You probably meant to set the timezone on your formatter, not the Calendar (or in addition the the Calendar, it is not 100% clear what you mean to accomplish)! The timezone used to create the human representation comes from the SimpleDateFormat. All "timezone" information is lost from the Calendar when you convert it back into a java.util.Date by calling getTime().

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

The code:

编码:

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

is printing 14/01/2014 10:12:00because 11AM UTC displayed in Syndey (the timezone of your formatter) is 10PM! (use HH in the format for 24 hour time)

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

This would print what it seems like you meant to do:

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

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

The concept of 'UTC milliseconds' is meaningless. A quantity of milliseconds is just a fixed point in history, it has no timezone associated with it. We add a timezone to it to convert it into human-readable representations.

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

edit: Yes, the ambiguity of using 'EST' for both (US) Eastern Time and (Australian) Eastern Time has been a pitfall in Java since forever.

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

回答by Basil Bourque

UPDATE:This Answer is now out-of-date. The Joda-Timelibrary is now supplanted by the java.timeframework built into Java 8 and later. See this new Answer.

更新:此答案现已过时。该乔达时库现在被取代java.time内置到Java 8和更高版本的框架。看到这个新答案

Three-Letter Codes

三字母代码

You should avoid using 3 or 4 letter time zone codes such as ESTor IST. They are neither standard nor unique.

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

Use proper time zone names, mostly Continent/CityOrRegionsuch as America/Montrealor Asia/Kolkata.

使用正确的时区名称,主要是Continent/CityOrRegion例如America/Montrealor Asia/Kolkata

Joda-Time

乔达时间

The java.util.Date/Calendar classes are notoriously bad. Avoid using them. Use either Joda-Timeor, in Java 8, the new java.time.* classesdefined by JSR 310 and inspired by Joda-Time.

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

Notice how much simpler and more obvious is the Joda-Time code shown below. Joda-Time even knows how to count –?January is 1, not 0!

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

Time Zone

时区

In Joda-Time, a DateTimeinstance knows its own time zone.

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

Sydney Australiahas a standard time of 10 hours ahead of UTC/GMT, and a Daylight Saving Time (DST) of 11 hours ahead. DST applies to the date specified by the question.

澳大利亚悉尼的标准时间比 UTC/GMT 早 10 小时,夏令时 (DST) 比 UTC/GMT 早 11 小时。DST 适用于问题指定的日期。

Tip: Don't think like this…

提示:不要这样想……

UTC time is 11 hours later than mine

UTC 时间比我晚 11 小时

Think like this…

这样想……

Sydney DST is 11 hours ahead of UTC/GMT.

悉尼夏令时比 UTC/GMT 早 11 小时。

Date-time work becomes easier and less error-prone if you think, work, and store in UTC/GMT. Only convert to localized date-time for presentation in the user-interface. Think globally, display locally. Your users and your servers can easily move to other time zones, so forget about your owntime zone. Always specify a time zone, never assume or rely on default.

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

Example Code

示例代码

Here is some example code using Joda-Time 2.3 and Java 8.

下面是一些使用 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).

Dump to console…

转储到控制台...

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

When run…

运行时…

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

回答by Basil Bourque

tl;dr

tl;博士

Use modern java.timeclasses.

使用现代java.time类。

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

1389658320000

1389658320000

Going the other direction.

走向另一个方向。

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" ) 
    )
)

14/01/2014 11:12:00 AEDT

14/01/2014 11:12:00 澳大利亚东部时间

java.time

时间

You are using terrible date-time classes that were made obsolete years ago by the adoption of JSR 310 defining the modern java.timeclasses.

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

I am curious about timezone in Java.

我对 Java 中的时区很好奇。

FYI, an offset-from-UTC is merely a number of hours-minutes-seconds. When we say “UTC” or put a Zat the end of a string, we mean an offset of zero hours-minutes-seconds, for UTC itself.

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

A time zone is much more. A time zone is a history of past, present, and future changes to the offset used by the people of a particular region. Politicians around the world have an odd penchant for changing the offset of their jurisdiction.

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

I want to get UTC time in milliseconds from a device and send to server.

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

For the current moment, use Instant. An Instantinternally is the number of whole seconds seconds since the epoch reference of first moment of 1970 UTC, plus a fraction of a second in nanoseconds.

目前,使用Instant. 的Instant内部是自1970年以来的UTC第一时刻的历元基准的整秒的秒数,加在纳秒几分之一秒。

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

Going the other direction.

走向另一个方向。

Instant instant = Instant.ofEpochMilli( millisecondsSinceEpoch ) ;

Server will convert it to local timezone …

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

Specify the time zone desired/expected by the user.

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

If no time zone is specified, the JVM implicitly applies its current default time zone. That default may change at any momentduring runtime(!), so your results may vary. Better to specify your desired/expected time zone explicitly as an argument. If critical, confirm the zone with your user.

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

Specify a proper time zone namein the format of Continent/Region, such as America/Montreal, Africa/Casablanca, or Pacific/Auckland. Never use the 2-4 letter abbreviation such as ESTor ISTas they are nottrue time zones, not standardized, and not even unique(!).

以、、 或等格式指定正确的时区名称。永远不要使用 2-4 个字母的缩写,例如或因为它们不是真正的时区,不是标准化的,甚至不是唯一的(!)。Continent/RegionAmerica/MontrealAfrica/CasablancaPacific/AucklandESTIST

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

… when it displays time to users

… 当它向用户显示时间时

Automatically localize for the user's language and culture.

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

To localize, specify:

要本地化,请指定:

  • FormatStyleto determine how long or abbreviated should the string be.
  • Localeto determine:
    • The human languagefor translation of name of day, name of month, and such.
    • The cultural normsdeciding issues of abbreviation, capitalization, punctuation, separators, and such.
  • FormatStyle确定字符串的长度或缩写。
  • Locale确定:
    • 用于翻译日名、月名等的人类语言
    • 决定缩写、大写、标点符号、分隔符等问题的文化规范

Example:

例子:

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

Timezone in my system is Australia/Sydney( UTC + 11:00)

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

The current default time zone of your server should be irrelevant to your program. Always specify the desired/expected time zone. Frankly, making optional the time zone (and Locale) argument of the various date-time methods is one of the very few design flaws in java.timeframework.

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

Tip: Generally best to set your servers to UTC as their current default time zone.

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

By the way, be clear that time zone and locale have nothingto do with one another. You might want Japanese language for displaying a moment as seen in Africa/Tunistime zone.

顺便说一句,要清楚时区和语言环境彼此无关。您可能需要日语来显示在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 year = 2014; …

整数年 = 2014 年;…

Note that java.timeuses sane numbering, unlike the legacy classes. Months are 1-12 for January-December, and weekdays are 1-7 for Monday-Sunday.

请注意,与遗留类不同,java.time使用合理的编号。一月至十二月的月份为 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[Australia/Sydney]

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

Generally best to automatically localize for display, as seen above. But if you insist, you can hard-code a formatting pattern.

通常最好自动定位以进行显示,如上所示。但如果你坚持,你可以硬编码格式模式。

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[Australia/Sydney]

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

Specify that formatting pattern of yours.

指定您的格式模式。

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

输出 = 14/01/2014 11:12:00 AEDT

Your Question was interested in a count of milliseconds since epoch of 1970-01-01T00:00:00Z. So adjust from the Australia time zone to UTC. Same moment, same point on the timeline, different wall-clock time.

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

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

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

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

Note the difference in hour-of-day between instantand zdt.

请注意instant和之间的小时差异zdt

I thought I could have 13/01/2014 00:12:00 for c2 because UTC time is 11 hours later than mine.

我以为我可以在 13/01/2014 00:12:00 获得 c2,因为 UTC 时间比我晚 11 小时。

? As you asked for, twelve minutes after 11 AM in Sydney zone is the same moment as twelve minutes after midnight in UTC, because Australia/Sydneyon that date is eleven hours ahead of UTC.

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

Calculate milliseconds since epoch.

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

long millisecondsSinceEpoch = instant.toEpochMilli() ;