Beefy Boxes and Bandwidth Generously Provided by pair Networks
Problems? Is your data what you think it is?
 
PerlMonks  

Custom date calculations using Date::Manip

by cavac (Curate)
on Nov 27, 2019 at 17:57 UTC ( #11109316=perlmeditation: print w/replies, xml ) Need Help??

Some time ago, i uploaded Acme::September::Eternal to cpan. Then, on request by stevieb at Re^3: Help with my crap, i got a variant of that project.

To quote: "I'm Canadian, so for us folk, it would be preferred if it was eternaldecemberize() where there's a single month of warm weather (August), and the rest December"

I have uploaded Acme::December::Eternal to CPAN. But before that, i wrote a small test program to check out the math involved. The premise is that the year starts on 1st of August, and except of August everything else is the month of December. Let's take a look (you might need to click, i've put most of the article into a readmore tag)...

#!/usr/bin/env perl use strict; use warnings; use Date::Manip; use Lingua::EN::Numbers::Ordinate; my $date = Date::Manip::Date->new(); $date->parse('2019-07-31'); my $month = $date->printf('%m'); $month =~ s/^0+//g; my $dayofyear = $date->printf('%j'); $dayofyear =~ s/^0+//g; my $dayofmonth = $date->printf('%d'); $dayofmonth =~ s/^0+//g; my $year = $date->printf('%Y'); # December starts on 1st of September... my $septstart = Date::Manip::Date->new(); $septstart->parse($year . '-09-01'); my $septday = $septstart->printf('%j'); $septday =~ s/^0+//g; my $outstring = ''; if($month == 8) { # August $outstring = ordinate($dayofmonth) . ' August ' . $year; } else { # December if($month > 8) { my $daycount = $dayofyear - $septday + 1; $outstring = ordinate($daycount) . ' December ' . $year } else { # Uh-oh, this is the continuation of last years december... my $decend = Date::Manip::Date->new(); $decend->parse($year . '-12-31'); my $decday = $decend->printf('%j'); $decday =~ s/^0+//g; my $prevyeardays = $decday - $septday + 1; my $daycount = $dayofyear + $prevyeardays; $year--; # previous years december $outstring = ordinate($daycount) . ' December ' . $year } } print $outstring, "\n";

Ok, that was quite a lot, let's go through it step by step:

#!/usr/bin/env perl use strict; use warnings; use Date::Manip; use Lingua::EN::Numbers::Ordinate; my $date = Date::Manip::Date->new(); $date->parse('2019-07-31');

The scripts starts with the usual boilerplate stuff, then loads Date::Manip. We also load Lingua::EN::Numbers::Ordinate, because that helps us to turn the number "1" into "1st", "2" to "2nd" and whatever else you crazy english speaking people do to numbers ;-)

We also make a Date::Manip::Date object with the date we want to turn into an EternalDecembertm date (this is a simple test script, no interactivity required).

my $month = $date->printf('%m'); $month =~ s/^0+//g; my $dayofyear = $date->printf('%j'); $dayofyear =~ s/^0+//g; my $dayofmonth = $date->printf('%d'); $dayofmonth =~ s/^0+//g; my $year = $date->printf('%Y');

In this step, we extract some values out of the date object. We need to know the year, the month, the day of the month (in case of August) and the day of the year (in case of not-August). We also remove leading zeros from the strings. The CPAN module does some extra stuff, but for the date calculation test this is enough.

# December starts on 1st of September... my $septstart = Date::Manip::Date->new(); $septstart->parse($year . '-09-01'); my $septday = $septstart->printf('%j'); $septday =~ s/^0+//g;

In case of not-August, we need to know the day of the year for the first of September as well (e.g. the first day of our EternalDecember). For this, we use the year extracted from the original date.

my $outstring = ''; if($month == 8) { # August $outstring = ordinate($dayofmonth) . ' August ' . $year;

August is easy, we just basically reassemble the string based on the original date values.

} else { # December if($month > 8) { my $daycount = $dayofyear - $septday + 1; $outstring = ordinate($daycount) . ' December ' . $year

If the original date involves the months september, october, november or december, we just calculate the number of days since first of september, call it december and we are good.

} else { # Uh-oh, this is the continuation of last years december... my $decend = Date::Manip::Date->new(); $decend->parse($year . '-12-31'); my $decday = $decend->printf('%j'); $decday =~ s/^0+//g; my $prevyeardays = $decday - $septday + 1; my $daycount = $dayofyear + $prevyeardays; $year--; # previous years december $outstring = ordinate($daycount) . ' December ' . $year } }

This is the special case of the lot. When the original date is before August, in our calendar this is still last years december. So need to add all the days beginning from last years 1st of September to year end, plus all the days in the current year up to our original date (e.g. day of the year).

You may note here that i don't account for the previous year being a leap year when getting the "day of the year" values for september-december. While leap years shift the offset of those days by one, they shift BOTH offsets at the same time. Since we calculate the difference, this will still result in the correct value.

Note: This is still technically incorrect, because there have been instances where countries skipped days on the calendar. For example, Samoa skipped the 30th of December 2011 so they could get to the other side of the dateline for some business reason. I'll have to fix my CPAN module to account for that kind of stuff.

print $outstring, "\n";

We output the calculated date string and we are done.

Note 2: Calculating exact dates in the future is not possible, no matter how good your date library is. Not only for reasons like countries skipping (or adding) days, switching calendars, changing timezones on short notice or redefining daylight savings time (or banning/reintroducing it) willy-nilly after every change of government. No, even if you could fix all that (hint: go into politics), the nice folks at the International Earth Rotation and Reference System Service who keep our atomic clocks in sync with our solar system release a new Bulletin C twice a year to tell you if there is going to be a leap second at the end of the six month period. So, good luck estimating future timestamps ;-)

perl -e 'use Crypt::Digest::SHA256 qw[sha256_hex]; print substr(sha256_hex("the Answer To Life, The Universe And Everything"), 6, 2), "\n";'

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlmeditation [id://11109316]
Front-paged by haukex
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others about the Monastery: (6)
As of 2019-12-05 22:42 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    Strict and warnings: which comes first?



    Results (153 votes). Check out past polls.

    Notices?