|
| 1 | +--- |
| 2 | +created_at: 2026-01-14 15:08:58 +0100 |
| 3 | +author: Szymon Fiedler |
| 4 | +tags: [ruby, rails, time, datetime, legacy] |
| 5 | +publish: false |
| 6 | +--- |
| 7 | + |
| 8 | +# Stop using DateTime in 2026 (unless you work for UNESCO) |
| 9 | + |
| 10 | +`DateTime` has been deprecated in Ruby since 3.0. It's 2026. Why are people still using it? |
| 11 | + |
| 12 | +<!-- more --> |
| 13 | + |
| 14 | +During a recent code review, we found this: |
| 15 | + |
| 16 | +```ruby |
| 17 | +whatever.starts_at = DateTime.now |
| 18 | +``` |
| 19 | + |
| 20 | +When asked why `DateTime` instead of `Time`, the response was: "`DateTime` handles a wider range of dates." |
| 21 | + |
| 22 | +That was partially true. In 2008. On 32-bit systems. |
| 23 | + |
| 24 | +## DateTime's range advantage died in Ruby 1.9.2 |
| 25 | + |
| 26 | +Before Ruby 1.9.2 (released in 2010), Time was limited by the system's `time_t` type — typically 32-bit signed integer covering 1901-2038. `DateTime` had a much wider range. |
| 27 | + |
| 28 | +[Ruby 1.9.2 changed this](https://ruby-doc.org/core-2.1.9/Time.html). Time started using a signed 63-bit integer representing nanoseconds since epoch, giving it a range of 1823-2116. For dates outside this range, `Time` uses `Bignum` or `Rational` — slower, but it works. |
| 29 | + |
| 30 | +The practical range advantage is gone. |
| 31 | + |
| 32 | +## Remember Rails 4.2? |
| 33 | + |
| 34 | +[_Pepperidge Farm Remembers_](https://knowyourmeme.com/memes/pepperidge-farm-remembers). |
| 35 | +Some time ago when upgrading Rails app from 4.2 to 5.0, the test suite fortunately failed. The culprit was surprising: `DateTime#utc` changed its return type. |
| 36 | + |
| 37 | +[Rails 4.2](https://api.rubyonrails.org/v4.2/classes/DateTime.html#method-i-utc): |
| 38 | + |
| 39 | +```ruby |
| 40 | +DateTime.now.utc.class |
| 41 | +# => DateTime |
| 42 | +``` |
| 43 | + |
| 44 | +[Rails 5.0](https://api.rubyonrails.org/v5.0/classes/DateTime.html#method-i-utc): |
| 45 | + |
| 46 | +```ruby |
| 47 | +DateTime.now.utc.class # => Time |
| 48 | +``` |
| 49 | + |
| 50 | +This broke several `Dry::Struct` objects with strict type definitions expecting `DateTime`. But instead of "fixing" the types, we asked a better question: *why were we using `DateTime` at all?* |
| 51 | + |
| 52 | +Rails 5's breaking change to `DateTime#utc` wasn't a bug — it was a nudge. It was telling you: stop using this class. |
| 53 | + |
| 54 | +[Struggling with upgrades? We have a solution for you](https://arkency.com/ruby-on-rails-upgrades/). |
| 55 | + |
| 56 | +## The UNESCO problem |
| 57 | + |
| 58 | +There's actually **one** legitimate use case for `DateTime`: historical calendar reforms. |
| 59 | + |
| 60 | +From [Ruby's own documentation](https://ruby-doc.org/stdlib-2.4.1/libdoc/date/rdoc/DateTime.html): |
| 61 | + |
| 62 | +> It's a common misconception that William Shakespeare and Miguel de Cervantes died on the same day in history - so much so that UNESCO named April 23 as World Book Day because of this fact. However, because England hadn't yet adopted the Gregorian Calendar Reform (and wouldn't until 1752) their deaths are actually 10 days apart. |
| 63 | +
|
| 64 | +Ruby's `Time` uses a proleptic Gregorian calendar — it projects the Gregorian calendar backwards, ignoring historical reality. October 10, 1582 doesn't exist in Italy (Pope Gregory XIII removed 10 days that October), but Ruby happily creates that timestamp. |
| 65 | + |
| 66 | +`DateTime` can handle different calendar reform dates: |
| 67 | + |
| 68 | +```ruby |
| 69 | +shakespeare = DateTime.iso8601('1616-04-23', Date::ENGLAND) |
| 70 | +cervantes = DateTime.iso8601('1616-04-23', Date::ITALY) |
| 71 | + |
| 72 | +(shakespeare - cervantes).to_i # => 10 days apart |
| 73 | +``` |
| 74 | + |
| 75 | +For cataloging historical artifacts or dealing with pre-1752 dates across different countries, `DateTime` is your tool. |
| 76 | + |
| 77 | +For literally everything else — which is 99.99% of applications — it's the wrong choice. |
| 78 | + |
| 79 | +Norbert Wójtowicz gave an excellent [talk about calendars at wroclove.rb](https://www.youtube.com/watch?v=YiLlnsq2fJ4) covering exactly these issues. |
| 80 | + |
| 81 | +## `DateTime`’s actual problems |
| 82 | + |
| 83 | +**No timezone support**. DateTime doesn't handle timezones. |
| 84 | + |
| 85 | +```ruby |
| 86 | +DateTime.now |
| 87 | +# => #<DateTime: 2026-01-14T13:00:00+00:00> |
| 88 | +# Why +00:00 when my system is CET (+01:00)? |
| 89 | +``` |
| 90 | + |
| 91 | +**Incompatible with Rails**. `ActiveSupport` extends `Time` with timezone support. `DateTime`? Barely. |
| 92 | + |
| 93 | +```ruby |
| 94 | +Time.current # ✅ Respects Rails.application.config.time_zone |
| 95 | +DateTime.now # ❌ Uses system timezone, ignores Rails config |
| 96 | +``` |
| 97 | + |
| 98 | +**Confusing arithmetic**: |
| 99 | + |
| 100 | +```ruby |
| 101 | +Time.now + 1 # => 1 second later |
| 102 | +DateTime.now + 1 # => 1 day later |
| 103 | +``` |
| 104 | + |
| 105 | +This has caused bugs. Many bugs. |
| 106 | + |
| 107 | +**Ignores DST**. `DateTime` doesn't track daylight saving time. We covered this in [our previous post about timezone bugs](#), but the summary: if you use DateTime for anything involving timezones, you will have bugs. |
| 108 | + |
| 109 | +**Performance**. `Time` is faster. Noticeably. |
| 110 | + |
| 111 | +## Why people still use it |
| 112 | + |
| 113 | +**"We've always used it"** |
| 114 | + |
| 115 | +The code was written in 2009. It's 2026. Update it. |
| 116 | + |
| 117 | +**"I need to store dates without time"** |
| 118 | + |
| 119 | +Use `Date`. That's what it's for. |
| 120 | + |
| 121 | +```ruby |
| 122 | +Date.current # ✅ Rails.application.config.time_zone aware |
| 123 | +``` |
| 124 | + |
| 125 | +**"The library I'm using returns DateTime"** |
| 126 | + |
| 127 | +Convert immediately: |
| 128 | + |
| 129 | +```ruby |
| 130 | +legacy_gem.fetch_date.to_time.in_time_zone |
| 131 | +``` |
| 132 | + |
| 133 | +## What to use instead |
| 134 | + |
| 135 | +For timestamps: `Time.current` or `Time.zone.now` |
| 136 | +For dates: `Date.current` |
| 137 | +For parsing: `Time.zone.parse('2026-01-14 13:00:00')` |
| 138 | + |
| 139 | +## The only exception |
| 140 | + |
| 141 | +If you're cataloging historical documents, artifacts, or working with dates before calendar reforms in different countries, `DateTime` is your tool. You need to track which calendar reform date applies. |
| 142 | + |
| 143 | +For everything else — modern applications, APIs, databases — use `Time`. |
| 144 | + |
| 145 | +`DateTime` is deprecated for a reason. |
| 146 | + |
| 147 | +## References |
| 148 | + |
| 149 | +- [Ruby Time documentation - Ruby 1.9.2 changes](https://ruby-doc.org/core-2.1.9/Time.html) |
| 150 | +- [Ruby DateTime documentation - UNESCO calendar problem](https://ruby-doc.org/stdlib-2.4.1/libdoc/date/rdoc/DateTime.html) |
| 151 | +- [Norbert Wójtowicz - It's About Time (wroclove.rb)](https://www.youtube.com/watch?v=YiLlnsq2fJ4) |
| 152 | +- [Ruby Style Guide: No DateTime](https://rubystyle.guide/#no-datetime) |
0 commit comments