Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Validating a time zone is valid in rails

I am using tsjzt: http://pellepim.bitbucket.org/jstz/ on the client side to grab the current users time zone which I store in my user object.

This works nicely and gives me timezones like "Europe/London". I want to validate when this is passed into the model that it is a valid timezone incase something bad happens.

So i found this question: Issue validating user time zone for Rails app on Heroku and tried out this validation:

validates_inclusion_of :timezone, :in => { in: ActiveSupport::TimeZone.zones_map(&:name) }

However the name is different to the tzinfo. I think my client side detected timezone string "Europe/London" is essentially the value component of the TimeZone mapping in the TimeZone class rather than the name - which in this case would be set to "London".

So I tried this :

validates_inclusion_of :timezone, :in => { in: ActiveSupport::TimeZone.zones_map(&:tzinfo) }

Neither the original answer on the other SO question or my altered one with :tzinfo is working as they both fail validation when :timezone is "Europe/London" when obviously that is a valid timezone!

What am I doing wrong with this timezone validation and how can I fix it?

like image 808
RenegadeAndy Avatar asked Aug 03 '15 16:08

RenegadeAndy


3 Answers

Looks like you want this:

validates_inclusion_of :timezone,
                       :in => ActiveSupport::TimeZone.all.map { |tz| tz.tzinfo.name }

On my machine, that list includes the following names:

...
"Europe/Istanbul",
"Europe/Kaliningrad",
"Europe/Kiev",
"Europe/Lisbon",
"Europe/Ljubljana",
"Europe/London",
...

However, a cleaner solution would be a custom validation method, like this:

validates_presence_of :timezone
validate :timezone_exists

private

def timezone_exists
  return if timezone? && ActiveSupport::TimeZone[timezone].present?
  errors.add(:timezone, "does not exist")
end

This is more flexible in the values it accepts:

ActiveSupport::TimeZone["London"].present? # => true
ActiveSupport::TimeZone["Europe/London"].present? # => true
ActiveSupport::TimeZone["Pluto"].present? # => false
like image 120
Matt Brictson Avatar answered Oct 21 '22 04:10

Matt Brictson


A more lightweight and performant solution would be using TZInfo::Timezone.all_identifiers instead of manipulating the list from ActiveSupport::TimeZone.all.

validates :timezone, presence: true, inclusion: { in: TZInfo::Timezone.all_identifiers }
like image 6
Weihang Jian Avatar answered Oct 21 '22 05:10

Weihang Jian


All of the other answers are good, especially Matt Brictson's. This one works when you have someone pass something like 'Paris' to the timezone, which can be used by rails, but isn't in the list (ActiveSupport::TimeZone) that are being checked. This validation checks if it is valid for rails, and allows for 'Paris' to be valid:

  validates_each :timezone do |record, attr, value|
    !!DateTime.new(2000).in_time_zone(value)
  rescue
    record.errors.add(attr, 'was not valid, please select one from the dropdown')
  end
like image 1
sec0ndHand Avatar answered Oct 21 '22 05:10

sec0ndHand