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.
display times in a specific user’s time zone, we can use Time’s in_time_zone method:
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.
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')
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
In summary
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.