我正在尝试在我的数据库中设置一个与服务器无关的日期时间,我认为最好的做法是设置一个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类?
你可以将ZonedDateTime转换为一个瞬时时间,然后直接与Date一起使用。
Date.from(java.time.ZonedDateTime.now().toInstant());
Date
是自纪元以来的毫秒数,因此与UTC有关。如果你打印它,将使用默认时区,但Date
类不知道用户的时区...例如,请参阅https://docs.oracle.com/javase/tutorial/datetime/iso/legacy.html中的“映射”部分。并且`LocalDateTime`明确不涉及时区 - 这可能会令人感到困惑... - assyliasZonedDateTime
。java.time.Instant
类是java.util.Date
的直接替代,两者都表示UTC中的一个时刻,虽然Instant
使用纳秒而不是毫秒的更细分辨率。您的解决方案应该是Date.from( Instant.now() )
。或者说,只需使用new Date()
,它具有相同的效果,捕获UTC中的当前时刻。 - Basil Bourquejava.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中的一个时刻,始终为UTC。上述代码与以下代码具有相同的效果:
new java.util.Date() // Capture current moment in UTC.
使用ZonedDateTime
在此处没有任何好处。如果您已经有一个ZonedDateTime
,请通过提取Instant
来调整为UTC。
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
对象。从ZonedDateTime中提取Instant
并传递给java.util.Date.from()
。
Instant
跟踪纳秒自纪元以来,而java.util.Date
跟踪毫秒自纪元以来。
您的问题和评论引发了其他问题。
作为一般最佳实践,您的服务器应将其主机操作系统设置为UTC。在我所知道的Java实现中,JVM会捕捉到这个主机操作系统设置作为其默认时区。
但是,您永远不应依赖于JVM的当前默认时区。与其选择主机设置,启动JVM时传递的标志可以设置另一个时区。更糟糕的是:任何应用程序的任何线程的任何时刻的任何代码都可以调用java.util.TimeZone::setDefault
来在运行时更改该默认值!
Timestamp
类型Timestamp
}}类型是从UTC的1970年第一时刻开始的毫秒计数。
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 driver文档,你可以传递一个java.util.Date
实例(不要与java.sqlDate
混淆)。因此,你可以从上面的代码中的instantTruncatedToMilliseconds
创建一个j.u.Date。
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;
}
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-Extra项目扩展了java.time的附加类。该项目是java.time可能未来添加的可靠性测试。您可能会在这里找到一些有用的类,例如Interval
, YearWeek
, YearQuarter
和更多。
如果您正在为Android使用ThreeTen backport,并且无法使用较新的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());
。它并不是那么底层。 - Ole V.V.对我来说,所接受的答案没有起作用。返回的日期始终是本地日期而不是原始时区的日期。 我住在UTC +2。
//This did not work for me
Date.from(java.time.ZonedDateTime.now().toInstant());
LocalDateTime ldt = LocalDateTime.now();
ZonedDateTime zdt = ldt.atZone(ZoneId.of("US/Hawaii"); // UTC-10
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
我们从毫秒数中创建日期(之前在这里回答过)。请注意,本地的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);
instant = zdt.toLocalDateTime().toInstant(ZoneOffset.UTC); return Date.from(instant);
在这里,我使用了硬编码的区域偏移量以满足自己的需求,但通常情况下使用它也是很明显的。 - Anton Duzenko以下是一个将当前系统时间转换为UTC时间的示例。首先,需要将ZonedDateTime格式化为字符串,然后使用java.text.DateFormat解析该字符串对象并转换成日期对象。
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();
}
对于像beehuang评论的Docker应用程序,您应该设置时区。
或者您可以使用withZoneSameLocal。例如:
2014-07-01T00:00+02:00[GMT+02:00]将被转换为
Date.from(zonedDateTime.withZoneSameLocal(ZoneId.systemDefault()).toInstant())
到 2014年7月1日中欧夏令时00:00:00 并且通过
Date.from(zonedDateTime.toInstant())
到2014年6月30日22:00:00 UTC
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的任何地区运行,则会产生问题。因为计算机不是云上的时区。您应该在代码中正确设置时区,例如Asia/Taipei。然后它将在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
,这是正确的。 - Ole V.V.Date.toString()
打印的时区缩写有关,这对于解释字符串至关重要。但这只是一个猜测。 - Ole V.V.您可以使用内置于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
跟踪秒和纳秒。Date
跟踪毫秒。从一个转换到另一个是正确的。 - scottb如果你只是现在感兴趣,那么可以简单地使用:
Date d = new Date();
java.util.Date
和java.sql.Date
。 - Basil Bourque