Rails:如何将日期时间字符串解析为特定时区

51

我正在使用Debian上的Rails 3.2和ruby 1.9.3。我有一个应用程序,通过HTML表单以字符串的形式收集日期、时间和时区。类似这样:

start_date: "04-15-2010",
start_time: "10:00:00",
timezone: "Central Time (US & Canada)"
我想做的是将这三个元素解析为一个日期,保存到我的数据库中,并保存为UTC时间。在这种情况下,一旦它在UTC时区内,起始时间将会增加7小时。 因此,一旦以UTC形式存储,存储的时间将为17:00而不是接收到的中央时间。
我尝试了以下代码来解析日期:
ActiveSupport::TimeZone[timezone].at DateTime.strptime("{ 2012-04-09 20:00:00 }", "{ %Y-%m-%d %H:%M:%S }").to_i

然而,我无法使用%Z将时区合并到结果时间中。 它要么不能解析,要么将时间解释为UTC而不是美国中部时间。 因此,我的问题是,如何将日期字符串强制转换为特定的时区,而不更改实际存储的日期/时间值。 我想能够将字符串解析为带有正确时区的日期/时间对象,并在那个时间点保留它,以便未来的时区转换是准确的。 我已经四处寻找,但找不到这样做的方法。 这很奇怪,因为这似乎是从HTML表单输入的日期常见的操作。 谢谢任何帮助。

7个回答

121

试试这个:

zone = "Central Time (US & Canada)"  

ActiveSupport::TimeZone[zone].parse("2013-04-03 17:47:00")

8
很棒,但有没有使用strptime的方法?我的日期格式是特定的,无法使用.parse。 - taelor
也许您指的是上面的答案。该解决方案使用了 #strptime。 - ryancheung
那考虑了夏令时吗? - Tallboy
@Tallboy 是的,时区包含夏令时信息,不仅仅是简单的“UTC+ _”值。 - François F
2
如果您已经拥有了 ActiveSupport::TimeZone 的实例,那么您可以在其上调用 .strptime(str, format) 来解析给定格式的日期字符串。 - Alan Heywood

62

使用String#in_time_zone

'1970-01-01 08:00:00'.in_time_zone('Europe/Berlin')
# Thu, 01 Jan 1970 08:00:00.000000000 CET +01:00

它将一个带有时间的字符串解析为日期,并返回一个新的TimeWithZone实例。适用于Rails 4.0+。

TZInfo::Timezone.all_identifiers可能会很方便,以获取支持的时区标识符列表。


1
这对我来说使用DateTime.parse是有效的。谢谢。 - daniel
通常情况下,我更喜欢使用strptime,因为您可以指定格式。但在像'20:00'.in_time_zone('UTC')这样的简单情况下,这个方法也可以。或者当您必须猜测格式时。 - x-yuri
1
这正是我所需要的。谢谢! - sambecker
2
这是最优雅的方式。谢谢。 - fongfan999
1
值得注意的是,如果您跳过区域名称参数,它将使用配置中设置的Rails应用程序时区。 - Artur79
显示剩余2条评论

25

%Z 是正确指定时区名称的方式。你试过以下方式吗?

date_and_time = '%m-%d-%Y %H:%M:%S %Z'
DateTime.strptime("04-15-2010 10:00:00 Central Time (US & Canada)",date_and_time)

“%Z”是否允许带有空格的时区名称?我一直以为它只接受像“EST5EDT”或“-0500”这样的格式。 - tadman
1
我不确定,上面的例子对我有效,并给出了结果“Thu, 15 Apr 2010 10:00:00 -0600”。如果您知道偏移量,也可以直接以“-0600”的形式传递它。我猜系统可以从“rake time:zones:all”中识别每个时区。 - 0x4a6f4672
1
我是想这么说的。谢谢 @0x4a6f4672! 这个不起作用:DateTime.strptime("{ 2012-04-09 20:00:00 Central Time (US & Canada) }", "{ %Y-%m-%d %H:%M:%S %Z}") 但是当我像你的例子一样去掉花括号时,它就起作用了。以下代码可以正常工作,唯一的区别是删除了花括号,我在一些strptime示例或文档中读到过,认为花括号是strptime工作的一部分:DateTime.strptime("2012-04-09 20:00:00 Central Time (US & Canada)", "%Y-%m-%d %H:%M:%S %Z")。我以为是时区中的空格引起了问题,但实际上是花括号。再次感谢。 - Matt Schwartz
11
这种方法行不通,因为DateTime不能正确处理夏令时。如果我输入了 DateTime.strptime 来表示4月15日上午10点,我会得到:Thu, 15 Apr 2010 10:00:00 -0600。实际上应该是 Thu, 15 Apr 2010 10:00:00 -0500,因为4月15日处于CDT(中部夏令时),而不是CST(中部标准时间)。请使用下面ryancheung的答案来获得正确的结果:ActiveSupport::TimeZone["Central Time (US & Canada)"].parse("2013-04-03 17:47:00")。 - idrinkpabst
我的 Time.zone.name 在 strf/ptime 中的 %Z 无法识别(一直保存在格林威治时间),因此我单独找到了偏移量,除以3600,然后补零并添加“+”进行格式化为“+0600”。真是疯狂,我不能只输入 DateTime.strptime(blah).in_time_zone 或 Time.zone.strptime(blah)。 - JosephK

2

这是我想出来的方法。虽然不是最美观的,但它可行。它允许使用指定的格式解析字符串,然后将其转换为我知道Time.zone.parse所需的格式。

class ActiveSupport::TimeZone
  def strptime(time, format='%m/%d/%Y')
    formatted = Time.strptime(time, format).strftime('%Y-%m-%d %T')
    parse(formatted)
  end
end

接下来您可以按照另一个问题中提到的方式进行操作,但需要指定格式:

zone = "Central Time (US & Canada)"  
ActiveSupport::TimeZone[zone].strptime('2013-04-03', '%Y-%m-%d')

如果您已经设置了时区:

Time.zone = "Central Time (US & Canada)" 
Time.zone.strptime('01/13/2006')

我通常使用默认的日期格式%m/%d/%Y,因为我的用户输入大多数情况下都是这种格式。您可以根据需要自定义日期格式,或者使用DateTime使用的默认格式,我相信它是iso8601(%FT%T%z)。


我的时区已经设置好了 - 看起来很不错,但是出现了这个错误:"undefined method `strptime' for #<ActiveSupport::TimeZone:0x0..." - JosephK
请注意,自5.0版本开始,ActiveSupport::TimeZone已添加了此方法。源代码在此处 https://github.com/rails/rails/commit/a5e507fa0b8180c3d97458a9b86c195e9857d8f6 - ngelx

2

我终于找到了一个肮脏但是确切的方法来做这件事。

首先,使用纯Ruby Time.strptime解析字符串,像这样:

time = Time.strptime('12 : 00 : PM', '%I : %M : %p')

这样你就可以获得解析后的时间,但还没有正确的时区。为了解决这个问题,让我们将时间转换为字符串形式,并使用标准的ActiveSupport::TimeZone#parse进行解析。
Time.zone.parse(time.to_s)

结果是 ActiveSupport::TimeWithZone,我们的时间已解析为正确的时区。

我们之所以要这样做,是因为 ActiveSupport::TimeZone 和 ActiveSupport::TimeWithZone 都不支持 strptime 方法。因此,我们必须使用核心 Ruby 的 strptime 解析时间,该方法没有时区信息,将其转换为可接受的 ActiveSupport 对象格式,然后再次解析它。


在此使用的 Time.zone.parse('2020-01-22') 是解析当前时区日期最简单的方式。值得注意的是,对于无效输入,它会返回 nil - jethro

-1

如果想让DateTime接受日期字符串并附加一个时区而不改变日期字符串的值,可以使用以下方法,它很简单,不会在闰年出现问题 :)

xx = DateTime.strptime("9/1/15 #{object.time_zone}", "%m/%d/%Y %Z")

-1

将特定日期格式转换为UTC。

ActiveSupport::TimeZone['UTC'].parse(Time.strptime('01/24/2019T16:10:16', "%m/%d/%YT%H:%M:%S").asctime)

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