TL;DR

Here we are with TASK #1 from The Weekly Challenge #138. Enjoy!

The challenge

You are given a year, $year in 4-digits form.

Write a script to calculate the total number of workdays in the given year.

For the task, we consider, Monday - Friday as workdays.

Example 1

Input: $year = 2021
Output: 261

Example 2

Input: $year = 2020
Output: 262

The questions

I guess that the laconic assertion about what workdays are put an end to all questions: there’s no holidays, vacations, or other fancy stuff. Just plain weeks with 5 workdays each. Right?!?

I’d probably ask if we can stick to dates in the Gregorian Calendar but I won’t wait for the answer and assume it’s a yes.

The solution

I already said that Mohammad Sajid Anwar is amazing and this challenge shows how kind he is: we can reuse a lot from the previous week!

We can look at the year by taking into consideration three parts:

  • the first week, considering it from the first day of the year up to and including the first Sunday;
  • the last week, considering it from the last Monday up to the end of the year
  • the rest of the year, which will by definition be composed by complete weeks only.

We will stick to the convention that Monday is 1 up to Sunday that is 7. If the first day of the week is $m$, then the first week:

  • will contain $n = 8 - m$ days, and
  • will contain $max(0, n - 2)$ work days.

Not convinced about that weird $max(\cdot)$? The first week wil always contain a Sunday, and possibly a Saturday, so whatever number of workdays will always have to exclude these two days. When we calculate $n - 2$, if we get a negative number then there are surely no workdays, so we use $max(\cdot)$ to clamp the value to $0$. It’s a math trick.

Similarly, the last week will have a number of days corresponding to the number of the last day. In this case, though, it will always start from Monday and increase with workdays up to a maximum of $5$. So, if the number of days in the week is $k$, the number of workdays in that week will be $min(k, 5)$, underlining that there’s a cap to 5 workdays in the week.

Last, the core of the year will have a number $d$ of says that is a multiple of $7$. Out of them, exactly $5$ are workdays… so the computation is easy: $\frac{7}{5}d$.

Enough talking, let’s get to the Raku code:

#!/usr/bin/env raku
use v6;

subset FullyGregorianYear of Int where * > 1582;
sub workdays (FullyGregorianYear $y) {
   my $bdow = Date.new($y, 1, 1).day-of-week;
   my $edow = Date.new($y, 12, 31).day-of-week;
   my $bdays = 8 - $bdow; # 1 - 7
   my $ydays = 365 + ($bdow == $edow ?? 0 !! 1) - $bdays - $edow;
   return max($bdays - 2, 0) + ($ydays / 7 * 5).Int + min($edow, 5);
}

sub MAIN (FullyGregorianYear $y = 2021) { workdays($y).put }

Time for Perl now:

#!/usr/bin/env perl
use v5.24;
use warnings;
use experimental 'signatures';
no warnings 'experimental::signatures';
use Time::Local 'timegm';
use List::Util qw< min max >;

sub dow ($y, $m, $d) { (gmtime(timegm(1, 1, 1, $d, --$m, $y)))[6] }
sub workdays ($y) {
   my $bdow = dow($y, 1, 1);
   my $edow = dow($y, 12, 31);
   my $bdays = 8 - $bdow; # 1 - 7
   my $ydays = 365 + ($bdow == $edow ? 0 : 1) - $bdays - $edow;
   return max($bdays - 2, 0) + ($ydays / 7 * 5) + min($edow, 5);
}

say workdays(shift // 2021);

I know, I know… I’m always translating stuff, but it works!!!