Modern functions in a post-modern language
Update: Time::Local 1.30 includes a new pair of functions timegm_posix
/timelocal_posix
which address all issues outlined in this article, including the issues with the traditional functions, at least as pertains to Time::Local’s purpose as an inverse of the gmtime
/localtime
Perl builtins.
The new _modern
function variants in Time::Local have come up a few times lately. I have some thoughts on them, but presenting my position dispassionately enough to be persuasive demands an essay of unfortunate length… so let’s get on with it.
Let me lead with the positive: it is a problem with the traditional functions that they would sometimes add 1900 to the year and sometimes a different value and sometimes nothing. This heuristic in the interface is bad. Doing something about it is a good idea.
Let me also lay out some simple statements of fact: Perl ships with gmtime
and localtime
functions which return a datetime represented as a list of numbers. Time::Local supplies inverse functions which take such a list and return the Unix epoch time that corresponds to that datetime. The Perl functions, among other things, return the year as the number of years since 1900. A correct inverse of the core functions would therefore simply add 1900 to the year passed.
The traditional Time::Local functions do not fully do this: they only do it if the year number is just big enough but not too big.
The added functions equally do not do this: they never do it. So they are even less of an inverse of the core functions. As it stands, it doesn’t matter which pair of functions you use: to get the correct inverse, you have to fix up the year value.
But the presence of the heuristic means that you can write timegm(gmtime())
and get the same value as time()
. You cannot write timegm_modern(gmtime())
– that is simply always incorrect.
Now, removing the heuristict certainly makes the added functions less magically DWIMmy than the traditional ones. If you look at these new functions in isolation, they are a better interface than the traditional ones. That’s positive, right?
Well, it’s only relative. What does it amount to in absolute terms? This is still a pair of six-parameter functions, with the least significant value expected first, and with some of the values being 0-based and some being 1-based. In absolute terms, this is merely a slightly less abysmal interface than before. One less heuristic doesn’t make it anywhere near good. You should not be writing code against this interface if you have better options.
Why would anyone design an interface that way? The obvious (and simple (in the sense of not going too far afield)) answer here is that the signatures of these functions mirror the return values of the gmtime
/localtime
core functions. That is the purpose of Time::Local: to be an inverse of the core functions. And it is not simply a purpose, but also a necessity: given that the core functions exist, something like Time::Local also needs to exist. The purpose of the module never was to be a good generic datetime interface, it always has been to fill this need.
Unfortunately the year heuristic means the module has always failed its purpose.
And more unfortunately still, the omission of that heuristic in the newly added functions has not changed that.
The added functions fixed the wrong problem.
(Ironically, the module’s documentation explains that the reason for the year heuristic is “to make the interpretation of the year easier for humans”. That is to say: the problem the newly added functions are supposed to fix was created in the first place by an attempt to make the module a better standalone interface, which it never can be anyway.)
Given all that, you may now be suitably placed to understand that I also have a bone to pick with the naming. Simply put, “modern” is not a description – it is a value judgement, tantamount to calling the functions _cool
or _chic
, just with a more serious-sounding epithet. Unfortunately merely labelling something _correct_this_time
does not make it so. Something like _fullyear
might have been a more appropriate name.
Needless to say, my opinion on the “modern” functions is not positive.
Now in practical terms, where does that leave us?
There are two possibilities if you are considering using these new functions. Firstly I suggest that you reconsider your use of Time::Local entirely: it is probably not the module you want to be using in the first place. But maybe you truly are using it as an inverse of the core gmtime
/localtime
functions? In that case, I suggest that you stick with the traditional functions and simply wrap them as Grinnz illustrates. In doing so you lose nothing but the need to depend on a recent version of Time::Local it is best to upgrade to version 1.30 and use the newer timegm_posix
/timelocal_posix
functions.
Leave a comment