Time Zones in Rails
Hi! I just added the Intended Use document in Formwork. I noticed that the time stamp is wrong (15:07 instead of 17:07). Can this be fixed? Important, because work was done out of business hours.
Hm. Weird. That difference of exactly two hours is screaming “time zone” to me. But how come is this happening? Our client is based in Germany, just like us. And our code explicitly sets the application time zone to “Europe/Berlin”. I don’t get it. Maybe he signed the document from another time zone? Or maybe our code is just not working as expected. This requires research.
I guess it’s time to learn about time zones in Rails.
Time zones in Rails
-
System time. It uses the time zone set in your server. It’s the time you get when you call Time.now and it is represented by an
Time
object. -
Application time. It uses the time zone set by the Rails application in config.time_zone. It’s the time you get when you call
Time.zone.now
,Time.current
or any expression like2.hours.from_now
and it is represented by anActiveSupport::TimeWithZone
object. - Database time. It uses the time zone set in the database, which should always be UTC. It’s the time you get when you run a SQL query.
- (Bonus) Browser time. It uses the time zone set in the browser. It’s the time rendered by
local_time
.
Time
, DateTime
and Date
. Understandably, they all use system time. System time is OK but it’s a bit cumbersome if you suddenly want to work in a different time zone, as you’d need to change the TZ
environment variable of your machine.That’s why Rails introduced its own version of Time:
ActiveSupport::TimeWithZone
. It provides the same API than Time’s, making Time
and TimeWithZone
instances interchangeable. Well, kind of interchangeable. I found some aspects where they differ, and it’s a bit annoying. More about this later.But the good thing about TimeWithZone is that you can easily change from one time zone to another by setting
Time.zone
to your favorite time zone identifier, like “America/New_York” or “Europe/Berlin”.Rails uses TimeWithZone objects all across its layers. When you run
User.first.created_at
in your console, you get a TimeWithZone object, with the time offset corresponding to the time zone your set in your application. Indeed, this is what we call the application time. You can easily define the application time by tweaking config.time_zone
in the config/application.rb
file.Though when Rails stores those times in the database, they are first transformed to UTC, the database time. Database time is UTC by default and should be left that way.
From system time to database time
Time.now
uses the system time and returns a Time object.> Time.now
=> 2023-07-10 06:19:05.0988883 +0000
> Time.now.class
=> Time
$ export TZ="America/New_York"
> Time.now
=> 2023-07-10 02:19:23.870709341 -0400
Time.zone.now
, getting a TimeWithZone object.> Time.zone.now
=> Mon, 10 Jul 2023 09:19:06.363576224 CEST +02:00
> Time.zone.now.class
=> ActiveSupport::TimeWithZone
> Time.zone
=> #<ActiveSupport::TimeZone:0x00007f95b03110f8
@name="Europe/Berlin",
@tzinfo=#<TZInfo::DataTimezone: Europe/Berlin>,
@utc_offset=nil>
Time.now
, always use Time.zone.now
.> Time.zone.now.class
=> ActiveSupport::TimeWithZone
> Time.current.class
=> ActiveSupport::TimeWithZone
> 2.hours.from_now.class
=> ActiveSupport::TimeWithZone
> User.firt.created_at.class
=> ActiveSupport::TimeWithZone
> ale = ActiveRecord::Base.connection.execute("
SELECT * FROM users
WHERE email = '[email protected]'
").first
> database_time = ale["created_at"].to_s
=> "2023-01-16 22:09:13 UTC"
Formats: where the pain begins
Time.zone.parse
.application_time = Time.zone.parse(database_time)
=> Mon, 16 Jan 2023 23:09:13.000000000 CET +01:00
We can see that if we retrieve the same value with Rails via ActiveRecord queries, the result is the same.
> User.find_by(email: "[email protected]").created_at
=> Mon, 16 Jan 2023 23:09:13.000000000 CET +01:00
Time.parse
and then call the getlocal
method.> system_time = Time.parse(database_time).getlocal
=> 2023-01-16 17:09:13 -0500
Time.parse
maintains the same format. But TimeZone.parse
did not! Hm, I don’t like that too much. It’s kind of inconsistent.Don’t panic yet, there’s still a handy way of reverting the format of our application time back to the database format: using
TimeWithZone#to_formatted_s(:db)
.> application_time.to_formatted_s(:db)
=> "2023-01-16 22:09:13"
OK, I sort of can understand the rationale behind it. The intention of the method is to mimic the time format in the database, which one could argue includes using the UTC timezone. Though I would say it should not be that way.
The main problem is that again this is not consistent with how Time uses
to_formatted_s(:db)
. For Time objects, the same method does not bring the time zone back to UTC. It stays loyal to the system’s time zone.> system_time.to_formatted_s(:db)
=> "2023-01-16 17:09:13"
Hi! I just added the Intended Use document in Formwork. I noticed that the time stamp is wrong (15:07 instead of 17:07). Can this be fixed? Important, because work was done out of business hours.
we can now see that
TimeWithZone#to_formatted_s(:db)
is the one to blame here. We are displaying times in UTC without informing the user. This is not what we want: the user should see the time according to his time zone. Or otherwise be informed that the time is UTC.More users, different time zones
So our first decision here is to start using
local_time
to display times in Formwork. local_time
reads the browser’s time zone and updates the HTML directly on the client side using JavaScript, setting all times tagged with data-local="time"
to the user’s time zone.What about exports? Should we show times in the user’s time zone? At the end, we don’t know the time zone of the person who will be reading the export. So our second decision is to show UTC times in exports while explicitly stating that they are UTC times.
This means times will be either on the user’s time zone (browser time) or they will be UTC (database time). Application time is never considered.
So… what’s the purpose of application time?
My understanding is that the main purpose of the application time is to set a default display time zone. This is really handy if
- all your users are in the same time zone because your display is correct without using JS.
- all your developers are in the same time zone because the console speaks our same time language.
Also, our preferred format in Formwork is
:db
. And as we saw, that format reverts TimeWithZone objects (application time) back to UTC time. The application time is actually not displayed anywhere!Bye, bye, Miss Application Time
config.time_zone
to “Europe/Berlin” as we have clients outside that time zone. Only sometimes we will display times in the browser time zone, which will be handled by local_time
. But never in the application time zone.So bye bye,
config.time_zone = "Europe/Berlin"
.Bye bye, application time.
On a different note: Do you need any help with your EU MDR efforts?
We've worked with 100+ companies and helped them certify their devices in weeks, not months. Talk to us now – first calls are free! Check out our services and prices here.
Or, if you don't like talking to humans, check out our Wizard. It's a foolproof, step-by-step video course for getting your compliance done yourself.
And if you're looking for the best QMS software for lean, founder-led companies, check out Formwork. It automates your compliance, and there's even a free version for you to try out!

Documenting Your Medical Device In Formwork

Formwork eQMS Pricing, And When You Should Upgrade


The EUDAMED Files: €9M/Year Budget and a €317K Hosting Mystery
Congratulations! You read this far.
Get notified when we post something new. Sign up for our free newsletter.
No spam, only regulatory rants. Unsubscribe anytime.
0 comments
No comments yet. Be the first one to share your thoughts!

Ale Solano
When I first discovered OpenRegulatory, I fell in love. They hit exactly in the frustrations we had suffered as a healthtech startup. I joined the team to help making compliance easier for everyone and bring more hackers into this industry.