在Rails和PostgreSQL中完全忽略时区
我正在处理Rails和Postgres的date和时间,并且遇到这个问题:
数据库是UTC。
用户在Rails应用程序中设置了一个select的时区,但只有在获取用户本地时间进行比较时才能使用。
用户存储时间,比如2012年3月17日晚上7点。 我不想要时区转换或时区存储。 我只想保存这个date和时间。 这样,如果用户更改了时区,它仍然会显示2012年3月17日晚上7点。
我只使用用户指定的时区在用户本地时区获取当前时间之前或之后的logging。
我目前使用“时间戳没有时区”,但是当我检索logging时,rails(?)将它们转换为应用程序中的时区,这是我不想要的。
Appointment.first.time => Fri, 02 Mar 2012 19:00:00 UTC +00:00
因为数据库中的logging似乎是以UTC出现的,所以我的黑客就是采取当前时间,用'Date.strptime(str,“%m /%d /%Y”)''来删除时区,然后执行我的用这个查询:
.where("time >= ?", date_start)
似乎必须有一个简单的方法来忽略周围的时区。 有任何想法吗?
数据typestimestamp
是timestamp without time zone
timestamp
的简称。
另一个选项timestamptz
是timestamp with time zone
缩写。
timestamptz
是date/时间系列中的首选types。 它有typispreferred
设置,这可能是相关的:
- 在PostgreSQL的两个date之间生成时间序列
时代
在内部 ,时间戳被存储为来自历元的计数。 Postgres使用UTC 2000年第一天的第一个时间点,即2000-01-01T00:00:00Z。 八个八位字节用于存储计数。 根据编译时间选项,该编号是:
- 一个8字节的整数 (默认),0到6位小数秒
-
一个浮点数(不赞成使用),0到10位数字的小数秒,其中精度迅速降低的时间更远离纪元。
现代Postgres安装使用8字节的整数。
请注意,Postgres不使用Unix时间 。 Postgres的时代是2000-01-01的第一个时刻,而不是Unix的1970-01-01。 虽然Unix时间的整秒分辨率,Postgres保持几秒钟。
timestamp
如果你定义了一个数据types的timestamp
[without time zone]
你告诉Postgres:“我没有明确地提供时区,而是假定当前时区。”Postgres保存时间戳 – 忽略时区修改器,如果你应该添加一!
当您稍后显示该timestamp
,您将逐字回到您input的内容。 与相同的时区设置一切都很好。 如果会话的时区设置发生更改,则timestamp
的含义也会发生变化 – 值保持不变。
timestamptz
timestamp with time zone
处理有细微的差别。 我在这里引用手册 :
对于
timestamp with time zone
,内部存储的值始终为UTC (通用协调时间…)
大胆重视我的。 时区本身从不存储 。 它是一个input修饰符,用于计算相应的UTC时间戳,存储 – 或输出修改器,用于计算本地时间显示 – 带附加时区偏移量。 如果input时未附加timestamptz
的偏移量,则假定会话的当前时区设置。 所有计算都使用UTC时间戳值完成。 如果您必须(或可能必须)处理多个时区,请使用timestamptz
。
像psql或pgAdmin这样的客户端或任何通过libpq进行通信的应用程序(如带有pg gem的Ruby)都会显示当前时区的时间戳加偏移量或根据请求的时区(见下文)。 它始终是相同的时间点 ,只有显示格式不同。 或者, 如手册所述 :
所有可识别时区的date和时间均以UTC内部存储。 在显示给客户端之前,它们将转换为由TimeZoneconfiguration参数指定的区域中的本地时间。
考虑这个简单的例子(在psql中):
db =#SELECT timestamptz'2012-03-05 20:00 +03 '; timestamptz ------------------------ 2012-03-05 18:00:00 +01
大胆重视我的。 这里发生了什么?
我为input文字select了一个任意的时区偏移+3
。 对于Postgres,这只是inputUTC时间戳2012-03-05 17:00:00
的许多方法2012-03-05 17:00:00
。 查询的结果显示在我的testing中的当前时区设置维也纳/奥地利 ,冬季时偏移+1
,夏季时偏移+2
: 2012-03-05 18:00:00+01
,因为它落入冬季。
Postgres已经忘记了如何input这个值。 所有它记得的是价值和数据types。 就像十进制数字一样。 numeric '003.4'
, numeric '3.40'
或numeric '+3.4'
– 都会导致完全相同的内部值。
AT TIME ZONE
只要你掌握了这个逻辑,你就可以做任何你想做的事情。 现在缺less的东西,是根据特定时区解释或表示时间戳文字的工具。 这就是AT TIME ZONE
结构出现的地方。注意两种不同的用例。 timestamptz
被转换为timestamp
,反之亦然。
inputUTC时间戳2012-03-05 17:00:00
:
SELECT timestamp '2012-03-05 17:00:00' AT TIME ZONE 'UTC'
要显示与UTC时间戳相同的时间戳值:
SELECT timestamp '2012-03-05 17:00:00' AT TIME ZONE 'UTC' AT TIME ZONE 'UTC'
这是正确的,在AT TIME ZONE 'UTC'
两次 。 第一个将时间戳文字解释为UTC时间戳并输出typestimestamptz
。 第二个将它转换回timestamp [without time zone]
。
例子
SELECT ts AT TIME ZONE 'UTC' FROM ( VALUES (timestamptz '2012-03-05 17:00:00+0') , (timestamptz '2012-03-05 18:00:00+1') , (timestamp '2012-03-05 18:00:00+1') -- ① loaded footgun! , (timestamp '2012-03-05 11:00:00' AT TIME ZONE '+6') , (timestamp '2012-03-05 17:00:00' AT TIME ZONE 'UTC') , (timestamp '2012-03-05 07:00:00' AT TIME ZONE 'US/Hawaii') -- ② , (timestamp '2012-03-05 07:00:00' AT TIME ZONE 'HST') -- ② ) t(ts);
返回相同的UTC时间戳7(6)相同的行2012-03-05 17:00:00
。
①第三排,标记为装载的步枪, 适合我 ,但只能巧合。 如果明确地将时间戳文字转换为timestamp [without time zone]
, 则时区偏移将被忽略 ! 它会根据当地时区进行解释,而在我的情况下恰好是相同的时区+1
。 但可能不适合你 – 这将导致不同的价值。
②夏威夷时间的最后两行带有时区名称和时区缩写 ,因此可能会有所不同。 像'US/Hawaii'
这样'US/Hawaii'
时区名称会自动识别DST(夏令时)规则和所有其他(历史)的移位,而像HST
这样的缩写只是一个固定偏移量的哑码。 您可能需要为夏季/冬季时间附加不同的缩写。 该名称正确解释给定时区的任何时间戳。 缩写是便宜的,但是对于给定的年份和时间来说,这个缩写应该是正确的:
- 应用于时间戳时,具有相同属性的时区名称会产生不同的结果
夏令时不是人类有史以来最明智的想法之一。
因为你的数据库是UTC, AT TIME ZONE 'UTC'
是默认值,可以在本地丢弃。
你的问题
用户存储时间,比如2012年3月17日晚上7点。 我不想要时区转换或时区存储。
时区本身从不存储。 使用上述方法之一inputUTC时间戳。
我只使用用户指定的时区在用户本地时区获取当前时间之前或之后的logging。
您可以为不同时区的所有客户端使用一个查询。
绝对全球时间:
SELECT * FROM tbl WHERE time_col > (now() AT TIME ZONE 'UTC')::time
根据当地时钟的时间:
SELECT * FROM tbl WHERE time_col > now()::time
不厌倦了背景资料呢? 手册里有更多的内容。
如果您想默认情况下处理UTC:
在config/application.rb
,添加:
config.time_zone = 'UTC'
然后,如果你存储当前用户的时区名称是current_user.timezone
你可以说。
post.created_at.in_time_zone(current_user.timezone)
current_user.timezone
应该是一个有效的时区名称,否则会得到ArgumentError: Invalid Timezone
,请参阅完整列表 。