Peter
Peter Campbell Smith

Unique arrays and
differing dates

Weekly challenge 183 — 19 September 2022

Week 183 - 19 Sep 2022

Task 2

Task — Date difference

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 of years and days only.

Examples


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

Analysis

Date and time calculations are always complicated by leap years, and if time of day is included, by daylight saving time and in the extreme by leap seconds. Thankfully we only have leap years to consider in this task.

From the examples given we may deduce that a 'year' means the period between a 'date' (month and day) in one year until the same date in the following year, which could be 365 or 366 days. So what we are looking for is the number of 'years' from $date1 until the last 'date' preceding $date2, and then the number of days between that 'date' and $date2.

Let's start with $date1. Make a note of its month and day - call that $anniversary. Step forward day by day until you get to $date2, incrementing $days by one each day, or if the date matches $anniversary, increment $years and set $days to zero.

The only slightly messy bit is incrementing the date, but timelocal/localtime make that easy enough. Note that the Posix version of timelocal can handle signed 64 bit second values, so that covers any sensible dates.

If you're looking at really long gaps between $date1 and $date2 of course this method is relatively slow. But on my machine it comes up with the late Queen Elizabeth's lifespan as 96 years 140 days in just a few seconds.

Try it 

Try running the script with any input:



example: 2000-01-01



example: 2023-12-31

Script


#!/usr/bin/perl

# Peter Campbell Smith - 2022-09-20
# PWC 183 task 2

use v5.28;
use utf8;
use warnings;
use Time::Local qw(timelocal_posix);

my (@tests, $test, $date1, $date2, $days, $years, $anniversary, $month_day);

@tests =  (
    ['2019-02-10', '2022-11-01'],
    ['2020-09-15', '2022-03-29'],
    ['2019-12-31', '2020-01-01'],
    ['2019-12-01', '2019-12-31'],
    ['2019-12-31', '2020-12-31'],
    ['2019-12-31', '2021-12-31'],
    ['2020-09-15', '2021-09-16'],
    ['2019-09-15', '2021-09-16'],
    ['1926-04-21', '2022-09-08'],
    ['2022-09-20', '2022-09-19']);

# loop over input tests
for $test (@tests) {
    ($date1, $date2) = @$test;
    
    # output input
    say qq[\nInput:  \$date1 = $date1\n        \$date2 = $date2];
    if ($date2 lt $date1) {
        say qq[\$date2 = $date2 precedes \$date1 = $date1];
        next;
    }
    
    # initialise
    $years = 0;
    $days = 0;
    $anniversary = substr($date1, 5, 5);   # month and day of date1
    
    # increment date1 until it reaches date2
    while ($date1 lt $date2) {
        $date1 = date_add($date1, 1);
        $month_day = substr($date1, 5, 5);
        
        # if month and day match date1, increment years and reset days to 0
        if ($month_day eq $anniversary) {
            $years ++;
            $days = 0;
            
        # otherwise just increment days
        } else {
            $days ++;
        }
    }
    
    # format per Mohammad
    say qq[Output: ] . ($years == 0 ? '' : ($years == 1 ? '1 year ' : qq[$years years ])) .
        ($days == 0 ? '' : ($days == 1 ? '1 day' : qq[$days days]))
}
    
sub date_add {  #  (date, days)

    # adds days to date
    my (@tt);
    if ($_[0] =~ m|^(....)-(..)-(..)|) {
        @tt = localtime(timelocal_posix(0, 0, 12, $3, $2 - 1, $1 - 1900) + $_[1] * 86400);
        return sprintf('%04d-%02d-%02d', $tt[5] + 1900, $tt[4] + 1, $tt[3]);
    } else {
        return 0;
    }
}
    

Output


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

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

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

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

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

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

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

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

Input:  $date1 = 1926-04-21
        $date2 = 2022-09-08
Output: 96 years 140 days

Input:  $date1 = 2022-09-20
        $date2 = 2022-09-19
$date2 = 2022-09-19 precedes $date1 = 2022-09-20