Ruby on Rails
ROR migration
Date to DateTime
database migration
ActiveRecord

Change a column type from Date to DateTime during ROR migration

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Changing a Rails column from date to datetime looks simple, but the safe approach depends on whether you care about existing data and time zone behavior. A direct schema change may work for small cases, while production systems often need an explicit backfill strategy.

The Simple Migration Path

If your database can cast the value safely and you are comfortable with existing dates becoming midnight timestamps, Rails can perform a direct type change.

ruby
1class ChangePublishedOnToPublishedAt < ActiveRecord::Migration[7.1]
2  def change
3    change_column :posts, :published_on, :datetime
4  end
5end

This is the shortest solution, but it leaves important behavior to the database adapter. Existing date values usually become datetimes at 00:00:00, and the exact interpretation may vary depending on database and time zone configuration.

For that reason, direct conversion is best when:

  • The table is small.
  • The application does not depend on a specific time of day for historical rows.
  • You have tested the cast in the same database engine used in production.

A Safer Two-Step Migration

For important data, create a new datetime column, backfill it explicitly, then swap names. This makes the transformation easier to reason about and easier to roll back.

ruby
1class MigratePublishedOnToPublishedAt < ActiveRecord::Migration[7.1]
2  def up
3    add_column :posts, :published_at, :datetime
4
5    execute <<~SQL
6      UPDATE posts
7      SET published_at = published_on
8      WHERE published_on IS NOT NULL
9    SQL
10
11    remove_column :posts, :published_on
12  end
13
14  def down
15    add_column :posts, :published_on, :date
16
17    execute <<~SQL
18      UPDATE posts
19      SET published_on = DATE(published_at)
20      WHERE published_at IS NOT NULL
21    SQL
22
23    remove_column :posts, :published_at
24  end
25end

This version is more verbose, but it gives you full control. You can also adjust the backfill to set a chosen hour rather than accepting midnight.

Handling Time Zones Explicitly

The biggest conceptual trap is that date has no time zone or time-of-day information. When you convert it to datetime, you are inventing both. If your app uses config.time_zone and Active Record time zone conversion, decide what the new timestamp should mean before migrating.

For example, you may want every historical date to become noon UTC or midnight in the application zone. That decision should be explicit in the migration logic, not an accidental side effect.

ruby
1class BackfillEventStartsAt < ActiveRecord::Migration[7.1]
2  def up
3    add_column :events, :starts_at, :datetime
4
5    Event.reset_column_information
6    Event.find_each do |event|
7      next unless event.start_date
8
9      event.update_columns(
10        starts_at: Time.zone.parse("#{event.start_date} 09:00:00")
11      )
12    end
13  end
14end

For large tables, avoid row-by-row Ruby updates because they are slow. Prefer SQL backfills when possible, or run the data migration separately from the schema migration.

Testing the Migration

Before running this in production, test three things:

  1. Existing data converts as expected.
  2. New application code reads and writes the new type correctly.
  3. Rollback behavior is acceptable if deployment must be reversed.

It is also wise to check indexes, validations, and forms. A column type change affects more than the schema. Date pickers, serializers, and API responses often need updates too.

Common Pitfalls

One common mistake is assuming change_column is fully reversible and behaves identically across databases. It may not.

Another issue is ignoring time zone semantics. A historical 2024-05-10 date does not tell you whether the intended timestamp should be midnight local time, midnight UTC, or some business-specific hour.

It is also easy to combine schema change and heavy backfill work in a single long migration that locks a large table. For busy production systems, split the work into safer deployment steps.

Summary

  • 'change_column is the shortest way to move from date to datetime, but it may hide important casting details.'
  • A staged migration with a new column and explicit backfill is safer for important production data.
  • Decide what time and time zone old date values should map to before migrating.
  • Test conversion, rollback, and application behavior, not just the schema change itself.
  • For large tables, avoid long blocking migrations and plan the backfill carefully.

Course illustration
Course illustration

All Rights Reserved.