LoginSignup
15
12

More than 5 years have passed since last update.

ruby で timezone を指定して Time#strptime する

Last updated at Posted at 2015-07-24

問題: ruby の Time#strptime を使って、文字列を Time オブジェクトに変換したいのだが、その際に timezone を指定したい。が、strptime には timezone 引数がない。

require 'time'
Time.strptime('2015-01-01', '%Y-%m-%d') #=> 2015-01-01 00:00:00 +0900

# おれが欲しいのは 2015-01-01 00:00:00 -0800 なんだーー!!

ActiveSupport は使わない。スレッドセーフじゃなくなるので ENV['TZ'] も使わない。

数値形式の場合

[+-]HH:MM, [+-]HHMM, [+-]HH のような数値形式で指定する場合

strptime の %z を利用できる。

timezone = "-08:00"

date = "2015-01-01"
format = "%Y-%m-%d"

time = Time.strptime("#{date} #{timezone}", "#{format} %z") #=> 2015-01-01 00:00:00 -0800

Region/Zone 形式の場合

tzinfo gem を使わざるを得ない

timezone = 'America/Los_Angeles'

date = "2015-01-01"
format = "%Y-%m-%d"

require 'tzinfo'
time = Time.strptime(date, format) #=> 2015-01-01 00:00:00 +0900
utc_offset = time.utc_offset #=> 32400
tz = TZInfo::Timezone.get(timezone)
zone_offset = tz.period_for_utc(time).utc_total_offset #=> -28800
time.localtime(zone_offset) + utc_offset - zone_offset #=> 2015-01-01 00:00:00 -0800

tzinfo はJST, PST のような略記には非対応。
tzinfo が利用している IANA Time Zone Databaseにそのような情報がないため。

Timezone Abbreviations

JST, PST のような略記には strptime が一部対応している。ただし、https://bugs.ruby-lang.org/issues/12190 に起票した通り、Time.strptime よりも DateTime.strptime のほうが対応しているものが多かったりする。

しかし、例えば CST とかいた時に Central Standard Time (USA) であるべきなのか、China Standard Time であるべきなのかは意見のわかれるところで、Rubyとしてはあまり頑張るべきではない、という結論に至っているようだ、

なので、今回は UTC ぐらいはサポートするとして、その他はサポートしない方向。

まとめるとこう

require 'time'
require 'tzinfo'

# [+-]HH:MM, [+-]HHMM, [+-]HH
NUMERIC_PATTERN = %r{\A[+-]\d\d(:?\d\d)?\z}

# Region/Zone, Region/Zone/Zone
NAME_PATTERN = %r{\A[^/]+/[^/]+(/[^/]+)?\z}

def strptime_with_zone(date, format, timezone)
  time = Time.strptime(date, format)
  _utc_offset = time.utc_offset
  _zone_offset = zone_offset(timezone)
  time.localtime(_zone_offset) + _utc_offset - _zone_offset
end

def zone_offset(timezone)
  if NUMERIC_PATTERN === timezone
    Time.zone_offset(timezone)
  elsif NAME_PATTERN === timezone
    tz = TZInfo::Timezone.get(timezone)
    tz.current_period.utc_total_offset
  elsif "UTC" == timezone # special treatment
    0
  else
    raise ArgumentError, "timezone format is invalid: #{timezone}"
  end
end
puts strptime_with_zone("2015-01-01", "%Y-%m-%d", "Asia/Taipei")
#=> 2015-01-01 00:00:00 +0800

=> gem にしました https://github.com/sonots/time_with_zone

15
12
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
12