我正在尝试在我的数据库中设置服务器不可知的日期时间,我相信这样做的最佳做法是设置UTC DateTime。我的数据库服务器是Cassandra,Java的db驱动程序只能理解Date类型。
所以假设在我的代码中我使用新的Java 8 ZonedDateTime来获取UTC(ZonedDateTime.now(ZoneOffset.UTC)
),我该如何将这个ZonedDateTime实例转换为" legacy"日期类?
答案 0 :(得分:118)
您可以将ZonedDateTime转换为瞬间,您可以直接使用Date。
Date.from(java.time.ZonedDateTime.now().toInstant());
答案 1 :(得分:46)
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的时刻,始终为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.
)
Answer ssoltanid正确解决了您的具体问题,如何将新学校的java.time对象(ZonedDateTime
)转换为旧学校java.util.Date
对象。从ZonedDateTime中提取Instant
并传递给java.util.Date.from()
。
请注意,遭受数据丢失,因为Instant
追踪nanoseconds epoch,而java.util.Date
跟踪milliseconds,因为纪元
您的问题和意见引发了其他问题。
您的服务器通常应将其主机操作系统设置为UTC作为最佳做法。在我所知道的Java实现中,JVM将此主机操作系统设置作为其默认时区。
但是你永远不应该依赖JVM当前的默认时区。启动JVM时传递的标志可以设置另一个时区,而不是选择主机设置。更糟糕的是:任何时候任何应用程序的任何线程中的任何代码都可以调用java.util.TimeZone::setDefault
来改变运行时的默认值!
Timestamp
输入任何decent database和驱动程序都应自动处理将传递的日期时间调整为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 driver doc,您可以传递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;
}
注意所有这些代码与问题中的区别。问题的代码试图将ZonedDateTime实例的时区调整为UTC。但这不是必要的。从概念上讲:
ZonedDateTime = Instant + ZoneId
我们只提取已经是UTC的Instant部分(基本上是UTC,请阅读类文档以获取精确的详细信息)。
java.time框架内置于Java 8及更高版本中。这些类取代了麻烦的旧legacy日期时间类,例如java.util.Date
,Calendar
和& SimpleDateFormat
现在位于Joda-Time的maintenance 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的试验场。您可以在此处找到一些有用的课程,例如Interval
,YearWeek
,YearQuarter
和more。
答案 2 :(得分:3)
以下是将当前系统时间转换为UTC的示例。它涉及将ZonedDateTime格式化为String,然后使用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();
}
答案 3 :(得分:3)
如果您使用的是ThreeTen backport Android,并且无法使用较新的Date.from(Instant instant)
(需要最低限度的API 26),您可以使用:
ZonedDateTime zdt = ZonedDateTime.now();
Date date = new Date(zdt.toInstant().toEpochMilli());
或:
Date date = DateTimeUtils.toDate(zdt.toInstant());
中的建议
答案 4 :(得分:1)
您可以使用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);
答案 5 :(得分:0)
如果您现在只对此感兴趣,那么只需使用:
Date d = new Date();
答案 6 :(得分:0)
我用这个。
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的任何区域中运行,都会产生问题。因为计算机不是云上的时区。您应该在Code中设置正确的时区。例如,亚洲/台北。然后它将在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);
}
}
答案 7 :(得分:0)
对于像beehuang评论的docker应用程序,您应该设置时区。
或者,您可以使用 withZoneSameLocal 。例如:
2014-07-01T00:00 + 02:00 [GMT + 02:00]转换为
Date.from(zonedDateTime.withZoneSameLocal(ZoneId.systemDefault()).toInstant())
到 CEST 2014年7月1日星期二00:00:00 和
Date.from(zonedDateTime.toInstant())
至 2014年6月30日星期一UTC时间
答案 8 :(得分:0)
接受的答案对我不起作用。返回的日期始终是本地日期,而不是原始时区的日期。我住在UTC + 2。
LocalDateTime ldt = LocalDateTime.now();
ZonedDateTime zdt = ldt.atZone(ZoneId.of("US/Hawaii"); // UTC-10
我想出了两种方法来从ZonedDateTime获取正确的日期。
说你有这个针对夏威夷的ZonedDateTime
Instant zulu = Instant.now(); // GMT, UTC+0
ZonedDateTime zdt = zulu.atZone(ZoneId.of("UTC"));
或最初要求的UTC
Date date1 = Timestamp.valueOf(zdt.toLocalDateTime());
替代1
我们可以使用java.sql.Timestamp。这很简单,但也可能会削弱您的编程完整性
ZoneOffset localOffset = ZoneOffset.systemDefault().getRules().getOffset(LocalDateTime.now());
long zonedMillis = 1000L * zdt.toLocalDateTime().toEpochSecond(localOffset) + zdt.toLocalDateTime().getNano() / 1000000L;
Date date2 = new Date(zonedMillis);
替代2
我们从毫秒中创建日期(早于here进行了回答)。请注意,必须使用本地ZoneOffset。
{{1}}