The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/bin/env perl

use strict;
use warnings;

use Test::More;

use DateTime;
use DateTime::SpanSet;


# Check that SpanSets return spans with the correct endpoints during daylight
# savings changeovers given a weekly recurrence.

BEGIN {
    if (eval 'use DateTime::Event::Recurrence; 1') {
        plan tests => 552;
    }
    else {
        plan skip_all => 'DateTime::Event::Recurrence required for this test.';
    }
}


test_span_set_on_day(4, 'Thursday', 27,  3);
test_span_set_on_day(5, 'Friday',   28,  3);
test_span_set_on_day(6, 'Saturday', 29,  3);

test_span_set_on_day(6, 'Saturday',  4, 10);
test_span_set_on_day(7, 'Sunday',    5, 10);
test_span_set_on_day(1, 'Monday',    6, 10);


sub test_span_set_on_day {
    my ($day_index, $day_name, $day_of_month, $month) = @_;

    my $span_set =
        DateTime::SpanSet
            ->from_sets(
                start_set   =>
                    DateTime::Event::Recurrence->weekly(
                        days => $day_index, hours => 8, minutes => 30,
                    ),
                end_set     =>
                    DateTime::Event::Recurrence->weekly(
                        days => $day_index, hours => 15, minutes => 30,
                    ),
            )
            ->set_time_zone('Asia/Jerusalem');


    my $expected_date  = new_as_of_time($month, $day_of_month, 0);


    my $expected_start =
        $expected_date->clone()->add(days => -7)->set_hour(8)->set_minute(30);
    my $expected_end   =
        $expected_date->clone()->add(days => -7)->set_hour(15)->set_minute(30);
    # Skip 2am due to daylight savings change.
    foreach my $hour (0..1, 3..8) {
        my $as_of_time = new_as_of_time($month, $day_of_month, $hour);
        my $span       = $span_set->current($as_of_time)->span();

        test_span_end_point(
            'start', $span->start(), $expected_start, $as_of_time,
        );
        test_span_end_point(
            'end', $span->end(), $expected_end, $as_of_time,
        );
    }


    $expected_start = $expected_date->clone()->set_hour(8)->set_minute(30);
    $expected_end   = $expected_date->clone()->set_hour(15)->set_minute(30);
    foreach my $hour (9..23) {
        my $as_of_time = new_as_of_time($month, $day_of_month, $hour);
        my $span       = $span_set->current($as_of_time)->span();

        test_span_end_point(
            'start', $span->start(), $expected_start, $as_of_time,
        );
        test_span_end_point(
            'end',   $span->end(),   $expected_end,   $as_of_time,
        );
    }

    return;
}


sub new_as_of_time {
    my ($month, $day_of_month, $hour) = @_;

    return
        DateTime->new(
            year        => 2008,
            month       => $month,
            day         => $day_of_month,
            hour        => $hour,
            time_zone => 'Asia/Jerusalem'
        );
}


sub test_span_end_point {
    my ($end_point_name, $end_point, $expected_time, $test_input_time) = @_;

    my $expected_ymd      = $expected_time->ymd();
    my $expected_hms      = $expected_time->hms();
    my $test_input_string =
        $test_input_time->ymd() . q< > . $test_input_time->hms();

    is(
        $end_point->ymd(),
        $expected_ymd,
        "Date for $end_point_name of span at $test_input_string.",
    );
    is(
        $end_point->hms(),
        $expected_hms,
        "Time of day for $end_point_name of span at $test_input_string.",
    );

    return;
}