我对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小时。
答案 0 :(得分:11)
您应该避免使用3个或4个字母的时区代码,例如EST
或IST
。它们既不标准也不独特。
使用proper time zone names,主要是Continent/CityOrRegion
,例如America/Montreal
或Asia/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)
使用现代的 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 类的JSR 310而过时了。
仅供参考,与UTC的偏移量仅是数小时-数分钟-秒。当我们说“ UTC”或在字符串的末尾放置我对Java中的时区感到好奇。
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/Montreal
,Africa/Casablanca
或Pacific/Auckland
。切勿使用2-4个字母的缩写,例如EST
或IST
,因为它们不是真正的时区,不是标准化的,甚至不是唯一的(!)。
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
请注意instant
和zdt
在一天中的小时差异。
我认为我可以为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()));