Note that the value of “morning” is given as a UTC time; this is because TZInfo has to return something, and since Ruby’s Time class does not allow you to set it to arbitrary time zones, TZInfo simply returns the value as UTC, requiring you, as the programmer, to keep track of its real time zone. Thus, you cannot use Time#utc to convert the value to real UTC, though that is the most natural operation. Instead you have to do zone.local_to_utc(morning), which is non-intuitive and requires that you have access to the time zone object whenever you want to do the conversion.

Reader Comments

Wow – this is great! Timezones really are the worst. I’m definately looking forward to playing around with the plugin. Does it depend on tz_info and tzinfo_timezone?

Zack Chandler2 Feb 2007

Generally, it would be nice if Ruby’s date and time classes were redesigned completely.

Daniel Schierbeck2 Feb 2007

Wow! That is neat! :D Thanks a bunch :)

coder_2 Feb 2007

Zack, yes, it depends on the tzinfo_timezone plugin, which in turn requires the tzinfo gem.

Jamis2 Feb 2007

Sweet! Quite a life saver.

But, uh, what’s the yield in set_timezone for? Under what circumstances would you pass a block to that method?

gcnovus2 Feb 2007

Thank you Jamis. We’ve been putting this off, now I’m glad we did. :)

atmos2 Feb 2007

gcnovus: that’s how around_filter works.

Tom2 Feb 2007

gcnovus, the around filter gets called with a block. When you yield, the rest of the action occurs, thus letting you wrap code “around” the action.

Jamis2 Feb 2007

Film guide: Ride, Rubyo! (1950, MGM) – One man has the courage to stand up for his community against the threat of inconsistent and inefficient Rails practices. Starring Howard Keel as Jamis Cameron.

Chris Mear2 Feb 2007

Shopify now runs on tztime plugin. It has been using tzinfo_timezone since launch. Thanks a lot jamis for making my life much easier :)

tobi2 Feb 2007

So if this uses the tzinfo gem would I be correct in thinking it takes in to account daylight saving time for those timezones that DST applies to?

Alex2 Feb 2007

Jamis: This is great! Time zones aren’t fun to deal with, but I can’t stand Daylight Savings Time.

Thanks for making my life easier too!

Jason2 Feb 2007

but i swear, i will hav your baby some day.

jamis_im_a_guy2 Feb 2007

Alex, correct, it will account for daylight savings, which is another huge reason for its existence.

And, im_a_guy…I’m flattered, I think. ;)

Jamis2 Feb 2007

Does this plugin take into account the DST changes for 2007 that go into effect for the U.S.?

Taken from a random google hit via “2007 dst changes” :

”....the federal government announced a major change in Daylight Saving Time. In Aug. 2005, Congress passed an energy bill that included extending Daylight Saving Time by about a month. Beginning in 2007, DST will start the second Sunday of March and end on the first Sunday of November.”

Rob3 Feb 2007

Rob, no, this plugin does not. However, the TZInfo lib, which this plugin relies on, might. You’d need to check with that.

Jamis3 Feb 2007

Is this why you needed the acts_like method? I thought I might see usage of it in this after seeing your comment http://dev.rubyonrails.org/ticket/7059#comment:4 mentioning you needed it internally.

Dan Manges3 Feb 2007

Dan, yes, it allows you to assign a TzTime instance to an ActiveRecord and have it handled like a Time instance:

1

record.alert_at = TzTime.now

Jamis3 Feb 2007

Shouldn’t TzTime.at(Time.now) match TzTime.now, with both 6 hours ahead of Time.now (CST)? Is there something that I’m not understanding, or should this look different?

Kjell, that’s correct, because Time.now will be interpreted by TzTime.at(time) as being in the UTC time zone. It is not converted to UTC, it is literally considered to be UTC, ignoring whatever time zone Ruby says the time instance has. Thus, if it is 14:00 in your time zone, and you do TzTime.at(Time.now), the result will be a TzTime instance with a value of 14:00 in whatever zone is currently active for TzTime.

Make sense? In other words, TzTime.now is not guaranteed to be the same as TzTime.at(Time.now), unless TzTime.zone is the same time zone as the local host is currently configured for.

Jamis3 Feb 2007

Jamis, this looks great. It should prove helpful for a lot of TZInfo users.

Rob, all versions of TZInfo released so far have included the new daylight savings rules for the US. I’d recommend always using the latest version though in order to get the most up to date timezone definitions.

Phil Ross4 Feb 2007

Hi Jamis,
first, this is awesome. Thank you for making this public.

I’m not using the TzinfoTimezone because I need the full list of timezones specified in TZInfo. The only dependency on TzinfoTimezone is in #period method. If we change it we can allow TzTime.zone to accept either a ‘TzinfoTimezone’ or a ‘TZInfo::DataTimezone’ object. What do you think of this change?

After years of phobia, I finally feel ready to make a timezone-aware application. Thank you!

Rudi Cilibrasi10 Feb 2007

Hey Jamis, thanks for all the useful plugins in the repository. They always prove helpful.

In the README example you originally convert the Task.alert_at time to UTC explicitly before creating the Task. After adding the TzTime plugin you convert the Task.alert_at value to TzTime.at(self.alert_at), which should just give you the given time with the user’s timezone attached if I understand correctly.

So in the first example you’ll store it as the correlating UTC time, and in the second example you’ll store it as the user’s time zone’s time? Doesn’t that add some complexity to Unit tests for models that have any time-related code? And doesn’t it make any kind of reporting or other types of queries more copmlex because they’d rely on the time zone column of the user table (even though it’s just a little more SQL)? Is there a “best practice” for the time zone in which you store data?

Tieg10 Feb 2007

Tieg, actually, the time is ALWAYS stored as UTC in the database. TzTime#to_s(:db) will automatically convert the time to UTC, so even though it looks like you are assinging a time in the user’s timezone to that attribute, underneath it will be automatically converted to UTC before being saved.

That’s one of the aspects of TzTime that make it so useful: it helps make sure the times in the DB are always normalized to UTC, without you having to do anything special to ensure it.

Jamis10 Feb 2007

Ah, that makes sense now. Thanks for the explanation!

Tieg10 Feb 2007

This is great for setting times in the correct TZ, but what about when you get a time back from the database (UTC) and have to display it on a view in the users local time zone?

TzTime.zone.utc_to_local(Time.now.utc) will give you the correct time, however the TZ will still be set to UTC.

Should we be doing something like:
TzTime.at(TzTime.zone.utc_to_local(Time.now.utc)

Or, as a helper:
def utc2local(time)
TzTime.at(TzTime.zone.utc_to_local(time)
end

Or am I missing something completely about going back the other way?

Justin12 Feb 2007

I still can’t get my head wrapped around strftime as it relates to TzInfo (and now, by extension, TzTime). See below ... why “UTC” and not “MST”?

Henry, that occurs because Ruby does not store the time zone with Time instances. Or rather, it only lets you know whether the object is in UTC, or “local” (meaning, the time zone of the host machine). TZInfo, then, returns all time objects with a time zone of UTC, and requires the caller to keep track of which time zone the record is REALLY in.

The “%Z” argument to strftime, then, is pretty useless when dealing with time zones, since it can only know about what the underlying Time object reports.

The better way is to use zone.period.abbreviation.to_s to get at the time zone name, instead of %Z>

Just to confirm, this plugin only works for Edge Rails, not the 1.2.2 gem version, correct?

Geoff B14 Feb 2007

Thanks, Jamis. And of course thanks for a terrific and time saving plugin. (I have lots of apps using variations of the steps you describe above.) On a sidenote, I noticed TzTime is at rev 6155. How did that happen? Do you auto commit or something?

Henry15 Feb 2007

I have several questions about this plugin:

1) I echo Geoff B’s question about the Rails version required for this plugin, because I’m running Rails 1.2.1 and…

2) When I do something like: MyObject.create(:foo_at => t), where t is a TzTime object, t does not automatically get converted to a DB compatible format (rather, Rails attempts to insert the YAML-serialized version of the object into the field). I have to do time.utc or time.to_s(:db) instead, explicitly. If I understand the README correctly, I shouldn’t need to do that.

3) I echo Justin’s question above on how to convert a UTC time fetched from the database back to the user’s timezone. Shouldn’t there be a TzTime.from_utc method or something, since TzTime.at(t) assumes t is in the host’s timezone? (And if this is the case, .at seems like a bad name for that method—.from_local would be more helpful). Maybe I’m missing something obvious here. Please advise.

Tom Smyth17 Feb 2007

Geoff/Tom: it works as advertised on edge rails only. It will work with caveats in 1.2.x.

Henry: TzTime shares a repository with Rails, so the revno you see is the revno for the entire Rails repository, not just for the TzTime plugin.

Tom: for #1 and #2, you need edge rails. To convert a UTC time to user local time, I do TzTime.zone.utc_to_local(foo.created_at). It would be trivial to wrap that up in a helper if you find that too verbose, though.

Jamis19 Feb 2007

Hi Jamis

I just wondered if you tend to condition your set_timezone code with TzTime.zone = current_user.time_zone unless current_user.nil? or do you use skip_filter in the controllers where there is no current_user (e.g. login or signup)

Fred21 Feb 2007

Fred, it’s definitely easier to use a condition in the filter, but the drawback of that is it can hide bugs. If you expect a particular call to have set the current_user value, it’s nice to have that assumption tested as early in the stack as possible.

So, you have to find a balance. I’ve done it both ways (condition vs. skip_filter).

Jamis22 Feb 2007

Re: above question about DST changes for 2007, looks like this already handled by the latest version of the TzInfo gem: