Postgres
在 Postgres 中,当您指的是某个日期的任何地方的上午 9 点或任何地方的上午 9 点时,请使用列数据类型 TIMESTAMP WITHOUT TIME ZONE。输入中包含的任何时区或与 UTC 的偏移量都将被忽略,日期和时间按原样(无调整)并存储。这种数据类型故意缺少time zone 或offset-from-UTC 的任何概念。
对于没有日期的时间,使用TIME WITHOUT TIME ZONE 数据类型。 Postgres 还提供TIME WITH TIME ZONE 只是因为它是 SQL 规范所要求的;这个WITH 类型是无意义的,永远不应该使用。
Postgres 是此类项目的绝佳选择,因为它在 its data types 和 its functions 中都提供了出色的日期时间支持。数据库的日期时间特征差异很大。
Java
在 Java 后端,只使用现代的 java.time 类。这些年前,它取代了与最早的 Java 版本捆绑在一起的 可怕 旧日期时间类。
如果尚未使用 Java 8 或更高版本,请在 ThreeTen-Backport 项目的 Java 6 和 7 的反向移植中找到几乎所有相同的功能。非常值得将这个库添加到您的项目中。来自为您带来 java.time 类和 Joda-Time 项目的同一批人,均由同一个人 Stephen Colebourne 领导。
LocalDateTime
在 java.time 中,使用 LocalDateTime 类表示您的意思是某个日期的任何地方/任何地方的上午 9 点。与 Postgres 中的 TIMESTAMP WITHOUT TIME ZONE 一样,此类故意缺少任何区域或偏移量的概念。
LocalDateTime ldt = LocalDateTime.of( 2018 , 1 , 23 , 15 , 0 , 0 , 0 ) ; // 3 PM on 23rd of January this year.
LocalTime
如果您只指时间,没有日期,请使用 LocalTime 类。
LocalTime lt = LocalTime.of( 15 , 0 ) ; // 3 PM.
JDBC
从 JDBC 4.2 及更高版本开始,您可以通过 getObject 和 setObject 方法与数据库交换 java.time 对象。
LocalDateTime ldt = myResultSet.getObject( … , LocalDateTime.class ) ;
如果您的 JDBC 驱动程序尚未更新到 4.2,则回退到糟糕的旧遗留类,但立即转换为 java.time 类。
鉴于遗留类缺少一个没有时区的日期加时间类,我们必须伪造它。使用java.sql.Timestamp,它代表 UTC 中的时刻,分辨率为纳秒,忽略它是 UTC 的事实。
java.sql.Timestamp ts = myResultSet.getTimestamp( … ) ;
对于 Java 8 及更高版本,使用添加到旧类的新方法进行转换。首先转换为java.time.Instant,它也代表UTC 中的一个时刻,分辨率为纳秒。然后通过有效地去除 UTC 的概念转换为LocalDateTime。
Instant instant = ts.toInstant() ; // Convert from legacy class to modern one.
LocalDateTime ldt = LocalDateTime.ofInstant( instant , ZoneOffset.UTC ) ; // Remove the concept of UTC (or any other offset or zone) from our data.
对于使用 ThreeTen-Backport 库的 Java 6 和 7,请使用其实用程序 DateTimeUtils 类中的转换方法。
org.threeten.bp.Instant instant = org.threeten.bp.DateTimeUtils.toInstant( ts ) ; // Convert from legacy class to modern.
org.threeten.bp.LocalDateTime ldt = LocalDateTime.ofInstant( instant , ZoneOffset.UTC ) ; // Remove the concept of UTC (or any other offset or zone) from our data.
ZonedDateTime
根据定义,Local… 类没有真正的意义,除非您将它们放在时区的上下文中。 LocalDateTime 不是片刻,不是代表时间线上的一个点吗。
以continent/region 的格式指定proper time zone name,例如America/Montreal、Africa/Casablanca 或Pacific/Auckland。切勿使用 2-4 个字母的缩写,例如 PST 或 BST 或 EST 或 IST,因为它们不是真正的时区,没有标准化,甚至不是唯一的(!) .
LocalDateTime ldt =
LocalDateTime.of(
LocalDate.of( 2018 , Month.January , 23 ) ,
LocalTime.of( 9 , 0 )
)
;
ZoneId zLosAngeles = ZoneId.of( "America/Los_Angeles" ) ; // Seattle time zone.
ZonedDateTime zdtSeattle = ldt.atZone( zLosAngeles ) ;
ZoneId zChicago = ZoneId.of( "America/Chicago" ) ;
ZonedDateTime zdtChicago = ldt.atZone( zChicago ) ;
ZoneId zLondon = ZoneId.of( "Europe/London" ) ;
ZonedDateTime zdtLondon = ldt.atZone( zLondon ) ;
我们有三个ZonedDateTime 对象:zdtSeattle、zdtChicago 和zdtLondon。这些都是今年早些时候 1 月 23 日上午 9 点。了解这是三个非常不同的时刻,每一个都比你向东早几个小时。它们都有相同的挂钟时间(23 日上午 9 点),但时间轴上的三个不同点。
JavaScript
虽然我对 JavaScript 的了解还不足以肯定地说,但我怀疑那里有任何库可以像日期时间处理那样丰富。 java.time 框架是业界领先的。
至于 Web 客户端用户界面开发,我使用 Vaadin,所以这不是问题:后端的纯 Java 会自动生成 Web 浏览器所需的 HTML/CSS/DOM/JavaScript。
找到一种在 JavaScript 中捕获本地时间的方法
关于在客户端机器中检测当前默认时区,我不是专家,但我记得浏览器不返回指定时区,只返回与 UTC 的偏移量。有关可能的解决方案,请参阅Answer by Matt Johnson。在任何应用程序(桌面或网络)中,最终,如果正确的时区至关重要,那么您必须询问或与用户确认所需/预期的时区。明智的做法是始终在用户界面的某个位置指出您的应用正在使用哪个时区。
如果您需要在 Java 后端和前端的 JavaScript 代码之间交换日期时间值,您主要有两种选择:
ISO 8601
ISO 8601 标准定义了各种用于交换日期时间值的文本格式。这些都是为了避免歧义而明智地设计的。它们很容易被机器解析,并且很容易被跨文化的人类阅读。
java.time 类在生成/解析字符串时默认使用这些格式。
从纪元开始计数
我不推荐这种方法,因为它令人困惑且容易出错,并且在发送或接收的人和图书馆之间容易产生歧义和不正确的假设。
epoch reference date 是用作基线的时间点。然后一些向前或向后的计数是由一些粒度组成的。
一个大问题是各种系统都使用dozens of epoch references。 java.time 类默认使用 UTC 1970 年第一刻的 Unix time 纪元,1970-01-01T00:00Z。
另一个问题是有很多粒度,例如整秒、毫秒、微秒和纳秒。程序员必须清楚地记录/传达正在发挥作用的粒度。
如果您要将您的三个商店的三个开业时刻发送到 JavaScript 作为从 epoch 计数,您将发送三个不同的数字。
long millisecondsSeattle = zdtSeattle.toInstant().toEpochMilli() ;
long millisecondsChicago = zdtChicago.toInstant().toEpochMilli() ;
long millisecondsLondon = zdtLondon.toInstant().toEpochMilli() ;
在三个不同的时刻产生三个不同的数字。
关于java.time
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 4.2 或更高版本的JDBC driver。不需要字符串,不需要java.sql.* 类。
从哪里获得 java.time 类?
ThreeTen-Extra 项目通过附加类扩展了 java.time。该项目是未来可能添加到 java.time 的试验场。您可以在这里找到一些有用的类,例如Interval、YearWeek、YearQuarter 和more。