# [Chicago-talk] Calculating Holidays

Thu Jul 12 22:05:01 PDT 2012

```On 7/12/2012 8:45 PM, Joel Berger wrote:
> I tried this using a free web service, and I got right to the point of
> dealing with DateTime math, so I leave that to you :-)
>
> The webservice is at http://www.holidaywebservice.com
> my code is at https://gist.github.com/3102219
>
> All it does now is get the holidays this month. From there, after
> deciding if you have passed the last one, add a month and request
> again. Repeat as needed.

I took a crack at it as well.  My goal was simplicity without using a
lot of complex (and perhaps poorly documented) objects that like
DateTime::Set::ICal .

I saw magically generating holidays as the hard part and I'd prefer not
to be tied to a web service, so I found a list of holidays online and
pasted them into a __DATA__ section.

Once you have that semi-ugly solution, the date logic is very simple:
just compare the whole list of dates to today, or a date of your choice;
discard past dates and return the date with the smallest delta.

I don't know what Richard is trying to do, but this script gives
complete control over what's a holiday and once you find the holidays,
it's self-contained.  Of course,  it will run out of holidays some day;
as written, the script will stop working in 2014.  It probably wouldn't
take long to add 50 years of holidays. If you were running this script
several times a second on a server AND you had 50 years of holidays, you
might need a more refined/elegant algorithm (e.g., ignore all years'
data except this year and next year).

I did find Date::Manip extraordinarily frustrating to work with. This
line took 10 minutes of my life:

my \$sec = Delta_Format(\$d, 0, "%sh"); # convert to seconds

%Xv   :  print  the value of the field X

%Xd   :  print  the value of the field Xand  all
smaller units in terms of X

%Xh   :  print  the value of field Xand  all
larger units in terms of X

%Xt   :  print  the value of all fields in
terms of X

So I originally tried to convert to days directly:

my \$days = Delta_Format(\$d, 2, "%dt"); # convert to days

but this always returns zero because the deltas all look like
0:0:0:0:1272:0:0.  I would think that 1272 hours "in terms of X" where
X=days would be something like 53 days.  But I was wrong, somehow it's 0
days.  I have no idea why the deltas are like this. Date::Manip::Delta
seems to suggest that the deltas returned from Date::Manip functions
should be normalized (whatever "been made consistent with the type of
data they represent" means) and the deltas I was getting seems
non-normalized.  So maybe that's a bug or maybe there's some reason like
hours results in an integer representation or something.  But when I
switched to seconds, it works.  That kinda sucks, but who knows WTF is
supposed to be happening because X isn't actually ever defined in the
description of Delta_Format()  ... Maybe 'd' doesn't means days and is
undefined, so I get a zero. I first thought that X=4 because I wanted
the fourth field... But no, if you review Date::Manip::Delta ... well,
it certainly won't condescend to even mentioning Delta_Format() but it
does describe a printf method and says "Here, X is one of
(y,M,w,d,h,m,s)".  Examining Date::Manip::Examples might also have
helped clue me to X.  So, if you like programming to be like a text
adventure game... Date::Manip is the library for you!

Anyway, here's what I get when I run this today (just after midnight in
Chicago on 7/13/2012):

The next holiday is Labor Day on Sep 3, 2012 in about 52 days
The next holiday after Jan 2, 2013 is Martin Luther King Day on Jan 21,

And below is the script.

-Alan

#!/usr/bin/perl

use strict;
use warnings;

use Date::Manip;
# I'm going to assume DM6;see the Date::Manip documentation

our %holidays = ();
while (my \$line = <DATA>) {
\$line =~ s/[\r\n]+//g; # remove newlines
\$line =~ s/#.*\$//g;  # remove comments
\$line =~ s/^\s+//g;  # remove leading psaces
\$line =~ s/\s+\$//g;  # remove trailing psaces
\$line =~ s/\s*\|\s*/|/g;  # remove spaces around the delimiter
next unless( \$line ); # skip blank/comment lines
my(\$date, \$holiday) = split /\|/, \$line;
\$holidays{\$date} = \$holiday; # NB: don't reverse this
}

close(DATA);

{ # first example

my(\$inseconds, \$next_holday, \$next_date) = next_holiday();
printf "The next holiday is \$next_holday on \$next_date in about %.0f
days\n", \$inseconds/(24*3600);

}

{ # second example

my \$today = 'Jan 2, 2013';
my(\$inseconds, \$next_holday, \$next_date) = next_holiday( \$today );
printf "The next holiday after \$today is \$next_holday on \$next_date

}

sub next_holiday {
my \$from_date = shift || 'today';

my \$soonest = 32000000; # slightly more than a year of seconds
my \$soonest_date = '';
my \$soonest_hol = '';
for my \$date ( keys %holidays ) {
my \$d = DateCalc(\$from_date,\$date);
#print "date-\$date, hol=\$holidays{\$date}, d=\$d\n";
my \$sec = Delta_Format(\$d, 0, "%sh"); # convert to seconds
next unless( \$sec >= 0 ); # skip past holidays
#print "\$date, \$holidays{\$date} : sec=\$sec, soonest=\$soonest\n";
if( \$sec < \$soonest ) {
\$soonest = \$sec;
\$soonest_date = \$date;
\$soonest_hol = \$holidays{\$date};
}
}

return( \$soonest, \$soonest_hol, \$soonest_date );

}

__DATA__
# cut-and-pasted from
http://www.timeanddate.com/calendar/?year=2012&country=1
# and http://www.timeanddate.com/calendar/?year=2013&country=1
Jan 1, 2012|New Year's Day
Jan 2, 2012|New Year's Day observed
Jan 16, 2012|Martin Luther King Day
Feb 14, 2012|Valentine's Day
Feb 20, 2012|Presidents' Day
Apr 8, 2012|Easter Sunday
May 13, 2012|Mother's Day
May 28, 2012|Memorial Day
Jun 17, 2012|Father's Day
Jul 4, 2012|Independence Day
Sep 3, 2012|Labor Day
Oct 8, 2012|Columbus Day (Most regions)
Oct 31, 2012|Halloween
Nov 6, 2012|Election Day
Nov 11, 2012|Veterans Day
Nov 12, 2012|Veterans Day observed
Nov 22, 2012|Thanksgiving Day
Dec 24, 2012|Christmas Eve
Dec 25, 2012|Christmas Day
Dec 31, 2012|New Year's Eve
Jan 1, 2013|New Year's Day
Jan 21, 2013|Martin Luther King Day
Feb 14, 2013|Valentine's Day
Feb 18, 2013|Presidents' Day
Mar 31, 2013|Easter Sunday
May 12, 2013|Mother's Day
May 27, 2013|Memorial Day
Jun 16, 2013|Father's Day
Jul 4, 2013|Independence Day
Sep 2, 2013|Labor Day
Oct 14, 2013|Columbus Day (Most regions)
Oct 31, 2013|Halloween
Nov 11, 2013|Veterans Day
Nov 28, 2013|Thanksgiving Day
Dec 24, 2013|Christmas Eve
Dec 25, 2013|Christmas Day
Dec 31, 2013|New Year's Eve
# paste more data here for additional years...

--
Assistant Professor of Industrial and Organizational Psychology
College of Psychology
Illinois Institute of Technology
3101 South Dearborn, 2nd floor
Chicago IL 60616

+312.567.5933 (Campus)
+815.588.3846 (Home Office)
+312.567.3493 (Fax)

Announcing the Journal of Computerized Adaptive Testing (JCAT), a
peer-reviewed electronic journal designed to advance the science and
practice of computerized adaptive testing: http://www.iacat.org/jcat

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.pm.org/pipermail/chicago-talk/attachments/20120713/63e32fde/attachment-0001.html>
```