关于Ruby的时间29 Jan 2015
- UTC & GMT
- Timezone gem?
ActiveSupport::TimeZone The TimeZone class serves as a wrapper around TZInfo::Timezone instances.
- Limit the set of zones provided by TZInfo to a meaningful subset of 146 zones
- Retrieve and display zones with a friendlier name
- Lazily load TZInfo::Timezone instances only when they’re needed.
- Create ActiveSupport::TimeWithZone instances via TimeZone’s local, parse, at and now methods.
set config.time_zone in the Rails Application, you can access this TimeZone object via Time.zone:
class Application < Rails::Application config.time_zone = ‘Eastern Time (US & Canada)’ end
Time.zone # => #
You shouldn’t ever need to create a TimeWithZone instance directly via new. Instead use methods local, parse, at and now on TimeZone instances, and in_time_zone on Time and DateTime instances.
Time.zone = ‘Hawaii’ # => ‘Hawaii’ DateTime.utc(2000).in_time_zone # => Fri, 31 Dec 1999 14:00:00 HST -10:00 Date.new(2000).in_time_zone # => Sat, 01 Jan 2000 00:00:00 HST -10:00
Time.utc(2000).in_time_zone(‘Alaska’) # => Fri, 31 Dec 1999 15:00:00 AKST -09:00 DateTime.utc(2000).in_time_zone(‘Alaska’) # => Fri, 31 Dec 1999 15:00:00 AKST -09:00 Date.new(2000).in_time_zone(‘Alaska’) # => Sat, 01 Jan 2000 00:00:00 AKST -09:00
see all the available time zones, run:
The default time zone in Rails is UTC.
Apply user’s preferred time zone:
# app/controllers/application_controller.rb around_action :set_time_zone, if: :current_user private def set_time_zone(&block) Time.use_zone(current_user.time_zone, &block) end
display times in a specific user’s time zone, we can use Time’s
Working with APIs
When working with APIs, it is best to use the ISO8601 standard, which represents date/time information as a string. ISO8601’s advantages are that the string is unambiguous, human readable, widely supported, and sortable.
> timestamp = Time.now.utc.iso8601 => "2015-07-04T21:53:23Z"
The Z at the end of the string indicates that this time is in UTC, not a local time zone. To convert the string back to a Time instance, we can say:
> Time.iso8601(timestamp) => 2015-07-04 21:53:23 UTC
# This is the time on my machine, also commonly described as "system time" > Time.now => 2015-07-04 17:53:23 -0400 # Let's set the time zone to be Fiji > Time.zone = "Fiji" => "Fiji" # But we still get my system time > Time.now => 2015-07-04 17:53:37 -0400 # However, if we use `zone` first, we finally get the current time in Fiji > Time.zone.now => Sun, 05 Jul 2015 09:53:42 FJT +12:00 # We can also use `current` to get the same > Time.current => Sun, 05 Jul 2015 09:54:17 FJT +12:00 # Or even translate the system time to application time with `in_time_zone` > Time.now.in_time_zone => Sun, 05 Jul 2015 09:56:57 FJT +12:00 # Let's do the same with Date (we are still in Fiji time, remember?) # This again is the date on my machine, system date > Date.today => Sat, 04 Jul 2015 # But going through `zone` again, and we are back to application time > Time.zone.today => Sun, 05 Jul 2015 # And gives us the correct tomorrow according to our application's time zone > Time.zone.tomorrow => Mon, 06 Jul 2015 # Going through Rails' helpers, we get the correct tomorrow as well > 1.day.from_now => Mon, 06 Jul 2015 10:00:56 FJT +12:00
Time zone related querying
Rails saves timestamps to the database in UTC time zone. We should always use Time.current for any database queries, so that Rails will translate and compare the correct times.
Post.where("published_at > ?", Time.current) # SELECT "posts".* FROM "posts" WHERE (published_at > '2015-07-04 17:45:01.452465')
A summary of do’s and don’ts with time zones
* Time.now * Date.today * Date.today.to_time * Time.parse("2015-07-04 17:05:37") * Time.strptime(string, "%Y-%m-%dT%H:%M:%S%z")
* Time.current * 2.hours.ago * Time.zone.today * Date.current * 1.day.from_now * Time.zone.parse("2015-07-04 17:05:37") * Time.strptime(string, "%Y-%m-%dT%H:%M:%S%z").in_time_zone
Testing time zones
Rails 4.1 added a ActiveSupport::Testing::TimeHelpers module, with three useful methods: travel, travel_back, and travel_to. We can use these methods to freeze time within blocks in our tests.
If using an older version of Rails, there are three gems to help us set and freeze the time in our tests: Timecop, Delorean, and Zonebie. As much as I love the reference to go back_to_the_present, I usually use Timecop.
new_time = Time.zone.parse("2014-10-19 1:00:00") # With Timecop, we can freeze the time, Timecop.freeze new_time # or Timecop.travel new_time # but will need to clean up after the spec, and return to current time Timecop.return # Alternatively we can use blocks, which only freeze the time inside our block Time.use_zone("Sydney") do end # With Delorean, the syntax is a touch different Delorean.time_travel_to("1 month ago") do end Delorean.back_to_the_present # And Zonebie sets the time zone to a random one each time we run our tests Zonebie.set_random_timezone
- Always work with UTC.
- Use Time.current or Time.zone.today.
- Use testing helper methods of your choice to freeze the time in your tests, preferably by using a block.