tl;博士
除了手动更改主机上的系统时钟之外,是否有其他方法可以通过代码或 JVM 参数覆盖当前时间(如 System.currentTimeMillis 所示)?
是的。
Instant.now(
Clock.fixed(
Instant.parse( "2016-01-23T12:34:56Z"), ZoneOffset.UTC
)
)
Clock在java.time
对于可插入时钟替换问题,我们有一个新的解决方案,以方便使用 faux 日期时间值进行测试。 Java 8 中的 java.time package 包含一个抽象类 java.time.Clock,具有明确的目的:
允许在需要时插入备用时钟
您可以插入自己的Clock 实现,尽管您可能会找到一个已经满足您需求的实现。为方便起见,java.time 包含静态方法以产生特殊实现。这些替代实现在测试期间可能很有价值。
改变节奏
各种tick… 方法产生的时钟以不同的节奏递增当前时刻。
默认的Clock 报告的时间更新频率与Java 8 中的milliseconds 和Java 9 中的nanoseconds 一样好(取决于您的硬件)。您可以要求以不同的粒度报告真实的当前时刻。
假时钟
有些时钟可能会撒谎,产生与主机操作系统硬件时钟不同的结果。
例如,锁定今年最早的圣诞节的第一刻。换句话说,当Santa and his reindeer make their first stop。现在最早的时区似乎是Pacific/Kiritimati+14:00。
LocalDate ld = LocalDate.now( ZoneId.of( "America/Montreal" ) );
LocalDate xmasThisYear = MonthDay.of( Month.DECEMBER , 25 ).atYear( ld.getYear() );
ZoneId earliestXmasZone = ZoneId.of( "Pacific/Kiritimati" ) ;
ZonedDateTime zdtEarliestXmasThisYear = xmasThisYear.atStartOfDay( earliestXmasZone );
Instant instantEarliestXmasThisYear = zdtEarliestXmasThisYear.toInstant();
Clock clockEarliestXmasThisYear = Clock.fixed( instantEarliestXmasThisYear , earliestXmasZone );
使用那个特殊的固定时钟总是返回相同的时刻。我们在Kiritimati 得到圣诞节的第一刻,UTC 显示 14 小时前的wall-clock time,即 12 月 24 日之前的上午 10 点。
Instant instant = Instant.now( clockEarliestXmasThisYear );
ZonedDateTime zdt = ZonedDateTime.now( clockEarliestXmasThisYear );
instant.toString(): 2016-12-24T10:00:00Z
zdt.toString(): 2016-12-25T00:00+14:00[太平洋/Kiritimati]
见live code in IdeOne.com。
真实时间,不同时区
您可以控制Clock 实现分配的时区。这在某些测试中可能很有用。但我不建议在生产代码中这样做,您应该始终明确指定可选的 ZoneId 或 ZoneOffset 参数。
您可以指定 UTC 为默认时区。
ZonedDateTime zdtClockSystemUTC = ZonedDateTime.now ( Clock.systemUTC () );
您可以指定任何特定的时区。以continent/region 的格式指定proper time zone name,例如America/Montreal、Africa/Casablanca 或Pacific/Auckland。切勿使用 3-4 个字母的缩写,例如 EST 或 IST,因为它们不是真正的时区,没有标准化,甚至不是唯一的(!)。
ZonedDateTime zdtClockSystem = ZonedDateTime.now ( Clock.system ( ZoneId.of ( "America/Montreal" ) ) );
您可以指定 JVM 当前的默认时区应该是特定 Clock 对象的默认时区。
ZonedDateTime zdtClockSystemDefaultZone = ZonedDateTime.now ( Clock.systemDefaultZone () );
运行此代码进行比较。请注意,它们都报告同一时刻,时间轴上的同一点。它们仅在wall-clock time 上有所不同;换句话说,三种方式来表达同一件事,三种方式来展示同一时刻。
System.out.println ( "zdtClockSystemUTC.toString(): " + zdtClockSystemUTC );
System.out.println ( "zdtClockSystem.toString(): " + zdtClockSystem );
System.out.println ( "zdtClockSystemDefaultZone.toString(): " + zdtClockSystemDefaultZone );
America/Los_Angeles 是运行此代码的计算机上的 JVM 当前默认区域。
zdtClockSystemUTC.toString(): 2016-12-31T20:52:39.688Z
zdtClockSystem.toString(): 2016-12-31T15:52:39.750-05:00[美国/蒙特利尔]
zdtClockSystemDefaultZone.toString(): 2016-12-31T12:52:39.762-08:00[America/Los_Angeles]
根据定义,Instant 类始终采用 UTC。所以这三个与区域相关的Clock 用法具有完全相同的效果。
Instant instantClockSystemUTC = Instant.now ( Clock.systemUTC () );
Instant instantClockSystem = Instant.now ( Clock.system ( ZoneId.of ( "America/Montreal" ) ) );
Instant instantClockSystemDefaultZone = Instant.now ( Clock.systemDefaultZone () );
instantClockSystemUTC.toString(): 2016-12-31T20:52:39.763Z
instantClockSystem.toString(): 2016-12-31T20:52:39.763Z
instantClockSystemDefaultZone.toString(): 2016-12-31T20:52:39.763Z
默认时钟
Instant.now 默认使用的实现是Clock.systemUTC() 返回的实现。这是您未指定Clock 时使用的实现。在pre-release Java 9 source code for Instant.now 中亲自查看。
public static Instant now() {
return Clock.systemUTC().instant();
}
OffsetDateTime.now 和 ZonedDateTime.now 的默认 Clock 是 Clock.systemDefaultZone()。见source code。
public static ZonedDateTime now() {
return now(Clock.systemDefaultZone());
}
默认实现的行为在 Java 8 和 Java 9 之间发生了变化。在 Java 8 中,尽管类能够存储nanoseconds 的分辨率,但捕获当前时刻的分辨率仅为milliseconds。 Java 9 带来了一个新的实现,能够以纳秒的分辨率捕捉当前时刻——当然,这取决于您的计算机硬件时钟的能力。
关于java.time
java.time 框架内置于 Java 8 及更高版本中。这些类取代了麻烦的旧 legacy 日期时间类,例如 java.util.Date、Calendar 和 SimpleDateFormat。
要了解更多信息,请参阅Oracle Tutorial。并在 Stack Overflow 上搜索许多示例和解释。规格为JSR 310。
Joda-Time 项目现在位于maintenance mode,建议迁移到java.time 类。
您可以直接与您的数据库交换 java.time 对象。使用符合JDBC 4.2 或更高版本的JDBC driver。不需要字符串,不需要java.sql.* 类。 Hibernate 5 & JPA 2.2 支持 java.time。
从哪里获取 java.time 类?