Eric Brine wrote:
>The binary search found:
...
>2013-11-03T03:45:00 UTC
>2013-11-03T04:07:30 UTC

You should only examine integral minutes.  Truncate to the minute
when bisecting, before you pass your bisected time to DateTime.  Or,
to avoid fractions entirely, extend the search radius to 2048 minutes.
Either way, do not burden DateTime with any arithmetical job.

Here's an implementation of what I mean:

    sub start_of_day {
        my($tgt_date_str, $zone) = @_;
        $tgt_date_str =~ /\A([0-9]{4})-([0-9]{2})-([0-9]{2})\z/ or die;
        my $tgt_date_ut = DateTime->new(year => "$1", month => "$2",
                day => "$3", time_zone => "UTC");
        my($tgt_date_epoch_min) = $tgt_date_ut->epoch / 60;
        my($tgt_date_rd) = $tgt_date_ut->utc_rd_values;
        my $left_epoch_min = $tgt_date_epoch_min - 1440;
        my $right_epoch_min = $tgt_date_epoch_min + 1440;
        while(($right_epoch_min - $left_epoch_min) > 1) {
            my $try_epoch_min = ($left_epoch_min + $right_epoch_min) >> 1;
            my $try_dt = DateTime->from_epoch(epoch => $try_epoch_min*60,
                    time_zone => $zone);
            (($try_dt->local_rd_values)[0] >= $tgt_date_rd ?
                    $right_epoch_min : $left_epoch_min) = $try_epoch_min;
        }
        return DateTime->from_epoch(epoch => $right_epoch_min*60);
    }

Use as in:

    @ARGV == 2 or die;
    my($tgt_date_str, $zone_name) = @ARGV;
    my $zone = DateTime::TimeZone->new(name => $zone_name);
    print start_of_day($tgt_date_str, $zone), "Z\n";

-zefram

Reply via email to