ActiveSupport::TimeZone
class is ActiveSupport::TimeZone
for working with time zones. It is supplied as part of the ActiveSupport
library from the standard Ruby on Rails ActiveSupport
. It is a wrapper around the tzinfo gem , which, in turn, provides a ruby ​​interface to tzdata . It provides methods for working with time, and is also actively used in the ActiveSupport extended Time class from the standard Ruby library to fully work with time zones. Well, in the class ActiveSupport::TimeWithZone
from Ruby on Rails, which is stored in itself not only the time with the offset, but also the time zone itself. Many methods at the time zone return ActiveSupport::TimeWithZone
, but in most cases you will not even feel it. What is the difference between these two classes is written in the documentation , and this difference is useful to know.ActiveSupport::TimeZone
it can be noted that it uses its own “human-readable” identifiers for time zones, which sometimes causes inconvenience, and also that these identifiers are not for all time zones available in tzdata, but also it's fixable.config/application.rb
file after creating a new application: config.time_zone = 'Moscow'
zone
method of the Time
class.Moscow
instead of Europe/Moscow
, but if you look at the output of the inspect
method for a time zone object, we will see that inside there is a mapping to the tzdata identifier: > Time.zone => #<ActiveSupport::TimeZone:0x007f95aaf01aa8 @name="Moscow", @tzinfo=#<TZInfo::TimezoneProxy: Europe/Moscow>>
ActiveSupport::TimeWithZone
):now
method, which returns the current time in this time zone. Time.zone.now # => Sun, 16 Aug 2015 22:47:28 MSK +03:00
parse
method, which, like the parse
method of the Time
class, parses a string with time into an object of the Time
class, but at the same time immediately translates it into the time zone of this object. If the line does not indicate an offset from UTC, then at the same time this method decides that the line indicates the local time of this time zone. ActiveSupport::TimeZone['Novosibirsk'].parse('2015-06-19T12:13:14') # => Fri, 19 Jun 2015 12:13:14 NOVT +06:00
at
method converts the Unix timestamp (the number of seconds from January 1, 1970), which, as you know, is always in UTC, into an object of type Time
in this time zone. Time.zone.at(1234567890) #=> Sat, 14 Feb 2009 02:31:30 MSK +03:00
local
method, which allows you to programmatically construct the time in the right time zone from individual components (year, month, day, hour, and so on). ActiveSupport::TimeZone['Yakutsk'].local(2015, 6, 19, 12, 13, 14) # => Fri, 19 Jun 2015 12:13:14 YAKT +09:00
ActiveSupport::TimeZone
class is also actively used in operations with objects of the Time
class and adds several useful methods to it, for example:Time.zone
returns an object of class ActiveSupport::TimeZone
, representing the time zone, which is currently valid for the entire application (and can be changed).Time.zone_default
will return the time zone that you specified in the config/application.rb
file.with_zone
method allows with_zone
to temporarily change the current time zone for all code running in the block passed to it.Time#in_time_zone
object Time#in_time_zone
allows Time#in_time_zone
to change the time zone of an existing object (returns an object of type ActiveSupport::TimeWithZone
): Time.parse('2015-06-19T12:50:00').in_time_zone('Asia/Tokyo') # => Fri, 19 Jun 2015 18:50:00 JST +09:00
Time.current
along with Date.current
and Time.now
along with Date.today
. The difference between them is that the first (those that are current
) return the time or date in the application's time zone as an object of type ActiveSupport::TimeWithZone
, in the same zone that currently returns the Time.zone
method and adds these Ruby methods on Rails, and the latter return time in the time zone, attention, server operating systems, and go to the standard Ruby library (they return, respectively, just Time
). Be careful - there are strange bugs that are not reproducible locally, so always use Time.current
and Date.current
. # app/controllers/application_controller.rb class ApplicationController < ActionController::Base around_action :with_time_zone, if: 'current_user.try(:time_zone)' protected def with_time_zone(&block) time_zone = current_user.time_zone logger.debug " : #{time_zone}" Time.use_zone(time_zone, &block) end end
User
model with a certain time_zone
method that returns an ActiveSupport::TimeZone
object with the user's time zone.nil
, then using the around_action around_action
we call the class method Time.use_zone
and continue processing the request in the block passed to it. Thus, all times in all views will be automatically displayed in the user's time zone. Voila!tzdata
identifier, and to convert it to an object, this method is used in the file app/models/user.rb
: # +ActiveSupport::TimeZone+ # , TZ database. def time_zone unless @time_zone tz_id = read_attribute(:time_zone) as_name = ActiveSupport::TimeZone::MAPPING.select do |_,v| v == tz_id end.sort_by do |k,v| v.ends_with?(k) ? 0 : 1 end.first.try(:first) value = as_name || tz_id @time_zone = value && ActiveSupport::TimeZone[value] end @time_zone end
Europe/Moscow
type tzdata identifier stored in the database into an ActiveSupport::TimeZone
object, the identifier of which is simply Moscow
. The reason that I store the tzdata
time zone id
in the database, and not the rail one, lies in the interoperability - everyone understands the id
from tzdata
, and only Ruby on Rails understands the id
time zone rails. # TZ Database, # — +ActiveSupport::TimeZone+ def time_zone=(value) tz_id = value.respond_to?(:tzinfo) && value.tzinfo.name || nil tz_id ||= TZInfo.Timezone.get(ActiveSupport::TimeZone::MAPPING[value.to_s] || value.to_s).identifier rescue nil # — @time_zone = tz_id && ActiveSupport::TimeZone[ActiveSupport::TimeZone::MAPPING.key(tz_id) || tz_id] write_attribute(:time_zone, tz_id) end
tzdata
identifier to the database is the PostgreSQL we use works well with time zones. Having the tzdata
identifier in the database, you can quite conveniently look at the local time in the user's time zone and debug various problems with time zones using queries like: SELECT '2015-06-19T12:13:14Z' AT TIME ZONE 'Europe/Moscow';
News.where('published_at >= ? AND published_at <= ?', Date.today, Date.tomorrow)
News.where('published_at >= ? AND published_at < ?', Time.current.beginning_of_day, Time.current.beginning_of_day + 1.day) # => News Load (0.8ms) SELECT "news".* FROM "news" WHERE (published_at >= '2015-08-16 21:00:00.000000' AND published_at < '2015-08-17 21:00:00.000000') ORDER BY "news"."published_at" DESC
Time.parse('Mon May 18 2015 22:16:38 GMT+0600 (NOVT)') # => 2015-11-01 22:16:38 +0600
2015-05-18T22:16:38+06:00
.toISOString()
. And, for example, Angular.js serializes time in ISO 8601 by default (and does it right!).parse
it with the appropriate method of the Time
class, and leave the parse
method for backward compatibility. Like this: Time.iso8601(params[:till]) rescue Time.parse(params[:till])
params[:till]
is passed to time without offset from UTC, both methods (and iso8601
and parse
) will parse
it as if it were local time in the server time zone, and not applications. Do you know what time zone your server is in? I have in different. A more bulletproof time parsing method will look like this (unfortunately ActiveSupport::TimeZone
does not have the iso8601
method, which is a pity): Time.strptime(params[:till], "%Y-%m-%dT%H:%M:%S%z").in_time_zone rescue Time.zone.parse(params[:till])
Time.parse
will simply return an earlier point in time to you, and Time.zone.parse
throw a TZInfo::AmbiguousTime
exception. Time.zone.parse("2014-10-26T01:00:00") # TZInfo::AmbiguousTime: 2014-10-26 01:00:00 is an ambiguous local time. Time.zone.parse("2014-10-26T01:00:00+04:00") # => Sun, 26 Oct 2014 01:00:00 MSK +04:00 Time.zone.parse("2014-10-26T01:00:00+03:00") # => Sun, 26 Oct 2014 01:00:00 MSK +03:00 Time.zone.parse("2014-10-26T01:00:00+04:00").utc # => 2014-10-25 21:00:00 UTC Time.zone.parse("2014-10-26T01:00:00+03:00").utc # => 2014-10-25 22:00:00 UTC
timezone_select
display Russian time zones first or even the only ones. In the future, it will be possible to do without this - I sent the Pull Request to Ruby on Rails, but so far, unfortunately, it hangs without activity: https://github.com/rails/rails/pull/20625 # config/initializers/timezones.rb class ActiveSupport::TimeZone @country_zones = ThreadSafe::Cache.new def self.country_zones(country_code) code = country_code.to_s.upcase @country_zones[code] ||= TZInfo::Country.get(code).zone_identifiers.select do |tz_id| MAPPING.key(tz_id) end.map do |tz_id| self[MAPPING.key(tz_id)] end end end # - app/views = f.input :time_zone, priority: ActiveSupport::TimeZone.country_zones(:ru)
# config/initializers/timezones.rb ActiveSupport::TimeZone::MAPPING['Simferopol'] = 'Europe/Simferopol' ActiveSupport::TimeZone::MAPPING['Omsk'] = 'Asia/Omsk' ActiveSupport::TimeZone::MAPPING['Novokuznetsk'] = 'Asia/Novokuznetsk' ActiveSupport::TimeZone::MAPPING['Chita'] = 'Asia/Chita' ActiveSupport::TimeZone::MAPPING['Khandyga'] = 'Asia/Khandyga' ActiveSupport::TimeZone::MAPPING['Sakhalin'] = 'Asia/Sakhalin' ActiveSupport::TimeZone::MAPPING['Ust-Nera'] = 'Asia/Ust-Nera' ActiveSupport::TimeZone::MAPPING['Anadyr'] = 'Asia/Anadyr'
# config/locales/ru.yml ru: timezones: Simferopol: Omsk: Novokuznetsk: Chita: Khandyga: Sakhalin: Ust-Nera: - Anadyr:
tzdata
directly to the browser to the user (yes, users will again have to download extra kilobytes). But, nevertheless, with it you can do everything. Or almost everything.parseZone
method of the Moment itself: moment.parseZone(ISO8601Timestamp)
moment.tz(timestamp, formatString, timezoneIdentifier)
new Date()
!), « » .tzdata
(, tzdata
).Source: https://habr.com/ru/post/266681/
All Articles