Ernie Miller

No, I don't work in NYC, DC, or the valley, and I'm cool with that.

RSS Feed

Updating MySQL DATETIMEs for Rails 2.1 Time Zones

Posted by Ernie on June 2, 2008 at 5:19 pm

So, if you haven’t heard, Rails 2.1 was released yesterday, And it comes chock full of time zoney goodness. But wait, don’t run off and enable time zone support in your app without reading this first!

If you checked out Geoff Buesing’s excellent introduction to using the time zone features, you may have noticed this little gotcha near the end of the article:

The new time zone features assume that the database is storing times in UTC, so if you’re currently storing times in the database in a zone other than UTC, you’ll need to migrate existing data to UTC.

If you’re relatively new to Rails development, you probably have a couple of questions.

Am I currently storing my times in UTC?

Probably not. For a standard Rails 2.0 app, environment.rb will contain the following line, commented as you see here:

  # Make Active Record use UTC-base instead of local time
  # config.active_record.default_timezone = :utc

In other words, unless your system’s local time is configured for UTC or you uncommented this line, you were probably storing DATETIME fields like created_at and updated_at in the local system time. If you just enable Rails 2.1 time zone support, all of your old time data will show up incorrectly, because you’ll be adjusting data for your local time zone that was already saved in your local time zone. Now, if you’ve got a huge production Rails site with users all over the world, then you probably are storing times in UTC and using some sort of time zone support already. But then, you also probably aren’t reading this blog.

How do I migrate my existing data to UTC?

If you’re using MySQL, here’s how: CONVERT_TZ.

Standard disclaimer: Always make a backup of your data before doing bulk updates. I am not responsible for data corrupted as a result of trying to follow this guide, yada yada…

Shut down your Rails app and connect to your application’s SQL database using something like MySQL Query Browser. Regardless of whether your MySQL server had its time zone tables populated for name-based time zones, it will understand the two time zones we are going to use, “SYSTEM”, for your server’s time zone, and “+00:00″, or UTC. For each table containing DATETIME columns which are already saved in your system’s local time zone, issue an update to those columns using CONVERT_TZ. For instance, I have a table called “users” with “created_at” and “updated_at” columns, so I will issue:

UPDATE users SET created_at=CONVERT_TZ(created_at,'SYSTEM','+00:00'),
                 updated_at=CONVERT_TZ(updated_at,'SYSTEM','+00:00')

Since there’s no WHERE clause in this query, it will update those two columns in every row in the table. Repeat as necessary. BE CAREFUL! Don’t run these queries against a specific column more than once. If you’re in any way uncomfortable making this change, you should probably have someone else do it.

Once all of your DATETIME columns have been updated, you can safely set up your system’s time zone in environment.rb to enable time zone support:

  # Run "rake -D time" for a list of tasks for finding time zone names.
  # Uncomment to use default local time.
  config.time_zone = 'Eastern Time (US & Canada)'

After starting up your Rails app, all of your time data should continue to show up properly, and new data will be saved in UTC.

Filed under Uncategorized
Tagged as , ,
You can leave a comment, or trackback from your own site.
  • http://blog.spoolz.com Karl

    Banged my head on this one for a while. My MySQL server was set for ‘MST’ and I thought Rails 2.1 would read the date with the timezone from the tables correctly and NOT convert them to local time as no conversion is needed. Wrong. Apparently Rails ignores the MySQL TZ and *assumes* the time is UTC.

    The oddest part is that when you inspect a AR object, it shows the UNCONVERTED time. But then you try to use it, it silently converts it. Just lost two hours trying to track that one down.

    This one is going to bite more than just a few people. They should have NOT turned on TZ support by default.

    Thanks for that SQL snippet to do the conversion. You might also want to include how to set MySQL’s default timezone to UTC so that if someone else is looking at your datetime columns, they will know they are in UTC.

  • http://thebalance.metautonomo.us Ernie

    Karl,

    Glad I could help. Yeah, the MySQL TZ setting is basically just for MySQL-specific uses. It’s important to keep in mind that DATE, TIME, and DATETIME columns don’t count. There’s no guarantee after all that those column types are really storing timestamps — perhaps they’re just being used to store absolute dates and times to display the same everywhere. MySQL does the right thing in not converting them, but I can understand how it might be confusing. In the MySQL docs linked to above they explain it. Here’s the relevant bit, quoted and emphasized for convenience:

    The current session time zone setting affects display and storage of time values that are zone-sensitive. This includes the values displayed by functions such as NOW() or CURTIME(), and values stored in and retrieved from TIMESTAMP columns. Values for TIMESTAMP columns are converted from the current time zone to UTC for storage, and from UTC to the current time zone for retrieval. The current time zone setting does not affect values displayed by functions such as UTC_TIMESTAMP() or values in DATE, TIME, or DATETIME columns.

  • Pingback: A Fresh Cup » Blog Archive » Double Shot #233

  • Richard Poirier

    Thanks a lot. Just what I was looking for. I put the update in a migration and made it generic to just convert all my datetime or time columns to UTC:


    tables.each do |table|
    columns(table).each do |column|
    if column.type == :datetime or column.type == :time
    update "UPDATE #{table} SET #{column.name} = CONVERT_TZ(#{column.name}, 'SYSTEM', '+00:00')"
    end
    end
    end

  • http://pixelwareinc.com Eric Anderson

    @Richard Poirier – I like you method of doing it generically across all fields.

    I use SQLite for development so the update statement provided doesn’t work great for me but I enhanced yours a bit with the following pastie that uses Rails to do the conversion.

    http://pastie.org/322888

  • Paweł Kondzior

    This is my version bases on Eric Anderson version for mysql, it’s much quicker on bigger DB, it dosen’t use AR code to load/save record. But this is probably only for mysql, you should edit sql for other RDBMS

    http://pastie.org/private/xkxs6kpukzqdzviz4uvjg

  • http://penguincoder.org Andrew Coleman

    Thanks man, I looked at the docs for this and it did not immediately appear obvious to use that function in this manner. Works like a champ.

  • Pingback: Millarian » Blog Archive » Quick Tip: Rails 2.1 Time Zones

About

I'm Ernie Miller. But then, you probably knew that by looking at the page title, or the URL. I'm a Ruby programmer in Louisville, Kentucky. This blog used to be called "metautonomo.us", which I thought was kind of clever, but nobody, including me, could type it. Lesson learned.