如何在Ruby中修改DateTime的时区?

36

读取、写入和序列化日期和时间,同时保持时区不变,变得越来越麻烦了。我正在使用Ruby(和Rails 3.0),并尝试更改DateTime的时区(到UTC),但不改变时间本身。

我想要这个:

t = DateTime.now
t.hour
-> 4
t.offset = 0
t.hour
-> 4
t.utc?
-> true

我最接近的是这个,但它不直观。
t = DateTime.now
t.hour
-> 4
t += t.offset
t = t.utc
t.hour
-> 4
t.utc?
-> true

有更好的方法吗?

3
如果您将“now”作为时间戳,那么在数据库中存储该时间的UTC标记版本是不正确的,对吗?我个人认为,当系统的所有部分都使用UTC时,头痛最少,并且只有在视图中才需要根据服务器或浏览器的时区进行调整显示。 - Phrogz
这很困难,因为我从CSV文件中读取日期时,时区是未知或无关紧要的,所以我将它们存储为UTC在数据库中。但是当使用来自本地区域的日期(即大于“2010/01/01 8:00 pm”)进行查询时,它会先将该时间转换为UTC(这是我的问题来源),然后再将其用于查询。我试图让整个应用程序都是时区不可知的,以便用户可以加载具有7:00-8:00范围内时间的CSV文件,然后查询7:00-8:00并返回相同的记录集。相反,Ruby假定解析的时间处于本地区域。 - Daniel Beardsley
7个回答

49

在使用 Rails 3 时,你正在寻找 DateTime.change() 方法。

dt = DateTime.now
=> Mon, 20 Dec 2010 18:59:43 +0100
dt = dt.change(:offset => "+0000")
=> Mon, 20 Dec 2010 18:59:43 +0000

2
就此而言,.change 似乎在2.3中也可用。 - joshaidan
7
更有帮助的做法是实际上链接到答案。我认为这就是答案:https://dev59.com/uW025IYBdhLWcg3wjGpO#14007547 - micapam
或者简单地使用 DateTime.now.utc。 - courtsimas
DateTime.now.utc会改变时间。 - Pavan Katepalli
2
请注意:这将改变所表示的绝对时间。 - Alexander Flenniken
这是 DateTime.change 的文档:https://apidock.com/rails/DateTime/change - Julien Lamarche

13

正如@Sam所指出的,仅仅改变偏移量是不够的,并且可能会导致错误。为了抵抗DST时钟进步的影响,转换应该按照以下方式进行:

d = datetime_to_alter_time_zone
time_zone = 'Alaska'

DateTime.new
  .in_time_zone(time_zone)
  .change(year: d.year, month: d.month, day: d.day, hour: d.hour, min: d.min, sec: d.sec)

1
这可能是最好的被接受的答案。 您还可以执行 Date.parse(“2022-10-10”).in_time_zone('Asia / Kolkata') 当您不涉及小时/分钟/秒时,该行长度要小得多。 - cegprakash

11
d = DateTime.now
puts [ d, d.zone ]
#=> 2010-12-17T13:28:29-07:00
#=> -07:00

d2 = d.new_offset(3.0/24)
puts d2, d2.zone
#=> 2010-12-17T23:28:29+03:00
#=> +03:00

编辑:本回答未考虑到另一个答案的评论提供的信息,即希望在时区更改后报告的“小时数”保持不变。


“new_offset”听起来很有用,文档看起来也像是我需要的,但它只是从一个偏移转换到另一个偏移。正如问题所述,我想改变偏移而不改变时间。 - Daniel Beardsley
1
对我来说,这个DateTime.now.new_offset(-8.0/24)是这个页面上唯一能满足我的需求的东西 - 将银行交易大致放在太平洋时间(DST可以忽略)。这很可能是因为我的简单脚本中没有包括提到的框架或任何Active内容。 - Marcos

10

使用数字偏移量,例如“+1000”,在全年范围内都不起作用,因为会受到夏令时的影响。请查看ActiveSupport::TimeZone。更多信息请点击此处


8
最好提供一个使用 ActiveSupport::TimeZone 的示例,而不仅仅是链接到文档。 - Joshua Pinter
@Sam OP 询问如何“转换”为UTC。 如果我们有有效的时间戳和有效的偏移量,可能会遇到什么问题呢? - x-yuri

9

我建议使用一个Time对象。获取当前本地时间,然后增加UTC偏移量并将其转换为UTC,如下所示:

t = Time.now # or Time.parse(myDateTime.asctime)
t # => Thu Dec 16 21:07:48 -0800 2010
(t + t.utc_offset).utc # => Thu Dec 16 21:07:48 UTC 2010

虽然Phrogz的评论中提到,如果你只想以位置无关的方式存储时间戳,那么只需使用当前的UTC时间:

Time.now.utc

这会带来很大的性能损失。这似乎比我已经拥有的解决方案更慢、更复杂。 - Daniel Beardsley
如果您并不关心 DateTime 对象(只需要当前时间),那么可以跳过解析并使用 Time.now。我稍微修改了我的回答。 - maerics
这与我上面给出的解决方案完全相同,只不过增加了一个字符串解析步骤。 - Daniel Beardsley

2
如果有人(比如我)需要将时间区以字符串形式表示,例如“美国和加拿大太平洋时区”,那么我发现以下是最好的方法:
datetime.change(ActiveSupport::TimeZone[time_zone_string].formatted_offset(false))

它是否包含夏令时? - new2cpp
@new2cpp 当我们想要保持时间和一切不变,只想更改时区时,您是什么意思?我认为夏令时不适用于这里。那是一个不同的问题。 - Zia Ul Rehman Mughal

-4

您可以通过在配置文件(config/application.rb)中添加以下行来指定应用程序要使用的时区: config.time_zone = '孟买'

您可以在此处找到相关的官方文档:http://api.rubyonrails.org/classes/ActiveSupport/TimeZone.html

另一个选项是“Monkey Patch” 'DateTime' 类。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接