DateTime::Duration refinement

Ovid on 2008-06-16T08:50:54

Recently we found we needed to support DateTime::Duration objects a bit more "naturally". Specifically, we needed something like this:

my $duration = Our::DateTime::Duration->new( seconds => 23.127 );
# later
print $duration->in_seconds;   # 23.127

Note that this doesn't work in DateTime::Duration. We've already subclassed it for our own purposes, so it was natural (*cough*) to add this feature, but even though the interface is simple, the guts are horrifying and confusing. I'm hoping one of you can tell me the error of my ways :)

I made this work by hijacking "nanoseconds", something that the duration objects already support but which we do not use (the following code is simplified to show the relevant logic).

sub new {

    my ( $self, %args ) = @_;

    if ( exists $args{seconds} && $args{seconds} =~ /\./ ) {
        my $seconds = $args{seconds};
        @args{qw[seconds nanoseconds]} = split /\./ => $seconds;
        $args{nanoseconds} ||= 0;

        my $length = length $args{nanoseconds};
        if ( $length > 9 ) {
            X::TVA::Field::Format::Duration::Seconds::Invalid->throw(
                seconds => $seconds,
            );
        }

        # XXX need to zero fill to fill out the 'billions'
        $args{nanoseconds} .= '0' x ( 9 - $length );
    }
    return $self->SUPER::new(%args);
}

You'll note the "zero padding" to get nanoseconds correct. We have to do something very similar to make the in_seconds method work.

sub in_seconds {
    my $d = shift;

    my $seconds = (($d->hours * 60) + $d->minutes) * 60 + $d->seconds;
    if ( my $nanoseconds = $d->nanoseconds ) {
        $nanoseconds =  ( '0' x ( 9 - length($nanoseconds) ) ) . $nanoseconds;
        $seconds .= ".$nanoseconds";
        $seconds += 0; # handy way to trim those trailing zeros
    }
    return $seconds;
}

This is horrifying to me. I'm using string manipulation to do math :( Do you have anything better?


what am I missing?

fireartist on 2008-06-16T10:17:22

I can't tell if I'm being really dense, but what's the difference between your desired output, and this...?

$d = DateTime::Duration->new( seconds => 23.127 );
print $d->delta_seconds; # 23.127

In which case, wouldn't this provide the 'natural' method name...

*DateTime::Duration::in_seconds = \&DateTime::Duration::delta_seconds;