[Chicago-talk] Calculating Holidays

Alan Mead amead2 at alanmead.org
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

because Delta_Format advertises:

    %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):

[amead at cow3 perl]$ ./tmp.pl
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, 
2013 in about 19 days

And below is the script.



use strict;
use warnings;

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

# read the holiday data
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


{ # 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 
in about %.0f days\n", $inseconds/(24*3600);


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 );


# cut-and-pasted from 
# 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...

Alan D. Mead, Ph.D.
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>

More information about the Chicago-talk mailing list