What's wrong with this picture:
$ perl -MDateTime -e 'print DateTime->now()' 2006-04-04T23:08:37 $ perl -e 'print scalar localtime' Tue Apr 4 19:09:10 2006
It seems that DateTime->now() doesn't do what I thought it did. I thought it returned the current date and time at this point on the planet. What it actually does is return the current date and time in UTC! (And please don't tell me that DateTime can't reliably figure out my timezone. Perl's localtime() manages just fine!) (You can, however, lambaste me for not reading the DateTime docs, since it's quite clear that now() doesn't return local time.)
Unfortunately I discovered this only in the late stages of testing version 3 of a large application that makes moderately extensive use of Datetime. That means the "easy" way to fix this, namely:
$ perl -MDateTime -e 'print DateTime->now(time_zone => "local")' 2006-04-04T19:12:03
Isn't so easy, as it would mean touching a lot of files.
As far as I can tell I don't have a lot of good options here. I'm going to make a sub-class of DateTime which defaults to "local" for now(), new() and from_epoch(), but that will still mean touching lots of files. I did find discussion of this problem on the DateTime lists, but I don't think a solution made it into the module.
I did discover that Tim Bunce considers my chosen solution "trivial", whatever that means!
-sam
Re:hate to be harsh but ...
samtregar on 2006-04-05T07:08:32
Hard to say for sure. I think the biggest factor is that although we do lots of date math in the application the actual times don't matter very much to the client. They'd tell us within a few days if we ever got the dollar amounts wrong but noticing a shift of a few hours on a monthly or weekly report is pretty tough.Another factor is that a lot of the most important dates were right. MySQL has a DWIM that matched our expectations - NOW() really is now, right here, on this spot on the planet. So timestamps in the DB were mostly accurate.
-sam
Suppose that DateTime
were the other way round, and defaulted to the local timezone. Then somebody could've made an equivalent blog post to yours but comparing DateTime->now
with gmtime
and complaining that they are different!
There are 2 different possible behaviours. Obviously the module can only do 1 of them by default.
I've been inconvenienced the other way round, when I discovered that our MySQL database was storing timestamps in local time rather than UTC — and not storing the timezone. So this means that in autumn when the clocks go back we have 1 hour's worth of recorded times that actually represent 2 hours' data, and no way of distinguishing them.
Smylers
Re:Other Way Round?
samtregar on 2006-04-05T07:13:39
You're striking at the heart of the matter by bringing up MySQL. That's our DB and it's probably the first place I ever saw a "NOW()" function. As you mention NOW() in MySQL is just like "now" in real life - it's local time. So when I see DateTime->now(), well, I expect the same. And if I called you up right now and said "hey, what time is it now?" you'd probably meet my expectations too!Sure, that doesn't mean that everyone should expect that or that the developers of DateTime are morons. They just have different expections, I suppose.
Although, if you actually read the link in my post you'll find that they don't defend the semantics of "now is UTC". They actually don't think they can reliably know the local time! I find that, well, nuts. If you can't figure out local-time on Windows then what kind of a platform is it? What does Perl do for "localtime()", give up? The lowest common denominator here is pretty damn low.
-sam
Re:Other Way Round?
pijll on 2006-04-05T08:55:48
Information on the local time may be easy to find everywhere, but the info on the timezone may be much harder. You can get the time difference between localtime and UTC, but that does not uniquely define a timezone.E.g., in your example, the only thing we know about your timezone is that it is '+0400' now, but we don't know what DST rules you have. So instead of guessing the correct timezone, DateTime chooses to let the user decide.
Re:Other Way Round?
Smylers on 2006-04-05T12:33:56
As you mention NOW() in MySQL is just like "now" in real life - it's local time.Except that it isn't — it's more like floating time, because it doesn't also note the timezone. That's just wrong, because it means that MySQL's timestamps don't map to a particular moment in time, but to several possible moments. And even if you know the physical location, because of daylight-saving time that isn't good enough.
I don't mind at all which timezone MySQL uses to display its times. And I certainly don't care how it stores them internally. But I do care that if I tell it to note an event as having just occurred that it records sufficient information about when 'now' is such that when I later ask it for when that event happened it can give me an answer which unambiguously refers to just 1 moment in time.
So in practice that means you have to know the timezone of stored times. That can either be explicit (by storing a timezone with every time) or by being documented that all times are in UTC (or some other ‘standard’).
So when I see DateTime->now(), well, I expect the same.But it is doing the same! Calling
DateTime->now
is constructing an object which unambiguously refers to the current moment in time. There are lots of ways of displaying that. By default it happens to display in UTC, but that's to do with how the data is displayed, not which moment is being represented. (And it doesn't seem fair to blame a constructor for the output!) You could also note that it displays the date by default in ISO8601 format, which has the fields in an order that most humans don't use in their day-to-day lives. Perhaps it should display the date in a more familiar format? But even so that wouldn't affect which date is being displayed.Consider this one-liner:
% perl -MDateTime -wle 'my $now = DateTime->now; print $now; $now->set_time_zone("America/New_York"); print $now'Note how the time initially stored is in UTC, but is (internally) labelled as such — because when you tell it which timezone you wish to view the time in you are not merely applying a timezone label to an existing floating time (which would result in the time staying the same in the subsequent
This isn't unusual behaviour. If you do
my $num = 0x10
then Perl stores the actual integer to which you're referring (sixteen), not the digits one and zero. If you try to print it you'll get 16 by default. If you want that integer displayed in a different base then there are ways of specifying that. But 0x10 and 16 are undoubtedly the same integer. And now is undoubtedly now, regardless of the number of ways of writing it.And if I called you up right now and said "hey, what time is it now?" you'd probably meet my expectations too!Do you know where in the world I am? If not you might find it confusing if I just gave you a time in my local timezone (which is different from yours) without telling you what that was.
And while most of the time if somebody asked me the time I wouldn't give them the timezone (because I'd presume they know which timezone I'm in), if somebody happened to ask me the time on the day that the clocks change for daylight saving then I'd probably clarify my answer by stating explicitly which time I was referring to.
I'd be satisfied if MySQL only stored timezone information with times that are potentially ambiguous (that hour when the clocks go back every autumn); that's the minimum required. But surely it's better to be consistent, and simpler for everybody involved, if it always included it?
Sure, that doesn't mean that everyone should expect that or that the developers of DateTime are morons. They just have different expections, I suppose.Exactly — whichever you pick, somebody's going to prefer t'other. So
DateTime
can be seen as having arbitrarily chosen one of several ways which would be correct; it's straightforward to get any behaviour you want.Whereas MySQL has chosen a way which is definitely wrong, because if it isn't doing what you want it forgets vital information and there's no way of getting back out of it the information you put in. Which, when you think about it, is a pretty fundamental requirement for a database.
Smylers
Re:Other Way Round?
jdavidb on 2006-04-05T13:35:07
You could also note that it displays the date by default in ISO8601 format, which has the fields in an order that most humans don't use in their day-to-day lives.
I do, now, ever since I first used DateTime and thought, "What is that weird format, and why do they use it?"
sub DateTime::now {
push @_ => time_zone => "local";
goto &DateTime::__now;
}
Re:__
Matts on 2006-04-05T16:05:10
Use unshift, otherwise the time_zone can't be overridden.Re:__
samtregar on 2006-04-05T17:01:41
Hhaha. Where I work that's a hanging offense!-sam
Re:__
Abigail on 2006-04-06T09:50:26
I'm interested to know why that's a "hanging offense".
I simply switched my own personal timezone to UTC. My watch is set to UTC, my PC timezone is set to UTC, my TZ variables everywhere I go are set to UTC, my Wikipedia preferences are set to UTC, my forum preferences many places on the net are set to UTC. I know when the sun rises and sets. I'm always reminded to convert timestamps for the benefit of my coworkers, 100% of whom are all distributed across the other three continental U.S. timezones. The hardest part is twice a year when I have to remember the change in offset.
Thank you for pointing out the time_zone => 'local' option. I don't believe I'd noticed that. Interestingly enough, I just tested, and when I do this under Cygwin after unsetting my TZ variable, it comes out to my local timezone. I wonder how it knows; Windows is set to UTC. Probably some administrator-set option somewhere. (Seems like at one time DateTime couldn't figure out my local timezone on its own.)
BTW, if the proposed solution's only drawback is that it touches a lot of files, and you've come up with an alternative which also requires touching a lot of files and some additional work and is slightly substandard, why not just go with the proposed solution? You have to touch all those files anyway.
Why not make DateTime->now() return localtime and create a utc() method? I guess it would break backwards compatibility, though.