日期时间工作很棘手
我是java的新手,因此很长一段时间以来一直无法解决这个问题。
即使是经验丰富的程序员,日期时间处理也出奇地棘手。这些概念相当模糊。各行各业的各种人都发展了各种做法,从而使事情复杂化。
幸运的是,Java 现在已经内置了业界领先的日期时间工作框架:在 JSR 310 中定义的java.time 。
明确指定所需/预期的时区
我使用的是 Windows XP,并且机器设置为 TimeZone:
主机操作系统不应影响您在 Java 中的日期时间处理工作。
永远不要依赖主机当前的默认时区。该默认值可以随时更改,因此它超出了程序员的控制范围。相反,请在 Java 代码中指定所需/预期的时区。
ZoneId z = ZoneId.of( "America/New_York" ) ;
EDT
/Eastern Time
不是时区
… 东部时间(美国和加拿大)。
没有真正的“东部时间”这样的东西。这个不精确的术语是一个手提包集合名词,用于描述各个时区使用的当前与 UTC 的偏移量。在进行编程时,忘掉“东部时间”、“东部标准时间”和其他伪时区。
偏移与区域
了解偏移只是本初子午线之前或之后的几个小时-分钟-秒。偏移量看起来像+05:30
or -05:00
。
时区更多。时区是特定地区的人们使用的偏移量的过去、现在和未来变化的历史。时区的规则是由政客们反复设定的,并且以惊人的频率变化。
正确的时区名称由Continent/Region
或America/Montreal
组成America/New_York
。请参阅Wikipedia 上的此区域列表(可能不是最新的)。
ISO 8601
将类似:20101012 15:56:00 EST 的字符串写入文件。
对于被序列化为文本的日期时间值的格式,我们有一个正式的国际标准:ISO 8601。不要自己发明!标准格式经过精心设计,实用、易于机器解析,并且易于跨文化的人类阅读。
例如,魁北克省 2010 年 10 月 12 日将近下午 4 点将是:
2010-10-12T15:56-04:00
java.time类在解析/生成文本时默认使用 ISO 8601 格式。所以不需要指定格式模式。用于表示通过特定时区看到的时刻。ZonedDateTime
ZoneId z = ZoneId.of( "America/Montreal" ) ;
ZonedDateTime zdt = ZonedDateTime.of( 2010 , 10 , 12 , 15 , 56 , 0 , 0 , z ) ;
该ZonedDateTime::toString
方法明智地扩展了标准格式,将区域名称附加在方括号中。
zdt.toString(): 2010-10-12T15:56-04:00[美国/蒙特利尔]
解析这样的字符串。
ZonedDateTime zdt2 = ZonedDateTime.parse( "2010-10-12T15:56-04:00[America/Montreal]" ) ;
boolean sameZdts = zdt.equals ( zdt2 ) ;
相同的Zdts:真
查看在 IdeOne.com 上实时运行的代码。
夏令时 (DST)
EST 是我一直想要的,而不是 EDT。
你的目标没有意义。
这些伪区域 ( EST
& EDT
) 旨在指示夏令时 (DST)何时生效以及标准时间何时生效。因此,希望始终在遵守 DST 的区域(时区)中使用标准时间是矛盾的,而且是不可能的。
因此,如果您试图通过特定地区(时区)的人们使用的挂钟时间来表示一个时刻,您应该通过 指定日期、时间和时区ZoneId
,让java.time处理 DST 是否生效的问题。
LocalDate ld = LocalDate.of( 2010 , Month.OCTOBER , 12 ) ;
LocalTime lt = LocalTime.of( 15 , 56 , 0 ) ;
ZoneId z = ZoneId.of( "America/New_York" ) ;
ZonedDateTime zdt = ZonedDateTime.of( ld , lt , z ) ;
zdt.toString(): 2010-10-12T15:56-04:00[America/New_York]
要在 UTC 中查看同一时刻,请提取Instant
. 同一时刻,时间线上的同一点,不同的挂钟时间。
Instant instant = zdt.toInstant() ;
即时.toString(): 2010-10-12T19:56:00Z
请参阅上面的代码在 IdeOne.com 上实时运行。
预订未来的约会
正在处理我需要告诉的服务器:“在 20101015 15:30:30 xxx 执行任务”xxx 是时区。无论是哪个月份,服务器都只了解 EST 而不是 EDT。因此,EDT 令人不快。
关于 EST 与 EDT,我们已经将您的担忧视为毫无意义/无关紧要。当特定时区切入/切出 DST 时,标准时间与夏令时的这些标志会自动切换。
至于未来的预约,您无法知道夏令时(DST)是否有效。您不知道是否对您的时区规则进行了其他一些古怪的调整。
如上所述,时区定义由反复无常的政客控制。在世界各地,他们对频繁变化表现出惊人的嗜好。几十年来,美国已多次更改其夏令时转换的日期。近年来,俄罗斯多次采用和放弃夏令时。最新的时尚是全年保持 DST,最近由土耳其和摩洛哥完成,美国也即将这样做。
因此,如果您希望在一天中的某个时间完成约会或任务,您可以将其表示为 a LocalDateTime
,它表示没有偏移/区域上下文的日期和时间,另外还分别表示预期时区 ( ZoneId
)。
LocalDate ld = LocalDate.of( 2020 , Month.MARCH , 15 ) ;
LocalTime lt = LocalTime.of( 15 , 0 ) ;
LocalDateTime ldt = LocalDateTime.of( ld , lt ) ;
ZoneId z = ZoneId.of( "America/Thunder_Bay" ) ;
上面的代码描述了我们打算什么时候发生,但无法确定某个时刻。我们不知道 3 月 15 日下午 3 点何时会发生,因为控制America/Thunder_Bay
时区的政客随时可能重新定义时区。所以我们所能做的就是偶尔动态地尝试一下。当您需要建立日历,或安排某事发生时,您必须将时区应用到LocalDateTime
(不是片刻)以产生ZonedDateTime
(片刻)。
ZonedDateTime zdt = ZonedDateTime.of( ldt , z ) ;
Instant instant = zdt.toInstant() ; // Adjust from zone to UTC (an offset of zero). Same moment, same point on the timeline.

执行者
要安排您的任务运行,请了解Java 中内置的Executors 框架。具体来说,ScheduledExecutorService
类。
冷杉计算您等待运行任务的经过时间。
Duration duration = Duration.between( Instant.now() , instant );
将要运行的任务定义为Runnable
. 这里我们使用现代的 lambda 语法,但这不是重点。
Runnable runnable = ( ) -> {
System.out.println( "Running our task now: " + Instant.now() );
};
安排您的任务运行。ScheduledFuture
如果要跟踪完成,可以选择捕获返回的。
final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
ScheduledFuture future = scheduler.schedule( runnable , duration.toMinutes() , TimeUnit.MINUTES );
重要提示:请务必正常关闭您的ScheduledExecutorService
. 否则,线程池可能会继续运行,即使在您结束程序之后也是如此。