
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