我在Rails和Postgres中处理日期和时间时遇到了这个问题:
数据库使用UTC。
用户在Rails应用程序中设置一个时区选项,但它仅在获取用户的本地时间以比较时间时使用。
用户存储一个时间,比如2012年3月17日晚上7点。我不想进行时区转换或存储时区。我只想保存日期和时间。这样,如果用户更改了时区,它仍将显示2012年3月17日晚上7点。
我只使用用户指定的时区来获取用户本地时区中当前时间“之前”或“之后”的记录。
我目前使用的是“不带时区的时间戳”,但是当我检索记录时,rails(?)会在应用程序中将它们转换为时区,这是我不想要的。
Appointment.first.time
=> Fri, 02 Mar 2012 19:00:00 UTC +00:00
因为数据库中的记录看起来是UTC,所以我的技巧是获取当前时间,用'Date.strptime(str,“%m/%d/%Y”)'删除时区,然后用它执行查询:
.where("time >= ?", date_start)
看起来一定有一个更简单的方法可以忽略周围的时区。有什么想法吗?
4条答案
按热度按时间pinkon5k1#
Postgres有两种不同的时间戳数据类型:
timestamp with time zone
,简称:* * 一米一米一**timestamp without time zone
,简称:* * 一米三米一x**timestamptz
是日期/时间系列中的 * preferred * 类型,它在pg_type
中设置了typispreferred
,可以是相关的:内部存储和epoch
在内部,时间戳在磁盘和RAM上占用8字节的存储空间。它是一个整数值,表示从Postgres纪元2000 - 01 - 01 00:00:00 UTC开始的微秒计数。
Postgres还内置了常用的UNIX time从UNIX纪元1970 - 01 - 01 00:00:00 UTC开始计秒的知识,并在函数
to_timestamp(double precision)
或EXTRACT(EPOCH FROM timestamptz)
中使用。The source code:
以及:
微秒分辨率转换为秒的最大6个小数位数。
一米九一x
对于**
timestamp
**,没有显式提供时区。Postgres * 忽略 * 任何错误添加到输入文本中的时区修饰符!显示时数没有变化。所有事情都发生在同一个时区,这很好。对于不同的时区,* 含义 * 会改变,但 * 值 * 和 * 显示 * 保持不变。
一米十一分
timestamptz
**的处理有细微的不同。我在此引用手册:对于
timestamp with time zone
,内部存储值为始终采用UTC(通用协调时间...)我的。**时区本身不会被存储。它是一个输入修饰符,用于计算相应的UTC时间戳,它被存储-或和输出装饰器用于计算显示的本地时间-附加时区偏移量。如果你没有在输入上附加
timestamptz
的偏移量,假定会话的当前时区设置。所有计算均使用UTC时间戳值完成。如果您(可能)必须处理多个时区,请使用timestamptz
。换句话说:如果对假设的时区有任何疑问或误解,请使用timestamptz
。适用于大多数用例。psql、pgAdmin等客户端或任何通过libpq通信的应用程序(如带有pg gem的Ruby)都会显示时间戳加上 * 当前时区 * 的偏移量,或者根据 * 请求的 * 时区(见下文)显示。它总是 * 相同的时间点 *,只是显示格式不同。或者,如手册所述:
所有时区识别的日期和时间都以UTC格式存储在内部。在显示给客户端之前,它们将被转换为
TimeZone
配置参数指定的时区的本地时间。psql中的示例:
这里发生了什么事?
对于Postgres,这只是输入UTC时间戳
2012-03-05 17:00:00
的多种方法之一,在我的测试中,对于当前时区设置 * Vienna/Austria *,查询结果是 * displayed *,它在冬季的偏移量为+1
,在夏季的偏移量为+2
("夏令时",DST)。所以2012-03-05 18:00:00+01
作为DST只在以后才起作用。Postgres会立即忘记输入的文字,它所记住的只是数据类型的值,就像十进制数
numeric '003.4'
或numeric '+3.4'
一样,两者都得到完全相同的内部值。AT TIME ZONE
现在缺少的是一个根据特定时区解释或表示时间戳的工具。这就是**
AT TIME ZONE
**结构的用武之地。有两种不同的用例。timestamptz
转换为timestamp
,反之亦然。要输入UTC时间
timestamptz
2012-03-05 17:00:00+0
:...这相当于:
要显示与EST
timestamp
(东部标准时间)相同的时间点:没错,
AT TIME ZONE 'UTC'
* twice *。第一个函数将timestamp
值解释为(给定的)UTC时间戳,返回类型timestamptz
。第二个函数将timestamptz
转换为给定时区"EST"中的timestamp
--此时挂钟在EST时区中显示的内容。示例
返回8(或9)个 * 相同 * 的行,其中timestamptz列包含相同的UTC时间戳
2012-03-05 17:00:00
。第9行在我的时区中可以工作,但这是一个邪恶的陷阱。请参见下文。①夏威夷时间时区***名称***和时区***缩写**的6 - 8行以DST为准(夏令时),并且可能不同,尽管当前不是。像
'US/Hawaii'
这样的时区名称自动知道DST规则和所有历史班次,而像HST
这样的缩写只是固定偏移量的哑代码。您可能需要为夏令时/标准时间附加不同的缩写。 name * 正确地解释了给定时区的 * 任何 * 时间戳。一个 *缩写 * 是便宜的,但需要是给定时间戳的正确缩写:②第9行,标记为 * loaded footgun * 对我 * 有效,但只是巧合。如果显式地将一个字面值强制转换为
timestamp [without time zone]
,则忽略任何时区偏移!只使用裸时间戳。然后,在示例中,该值自动强制转换为timestamptz
以匹配列类型。对于此步骤,假定当前会话的设置为timezone
。在我的情况下,恰好是同一时区+1
(欧洲/维也纳)。但在您的情况下可能不是-这将导致不同的值。简而言之:不要将timestamptz
文本强制转换为timestamp
,否则将丢失时区偏移量。你的问题
用户存储一个时间,比如2012年3月17日晚上7点。我不想进行时区转换或存储时区。
时区本身从不存储。请使用上述方法之一输入UTC时间戳。
我只使用用户指定的时区来获取用户本地时区中当前时间"之前"或"之后"的记录。
可以对不同时区的所有客户端使用一个查询。
对于绝对全球时间:
对于根据当地时钟的时间:
还不厌倦背景信息?There is more in the manual.
6ojccjat2#
如果您想默认使用UTC:
在
config/application.rb
中,添加:然后,如果您存储的当前用户时区名称为
current_user.timezone
,则可以说。current_user.timezone
应该是有效的时区名称,否则将得到ArgumentError: Invalid Timezone
,请参见full list。kulphzqa3#
不知道欧文的答案是否包含了问题的解决方案(仍然包含了大量有用的信息),但我有一个
较短溶液:
.where("created_at > ?", (YOUR_DATE_IN_THE_TIMEZONE).iso8601)
为什么所有的混乱发生
当你尝试实现像
.where("created_at > ?", YOUR_DATE_IN_THE_TIMEZONE)
这样的东西时,Rails会介入并将时间转换为服务器时间(很可能是UTC),从而将日期转换为时间戳(没有时区格式的时间戳),这就是为什么所有关于in_time_zone
的讨论都是毫无意义的。iso8601的工作原理
当你调用
iso8601
时,你的日期被转换成字符串,Rails不能“刹车”,必须照原样传递给Postgres。别忘了投赞成票
bq3bfh9z4#
在我的Angular/Typescript/Node API/PostgreSQL环境中,我也遇到过类似的难题,加上时间戳精度,这里是complete answer and solution