TL;DR

On with TASK #2 from The Weekly Challenge #183. Enjoy!

# The challenge

You are given two dates, $date1 and $date2 in the format YYYY-MM-DD.

Write a script to find the difference between the given dates in terms on years and days only.

Example 1

Input: $date1 = '2019-02-10'$date2 = '2022-11-01'
Output: 3 years 264 days


Example 2

Input: $date1 = '2020-09-15'$date2 = '2022-03-29'
Output: 1 year 195 days


Example 3

Input: $date1 = '2019-12-31'$date2 = '2020-01-01'
Output: 1 day


Example 4

Input: $date1 = '2019-12-01'$date2 = '2019-12-31'
Output: 30 days


Example 5

Input: $date1 = '2019-12-31'$date2 = '2020-12-31'
Output: 1 year


Example 6

Input: $date1 = '2019-12-31'$date2 = '2021-12-31'
Output: 2 years


Example 7

Input: $date1 = '2020-09-15'$date2 = '2021-09-16'
Output: 1 year 1 day


Example 8

Input: $date1 = '2019-09-15'$date2 = '2021-09-16'
Output: 2 years 1 day


# The questions

Why on earth are we mixing years and days without giving a proper specification about what weâ€™re expecting back?

OK, weâ€™re going to assume that the following is correct:

• advance date1 for as many years as the difference in years between the two dates
• if the landing date1 does not exist (Feb 29th in a non-leap year) go back one day
• if the landing date1 comes after date2, go back one year
• count the number of days

# The solution

I hate this kind of date processing stuff, especially for such under-specified situations. If thereâ€™s anything I learned is that any serious date business MUST be done with the right library, and this is definitely something that a library does not help with.

In addition, I generally hate having to deal with date fiddling.

Last, I hate having to hate.

So Iâ€™m doing this only for manwar, who is a nice person.

For this reason, this is my Perl solution:

#!/usr/bin/env perl
use v5.24;
use warnings;
use experimental 'signatures';
no warnings 'experimental::signatures';

use Time::Local 'timegm';
use Test::More;

my @tests = qw<

2019-02-10 2022-11-01 3 264
2020-09-15 2022-03-29 1 195
2019-12-31 2020-01-01 0 1
2019-12-01 2019-12-31 0 30
2019-12-31 2020-12-31 1 0
2019-12-31 2021-12-31 2 0
2020-09-15 2021-09-16 1 1
2019-09-15 2021-09-16 2 1

2019-02-28 2020-02-27 0 364
2019-02-28 2020-02-28 1 0
2019-02-28 2020-02-29 1 1
2019-02-28 2020-03-01 1 2
2019-03-01 2020-02-28 0 364
2019-03-01 2020-02-29 0 365
2019-03-01 2020-03-01 1 0

2020-02-28 2021-02-27 0 365
2020-02-28 2021-02-28 1 0
2020-02-28 2021-03-01 1 1
2020-02-29 2021-02-28 0 365
2020-02-29 2021-03-01 1 1

>;

while (@tests) {
my ($date1,$date2, $exp_years,$exp_days) = splice @tests, 0, 4;
my ($got_years,$got_days) = years_and_days($date1,$date2);
is $got_years,$exp_years, "($date1 vs$date2) years OK";
is $got_days,$exp_days, "($date1 vs$date2) days OK";
}

done_testing();

sub years_and_days ($date1,$date2) {
my %parts_of;
for my $date ($date1, $date2) { complain($date, 'undefined') unless defined $date; my @pts =$date =~ m{\A ([1-9]\d{3})-(\d\d)-(\d\d) \z}mxs
or complain($date, 'no match'); complain($date, 'too low') unless $pts[0] > 1585;$_ += 0 for @pts;
$parts_of{$date} = {
year => $pts[0], in_year => sprintf('%02d-%02d',$pts[1], $pts[2]), month =>$pts[1],
day => $pts[2], }; date_exists($parts_of{$date}) or complain($date, 'inexistent');
# OK, the date is valid
}

complain($date2, 'too early') if$date2 lt $date1; ($date1, $date2) = @parts_of{$date1, $date2}; # first rought evaluation of years between my$years = $date2->{year} -$date1->{year};
my $days = undef; # if the "in year" day of the first date comes before the corresponding # in the second one, just count the days in between if ($date1->{in_year} le $date2->{in_year}) { set_year($date1, $date2->{year});$days = days_diff($date1,$date2);
}

# otherwise we have to "lend a year" and calculated days across the
# change of the year by setting the first date in the previous year
else {
--$years; set_year($date1, $date2->{year} - 1);$days = days_diff($date1,$date2);
}

return ($years,$days);
}

sub epc ($d) { timegm(30, 30, 12,$d->{day}, $d->{month} - 1,$d->{year}) }
sub date_exists ($date) { eval { epc($date); 1 } }

sub set_year ($date,$year) {
$date->{year} =$year;

# We might land a valid starting Feb 29th onto an invalid Feb 29th so we
$date->@{qw< day in_year >} = (28, '02-28') unless date_exists($date);
return $date; } sub days_diff ($date1, $date2) { my$epoch1 = epc($date1); my$epoch2 = epc($date2); return int(($epoch2 + 3600 - $epoch1) / (24 * 3600)); } sub complain ($date, $reason) { die "invalid date:$reason\n" unless defined $date; die "invalid date '$date': \$reason\n";

No Raku this time, because I donâ€™t want to lose the -Ofun!