Tags

, , , ,

No, not just for the Christmas season, for a treasure trove of many holidays in many countries, all accessible by a REST API with JSON payloads, go to Holiday API where, for a modest fee (historical data are free for non-commercial use), a developer can get access to a very large database of past, present, and future holidays for many countries around the world. And now there is a Perl 6 API available for it! Scroll all the way down to the bottom of the home page to see it nestled snuggly in alphabetical order along with the popular languages: Go, Node.js, Perl 6, PHP, Python, and Ruby. (Notice there are some pretty high-flying companies who have been customers.)

The HolidayAPI website

The basic business model is: the owner, Josh, collects holiday information from countries around the world and makes the information available as JSON strings. In order to access the data one has to establish a free account which gives you a unique API key to accompany every https request for data.

I found the website when I needed a sole source for US holidays for improving and simplifying my 20-year-old calendar-making program. At the bottom of the single-page website I saw the list of published API languages, so I looked into them and found they were available on Josh’s Github account.

Using the existing APIs as a guide, I soon had a Perl 6 version cobbled together and working on historical data. The only problem I had with his data is that I could not find the holiday Armed Forces Day for the US. That holiday is also found in lots of other countries, but I did inform him of that shortcoming (and just filed a bug report). I paid for a month’s worth of service and got my required US holiday information for 2019.

The holiday data format

Here is a portion of the data from 2017 so you can see the format:

{
    "status": 200,
    "holidays": {
        "2017-12-31": [
            {
                "name": "New Year's Eve",
                "date": "2017-12-31",
                "observed": "2017-12-31",
                "public": true
            },
            {
                "name": "Sixth Day of Kwanzaa",
                "date": "2017-12-31",
                "observed": "2017-12-31",
                "public": false
            }
        ]
    }
}

Then the fun really started as I tried to use the data I had downloaded. Unfortunately, Josh’s brain and mine don’t see the data layout in the same way (although his format is probably the natural one to use when generating the data file). From my view, I would have keyed the individual holiday hashes by name and eliminated the duplication of the date, so the fragment above would look like this:

{
    "status": 200,
    "holidays": {
        "2017-12-31": {
            "New Year's Eve":  {
                "observed": "2017-12-31",
                "public": true
            },
            "Sixth Day of Kwanzaa": {
                "observed": "2017-12-31",
                "public": false
            }
        }
    }
}

But, given the existing format, I used this code to interpret it (I needed all the debug lines to ensure all was being interpreted properly):

use JSON::Tiny;
use Data::Dump;
our constant $afday = 'Armed Forces Day';
my $debug = 0;
my $jstr = slurp $jfil;  # $jfil is the JSON data from HolidayAPI
my %j = from-json $jstr; # the resultant hash
say Dump(%j, :color(False), :indent(4)) if $debug; # a dump to check format
# extract data into the incoming %holidays hash
# be sure to add the US Armed Forces Day holiday if not found
my $afday-found = 0;
for %j.keys.sort -> $k {
    say "  k: $k" if $debug;
    my @val = %j{$k};
    say "  type of \@val: {@val.^name}" if $debug;
    for @val -> @v {
        say "    type of \@v: {@v.^name}" if $debug;
        for @v -> $v {
            # this is the object of interest
            say "      type of \$v: {$v.^name}" if $debug;
            my $n = $v;
            # skip if not in our interest list
            unless %hnames{$n} {
                say "NOTE: skipping unwanted holiday '$n'..." if !$quiet;
                next;
            }
            ++$afday-found if $n eq $afday;
            # is it a Federal holiday?
            # using the holidayapi name:
            my $is-federal = %fed-holidays{$n} ?? 1 !! 0;
            # NOW TRANSLATE NAME TO OUR VERSION
            say "DEBUG: holidayapi name:  '$n'" if $debug;
            $n = %hnames{$n};
            say "DEBUG: our holiday name: '$n'" if $debug;
            my $d = $v;
            my $o = $v;
            my $p = $v;
            if $p ~~ /:i true/ {
                $p = 1;
            }
            elsif $p ~~ /:i false/ {
                $p = 0;
            }
            else {
                die "FATAL: Unrecognized 'public' value '$p'";
            }
            # check for dups
            if %holidays{$year}{$n} {
                die "FATAL: Dup holiday ($year) name '$n'";
            }
            %holidays{$year}{$n}       = $d;
            %holidays{$year}{$n}   = $o;
            %holidays{$year}{$n}     = $p;
            %holidays{$year}{$n} = $is-federal;
        }
        #say $v.^name;
    }
    #say "";
    #say "  v: $v";
}
# now add the US Armed Forces Day holiday if not found (these
# missing data were obtained from another source via an Internet
# search)
if !$afday-found {
    my $n = $afday;
    if %holidays{$year}{$n}:exists {
        die "FATAL: Dup holiday ($year) name '$n'";
    }
    my $day   = %afdays{$year}; # two digits as a string
    my $month = '05'; # always May
    my $date  = "{$year}-05-{$day}";
    %holidays{$year}{$n}       = $date;
    %holidays{$year}{$n}   = $date;
    %holidays{$year}{$n}     = 1;
    %holidays{$year}{$n} = 0;
}

Future data access

In order to access future data, the user must provide an electronic payment method such as a credit card. The account will be set up for auto-payment monthly or yearly, but Josh agreed I could use the service for one month and he would stop payment. I plan to do that every year as long as I keep producing a highly personalized calendar for my wife. The calendar code is being converted from Perl 5 to Perl 6, and I plan to make it a module when conversion and cleanup is complete. A copy of the 2017 version of the calendar is available here.

“Now for the rest of the story…” (thanks, Paul Harvey!)

Once I got a successful calendar and had incorporated his data satisfactorily, I thought to propose a Perl 6 API for the website and asked Josh if he would start an empty repo for it. He then created perl6-holidayapi so I could fork it, produce the new API, and submit a pull request (PR). I then got to work: I cleaned up my code, ran some local tests to ensure it worked, and submitted the PR. I kept checking as the weeks went by and finally saw Perl 6 listed as one of the available APIs! But, sadly, I saw that he hadn’t merged my PR. I then pinged him about it, trying not to be a pest, and just recently he did merge the code.

Postscript

Can I claim the honor of having the first published Perl 6 API for a commercial website? Who knows, but it’s exciting to see the Perl 6 name on the website nestled alphabetically between Go and Ruby.

 

Advertisements