简体   繁体   中英

How can I validate and parse an ISO 8601 timestamp with user's time zone?

I need to be able to receive a user-input timestamp, with an optional time zone component, validate that is it a valid ISO 8601 time representation, and parse it according to the user's configured time zone .

I'm using Rails 4.2.6 on Ruby 2.3. I had hoped that Time.zone ( ActiveSupport::TimeZone ) would have an equivalent implementation to Time::iso8601 so that I could rescue ArgumentError exceptions to determine if the user input was a valid ISO 8601 time representation. Then I could do something like:

user_time_zone = 'America/Los_Angeles' # Would actually be from user's stored settings
params = {when: '2016-04-01T01:01:01'} # Would actually be from user input

# Would actually use Time::use_zone in around_action filter
Time.use_zone(user_time_zone) do
  timestamp = Time.zone.iso8601 params[:when]
end

But, alas, no such method exists. And I can't find an equivalent one.

I can't use Time.zone.parse , because it treats ambiguous dates as valid (eg Time.zone.parse '04/11/16' # => Tue, 16 Nov 0004 00:00:00 LMT -07:52 ).

The best alternative I've been able to come up with so far is:

Time.use_zone(user_time_zone) do
  old_tz = ENV['TZ']
  ENV['TZ'] = Time.zone.name
  timestamp = Time.iso8601 params[:when] # => 2016-04-01 01:01:01 -0700
  ENV['TZ'] = old_tz
end

But this is ugly, messing around with an environment variable this way doesn't feel proper, and it and certainly isn't Rails-like. How can I validate and parse the time according to the user's time zone in a Rails way?

I suggest that you simply split the assignment into two steps: validate the ISO8601 format first and if valid, parse it:

user_time_zone = 'America/Los_Angeles'
params = { when: '2016-04-01T01:01:01' }

begin
  Time.iso8601(params[:when])      # raises ArgumentError if format invalid
rescue ArgumentError => e
  puts "invalid time format" 
  return
end

Time.use_zone(user_time_zone) do
  timestamp = Time.zone.parse(params[:when])
end

I think you can still use an around_action for your use case. That's what I use and it works well for me.

In my ApplicationController I have:

class ApplicationController < ActionController::Base  
  around_action :set_time_zone

  def set_time_zone
    old_time_zone = Time.zone
    Time.zone = user_time_zone
    yield
  ensure
    Time.zone = old_time_zone
  end
end

Any calls to Time will use the user's time zone within the scope of the request.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM