我正在尝试在数据库中设置服务器不可知日期时间,并且我相信这样做的最佳做法是设置UTC DateTime。我的数据库服务器是Cassandra,Java的数据库驱动程序仅理解Date类型。
因此,假设在我的代码中我正在使用新的Java 8 ZonedDateTime立即获取UTC(ZonedDateTime.now(ZoneOffset.UTC)
),如何将ZonedDateTime实例转换为“旧版” Date类?
我正在尝试在数据库中设置服务器不可知日期时间,并且我相信这样做的最佳做法是设置UTC DateTime。我的数据库服务器是Cassandra,Java的数据库驱动程序仅理解Date类型。
因此,假设在我的代码中我正在使用新的Java 8 ZonedDateTime立即获取UTC(ZonedDateTime.now(ZoneOffset.UTC)
),如何将ZonedDateTime实例转换为“旧版” Date类?
Answers:
您可以将ZonedDateTime转换为瞬时值,可以将其直接与Date一起使用。
Date.from(java.time.ZonedDateTime.now().toInstant());
Date
是自纪元以来的毫秒数-因此它与UTC有关。如果打印它,将使用默认时区,但是Date类不了解用户的时区...例如,请参阅docs.oracle.com/javase/tutorial/datetime/iso/legacy中的“映射”部分.html和LocalDateTime明确地未提及时区-这可能会引起混淆...
ZonedDateTime
。该java.time.Instant
班是直接更换java.util.Date
,均代表在UTC片刻尽管Instant
利用纳秒而不是毫秒更精细的分辨率。Date.from( Instant.now() )
应该是您的解决方案。或就此而言,new Date()
具有相同效果的就是捕获UTC中的当前时刻。
java.util.Date.from( // Transfer the moment in UTC, truncating any microseconds or nanoseconds to milliseconds.
Instant.now() ; // Capture current moment in UTC, with resolution as fine as nanoseconds.
)
尽管上面的代码没有意义。两者java.util.Date
和Instant
始终代表UTC中的时刻。上面的代码与以下内容具有相同的作用:
new java.util.Date() // Capture current moment in UTC.
使用这里没有好处ZonedDateTime
。如果您已有一个ZonedDateTime
,请通过提取一个来适应UTC Instant
。
java.util.Date.from( // Truncates any micros/nanos.
myZonedDateTime.toInstant() // Adjust to UTC. Same moment, same point on the timeline, different wall-clock time.
)
将答案通过ssoltanid正确解决您的具体问题,如何在新的学校java.time对象(转换ZonedDateTime
),以一个老派的java.util.Date
对象。Instant
从ZonedDateTime中提取并传递给java.util.Date.from()
。
请注意,您将遭受数据丢失的困扰,因为从纪元开始Instant
跟踪纳秒,而从纪元开始java.util.Date
跟踪毫秒。
您的问题和评论提出了其他问题。
通常,最好将服务器的主机操作系统设置为UTC。在我知道的Java实现中,JVM将此主机OS设置作为其默认时区。
但是,您永远不应依赖JVM的当前默认时区。在启动JVM时传递的标志可以设置另一个时区,而不是选择主机设置。更糟糕的是:在任何时候,任何应用程序中任何线程中的任何代码都可以java.util.TimeZone::setDefault
在运行时调用更改默认值!
Timestamp
型任何体面的数据库和驱动程序都应自动处理调整传递给UTC的日期时间以进行存储。我不使用Cassandra,但是它似乎对日期时间有一些基本的支持。文档说它的Timestamp
类型是同一时期(UTC 1970年的第一时刻)以来的毫秒数。
此外,Cassandra接受ISO 8601标准格式的字符串输入。幸运的是,java.time使用ISO 8601格式作为解析/生成字符串的默认值。在Instant
类的toString
实现将很好地做。
但是首先我们需要将ZonedDateTime的纳秒精度降低到毫秒。一种方法是使用毫秒创建新的Instant。幸运的是,java.time具有一些方便的方法来转换毫秒。
这是Java 8 Update 60中的一些示例代码。
ZonedDateTime zdt = ZonedDateTime.now( ZoneId.of( "America/Montreal" ) );
…
Instant instant = zdt.toInstant();
Instant instantTruncatedToMilliseconds = Instant.ofEpochMilli( instant.toEpochMilli() );
String fodderForCassandra = instantTruncatedToMilliseconds.toString(); // Example: 2015-08-18T06:36:40.321Z
或者根据此Cassandra Java驱动程序doc,您可以传递一个java.util.Date
实例(不要与混淆java.sqlDate
)。因此,您可以根据instantTruncatedToMilliseconds
上面的代码创建一个juDate 。
java.util.Date dateForCassandra = java.util.Date.from( instantTruncatedToMilliseconds );
如果经常这样做,则可以做成单线。
java.util.Date dateForCassandra = java.util.Date.from( zdt.toInstant() );
但是创建一个小的实用程序方法会更整洁。
static public java.util.Date toJavaUtilDateFromZonedDateTime ( ZonedDateTime zdt ) {
Instant instant = zdt.toInstant();
// Data-loss, going from nanosecond resolution to milliseconds.
java.util.Date utilDate = java.util.Date.from( instant ) ;
return utilDate;
}
请注意,所有这些代码与“问题”中的代码有所不同。该问题的代码试图将ZonedDateTime实例的时区调整为UTC。但这不是必需的。从概念上讲:
ZonedDateTime =即时+ ZoneId
我们只提取已在UTC中使用的Instant部分(基本上在UTC中,请阅读类文档以获取详细信息)。
该java.time框架是建立在Java 8和更高版本。这些类取代麻烦的老传统日期时间类,如java.util.Date
,Calendar
,和SimpleDateFormat
。
现在处于维护模式的Joda-Time项目建议迁移到java.time类。
要了解更多信息,请参见Oracle教程。并在Stack Overflow中搜索许多示例和说明。规格为JSR 310。
您可以直接与数据库交换java.time对象。使用与JDBC 4.2或更高版本兼容的JDBC驱动程序。不需要字符串,不需要类。java.sql.*
在哪里获取java.time类?
该ThreeTen-额外项目与其他类扩展java.time。该项目是将来可能向java.time添加内容的试验场。你可能在这里找到一些有用的类,比如Interval
,YearWeek
,YearQuarter
,和更多。
这是将当前系统时间转换为UTC的示例。它涉及将ZonedDateTime格式化为字符串,然后使用java.text DateFormat将String对象解析为日期对象。
ZonedDateTime zdt = ZonedDateTime.now(ZoneOffset.UTC);
final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd HH:mm:ss");
final DateFormat FORMATTER_YYYYMMDD_HH_MM_SS = new SimpleDateFormat("yyyyMMdd HH:mm:ss");
String dateStr = zdt.format(DATETIME_FORMATTER);
Date utcDate = null;
try {
utcDate = FORMATTER_YYYYMMDD_HH_MM_SS.parse(dateStr);
}catch (ParseException ex){
ex.printStackTrace();
}
如果您正在使用Android 的ThreeTen反向端口,并且不能使用较新的版本Date.from(Instant instant)
(需要最少的API 26),则可以使用:
ZonedDateTime zdt = ZonedDateTime.now();
Date date = new Date(zdt.toInstant().toEpochMilli());
要么:
Date date = DateTimeUtils.toDate(zdt.toInstant());
请同时阅读Basil Bourque答案中的建议
DateTimeUtils
带有转换方法的类,因此我将使用Date date = DateTimeUtils.toDate(zdt.toInstant());`。它不是那么低级。
您可以使用Java 8及更高版本中内置的java.time类来完成此操作。
ZonedDateTime temporal = ...
long epochSecond = temporal.getLong(INSTANT_SECONDS);
int nanoOfSecond = temporal.get(NANO_OF_SECOND);
Date date = new Date(epochSecond * 1000 + nanoOfSecond / 1000000);
Instant
s跟踪秒和纳秒。 Date
s的追踪毫秒数。从一个到另一个的这种转换是正确的。
我用这个
public class TimeTools {
public static Date getTaipeiNowDate() {
Instant now = Instant.now();
ZoneId zoneId = ZoneId.of("Asia/Taipei");
ZonedDateTime dateAndTimeInTai = ZonedDateTime.ofInstant(now, zoneId);
try {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(dateAndTimeInTai.toString().substring(0, 19).replace("T", " "));
} catch (ParseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
}
因为
Date.from(java.time.ZonedDateTime.ofInstant(now, zoneId).toInstant());
这是行不通的!!!如果您在计算机上运行应用程序,那不是问题。但是,如果您在AWS或Docker或GCP的任何区域中运行,则会产生问题。因为计算机不是您在云上的时区。您应该在代码中设置正确的时区。例如,亚洲/台北。然后它将在AWS或Docker或GCP中更正。
public class App {
public static void main(String[] args) {
Instant now = Instant.now();
ZoneId zoneId = ZoneId.of("Australia/Sydney");
ZonedDateTime dateAndTimeInLA = ZonedDateTime.ofInstant(now, zoneId);
try {
Date ans = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(dateAndTimeInLA.toString().substring(0, 19).replace("T", " "));
System.out.println("ans="+ans);
} catch (ParseException e) {
}
Date wrongAns = Date.from(java.time.ZonedDateTime.ofInstant(now, zoneId).toInstant());
System.out.println("wrongAns="+wrongAns);
}
}
ans=Mon Jun 18 01:07:56 CEST 2018
,这是不正确的,然后是wrongAns=Sun Jun 17 17:07:56 CEST 2018
,这是正确的。
接受的答案对我不起作用。返回的日期始终是本地日期,而不是原始时区的日期。我住在UTC + 2。
//This did not work for me
Date.from(java.time.ZonedDateTime.now().toInstant());
我想出了两种方法来从ZonedDateTime获取正确的日期。
假设您有这个ZonedDateTime用于夏威夷
LocalDateTime ldt = LocalDateTime.now();
ZonedDateTime zdt = ldt.atZone(ZoneId.of("US/Hawaii"); // UTC-10
或原先要求的UTC
Instant zulu = Instant.now(); // GMT, UTC+0
ZonedDateTime zdt = zulu.atZone(ZoneId.of("UTC"));
选择1
我们可以使用java.sql.Timestamp。这很简单,但也可能会削弱您的编程完整性
Date date1 = Timestamp.valueOf(zdt.toLocalDateTime());
选择2
我们从millis创建Date(前面已在此处回答)。请注意,本地ZoneOffset是必须的。
ZoneOffset localOffset = ZoneOffset.systemDefault().getRules().getOffset(LocalDateTime.now());
long zonedMillis = 1000L * zdt.toLocalDateTime().toEpochSecond(localOffset) + zdt.toLocalDateTime().getNano() / 1000000L;
Date date2 = new Date(zonedMillis);
如果您仅对现在感兴趣,则只需使用:
Date d = new Date();
java.util.Date
和java.sql.Date
。