Calculate the difference between two dates as years-and-days but without months?

Viewed 63

I'm trying to calculate the difference between two dates (the early date is June 11th, 1847) as a the number of years plus the number of days. If the second date were June 9th of 2021, I'd like to get back 173 years and 363 days.

I've been using this, though it's not quite what I want.

my $firstdate = DateTime::Format::Strptime->new(pattern => '%Y%m%d')->parse_datetime($papers{$paper}->{'firstday'});
my $today = DateTime::Format::Strptime->new(pattern => '%Y%m%d')->parse_datetime($date);
my $diff = $today - $firstdate;
printf("%d years and %d days\n", $diff->in_units(qw( years days )));

The output looks something like this:

173 years and 29 days

Of course, it's obvious why this is the case if I put months back into the in_units() method... it's because it's calculating those separately, and with the months included it'd give output of 11 months. Plus 29 days, and that's the 363 I want.

Given the regrettable fact of the occasional leap year, this isn't so simple as using the modulus operator.

Is there an elegant way to do this, or barring that, is there a practical way to do this?

1 Answers

Here's a way using DateTime::Format::Duration to compute how many days are between two dates:

#!/usr/bin/env perl
use strict;
use warnings;
use DateTime;
use DateTime::Format::Strptime;
use DateTime::Format::Duration;

# Given a DateTime, return one for the start of its year
sub first_day_of_year {
    my $dt = shift;
    return DateTime->new(year => $dt->year, month => 1, day => 1);
}

# Given a DateTime, return one for the end of its year
sub last_day_of_year {
    my $dt = shift;
    return DateTime->new(year => $dt->year, month => 12, day => 31);
}

sub years_and_days {
    my ($from, $to) = @_;

    my $formatter = DateTime::Format::Duration->new(normalize => 1, pattern => '%j');

    # Compute the years between two dates
    my $diff = $to - $from;
    my $years = $diff->in_units('years');

    # Simple case: dates less than a year apart
    if ($years == 0) {
        return (0, $formatter->set_base($from)->format_duration($diff));
    }

    # Calculate the days by working out the days from the $from date to
    # the end of its year and the days from the beginning of the $to
    # date's year to it.
    my $from_eoy = last_day_of_year $from;
    my $to_soy = first_day_of_year $to;
    my $first_days = $formatter->set_base($from_eoy)->format_duration($from_eoy - $from);
    my $last_days = $formatter->set_base($to_soy)->format_duration($to - $to_soy);
    my $days = $first_days + $last_days;

    return ($years, $days);
}


my $parser = DateTime::Format::Strptime->new(pattern => '%Y%m%d');
my $firstdate = $parser->parse_datetime('18470611');
my $today = $parser->parse_datetime('20210609');

my ($years, $days) = years_and_days $firstdate, $today;
# 173 years and 363 days.
printf "%d years and %d days.\n", $years, $days;
Related