将 postgres 时间戳限制为“一天结束”?

Jar*_*eck 4 postgresql constraint datatypes timestamp timezone

考虑到一个指定的表bananas
一个timestamp without time zone名为列end_time
一些 psql客户有set timezone to 'UTC'
其他 psql客户端有set timezone to 'US/Eastern'
服务器配置有timezone = 'UTC'postgresql.conf

如何编写支票约束bananas.end_time以确保end_time始终是一天的结束,定义为“美国/东部”一天的第 23 小时 59 分 59 秒?

我试过:

alter table bananas
add constraint ck_end_time_is_end_of_day
check (
  23 = date_part('hour', end_time at time zone 'UTC' at time zone 'US/Eastern')
  and 59 = date_part('minute', end_time at time zone 'UTC' at time zone 'US/Eastern') 
  and 59 = floor(date_part('second', end_time at time zone 'UTC' at time zone 'US/Eastern')) 
)
;
Run Code Online (Sandbox Code Playgroud)

这似乎正确地限制了该列,但它似乎效率极低,而且非常不可读。是否有更高效和/或更易读的实现?

Erw*_*ter 6

目前

虽然坚持你不幸的解决方案:

CHECK ((end_time AT TIME ZONE 'UTC' AT TIME ZONE 'US/Eastern')::time = '23:59:59'::time)
Run Code Online (Sandbox Code Playgroud)

没错,AT TIME ZONE 两次

  • 一审将您timestamp without time zone进入timestamp with time zone。那是假设您实际上是在存储 UTC 时间。

  • 第二个实例将timestamptz返回转换timestamp 为您给定的时区。现在您可以检查time组件是否是您想要的。

time转换为,而不是转换为字符串,这更便宜且更健壮。

要摆脱小数位数,您可以time(0)改为使用,但在您的示例中,它会入值而不是下限。相反,用 截断date_trunc(),这是floor()使用正数的更便宜的方法:

CHECK ((date_trunc('sec', end_time) AT TIME ZONE 'UTC' AT TIME ZONE 'US/Eastern')::time
        = '23:59:59'::time)
Run Code Online (Sandbox Code Playgroud)

正确的解决方案

timestamp值具有小数位数,使用时间分量'23:59:59'作为上限是一个不幸的决定。取而代之的是,使用00:00第二天作为独家上边界。这是微不足道的执行一个检查约束。

接下来,因为你正在处理多个时区,我会建议使用timestamptz的替代timestamp。内部存储与timestamp给定时区“UTC”中的相同,但输入/输出处理不同。

  • 时间戳在输出时自动转移到当前时区。
  • 输入带有时区偏移量的时间戳,以便根据 UTC 时间自动保存这些值。

关于 SO 的相关答案有更多细节。