Java 将时区设置为UTC,该时区已在UTC中
我有一个将时区设置为UTC的应用程序,如下所示:Java 将时区设置为UTC,该时区已在UTC中,java,postgresql,date,xmlgregoriancalendar,Java,Postgresql,Date,Xmlgregoriancalendar,我有一个将时区设置为UTC的应用程序,如下所示: // set default time zone to UTC TimeZone.setDefault(TimeZone.getTimeZone(ZoneOffset.UTC)); 这对于我们在数据库中存储为UTC的所有日期字段来说都很好。 但有一个日期已经以UTC表示,因此上述时区将再次以UTC表示 如何避免在UTC中再次设置 一些代码- public static void main(String[] args) throws Dataty
// set default time zone to UTC
TimeZone.setDefault(TimeZone.getTimeZone(ZoneOffset.UTC));
这对于我们在数据库中存储为UTC的所有日期字段来说都很好。
但有一个日期已经以UTC表示,因此上述时区将再次以UTC表示
如何避免在UTC中再次设置
一些代码-
public static void main(String[] args) throws DatatypeConfigurationException {
DatatypeFactory datatypeFactory = DatatypeFactory.
newInstance();
// set default time zone to UTC
TimeZone.setDefault(TimeZone.getTimeZone(ZoneOffset.UTC));
// coming from external program in UTC but assigned it here for reference
long someTimeToBeInUTC = 1528371000000L;
GregorianCalendar start = new GregorianCalendar();
start.setTimeInMillis(someTimeToBeInUTC);
XMLGregorianCalendar startXmlCalendar =
datatypeFactory.newXMLGregorianCalendar(start);
System.out.println(startXmlCalendar.
toGregorianCalendar().getTime());
}
想象一下,我有一个时间瞬间someTimeToBeInUTC
,与时区无关,然后因为行timezone.setDefault(timezone.getTimeZone(ZoneOffset.UTC))
它以UTC设置它
然后在hibernate中,我在hbm.xml中拥有存储该值的表的属性-
<property name="startTime" type="timestamp">
<column name="start_time" length="29" />
</property>
startTime的类型是postgres中没有时区的时间戳。
因此,当我们保存时,它会再次将其转换为UTC。毫秒时间与时区无关 以毫秒为单位的时间是精确的时间点,无论您在日历、日期或时间戳对象中的何处使用它 请仔细阅读Java文档。它始终表示从历元(1/1/1970 00:00:00 UTC)经过的毫秒数 时区仅在从这些对象使用其他表示时有效。e、 g.从toString()方法、从日历获取字段等) 更新 假设您的时间单位为千秒,
1528371250000L
然后您可以有两个日历(它们是时区感知的),一个是UTC日历,另一个是东部日历
请参阅完整的代码示例,以说明当两者中设置了以毫秒为单位的相同时间时会发生什么:
public static void main(String... args) throws Exception {
Calendar utcCalendar = Calendar
.getInstance(TimeZone.getTimeZone("UTC"));
utcCalendar.setTimeInMillis(1528371250000L);
System.out.println("UTC Calendar: \t\t" + (utcCalendar.get(Calendar.MONTH) +1)
+ "/" + utcCalendar.get(Calendar.DAY_OF_MONTH) + "/"
+ utcCalendar.get(Calendar.YEAR) + " "
+ utcCalendar.get(Calendar.HOUR_OF_DAY) + ":"
+ utcCalendar.get(Calendar.MINUTE) + ":"
+ utcCalendar.get(Calendar.SECOND) + " " + utcCalendar.getTimeZone().getID());
Calendar eastCalendar = Calendar.getInstance(TimeZone
.getTimeZone("EST"));
eastCalendar.setTimeInMillis(1528371250000L);
System.out.println("Eastern Calendar: \t"
+ (eastCalendar.get(Calendar.MONTH)+1) + "/"
+ eastCalendar.get(Calendar.DAY_OF_MONTH) + "/"
+ eastCalendar.get(Calendar.YEAR) + " "
+ eastCalendar.get(Calendar.HOUR_OF_DAY) + ":"
+ eastCalendar.get(Calendar.MINUTE) + ":"
+ eastCalendar.get(Calendar.SECOND) + " " + eastCalendar.getTimeZone().getID());
XMLGregorianCalendar xmlUtcCalendar = DatatypeFactory.newInstance()
.newXMLGregorianCalendar((GregorianCalendar) utcCalendar);
System.out.println("XML UTC Calendar: \t" + xmlUtcCalendar.toString());
XMLGregorianCalendar xmlEastCalendar = DatatypeFactory.newInstance()
.newXMLGregorianCalendar((GregorianCalendar) eastCalendar);
System.out.println("XML Eastern Calendar: \t" + xmlEastCalendar.toString());
}
输出:
UTC Calendar: 6/7/2018 11:34:10 UTC
Eastern Calendar: 6/7/2018 6:34:10 EST
XML UTC Calendar: 2018-06-07T11:34:10.000Z
XML Eastern Calendar: 2018-06-07T06:34:10.000-05:00
以毫秒为单位的时间与时区无关 以毫秒为单位的时间是精确的时间点,无论您在日历、日期或时间戳对象中的何处使用它 请仔细阅读Java文档。它始终表示从历元(1/1/1970 00:00:00 UTC)经过的毫秒数 时区仅在从这些对象使用其他表示时有效。e、 g.从toString()方法、从日历获取字段等) 更新 假设您的时间单位为千秒,
1528371250000L
然后您可以有两个日历(它们是时区感知的),一个是UTC日历,另一个是东部日历
请参阅完整的代码示例,以说明当两者中设置了以毫秒为单位的相同时间时会发生什么:
public static void main(String... args) throws Exception {
Calendar utcCalendar = Calendar
.getInstance(TimeZone.getTimeZone("UTC"));
utcCalendar.setTimeInMillis(1528371250000L);
System.out.println("UTC Calendar: \t\t" + (utcCalendar.get(Calendar.MONTH) +1)
+ "/" + utcCalendar.get(Calendar.DAY_OF_MONTH) + "/"
+ utcCalendar.get(Calendar.YEAR) + " "
+ utcCalendar.get(Calendar.HOUR_OF_DAY) + ":"
+ utcCalendar.get(Calendar.MINUTE) + ":"
+ utcCalendar.get(Calendar.SECOND) + " " + utcCalendar.getTimeZone().getID());
Calendar eastCalendar = Calendar.getInstance(TimeZone
.getTimeZone("EST"));
eastCalendar.setTimeInMillis(1528371250000L);
System.out.println("Eastern Calendar: \t"
+ (eastCalendar.get(Calendar.MONTH)+1) + "/"
+ eastCalendar.get(Calendar.DAY_OF_MONTH) + "/"
+ eastCalendar.get(Calendar.YEAR) + " "
+ eastCalendar.get(Calendar.HOUR_OF_DAY) + ":"
+ eastCalendar.get(Calendar.MINUTE) + ":"
+ eastCalendar.get(Calendar.SECOND) + " " + eastCalendar.getTimeZone().getID());
XMLGregorianCalendar xmlUtcCalendar = DatatypeFactory.newInstance()
.newXMLGregorianCalendar((GregorianCalendar) utcCalendar);
System.out.println("XML UTC Calendar: \t" + xmlUtcCalendar.toString());
XMLGregorianCalendar xmlEastCalendar = DatatypeFactory.newInstance()
.newXMLGregorianCalendar((GregorianCalendar) eastCalendar);
System.out.println("XML Eastern Calendar: \t" + xmlEastCalendar.toString());
}
输出:
UTC Calendar: 6/7/2018 11:34:10 UTC
Eastern Calendar: 6/7/2018 6:34:10 EST
XML UTC Calendar: 2018-06-07T11:34:10.000Z
XML Eastern Calendar: 2018-06-07T06:34:10.000-05:00
tl;博士
你似乎工作太辛苦了
您使用了错误的类。改用现代java.time类
myPreparedStatement.setObject(
… ,
Instant.ofEpochMilli( 1_528_371_000_000L )
)
你用错了。没有时区的时区类型不能用于存储特定时刻
避免设置默认时区
TimeZone.setDefault
此调用会立即影响JVM中所有应用程序的所有线程中的所有代码!所以,只有在最绝望的情况下才能打这个电话
相反,将所需/预期时区作为可选参数传递给许多java.time方法
主机操作系统和JVM的默认时区应该与代码无关。将所需/预期时区明确指定为(或)对象
避免遗留日期时间类。
与最早版本的Java捆绑在一起的旧日期时间类非常糟糕。这包括在代码中看到的gregoriacalendar
类。相反,使用它们的替代品,现代java.time类
具体来说,gregorianalendar
被替换为
Instant
java.time中的Instant
类是基本的构建块。该类表示时间线上的一个时刻,分辨率为(小数点的九(9)位)
显然,从1970-01-01T00:00:00Z开始,您有一个毫秒计数。只需通过调用将该数字解析为即时
对象
instant.toString():2018-06-07T11:30:00Z
时区
您可能希望在特定时区向用户显示即时
。应用ZoneId
以获取zoneDateTime
以大陆/地区
的格式指定,例如,或太平洋/奥克兰
。切勿使用3-4个字母的伪时区,如EST
或IST
,因为它们不是真正的时区,不是标准化的,甚至不是唯一的(!)
数据库
我不知道你在用xmlgorianicalendar
做什么。我不能解决Hibernate问题,因为我不是用户,尽管我知道
但是,如果你试图记录和检索一个与博士后的时刻,你太努力了
将UTC中的某一时刻存储到带有时区的时间戳的列中(不包括不带时区的!-见下文)
检索
Instant instant = myResultSet.getObject( … , Instant.class ) ;
请注意,SQL标准很少涉及日期-时间这一主题。不同的数据库在处理日期和时间方面表现得非常不同
Postgres的工作方式是带时区的时间戳有点用词不当。时区不作为时间戳的一部分存储。随输入一起提交的任何与UTC信息的偏移时区用于调整为UTC。UTC值将写入数据库。然后,区域/偏移被丢弃/遗忘。如果记住输入的原始区域/偏移对您很重要,则必须将其单独存储在另一列中
从Postgres检索带有时区值的时间戳时,您总是会得到UTC值。如果您的中间件工具将您(您的应用程序)与数据库连接起来,并将其自己的观点注入到一个理想的表示时区中,那么您可能看不到这一点
使用Java很容易避免这种时区注入混淆:传递Instant
对象,并检索Instant
对象。一个Instant
始终以UTC为单位。因此,调用ResultSet::getObject(…,Instant.class)
将始终忠实地表示数据库
String sql = "INSERT INTO tbl ( event ) VALUES ( ? ) ;" ; // Writing a moment into a column of type `TIMESTAMP WTH TIME ZONE`.
…
Instant instant = Instant.ofEpochMilli( 1_528_371_000_000L ) ; // Representing a moment in UTC.
myPreparedStatement.setObject( 1 , instant ) ; // As of JDBC 4.2 and later, we can directly exchange java.time objects with our database.
Instant instant = myResultSet.getObject( … , Instant.class ) ;
Instant instant = Instant.now().truncatedTo( ChronoUnit.MILLISECONDS ) ; // Lop off any nanoseconds to match Postgres storing microseconds.