Answer by Kouber Saparev 大部分是正确的,但在存储时区方面不正确。
Postgres 中的数据类型错误
UTC 时间戳。当我们的应用程序将该数据保存到 Postgres 时,它会将时间戳存储在没有时区的时间戳列中。
正如他在回答中指出的那样,您在 Postgres 数据库中使用了错误的数据类型。跟踪时刻时,您必须使用TIMESTAMP WITH TIME ZONE 类型的列。在插入或更新期间提供输入时,任何有关时区或与 UTC 的偏移量的随附信息都用于调整为 UTC。然后丢弃伴随的区域/偏移量。如果您需要记住原始区域/偏移量,则需要定义第二列并将该信息自己存储在那里。
Postgres 和 SQL 标准中的另一种类型是 TIMESTAMP WITHOUT TIME ZONE。这种类型故意缺少任何时区或与 UTC 偏移的概念。所以这种类型不能代表时刻,不能在时间轴上存储点。它存储代表大约 26-27 小时范围内的潜在时刻的值,即全球各个时区的范围。仅当您的意思是在任何地方或任何地方都有时间的日期时才使用此类型,但不是专门在某个地方。也用于您的意思是未来的约会足够远,以至于我们冒着政治家改变我们关心的任何时区使用的偏移量的风险。
始终指定时区
我们商店的 postgres 默认设置为我们的当地时间,山区时间
永远不要依赖主机操作系统、数据库服务器或 Java 虚拟机等工具的当前默认时区。始终在代码中指定所需/预期的时区。
提示:通常最好在 UTC 中工作以进行数据存储、数据交换和大部分业务逻辑。从 UTC 调整为仅用于向用户展示或业务规则需要的时区。
如上所述,Postgres 始终以 UTC 或根本没有区域/偏移量存储日期时间值。注意:您和 Postgres 之间使用的工具可能会对从数据库检索到的 UTC 值应用时区。虽然出于善意,但此反功能会产生一种错觉,即时区已存储,而实际上只有 UTC 存储在 TIMESTAMP WITH TIME ZONE 中,或者根本没有时区/偏移量存储在 TIMESTAMP WITHOUT TIME ZONE 中。
请注意,任何伴随输入到 TIMESTAMP WITHOUT TIME ZONE 列的区域信息都将被忽略,日期和时间按原样存储。
我需要对该时间戳执行一些偏移(将其移动到,例如 EST)
通常最好将您的数据库仅用于存储、查询和检索数据。要调整时区等数据,请在您的应用程序中执行此类工作。例如,在 Java 中使用行业领先的 java.time 类,在 .NET 中 Noda Time 项目(java.time 的前身的一个端口,@ 987654323@项目)。
使用 JDBC 4.2 或更高版本的 Java 示例代码。
LocalDateTime
对于TIMESTAMP WITHOUT TIME ZONE 列中的值,我们使用Java 中的相应类型LocalDateTime,缺少任何时区或与UTC 偏移的概念。
LocalDateTime ldt = myResultSet.getObject( … , LocalDateTime.class ) ; // Retrieve value from database.
String output = ldt.toString() ; // Generate text representing this date-with-time value in standard ISO 8601 format.
2018-01-23T01:23:45.123
如果您确定此日期和时间是针对 UTC 的,但在没有任何区域/偏移信息的情况下被错误地存储,您可以应用区域或偏移来修复损坏。
OffsetDateTime odt = ldt.atOffset( ZoneOffset.UTC ); // Apply an offset-from-UTC to a `LocalDateTime` lacking such information. Determines a moment.
OffsetDateTime
对于TIMESTAMP WITH TIME ZONE 列中的值,我们使用Java 中的相应类型OffsetDateTime(或Instant),表示UTC 中的时刻。
OffsetDateTime odt = myResultSet.getObject( … , OffsetDateTime.class ) ; // Retrieve value from database.
String output = odt.toString() ; // Generate text representing this date-with-time value in standard ISO 8601 format. A `Z` on the end indicates UTC, pronounced “Zulu”.
2018-01-23T01:23:45.123Z
ZonedDateTime
要通过北美中西部地区的人们使用的挂钟时间的镜头查看设置为 UTC 的 OffsetDateTime 值,请指定一个时区,例如 America/Edmonton 或 America/Denver .
以continent/region 的格式指定proper time zone name,例如America/Montreal、Africa/Casablanca 或Pacific/Auckland。切勿使用 2-4 个字母的缩写,例如 EST 或 IST,因为它们不是真正的时区,没有标准化,甚至不是唯一的 (!)。
ZoneId z = ZoneId.of( "America/Denver" ) ;
ZonedDateTime zdt = odt.atZoneSameInstant( z ) ;
看到这个code run live at IdeOne.com。我们看到的是同一时刻,但挂钟时间不同。
2018-01-22T18:23:45.123-07:00[美国/丹佛]
小心注入时区的工具和中间件
不幸的是,许多工具和中间件会自愿将一些默认时区应用于从数据库中检索到的时刻。虽然出于善意,但这会造成该区域已成为存储数据的一部分的错觉,而实际上时区是在检索时在存储后添加的。这种反特征造成了很多混乱。我希望通过以 UTC 格式报告存储的时刻,所有工具都清晰真实。
如果您使用Java,与JDBC 4.2 及更高版本,您可以与数据库交换java.time (JSR 310) (tutorial) 对象,并避免此时区注入。