I thought the first solution posted to your use Perl article was decent:

sub partition {
    my ($start, $end, $n) = @_;
    my $s = $start->epoch;
    my $e = $end->epoch;
    my $i = int( ($e - $s) / $n );    # interval
    return map DateTime->from_epoch(epoch => $s + $i * $_), 0 .. $n;
}

But I see you dislike it due to the math.  The second example requires
you know the interval type like you pointed out.  I see both sides on
this issue, but I think the FAQ code covers most ordinary situations
such as calendaring and scheduling.  How many people really need to
split hairs at the nanosecond level.
        I do understand your lament about the apparent simplicity of the
math and that it should be done in the object automatically.  The
trouble really comes with all the rounding right?  Here's another take
on the math based approach using hires_epoch dates.  The first sub
illustrates the potential rounding error.  The second one takes an extra
step to "eliminate" rounding errors, but doesn't truly deal with them.

sub partition_2 {
    my ($start, $end, $n) = @_;
    my $int = ($end->hires_epoch() - $start->hires_epoch()) / $n;
    return map { $start->clone->add(seconds => int($int + .5) * $_,
nanoseconds => ($int - int($int + .5)) * 1e9 * $_) } 0 .. $n;
}

sub partition_3 {
    my ($start, $end, $n) = @_;
    my $int = ($end->hires_epoch() - $start->hires_epoch()) / $n;
    my @array = map { $start->clone->add(seconds => int($int + .5) * $_,
nanoseconds => ($int - int($int + .5)) * 1e9 * $_) } 0 .. ($n - 1);
    return (@array, $end);
}

The following test routine will display the rounding issue when using
interval values of 11, 13, 22, i.e. a value that makes the nanosecond
interval eventually exceed the original end date.

use DateTime;
my $num_days = 7;
my $start = DateTime->now;
my $end = $start->clone();
$start->subtract(days => $num_days);

print $start, " --> ", $end, ".", $end->nanosecond(), "\n\n";

my @array = partition_2($start, $end, 11);
print join("\n", @array), "\n\n";
print "End date is wrong (", $array[-1], ".", $array[-1]->nanosecond(),
"\n\n" if ($array[-1] != $end);

my @array = partition_3($start, $end, 11);
print join("\n", @array), "\n\n";
print "End date is wrong (", $array[-1], ".", $array[-1]->nanosecond(),
"\n\n" if ($array[-1] != $end);


Hope this helps someone...

Bobby

-----Original Message-----
From: J. David Blackstone [mailto:[EMAIL PROTECTED] 
Sent: Tuesday, August 07, 2007 1:10 PM
To: [email protected]
Subject: Simple math: partitioning the time between two DateTimes


  Given two DateTime objects as input, and an integer $N, I want to 
partition the time in between them into $N equal intervals and return 
an array of $N + 1 evenly-spaced DateTimes that begins with the first 
input DateTime and ends with the last input DateTime.

  The straightforward way of doing this would seem to be to subtract 
the two DateTimes, divide the resultant interval by $N, multiply and 
add repeatedly.  But it doesn't work because DateTime::Duration just 
can't handle that kind of math.

  What's the right way to do this?  And what would it take to make a 
class comparable to DateTime::Duration that represents an interval of 
time that can actually be manipulated in this manner?

jdb

Reply via email to