tl;博士
- 使用现代 java.time 类。内置于 Java 中。
- 在某些地方的某些日期,这一天可能不从 00:00:00 开始。
- 避免使用模棱两可的术语“午夜”。
- 想想“一天的第一刻”。
- 搜索 Stack Overflow 以了解半开时间跨度。
一天的第一刻:
ZoneId z = ZoneId.of( "Pacific/Auckland" ) ; // Specify the region whose wall-clock time you want to perceive “today”. Specify time zone with `Continent/Region`, never the 3-4 letter pseudo-zones such as `EST` or `IST`.
LocalDate today = LocalDate.now( z ) ; // Capture the current date as seen in a specific time zone.
ZonedDateTime zdt = today.atStartOfDay( z ) ; // Let java.time determine first moment of the day.
Instant instant = zdt.toInstant() ; // Adjust from time zone to UTC. Same moment, same point on the timeline, different wall-clock time.
详情
这里有很多问题。
调整区域
如果 UTC 时间距离纪元 30 秒。如果可以表示/转换为 Thu Jan 1 00:00:30 EST 1970,
不,错了。你不能转换。这两个日期时间值不代表同一时刻。如果EST 指的是America/New_York 或America/Montreal 等时区,那么这两个值相差几个小时。
Instant thirtySecondsFromEpoch = Instant.EPOCH.plusSeconds( 30 ) ;
thirtySecondsFromEpoch.toString(): 1970-01-01T00:00:30Z
在北美东海岸的大部分地区看到同样的时刻。
ZoneId z = ZoneId.of( "America/New_York" ) ;
ZonedDateTime zdtAdjustedFromThirtySecondsFromEpoch = thirtySecondsFromEpoch.atZone( z ) ;
zdtAdjustedFromThirtySecondsFromEpoch.toString(): 1969-12-31T19:00:30-05:00[America/New_York]
因为北美东海岸的大多数人使用的挂钟时间比世界标准时间晚 5 小时,所以过去 30 秒的同一时刻被视为过去半分钟前一天晚上 7 点,1969 年的最后一天。
尝试相同的时间,即 1970 年第一天 30 秒后,但以 America/New_York 时区而不是 UTC 时间显示。现在我们正在谈论一个完全不同的时刻。新一天的半分钟发生在五个小时后比 UTC 的新一天的半分钟。
ZonedDateTime zdt = ZonedDateTime.of( 1970 , 1 , 1 , 0 , 0 , 30 , 0 , z ) ;
zdt.toString(): 1970-01-01T00:00:30-05:00[美国/纽约]
zdt.toInstant().toString(): 1970-01-01T05:00:30Z
时区
您忽略了时区的关键问题。
时区对于确定日期至关重要。对于任何给定的时刻,日期在全球范围内因区域而异。例如,Paris France 中午夜后几分钟是新的一天,而 Montréal Québec 中仍然是“昨天”。
以continent/region 的格式指定proper time zone name,例如America/Montreal、Africa/Casablanca 或Pacific/Auckland。切勿使用 3-4 个字母的缩写,例如 EST 或 IST,因为它们不是真正的时区,没有标准化,甚至不是唯一的 (!)。
java.util.Date::toString
Date 上的 toString 方法有一个善意但麻烦的行为,即在生成表示 Date 对象的 UTC 值的字符串时隐式应用 JVM 的当前默认时区。造成此区域存在于Date 中的错误印象。
这是避免这个糟糕课程的众多原因之一。请改用 java.time 类。
java.time
现代方法使用 java.time 类来代替麻烦的旧旧日期时间类,例如 Date。
如果您手头有Date,请通过添加到旧类的新方法转换为java.time.Instant。
Instant instant = myJavaUtilDate.toInstant() ;
避免谈论“午夜”,因为这是一个无定形的话题。相反,请专注于一天的第一刻,即一天的开始。
不要假设一天从 00:00:00 开始。夏令时 (DST) 等异常情况意味着一天可能从一天中的另一个时间开始,例如 01:00:00。让 java.time 确定一天的开始。
确定一天的开始意味着确定日期。如上所述,确定日期需要时区。因此,我们需要将您在 UTC 中的 Instant 调整为特定时区中的 ZonedDateTime (ZoneId)。
ZoneId z = ZoneId.of( "Africa/Tunis" ) ;
ZonedDateTime zdt = instant.atZone( z ) ;
从 ZonedDateTime 中提取仅包含日期的部分。
LocalDate ld = zdt.toLocalDate() ;
现在在指定时区询问一天中的第一刻。
ZonedDateTime zdtStartOfDay = ld.atStartOfDay( z ) ;
截断
您可以截断各种 java.time 对象。查找truncatedTo 方法,在该方法中传递TemporalUnit 对象(可能是ChronoUnit)以指定结果中所需的粒度。为此目的不需要 Apache DateUtils。
如果截断 Instant,您始终使用 UTC 作为时区逻辑。
Instant instantTrucToDay = Instant.now().truncatedTo( ChronoUnit.DAYS ) ;
您更有可能希望在上下文中截断除 UTC 之外的某个时区,如上所述。
ZonedDateTime zdtTruncToDay = zdt.truncatedTo( ChronoUnit.DAYS ) ;
顺便说一句,如果您的最终目标是使用整个日期,即没有任何时间或区域的仅日期,请坚持使用LocalDate。
如果 UTC 时间距离纪元 30 秒。如果可以表示/转换为 Thu Jan 1 00:00:30 EST 1970
不确定你在这里的意思。但请记住,在 1970-01-01T00:00:00Z (1970-01-01T00:00:30Z) 的纪元之后 30 秒,北美东海岸的同一时刻要早几个小时。这意味着前一天晚上 7 点左右。
Instant instantThirtySecsAfterEpoch = Instant.EPOCH.plusSeconds( 30 ) ;
instantThirtySecsAfterEpoch.toString(): 1970-01-01T00:00:30Z
ZoneId z = ZoneId.of( "America/New_York" ) ;
ZonedDateTime zdt = instantThirtySecsAfterEpoch.atZone( z ) ;
zdt.toString(): 1969-12-31T19:00:30-05:00[美国/纽约]
关于java.time
java.time 框架内置于 Java 8 及更高版本中。这些类取代了麻烦的旧 legacy 日期时间类,例如 java.util.Date、Calendar 和 SimpleDateFormat。
Joda-Time 项目现在位于maintenance mode,建议迁移到java.time 类。
要了解更多信息,请参阅Oracle Tutorial。并在 Stack Overflow 上搜索许多示例和解释。规格为JSR 310。
使用符合JDBC 4.2 或更高版本的JDBC driver,您可以直接与您的数据库交换java.time 对象。不需要字符串或 java.sql.* 类。
从哪里获得 java.time 类?
ThreeTen-Extra 项目通过附加类扩展了 java.time。该项目是未来可能添加到 java.time 的试验场。您可以在这里找到一些有用的类,例如Interval、YearWeek、YearQuarter 和more。