时区问题:如果JVM时区是UTC,则转换为UTC

时间:2017-08-03 14:54:09

标签: java timezone java-7 timestamp-with-timezone

由于我们的应用程序中存在时区,因此我遇到了一些问题。

我们有一些依赖服务向我们发送EST时间。我们在EST时区,所以之前没有任何问题。最近,我们搬到了UTC时区,开始看到这个问题。对我们来说,我们可能会再次来回(EST / UTC)。

所以我要做的是检查我的JVM时区,如果我的时区是UTC,我想将依赖服务的时间转换为UTC,如果我们在EST中则不做任何事情因为依赖服务总是响应美东时间。我如何实现这一目标?

Data1.java

XMLGregorianCalendar date;
XMLGregorianCalendar time;
//getter & setter

Util.java

String convertDate(Date d){
    SimpleDateFormat date = new SimpleDateFormat("yyyy-MM-dd");
            try {
                 String converted= dateFormat.format(d);
                return converted;
            } catch (ParseException e) {
                throw new RuntimeException(e);
            }
    }

    String convertTime(Date d){
            SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
            String Time = "";
            Time =  sdf.format(d);
            return Time;
    }
}

MyApp.java

String date = Util.convertDate(data1.getDate.toGregorianCalendar().getTime())
String time = Util.convertTime(data1.getDate.toGregorianCalendar().getTime())

这里,日期和时间字符串具有我想要更改为UTC的EST日期和时间,如果JVM时区是UTC并保持为JVM时区是EST

现有输出示例:

  

日期:2017-08-02
  时间:21:04:04

预期:

  

日期:2017-08-03
  时间:01:04:04

2 个答案:

答案 0 :(得分:1)

java.util.Date个对象has no format nor any timezone information。它只保留一个long值,表示自unix时期(1970-01-01T00:00Z)以来的毫秒数。

使用Date格式化SimpleDateFormat时,格式化程序使用系统的默认时区将此毫秒值转换为人类可读值(日/月/年/小时/分钟/秒)。 / p>

如果你得到2017-08-02 21:04:04,则表示运行代码的JVM中的默认时区是EST(技术上说话,EST实际上不是时区,更多关于下面的内容)。如果要将输出转换为UTC,则必须在格式化程序中设置它:

Date date = // Date corresponding to 2017-08-03 01:04:04 UTC (or 2017-08-02 21:04:04 EDT)

// date format
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
// set UTC
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
System.out.println(dateFormat.format(date)); // 2017-08-03

// time format
SimpleDateFormat timeFormat = new SimpleDateFormat("HH:mm:ss");
// set UTC
timeFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
System.out.println(timeFormat.format(date)); // 01:04:04

输出将是:

  

2017年8月3日
  1点04分04秒

要获取EST的值,只需更改时区:

// date format
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
// set to EST
dateFormat.setTimeZone(TimeZone.getTimeZone("America/New_York"));
System.out.println(dateFormat.format(date));

// time format
SimpleDateFormat timeFormat = new SimpleDateFormat("HH:mm:ss");
// set to EST
timeFormat.setTimeZone(TimeZone.getTimeZone("America/New_York"));
System.out.println(timeFormat.format(date));

输出将是:

  

2017年8月2日
  21时04分04秒

请注意,我使用了America/New_York,因为EST会产生错误的结果。理想情况是始终使用IANA timezones names(始终采用Region/City格式,例如America/New_YorkEurope/Berlin)。 避免使用3个字母的缩写(例如ESTPST),因为它们是ambiguous and not standard

lots of different timezones that use EST as a "short name",因此您可以将America/New_York更改为最适合您系统的时区。您可以致电TimeZone.getAvailableIDs()

获取可用时区列表

要检查默认时区,您可以使用TimeZone.getDefault().getID()并检查它是UTC还是GMT(这样您就知道要使用哪种格式 - 尽管建议您在内部使用UTC和例如,只有在向用户显示值时才转换为时区。

取决于默认时区是不理想的,因为它可以随时更改,甚至在运行时,或者由于其他人的错误配置(我之前遇到过这样的情况而且它不是好事)。但由于您似乎无法做出选择,只需检查getID()返回的值,然后根据该值确定要执行的操作。

Java新的日期和时间API

旧类(DateCalendarSimpleDateFormat)有lots of problemsdesign issues,它们将被新API取代。< / p>

如果您使用的是 Java 8 ,请考虑使用new java.time API。它更容易,less bugged and less error-prone than the old APIs

如果您使用的是 Java&lt; = 7 ,则可以使用ThreeTen Backport,这是Java 8新日期/时间类的绝佳后端。对于 Android ,有ThreeTenABP(更多关于如何使用它here)。

以下代码适用于两者。 唯一的区别是包名称(在Java 8中为java.time,在ThreeTen Backport(或Android的ThreeTenABP)中为org.threeten.bp),但类和方法名称是相同的

首先,您将GregorianCalendar转换为Instant

// convert calendar
Instant inst = Instant.ofEpochMilli(data1.getDate.toGregorianCalendar().getTimeInMillis());

在Java 8中,GregorianCalendar有一种直接进行转换的方法:

Instant inst = data1.getDate.toGregorianCalendar().toInstant();

然后你可以将瞬间转换为时区:

// convert to UTC
ZonedDateTime zdt = inst.atZone(ZoneOffset.UTC);

// or convert to a timezone
ZonedDateTime zdt = inst.atZone(ZoneId.of("America/New_York"));

然后您可以使用DateTimeFormatter对其进行格式化:

DateTimeFormatter dateFmt = DateTimeFormatter.ofPattern("yyyy-MM-dd");
DateTimeFormatter timeFmt = DateTimeFormatter.ofPattern("HH:mm:ss");

System.out.println(dateFmt.format(zdt));
System.out.println(timeFmt.format(zdt));

这将产生与上面相同的输出。

您还可以使用ZoneId.systemDefault().getId()检查JVM默认时区,并检查它是UTC还是GMT

答案 1 :(得分:1)

EST不是时区

媒体上看到的3-4个字母伪区域不是真正的时区。它们不是标准化的,甚至不是唯一的(!)。

continent/region的格式指定proper time zone name,例如America/MontrealAfrica/CasablancaPacific/Auckland

ZoneId z = ZoneId.of( "America/New_York" );

避免XMLGregorianCalendar

如果可能,请避免使用XMLGregorianCalendar。就像遗产Date&amp; Calendar&amp; GregorianCalendar类,现在由优秀的java.time类取代。

从不依赖于默认时区

依赖于主机操作系统的默认时区的不良做法,因为作为程序员无法控制。

依赖于JVM当前默认时区的同样糟糕的做法,因为它也在您的控制范围之外。 JVM中任何应用程序的任何线程中的任何代码都可以在运行时更改当前默认值。

听起来您的人们希望将主机操作系统的当前默认时区用作改变应用程序行为的信号。非常奇怪和笨拙的策略。您有许多其他路径可用:配置文件,JMXJNDI等。

获取当前默认时区

我知道JVM无法直接访问主机操作系统的当前默认时区。你可以通过命令行实用程序或其他一些方式去一些环形路径。

您可以获取JVM的当前默认值。默认情况下,我熟悉的大多数Java实现都将JVM设置为主机操作系统的默认设置。

ZoneId z = ZoneId.systemDefault() ;

转换为java.time

如果您获得XMLGregorianCalendar个对象,请通过java.time.ZonedDateTime转换为GregorianCalendar

GregorianCalendar gc = myXMLGregorianCalendar.toGregorianCalendar() ;
ZonedDateTime zdt = gc.toZonedDateTime() ;

调整到所需/预期的时区

ZonedDateTime提取InstantInstant类代表UTC中时间轴上的一个时刻,分辨率为nanoseconds(小数部分最多九(9)位)。

Instant instant = zdt.toInstant() ;

应用您想要的任何时区。对于UTC,请使用OffsetDateTime

OffsetDateTime odt = instant.atOffset( ZoneOffset.UTC ) ;

获取您的约会对象标准ISO 8601格式的时间字符串。

String outputDate = odt.toLocalDate().toString() ;
String outputTime = odt.toLocalTime().toString() ;

对于时区而不是仅仅偏离UTC,请使用ZonedDateTime。对于美国东海岸,也许你想要America/New_York

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

以标准ISO 8601格式生成字符串。

String outputDate = zdt.toLocalDate().toString() ;
String outputTime = zdt.toLocalTime().toString() ;

关于 java.time

java.time框架内置于Java 8及更高版本中。这些类取代了麻烦的旧legacy日期时间类,例如java.util.DateCalendar和&amp; SimpleDateFormat

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

要了解详情,请参阅Oracle Tutorial。并搜索Stack Overflow以获取许多示例和解释。规范是JSR 310

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

从哪里获取java.time类?

ThreeTen-Extra项目使用其他类扩展java.time。该项目是未来可能添加到java.time的试验场。您可以在此处找到一些有用的课程,例如IntervalYearWeekYearQuartermore