問題: 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