The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
BEGIN {
    $ENV{PERL_DATETIME_PP} = 1;
}

use strict;
use warnings;

use Test::More;

use DateTime;

{
    my $date1 = DateTime->new(
        year       => 2001, month  => 5, day    => 10,
        hour       => 4,    minute => 3, second => 2,
        nanosecond => 12,
        time_zone  => 'UTC'
    );

    my $date2 = DateTime->new(
        year       => 2001, month  => 6, day    => 12,
        hour       => 5,    minute => 7, second => 23,
        nanosecond => 7,
        time_zone  => 'UTC'
    );

    my $dur = $date2 - $date1;

    is( $dur->delta_months,  1,  'delta_months should be 1' );
    is( $dur->delta_days,    2,  'delta_days should be 2' );
    is( $dur->delta_minutes, 64, 'delta_minutes should be 64' );
    is( $dur->delta_seconds, 20, 'delta_seconds should be 20' );
    is(
        $dur->delta_nanoseconds, 999_999_995,
        'delta_nanoseconds should be 999,999,995'
    );

    is( $dur->years,       0,           'Years' );
    is( $dur->months,      1,           'Months' );
    is( $dur->weeks,       0,           'Weeks' );
    is( $dur->days,        2,           'Days' );
    is( $dur->hours,       1,           'Hours' );
    is( $dur->minutes,     4,           'Minutes' );
    is( $dur->seconds,     20,          'Seconds' );
    is( $dur->nanoseconds, 999_999_995, 'Nanoseconds' );
}

{
    my $date1 = DateTime->new(
        year      => 2001, month  => 5, day    => 10,
        hour      => 4,    minute => 3, second => 2,
        time_zone => 'UTC'
    );

    my $date2 = DateTime->new(
        year      => 2001, month  => 6, day    => 12,
        hour      => 5,    minute => 7, second => 23,
        time_zone => 'UTC'
    );

    my $dur = $date1 - $date2;

    is( $dur->delta_months,      -1,  'delta_months should be -1' );
    is( $dur->delta_days,        -2,  'delta_days should be -2' );
    is( $dur->delta_minutes,     -64, 'delta_minutes should be 64' );
    is( $dur->delta_seconds,     -21, 'delta_seconds should be 20' );
    is( $dur->delta_nanoseconds, 0,   'delta_nanoseconds should be 0' );

    is( $dur->years,       0,  'Years' );
    is( $dur->months,      1,  'Months' );
    is( $dur->weeks,       0,  'Weeks' );
    is( $dur->days,        2,  'Days' );
    is( $dur->hours,       1,  'Hours' );
    is( $dur->minutes,     4,  'Minutes' );
    is( $dur->seconds,     21, 'Seconds' );
    is( $dur->nanoseconds, 0,  'Nanoseconds' );

    $dur = $date1 - $date1;
    is( $dur->delta_days, 0, 'date minus itself should have no delta days' );
    is(
        $dur->delta_seconds, 0,
        'date minus itself should have no delta seconds'
    );

    my $new = $date1 - DateTime::Duration->new( years => 2 );
    is( $new->datetime, '1999-05-10T04:03:02', 'test - overloading' );
}

{
    my $d = DateTime->new(
        year      => 2001, month  => 10, day    => 19,
        hour      => 5,    minute => 1,  second => 1,
        time_zone => 'UTC'
    );

    my $d2 = $d->clone;
    $d2->subtract(
        weeks   => 1,
        days    => 1,
        hours   => 1,
        minutes => 1,
        seconds => 1,
    );

    ok( defined $d2, 'Defined' );
    is(
        $d2->datetime, '2001-10-11T04:00:00',
        'Subtract and get the right thing'
    );
}

# based on bug report from Eric Cholet
{
    my $dt1 = DateTime->new(
        year      => 2003, month  => 2, day    => 9,
        hour      => 0,    minute => 0, second => 1,
        time_zone => 'UTC',
    );

    my $dt2 = DateTime->new(
        year      => 2003, month  => 2,  day    => 7,
        hour      => 23,   minute => 59, second => 59,
        time_zone => 'UTC',
    );

    my $dur1 = $dt1->subtract_datetime($dt2);

    is( $dur1->delta_days,    1, 'delta_days should be 1' );
    is( $dur1->delta_seconds, 2, 'delta_seconds should be 2' );

    my $dt3 = $dt2 + $dur1;

    is(
        DateTime->compare( $dt1, $dt3 ), 0,
        'adding difference back to dt1 should give same datetime'
    );

    my $dur2 = $dt2->subtract_datetime($dt1);

    is( $dur2->delta_days,    -1, 'delta_days should be -1' );
    is( $dur2->delta_seconds, -2, 'delta_seconds should be -2' );

    my $dt4 = $dt1 + $dur2;

    is(
        DateTime->compare( $dt2, $dt4 ), 0,
        'adding difference back to dt2 should give same datetime'
    );
}

# test if the day changes because of a nanosecond subtract
{
    my $dt = DateTime->new(
        year      => 2001, month  => 6, day    => 12,
        hour      => 0,    minute => 0, second => 0,
        time_zone => 'UTC'
    );
    $dt->subtract( nanoseconds => 1 );
    is( $dt->nanosecond, 999999999, 'negative nanoseconds normalize ok' );
    is( $dt->second,     59,        'seconds normalize ok' );
    is( $dt->minute,     59,        'minutes normalize ok' );
    is( $dt->hour,       23,        'hours normalize ok' );
    is( $dt->day,        11,        'days normalize ok' );
}

# test for a bug when nanoseconds were greater in earlier datetime
{
    my $dt1 = DateTime->new(
        year       => 2000, month  => 1,  day    => 5,
        hour       => 0,    minute => 10, second => 0,
        nanosecond => 1,
        time_zone  => 'UTC',
    );

    my $dt2 = DateTime->new(
        year       => 2000, month  => 1,  day    => 6,
        hour       => 0,    minute => 10, second => 0,
        nanosecond => 0,
        time_zone  => 'UTC',
    );
    my $dur = $dt2 - $dt1;

    is( $dur->delta_days,    0,    'delta_days is 0' );
    is( $dur->delta_minutes, 1439, 'delta_minutes is 1439' );
    is( $dur->delta_seconds, 59,   'delta_seconds is 59' );
    is(
        $dur->delta_nanoseconds, 999_999_999,
        'delta_nanoseconds is 999,999,999'
    );
    ok( $dur->is_positive, 'duration is positive' );
}

{
    my $dt1 = DateTime->new(
        year       => 2000, month  => 1,  day    => 5,
        hour       => 0,    minute => 10, second => 0,
        nanosecond => 20,
        time_zone  => 'UTC',
    );

    my $dt2 = DateTime->new(
        year       => 2000, month  => 1,  day    => 5,
        hour       => 0,    minute => 10, second => 0,
        nanosecond => 10,
        time_zone  => 'UTC',
    );

    my $dur = $dt2 - $dt1;

    is( $dur->delta_days,        0,   'days is 0' );
    is( $dur->delta_seconds,     0,   'seconds is 0' );
    is( $dur->delta_nanoseconds, -10, 'nanoseconds is -10' );
    ok( $dur->is_negative, 'duration is negative' );
}

{
    my $dt1 = DateTime->new(
        year       => 2000, month  => 1,  day    => 5,
        hour       => 0,    minute => 11, second => 0,
        nanosecond => 20,
        time_zone  => 'UTC',
    );

    my $dt2 = DateTime->new(
        year       => 2000, month  => 1,  day    => 5,
        hour       => 0,    minute => 10, second => 0,
        nanosecond => 10,
        time_zone  => 'UTC',
    );

    my $dur = $dt2 - $dt1;

    is( $dur->delta_days,        0,   'delta_days is 0' );
    is( $dur->delta_minutes,     -1,  'delta_minutes is -1' );
    is( $dur->delta_seconds,     0,   'delta_seconds is 0' );
    is( $dur->delta_nanoseconds, -10, 'nanoseconds is -10' );
    ok( $dur->is_negative, 'duration is negative' );
}

{
    my $dt1 = DateTime->new(
        year       => 2000, month  => 1,  day    => 5,
        hour       => 0,    minute => 10, second => 0,
        nanosecond => 20,
        time_zone  => 'UTC',
    );

    my $dt2 = DateTime->new(
        year       => 2000, month  => 1,  day    => 5,
        hour       => 0,    minute => 11, second => 0,
        nanosecond => 10,
        time_zone  => 'UTC',
    );

    my $dur = $dt2 - $dt1;

    is( $dur->delta_days,        0,           'days is 0' );
    is( $dur->delta_seconds,     59,          'seconds is 59' );
    is( $dur->delta_nanoseconds, 999_999_990, 'nanoseconds is 999,999,990' );
    ok( $dur->is_positive, 'duration is positive' );
}

{
    my $dt1 = DateTime->new(
        year       => 2000, month  => 1,  day    => 5,
        hour       => 0,    minute => 11, second => 0,
        nanosecond => 10,
        time_zone  => 'UTC',
    );

    my $dt2 = DateTime->new(
        year       => 2000, month  => 1,  day    => 5,
        hour       => 0,    minute => 10, second => 0,
        nanosecond => 20,
        time_zone  => 'UTC',
    );

    my $dur = $dt2 - $dt1;

    is( $dur->delta_days,    0,   'days is 0' );
    is( $dur->delta_seconds, -59, 'seconds is -59' );
    is(
        $dur->delta_nanoseconds, -999_999_990,
        'nanoseconds is -999,999,990'
    );
    ok( $dur->is_negative, 'duration is negative' );
}

{
    my $dt1 = DateTime->new(
        year       => 2000, month  => 1,  day    => 5,
        hour       => 0,    minute => 11, second => 0,
        nanosecond => 20,
        time_zone  => 'UTC',
    );

    my $dur = $dt1 - $dt1;

    is( $dur->delta_days,        0, 'days is 0' );
    is( $dur->delta_seconds,     0, 'seconds is 0' );
    is( $dur->delta_nanoseconds, 0, 'nanoseconds is 0' );
    ok( !$dur->is_positive, 'not positive' );
    ok( !$dur->is_negative, 'not negative' );
}

{
    my $dt1 = DateTime->new( year => 2003, month => 12, day => 31 );
    my $dt2 = $dt1->clone->subtract( months => 1 );

    is( $dt2->year,  2003, '2003-12-31 - 1 month = 2003-11-30' );
    is( $dt2->month, 11,   '2003-12-31 - 1 month = 2003-11-30' );
    is( $dt2->day,   30,   '2003-12-31 - 1 month = 2003-11-30' );
}

{
    my $date1 = DateTime->new(
        year       => 2001, month  => 5, day    => 10,
        hour       => 4,    minute => 3, second => 2,
        nanosecond => 12,
        time_zone  => 'UTC'
    );

    my $date2 = DateTime->new(
        year       => 2001, month  => 6, day    => 12,
        hour       => 5,    minute => 7, second => 23,
        nanosecond => 7,
        time_zone  => 'UTC'
    );

    my $dur = $date2->subtract_datetime_absolute($date1);

    is( $dur->delta_months,  0,         'delta_months is 0' );
    is( $dur->delta_minutes, 0,         'delta_minutes is 0' );
    is( $dur->delta_seconds, 2_855_060, 'delta_seconds is 2,855,060' );
    is(
        $dur->delta_nanoseconds, 999_999_995,
        'delta_seconds is 999,999,995'
    );
}

{
    my $date1 = DateTime->new(
        year      => 2001, month  => 5, day    => 10,
        hour      => 4,    minute => 3, second => 2,
        time_zone => 'UTC'
    );

    my $date2 = DateTime->new(
        year      => 2001, month  => 6, day    => 12,
        hour      => 5,    minute => 7, second => 23,
        time_zone => 'UTC'
    );

    my $dur = $date1->subtract_datetime_absolute($date2);

    is( $dur->delta_months,      0,          'delta_months is 0' );
    is( $dur->delta_minutes,     0,          'delta_minutes is 0' );
    is( $dur->delta_seconds,     -2_855_061, 'delta_seconds is -2,855,061' );
    is( $dur->delta_nanoseconds, 0,          'delta_nanoseconds is 0' );
}

{
    my $date1 = DateTime->new( year => 2003, month => 9,  day => 30 );
    my $date2 = DateTime->new( year => 2003, month => 10, day => 1 );

    my $date3 = DateTime->new( year => 2003, month => 10, day => 31 );
    my $date4 = DateTime->new( year => 2003, month => 11, day => 1 );

    my $date5 = DateTime->new( year => 2003, month => 2, day => 28 );
    my $date6 = DateTime->new( year => 2003, month => 3, day => 1 );

    my $date7 = DateTime->new( year => 2003, month => 1, day => 31 );
    my $date8 = DateTime->new( year => 2003, month => 2, day => 1 );

    foreach my $p (
        [ $date1, $date2 ],
        [ $date3, $date4 ],
        [ $date5, $date6 ],
        [ $date7, $date8 ],
        ) {
        my $pos_diff = $p->[1]->subtract_datetime( $p->[0] );

        is( $pos_diff->delta_days,   1, "1 day diff at end of month" );
        is( $pos_diff->delta_months, 0, "0 month diff at end of month" );

        my $neg_diff = $p->[0]->subtract_datetime( $p->[1] );

        is( $neg_diff->delta_days,   -1, "-1 day diff at end of month" );
        is( $neg_diff->delta_months, 0,  "0 month diff at end of month" );
    }
}

{
    my $dt1 = DateTime->new(
        year      => 2005, month => 6, day => 11,
        time_zone => 'UTC',
    );

    my $dt2 = DateTime->new(
        year      => 2005, month => 11, day => 10,
        time_zone => 'UTC',
    );

    my $dur    = $dt2->subtract_datetime($dt1);
    my %deltas = $dur->deltas;
    is( $deltas{months},  4,  '4 months - smaller day > bigger day' );
    is( $deltas{days},    29, '29 days - smaller day > bigger day' );
    is( $deltas{minutes}, 0,  '0 minutes - smaller day > bigger day' );

    is(
        DateTime->compare( $dt1->clone->add_duration($dur), $dt2 ), 0,
        '$dt1 + $dur == $dt2'
    );

    # XXX - this does not work, nor will it ever work
    #    is( $dt2->clone->subtract_duration($dur), $dt1, '$dt2 - $dur == $dt1' );
}

{
    my $dt1 = DateTime->new(
        year      => 2005, month => 6, day => 11,
        time_zone => 'UTC',
    );

    my $dt2 = DateTime->new(
        year      => 2005, month => 11, day => 10,
        time_zone => 'UTC',
    );

    my $dur    = $dt2->delta_days($dt1);
    my %deltas = $dur->deltas;
    is( $deltas{months},  0,   '30 months - smaller day > bigger day' );
    is( $deltas{days},    152, '152 days - smaller day > bigger day' );
    is( $deltas{minutes}, 0,   '0 minutes - smaller day > bigger day' );

    is(
        DateTime->compare( $dt1->clone->add_duration($dur), $dt2 ), 0,
        '$dt1 + $dur == $dt2'
    );
    is(
        DateTime->compare( $dt2->clone->subtract_duration($dur), $dt1 ), 0,
        '$dt2 - $dur == $dt1'
    );
}

{
    my $dt = DateTime->new(
        year      => 2012,
        month     => 6,
        day       => 30,
        time_zone => 'floating',
    );

    my $default = $dt->clone()->subtract( months => 1 );
    is(
        $default->format_cldr('yyyy-MM-dd'),
        '2012-05-31',
        'default subtract uses preserve end_of_month mode'
    );

    my $with_mode = $dt->clone()->subtract(
        months       => 1,
        end_of_month => 'limit',
    );
    is(
        $with_mode->format_cldr('yyyy-MM-dd'),
        '2012-05-30',
        'set end_of_month mode to limit in call to subtract()'
    );

}

{
    my $dt = DateTime->new(
        year      => 2014,
        month     => 7,
        day       => 3,
        time_zone => 'floating',
    );

    $dt->subtract( days => 2 );
    is(
        $dt->date, '2014-07-01',
        'subtracting 2 days from a floating datetime'
    );
}

done_testing();