Reputation: 5690
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?
Upvotes: 22
Views: 6865
Reputation: 429
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
Upvotes: 1
Reputation: 8715
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 }
Upvotes: 7
Reputation: 11082
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
Upvotes: 36