A Date with CPAN, Part 3: Paving While Driving
[This is a post in a new, probably long-ass, series. You may want to begin at the beginning. I do not promise that the next post in the series will be next week. Just that I will eventually finish it, someday. Unless I get hit by a bus.]
Last time I went into more details about how I might go about creating a new date module, and what I would expect it to achieve. This time we clear out some housekeeping and try to nail down a design strategy.
Before I get into that, I should point out something fundamental: I’m still in the design process. I once read a story about Harlan Ellison1 in which he wrote a short story, in real time, with fans watching as he did so. As he finished each page, he posted it up on the wall (or wherever) for them to read. The point of the story, I believe, was about revision, and to illustrate an extreme situation which might be the only case in the world where you actually couldn’t go back and fix what you wrote at the beginning.
So this is a bit like that. Except that I won’t consider myself stuck with whatever I manage to produce at the beginning here; if I make a mistake or stumble across a better way later, I’ll go back and fix it. If someone points out something in the comments that makes better sense than what I had in mind, I may completely change direction. Hell, if someone comes along and says “hey, have you looked at Date:Dohickey?” and I go and look at Date::Dohickey, and it turns out that Date::Dohickey really does do everything I want, I may very well scrap the whole idea. So I wanted to give everyone fair warning that there may be sharp turns ahead. So hold on tight.
For that matter, in a comment to the first post in the series, the ever-helpful Aristotle actually did throw out several more date modules that I hadn’t yet found in my search. And I dutifully investigated them to see if they might solve my problem. How did they stack up?
Class::Date Originated by dLux back in 2001, and taken over by the Perl Weekly’s own Gábor Szabó2 last year, Class::Date has a few things going for it. Size-wise, it’s only about twice as big as Time::Piece, which makes it about 25% the size of DateTime, which is very reasonable. It seems to be immutable, has pretty good functionality and a decent interface,3 and it even handles conversion, which it farms out to Parse::Date, just as I plan to. Better yet, it has a nifty little internal array of functions to do its parsing, so it could theoretically be extensible to allow the user to add their own favorite string-to-datetime functions. I may totally steal that. One minor ding on the conversion aspect: it’s load-on-request, which is not quite the same as load-on-demand. That is, if you want to be able to parse arbitrary formats, you must specify that on your use
line, which I don’t like as much. But it’s not a deal-breaker. On the negative side, it handles truncation the same way DateTime does, which I don’t like. Implementation-wise, its object is an arrayref of dateparts, which I find less palatable than epoch seconds.4 And the fact that I’ve never heard of it isn’t encouraging. Will other programmers have confidence in the code? I just don’t know. I’m also not sure about the speed factor, but I guess at least someone considers it slow, because there exists ...
Panda::Date ... whose raison d’être seems to be to make Class::Date faster. But that appears to be about it.
Date::Handler This appears to be another module which is descended at least in spirit from Class::Date. The implementation seems to be more centered around epoch seconds, so that part’s an improvement at least. The functionality is decent, and the interface is not terrible, although I prefer Class::Date’s. Size-wise it clocks in at just a touch over Date::Piece, which isn’t too shoddy as a complete solution, although if it requires add-ons the cost could start to get concerning. Worse, it doesn’t seem to offer conversion, or even the sort-of-kind-of truncation that Class::Date does. I believe it’s immutable, but then so is Class::Date. In terms of stability, I can’t see any evidence that the community trusts it any more than Class::Date. In short, no offense to Date::Handler’s author, but I just can’t see any compelling reason to prefer it to its stated inspiration.
So I guess we’re trudging onward. Hopefully you as my faithful reader will appreciate getting a glimpse behind my design process curtain, even if we do end up wandering down a few blind alleys here and there. To put your mind a bit more at ease, let me assure you that I’ll be trying to stay ahead of my posts in the actual writing of code so that I don’t have to expose every painful mistake I make along the way. Once I get far enough along I’ll even toss out a link to the GitHub repo and you can follow along at home if you’re really into that sort of thing.
Of course, in order to have a GitHub repo (or even a directory to write code in), one needs a name. I suppose I could use a placeholder name and then change it later, but I find that whatever I start out calling a thing often sticks with it forever. Besides, a name can shape the direction of a design, or at least indicate that direction to the onlookers.5 So I went ahead and picked one: Date::Easy.
Now, before you dash down to the comments section to point out to me the PAUSE guideline that tells me not to name anything “Easy” (or “Simple” or “Tiny” or any of those), let me save you the trouble: yes, I’ve read that. I’m not particularly fond of it. If everyone listened to that guideline, we wouldn’t have awesome things like Path::Tiny or Email::Simple ... well, I guess we would, but they wouldn’t have those awesome names, which I personally find perfectly descriptive of what they do. And, since the A-#1 goal of this module is to make dates easy for Perl coders, I can’t really think of a better name. So send me suggestions for better names if you must, but know that I’m really happy with this one. Besides, I think more coders are going to find out about my module from this blog series than from random CPAN searches, so I’m already covered on the discovery score.
I also like the name because it’s nice and short. Naming CPAN modules is always a bit of a balancing act ... you want the name long enough to be fully descriptive of its function, but you don’t want it to be horrific to type all the time. Especially if your module is going to be object oriented: you not only have to type
use Date::Object::With::Conversion::And::Truncation;
but there’ll be some
my $d = Date::Object::With::Conversion::And::Truncation->new("11/1/2015");
type calls as well. Ick.6 And, as long as we’re talking about
use
lines, I really do want it be just use Date::Easy
: no having to list what functions you want exported. I want to keep it simple. Of course, some people don’t like that ... “don’t pollute my namespace!” they’ll cry. Well, fair enough. I think we can satisfy both camps.
I already said that one of the approaches I want to take is to have two classes: one for dates without times, and one for full datetimes. Now that we have a base module name, we can name these as well: Date::Easy::Date and Date::Easy::Datetime. So Date::Easy doesn’t really need to exist at all, technically speaking. But we’ll have it be just a shortcut for quick/easy exporting. That is, this:
use Date::Easy;
will just be a shorter way to write this:
use Date::Easy::Date ':all';
use Date::Easy::Datetime ':all';
So, if you like getting the kitchen sink with one line, you can have that. If you prefer being picky about what you allow into your namespace, you can have that too. Cool.
Now, I already mentioned last time that I wanted to make two different classes. But I didn’t go into much detail about why I wanted to do it this way. So let’s talk about that for a bit.
It’s certainly not the only way. But I already mentioned that I’m not happy with a simple truncate
method to implement the truncation feature (and recall that’s one of my big two). The problem with it is, there’s just too many things you can do to it to end up with a non-truncated value, even if your object is immutable. For instance, say you take a truncated DateTime or Time::Moment and add 6 hours to it, what do you get? Even if the original date is unchanged (due to immutability), the new value returned is no longer just a date: it’s a datetime. Having separate classes for date vs datetime can fix this in two ways:
- There simply won’t be any way to “add 6 hours” to a date, because it’s a nonsensical operation.
- Even if you somehow manage to do it, we can build truncation into all the proper points so that it’s simply not possible for a Date::Easy::Date to have a time of anything other than midnight.
This also frees us up for some other very cool things. For instance, what does this mean?
my $dt = Date::Easy::Datetime->new( "now" ) + 30;
Well, it means 30 seconds from now, obviously. However, what does this mean?
my $d = Date::Easy::Date->new( "today" ) + 30;
It seems to me that it ought to mean 30 days from today. And, since those are separate classes, it can mean that. Overloaded addition on a datetime will add seconds, while overloaded addition on a date will add days. Simple, intuitive, and not difficult to code, since we’re talking about two different classes.
So, separating the classes gives us better truncation, the ability to apply different semantics, and, as an added bonus, a simple way to provide both conservative and liberal exporting. We already decided that we want Date::Easy to be a thin(ish) wrapper around Time::Piece ... now we can be more specific: Date::Easy::Datetime should be just that, with the only added functionality being conversion, and perhaps a bit of ease for date addition. Date::Easy::Date will be mostly that, but with slightly different conversion (i.e. we’ll throw out any time part in a string we’re converting from) and a guarantee that the underlying epoch seconds will always be truncated. This gives us enough of a game plan to get started writing some code and look for where our challenges are going to be.
Next time, we’ll do just that.
__________
1 Which I can’t locate now, so it may be entirely apocryphal.
2 Note that dLux’s last name is also Szabó. Perhaps they’re related. Or perhaps Szabó is just a very common surname in Hungary.
3 Aristotle: If you’re reading this footnote, could you comment on why you said you find the API “a bit wonky”?
4 To be fair, Time::Piece is also represented as a blessed arrayref. But the epoch seconds is the only really important bit. The other bits in the array are just a cache of the results of calling localtime
(or gmtime
) on the epoch value. So it’s quite different from the implementation of Class::Date and several of the other date modules.
5 That would be you, faithful reader.
6 Actually, I’m going to try to avoid that style as much as possible. But you see my point.
I was fiddling with date parsing (i.e. conversion) ideas recently and left the end result here: https://metacpan.org/pod/Date::Parse::Lite
Might or might not contain some useful ideas.