Weekdays, Daylight and Raku

by Arne Sommer

Weekdays, Daylight and Raku

Published 7. December 2019

This is my response to the Perl Weekly Challenge #37.

Challenge 37.1

Write a script to calculate the total number of weekdays (Mon-Fri) in each month of the year 2019.

Jan: 23 days
Feb: 20 days
Mar: 21 days
Apr: 22 days
May: 23 days
Jun: 20 days
Jul: 23 days
Aug: 22 days
Sep: 21 days
Oct: 23 days
Nov: 21 days
Dec: 22 days

This is straight forward when we use a «Date» object, so I have chosen to add some extras:

File: weekdays
unit sub MAIN (Int $year = 2019, Bool :$sum);                     # [1]

my @day-count;                                                    # [2]
my @month-name = ("", "Jan", "Feb", "Mar", "Apr", "May", "Jun",   # [3]
                      "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");

my $date = Date.new($year, 1, 1);                                 # [4]

while $date.year == $year                                         # [5]
{
  @day-count[$date.month]++ if $date.day-of-week <= 5;            # [6]
  $date.=later(days => 1);                                        # [7]
}

say "Year: $year" unless $year == 2019;                           # [8]
say "@month-name[$_]: @day-count[$_] days" for 1 .. 12;           # [9]
say "Total: { @day-count.sum}" if $sum;                           # [10]

[1] The program lists a lot of numbers (weekdays), but doesn't add them up. Use the «--sum» command line option to get the sum. Specify another year, if 2019 doesn't suit you.

[2] We keep track of the number of days in each month here.

[3] We need the month names (abridged) for the output. They are numbered 1..12 (when we inspect the «Date» object; see below), so the first value in this array (with index 0) is empty.

[4] Create a «Date» object for New Year's Day of the given year.

[5] As long as the date is in the same year,

[6] • increase the daycount (numer of weekdays) for the given month («.month») if the day is one of monday-friday («$date.day-of-week <= 5»).

[7] • get the next day. Note the «.=» method call and assignent, so that we get the new «Date» back in the same variable. This is the same as «$date = $date.later(days => 1), but shorter.

[8] Print the year, if we have specified one on the command line.

[9] Print the 12 months, with names and the count.

[10] Print the total numer of weekdays, if requested with the «--sum» command line option. Note the use of «.sum» on an array to get the sum.

See docs.raku.org/type/Date for more information about the «Date» class.

Running it without arguments gives the output requested by the challenge:

$ raku weekdays 
Jan: 23 days
Feb: 20 days
Mar: 21 days
Apr: 22 days
May: 23 days
Jun: 20 days
Jul: 23 days
Aug: 22 days
Sep: 21 days
Oct: 23 days
Nov: 21 days
Dec: 22 days

Get the sum of the weekdays:

$ raku weekdays --sum
Jan: 23 days
Feb: 20 days
Mar: 21 days
Apr: 22 days
May: 23 days
Jun: 20 days
Jul: 23 days
Aug: 22 days
Sep: 21 days
Oct: 23 days
Nov: 21 days
Dec: 22 days
Total: 261

Another year:

$ raku weekdays --sum 2020
Year: 2020
Jan: 23 days
Feb: 20 days
Mar: 22 days
Apr: 22 days
May: 21 days
Jun: 22 days
Jul: 23 days
Aug: 21 days
Sep: 22 days
Oct: 22 days
Nov: 21 days
Dec: 23 days
Total: 262

Challenge 37.2

Write a script to find out the DayLight gain/loss in the month of December 2019 as compared to November 2019 in the city of London. You can find out sunrise and sunset data for November 2019 and December 2019 for London

I am not sure what the challenge actually asks for, but have chosen to present the daylight loss/gain for each day in November compared with the same day in December.

This time I'll show the result first, and the program afterwards:

$ raku daylight
Day  November  December  Difference
 1   9:40:44    8:11:53   -1:28:51
 2   9:37:10    8:09:53   -1:27:17
 3   9:33:37    8:07:57   -1:25:40
 4   9:30:07    8:06:07   -1:24:00
 5   9:26:38    8:04:22   -1:22:16
 6   9:23:11    8:02:42   -1:20:29
 7   9:19:45    8:01:08   -1:18:37
 8   9:16:22    7:59:40   -1:16:42
 9   9:13:01    7:58:17   -1:14:44
10   9:09:42    7:57:00   -1:12:42
11   9:06:25    7:55:50   -1:10:35
12   9:03:11    7:54:45   -1:08:26
13   8:59:59    7:53:46   -1:06:13
14   8:56:50    7:52:54   -1:03:56
15   8:53:44    7:52:07   -1:01:37
16   8:50:40    7:51:27     -59:13
17   8:47:39    7:50:54     -56:45
18   8:44:42    7:50:27     -54:15
19   8:41:48    7:50:06     -51:42
20   8:38:57    7:49:52     -49:05
21   8:36:09    7:49:44     -46:25
22   8:33:25    7:49:43     -43:42
23   8:30:45    7:49:48     -40:57
24   8:28:09    7:50:00     -38:09
25   8:25:36    7:50:19     -35:17
26   8:23:08    7:50:44     -32:24
27   8:20:44    7:51:15     -29:29
28   8:18:24    7:51:53     -26:31
29   8:16:09    7:52:37     -23:32
30   8:13:59    7:53:27     -20:32
31              7:54:24

The program

Entering the daylight values feels wrong, as I am a programmer. So I have chosen to download the two web pages, and parse the data out of them.

File: daylight (partial)
use LWP::Simple;         # [1]

my %url =                # [2]
( 
  nov => 'https://www.timeanddate.com/sun/uk/london?month=11&year=2019',
  dec => 'https://www.timeanddate.com/sun/uk/london?month=12&year=2019'
);

my %data;                # [3]

read-data("nov");        # [4]
read-data("dec");        # [4]

[1] We use this module to feth the web pages. Install it with «zef» if you don't have it.

[2] The URLs for the the two web pages.

[3] The data structure to hold the daylight values.

[4] Read the values. (I'll show this procedure in the next section.)

Fetching web pages all the time when writing and modifying a program is a waste of time and resources, so I have added caching:

File: daylight (partial)
my %file =                             # [5]
(  
  nov => '2019-nov.html',
  dec => '2019-dec.html'
);

sub read-data ($month)                 # [6]
{
  my $line;                            # [7]
 
  if %file{$month}.IO.e                # [8]
  {
    $line = slurp %file{$month};       # [8a]
  }
  else                                 # [9]
  {
    $line = LWP::Simple.get(%url{$month}).lines.join("\n");  # [9a]
    spurt %file{$month}, $line;                              # [9b]
  }

  $line ~~ /\<tbody\>(.*?)\<\/tbody\>/; # [10]

  for $0.Str.split("</tr>") -> $line    # [11]
  {
    $line ~~                                                 # [12]
      /data\-day\=(\d+) \s .*? \"c \s tr \s sep\-l\"\>(\d+\:\d+\:\d+)\<\/td\>/;
      # ########## 12a ############################## 12b 12b 12b 12b #######

    next unless $0;                   # [13]

    %data{$month}{$0.Str} = $1.Str;   # [14]
  }
}

[5] The names for the cached file version of the web pages.

[6] The procedure doing the job (fetching the web page and building up the data structure).

[7] I read the entire file in as a single line, as the source doesn't have newlines where they are useful for my parsing.

[8] If we have the cached file, read that file (8b) - instead of fetching it from the web.

[9] If not cached, get the web page as a single line (9a) and save it to the cached file (9b).

[10] The tabular data is located in a html table, between the «<tbody>» and «</tbody>» tags. The data is in the «$0» match object.

[11] We have to iterate over the dates, and splitting on the «</tr>» tag (end of table row) works. Note that we get a false row after the last date when doing things this way, but (13) takes care of that.

[12] Get the day («data-day=XXX») and the daylight value (placed inside a unique markup like this: «<td class="c tr sep-l">8:11:53<td>». The other fields (columns in the html table) have other classes.

[13] Skip non-matching lines. See (11).

[14] Update the data structure. Note that I coerce the match object to a string to avoid problems later on.

Then all that is left is printing the values, and the difference. I could have written a custom class for the time values («hh:mm:ss»), but have chosen the old fashion procedure approach instead:

File: daylight (partial)
say "Day  November  December  Difference";        # [15]

for 1..30 -> $day                                 # [16]
{ 
  my $nov = %data<nov>{$day};                     # [17]
  my $dec = %data<dec>{$day};                     # [18]

  say "{ $day.fmt('%2d') } { $nov.fmt('%9s') }  { $dec.fmt('%9s') } " ~ # [19]
      "{ (s2hms( hms2s($dec) - hms2s($nov))).fmt('%10s') }";
}

say "{ "31".fmt('%2d') }            {  %data<dec>{31}.fmt('%9s') }";    # [20]

sub hms2s ($hms)                                  # [21]
{
  my @parts = $hms.split(":") // return 0;
  return @parts[0] * 60 * 60 + @parts[1] * 60 + @parts[2];
}

sub s2hms ($s is copy)                            # [22]
{
  my $sign = "";
  if $s < 0 { $s = -$s; $sign = "-"; } 
  
  my $h = $s div 3600; $s -=  $h * 3600;
  my $m = $s div   60; $s -=  $m *   60;

  return "$sign$h:{ $m.fmt('%02d') }:{ $s.fmt('%02d') }" if $h;
  return "$sign$m:{ $s.fmt('%02d') }" if $s;
  return "$sign$s";
}

[15] The table header.

[16] Foreach day (the first 30 ones only, as November stops there).

[17] • print the November daylight value.

[18] • ditto for December.

[19] • and the difference between the two values.

[20] The 31st December value.

[21] The procedure converting «hh:mm:ss» values to seconds.

[22] The procedure converting seconds to «hh:mm:ss» values.

Note the extensive use of «fmt» to right justify the values (and zero pad minutes and hours; we want «1:01:04» and not «1:1:4»). The format strings are the same as for «printf» and «sprintf».

The complete program:

File: daylight
use LWP::Simple;

my %file = (
             nov => '2019-nov.html',
             dec => '2019-dec.html'
	   );


my %url = (
            nov => 'https://www.timeanddate.com/sun/uk/london?month=11&year=2019',
            dec => 'https://www.timeanddate.com/sun/uk/london?month=12&year=2019'
	  );

my %data;

read-data("nov");
read-data("dec");

sub read-data ($month)
{
  my $line;
 
  if %file{$month}.IO.e
  {
    $line = slurp %file{$month};
  }
  else
  {
    $line = LWP::Simple.get(%url{$month}).lines.join("\n");
    spurt %file{$month}, $line;
  }

  $line ~~ /\<tbody\>(.*?)\<\/tbody\>/;

  for $0.Str.split("</tr>") -> $line
  {
    $line ~~ /data\-day\=(\d+) \s .*? \"c \s tr \s sep\-l\"\>(\d+\:\d+\:\d+)\<\/td\>/;

    next unless $0;

    %data{$month}{$0.Str} = $1.Str;
  }
}

say "Day  November  December  Difference";

for 1..30 -> $day
{ 
  my $nov = %data<nov>{$day};
  my $dec = %data<dec>{$day};

  say "{ $day.fmt('%2d') } { $nov.fmt('%9s') }  { $dec.fmt('%9s') } { (s2hms( hms2s($dec) - hms2s($nov))).fmt('%10s') }";
}

say "{ "31".fmt('%2d') }            {  %data<dec>{31}.fmt('%9s') }";

sub hms2s ($hms)
{
  my @parts = $hms.split(":") // return 0;
  return @parts[0] * 60 * 60 + @parts[1] * 60 + @parts[2];
}

sub s2hms ($s is copy)
{
  my $sign = "";
  if $s < 0 { $s = -$s; $sign = "-"; } 
  
  my $h = $s div 3600; $s -=  $h * 3600;
  my $m = $s div   60; $s -=  $m *   60;

  return "$sign$h:{ $m.fmt('%02d') }:{ $s.fmt('%02d') }" if $h;
  return "$sign$m:{ $s.fmt('%02d') }" if $s;
  return "$sign$s";
}

Bells and Whistles

Comparing the values for November and December for London isn't much fun in the long run. So we can add support for user specified months/years. And other cities.

I'll start with the output this time as well.

$ raku daylight-turbo
      London     London
Day November   December   Difference
        2019       2019
 1   9:40:44    8:11:53    -1:28:51
 2   9:37:10    8:09:53    -1:27:17
 3   9:33:37    8:07:57    -1:25:40
 4   9:30:07    8:06:07    -1:24:00
 5   9:26:38    8:04:22    -1:22:16
 6   9:23:11    8:02:42    -1:20:29
 7   9:19:45    8:01:08    -1:18:37
 8   9:16:22    7:59:40    -1:16:42
 9   9:13:01    7:58:17    -1:14:44
10   9:09:42    7:57:00    -1:12:42
11   9:06:25    7:55:50    -1:10:35
12   9:03:11    7:54:45    -1:08:26
13   8:59:59    7:53:46    -1:06:13
14   8:56:50    7:52:54    -1:03:56
15   8:53:44    7:52:07    -1:01:37
16   8:50:40    7:51:27      -59:13
17   8:47:39    7:50:54      -56:45
18   8:44:42    7:50:27      -54:15
19   8:41:48    7:50:06      -51:42
20   8:38:57    7:49:52      -49:05
21   8:36:09    7:49:44      -46:25
22   8:33:25    7:49:43      -43:42
23   8:30:45    7:49:48      -40:57
24   8:28:09    7:50:00      -38:09
25   8:25:36    7:50:19      -35:17
26   8:23:08    7:50:44      -32:24
27   8:20:44    7:51:15      -29:29
28   8:18:24    7:51:53      -26:31
29   8:16:09    7:52:37      -23:32
30   8:13:59    7:53:27      -20:32
31              7:54:24  

Looks familiar? We have used the default values, but can specify them explicitly like this:

$ raku daylight-turbo --left="London:2019:11" --right="London:2019:12"

That gives the same output as without the arguments, so it isn't shown here.

We can swap the order:

$ raku daylight-turbo --left="London:2019:12" --right="London:2019:11"
      London     London
Day December   November   Difference
        2019       2019
 1   8:11:53    9:40:44     1:28:51
 2   8:09:53    9:37:10     1:27:17
 3   8:07:57    9:33:37     1:25:40
 4   8:06:07    9:30:07     1:24:00
 5   8:04:22    9:26:38     1:22:16
 6   8:02:42    9:23:11     1:20:29
 7   8:01:08    9:19:45     1:18:37
 8   7:59:40    9:16:22     1:16:42
 9   7:58:17    9:13:01     1:14:44
10   7:57:00    9:09:42     1:12:42
11   7:55:50    9:06:25     1:10:35
12   7:54:45    9:03:11     1:08:26
13   7:53:46    8:59:59     1:06:13
14   7:52:54    8:56:50     1:03:56
15   7:52:07    8:53:44     1:01:37
16   7:51:27    8:50:40       59:13
17   7:50:54    8:47:39       56:45
18   7:50:27    8:44:42       54:15
19   7:50:06    8:41:48       51:42
20   7:49:52    8:38:57       49:05
21   7:49:44    8:36:09       46:25
22   7:49:43    8:33:25       43:42
23   7:49:48    8:30:45       40:57
24   7:50:00    8:28:09       38:09
25   7:50:19    8:25:36       35:17
26   7:50:44    8:23:08       32:24
27   7:51:15    8:20:44       29:29
28   7:51:53    8:18:24       26:31
29   7:52:37    8:16:09       23:32
30   7:53:27    8:13:59       20:32
31   7:54:24             

We can change one of the months:

$ raku daylight-turbo --left="London:2019:02" --right="London:2019:12"
      London     London
Day February   December   Difference
        2019       2019
 1   9:10:06    8:11:53      -58:13
 2   9:13:28    8:09:53    -1:03:35
 3   9:16:51    8:07:57    -1:08:54
 4   9:20:17    8:06:07    -1:14:10
 5   9:23:45    8:04:22    -1:19:23
 6   9:27:15    8:02:42    -1:24:33
 7   9:30:47    8:01:08    -1:29:39
 8   9:34:20    7:59:40    -1:34:40
 9   9:37:55    7:58:17    -1:39:38
10   9:41:32    7:57:00    -1:44:32
11   9:45:11    7:55:50    -1:49:21
12   9:48:50    7:54:45    -1:54:05
13   9:52:31    7:53:46    -1:58:45
14   9:56:14    7:52:54    -2:03:20
15   9:59:57    7:52:07    -2:07:50
16  10:03:42    7:51:27    -2:12:15
17  10:07:28    7:50:54    -2:16:34
18  10:11:15    7:50:27    -2:20:48
19  10:15:03    7:50:06    -2:24:57
20  10:18:51    7:49:52    -2:28:59
21  10:22:41    7:49:44    -2:32:57
22  10:26:31    7:49:43    -2:36:48
23  10:30:22    7:49:48    -2:40:34
24  10:34:14    7:50:00    -2:44:14
25  10:38:07    7:50:19    -2:47:48
26  10:42:00    7:50:44    -2:51:16
27  10:45:53    7:51:15    -2:54:38
28  10:49:48    7:51:53    -2:57:55
29              7:52:37  
30              7:53:27  
31              7:54:24  

It handles short months (as February) without problems.

Or we can compare London with another city, e.g. Paris:

$ raku daylight-turbo --left="Paris:2019:02" --right="London:2019:02"
       Paris     London
Day February   February   Difference
        2019       2019
 1   9:26:24    9:10:06      -16:18
 2   9:29:25    9:13:28      -15:57
 3   9:32:28    9:16:51      -15:37
 4   9:35:33    9:20:17      -15:16
 5   9:38:41    9:23:45      -14:56
 6   9:41:49    9:27:15      -14:34
 7   9:45:00    9:30:47      -14:13
 8   9:48:13    9:34:20      -13:53
 9   9:51:27    9:37:55      -13:32
10   9:54:42    9:41:32      -13:10
11   9:57:59    9:45:11      -12:48
12  10:01:17    9:48:50      -12:27
13  10:04:37    9:52:31      -12:06
14  10:07:58    9:56:14      -11:44
15  10:11:20    9:59:57      -11:23
16  10:14:43   10:03:42      -11:01
17  10:18:08   10:07:28      -10:40
18  10:21:33   10:11:15      -10:18
19  10:24:59   10:15:03       -9:56
20  10:28:26   10:18:51       -9:35
21  10:31:54   10:22:41       -9:13
22  10:35:23   10:26:31       -8:52
23  10:38:53   10:30:22       -8:31
24  10:42:23   10:34:14       -8:09
25  10:45:54   10:38:07       -7:47
26  10:49:25   10:42:00       -7:25
27  10:52:57   10:45:53       -7:04
28  10:56:30   10:49:48       -6:42

I used the same month/year, but we don' have to.

Only some cities are supported, and the program gives the list:

$ raku daylight-turbo --left="Paris:2019:02" --right="Moss:2019:02"
City 'Moss' not supported. Use one of Berlin Birmingham Edinburgh
London Manchester New York Oslo Paris Sydney.
  in sub read-data at ./daylight-turbo line 30
  in sub MAIN at ./daylight-turbo line 22
  in block  at ./daylight-turbo line 3

And finally, the modified program:

File: daylight-turbo
use LWP::Simple;

unit sub MAIN (:$left = "London:2019:11", :$right = "London:2019:12"); # [1]

my $base-url = 'https://www.timeanddate.com/sun/';                     # [2]

my @months   = ('', 'January', 'February', 'March', 'April', 'May', 'June',
                    'July', 'August', 'September', 'October', 'November',
                    'December');                                       # [3]

my %city2url = (
		 Berlin     => 'germany/berlin',                       # [4]
		 Birmingham => 'uk/birmingham',
		 Edinburgh  => 'uk/edinburgh',
                 London     => 'uk/london',
                 Manchester => 'uk/manchester',
		'New York'  => 'usa/new-york',
		 Oslo       => 'norway/oslo',
		 Paris      => 'france/paris',
                 Sydney     => 'australia/sydney',
               );

my %data;

read-data($left);               # [5]
read-data($right);              # [5]

sub read-data ($city-month)     # [5]
{
  my $line;

  my ($city, $year, $month) = $city-month.split(':'); # [6]

  die "City '$city' not supported. Use one of { %city2url.keys.sort }."
    unless %city2url{$city};                          # [7]

  if "$city-month.html".IO.e                          # [8]
  {
    $line = slurp "$city-month.html";
  }
  else
  {
    my $url = $base-url ~ %city2url{$city} ~
              "?month=" ~ $month ~ "&year=" ~ $year;

    $line = LWP::Simple.get($url).lines.join("\n");

    spurt "$city-month.html", $line;
  }

  $line ~~ /\<tbody\>(.*?)\<\/tbody\>/

  for $0.Str.split("</tr>") -> $line 
  {
    $line ~~ 
      /data\-day\=(\d+) \s .*? \"c \s tr \s sep\-l\"\>(\d+\:\d+\:\d+)\<\/td\>/;

    next unless $0;

    %data{$city-month}{$0.Str} = $1.Str;        # [9]
  }
}

my ($left-city,  $left-year,  $left-month)  = $left.split(':');
my ($right-city, $right-year, $right-month) = $right.split(':');

say "    { $left-city.fmt('%8s') }   { $right-city.fmt('%8s') }";
say "Day { @months[$left-month].fmt('%8s') }   " ~
        "{ @months[$right-month].fmt('%8s') }   Difference";
say "    { $left-year.fmt('%8s')}   { $right-year.fmt('%8s') }";

for 1..31 -> $day
{ 
  my $left-value  = %data{$left}{$day};
  my $right-value = %data{$right}{$day};

  ! $left-value && ! $right-value && last;   # [10]

  print $day.fmt('%2d') ~ ' ';

  print $left-value                          # [11]
    ?? $left-value.fmt('%9s') ~ '  '
    !! ' ' x 11;
 
  print $right-value                         # [12]
   ?? $right-value.fmt('%9s') ~ '  '
   !! ' ' x 11;

  print "{ (s2hms( hms2s($right-value) - hms2s($left-value))).fmt('%10s') }"
    if $left-value && $right-value;          # [13]

  say '';
}

sub hms2s ($hms)
{
  my @parts = $hms.split(':') // return 0;
  return @parts[0] * 60 * 60 + @parts[1] * 60 + @parts[2];
}

sub s2hms ($s is copy)
{
  my $sign = "";
  if $s < 0 { $s = -$s; $sign = "-"; } 
  
  my $h = $s div 3600; $s -=  $h * 3600;
  my $m = $s div   60; $s -=  $m *   60;

  return "$sign$h:{ $m.fmt('%02d') }:{ $s.fmt('%02d') }" if $h;
  return "$sign$m:{ $s.fmt('%02d') }" if $s;
  return "$sign$s";
}

[1] The default values. Note the syntax; «city:year:month». I have named the columns «left value» and «right value», and this is reflected in the variable names in the entire program.

[2] The base URL. We get the actual URL by appending the city, year and month.

[3] The month names.

[4] The supported cities (and the corresponding URL part).

[5] Use «city:year:month» as arguments this time.

[6] Get the individual parts (city, year and month). Note the lack of error checking.

[7] Check for supported cities, and print a nice error message if it isn't.

[8] The cached files has the city, year and month in the file names.

[9] Use the city, year and month as key in the data structure.

[10] Exit the loop (1..31) if there are no data for the current day (both the left value and the right value). That is the case if both months are shorter than 31 days.

[11] Print the left value, if given, or padding otherwise.

[12] As above, but for the right value.

[13] Print the difference, if we have both values (left and right).

Perhaps a last running to show that the calculations back and forth are correct:

$ raku daylight-turbo --left="Berlin:2009:02" --right="Berlin:2009:02"
      Berlin     Berlin
Day February   February   Difference
        2009       2009
 1   9:04:40    9:04:40           0
 2   9:08:11    9:08:11           0
 3   9:11:45    9:11:45           0
 4   9:15:20    9:15:20           0
 5   9:18:58    9:18:58           0
 6   9:22:37    9:22:37           0
 7   9:26:18    9:26:18           0
 8   9:30:01    9:30:01           0
 9   9:33:46    9:33:46           0
10   9:37:32    9:37:32           0
11   9:41:20    9:41:20           0
12   9:45:09    9:45:09           0
13   9:49:00    9:49:00           0
14   9:52:52    9:52:52           0
15   9:56:45    9:56:45           0
16  10:00:39   10:00:39           0
17  10:04:34   10:04:34           0
18  10:08:30   10:08:30           0
19  10:12:28   10:12:28           0
20  10:16:26   10:16:26           0
21  10:20:25   10:20:25           0
22  10:24:25   10:24:25           0
23  10:28:25   10:28:25           0
24  10:32:26   10:32:26           0
25  10:36:28   10:36:28           0
26  10:40:31   10:40:31           0
27  10:44:34   10:44:34           0
28  10:48:37   10:48:37           0

Bells and Whistles, Part 2

We really should add error checking on the year and month values:

File: daylight-turbo2 (changes only)
sub read-data ($city-month)
{
  my $line;

  my ($city, $year, $month) = $city-month.split(':');

  die "Illegal month \"$month\" (use 01..12)"
     unless $month eq one <<01 02 03 04 05 06 07 08 09 10 11 12>>
  die "Illegal year \"$year\" (use 1900..2199)"
     unless 1900 <= $year.Int <= 2199;
  die "City '$city' not supported. Use one of { %city2url.keys.sort }."
    unless %city2url{$city};

Adding support for additional cities is tedious, but we can let the user do it:

File: daylight-turbo2 (changes only)

unit sub MAIN (*@citymapping,
               :$left = "London:2019:11",
               :$right = "London:2019:12");

my $base-url = 'https://www.timeanddate.com/sun/';

my @months   = ('', 'January', 'February', 'March', 'April', 'May', 'June',
                    'July', 'August', 'September', 'October', 'November',
                    'December');

my %city2url = (
		 Berlin     => 'germany/berlin',
		 Birmingham => 'uk/birmingham',
		 Edinburgh  => 'uk/edinburgh',
                 London     => 'uk/london',
                 Manchester => 'uk/manchester',
		'New York'  => 'usa/new-york',
		 Oslo       => 'norway/oslo',
		 Paris      => 'france/paris',
                 Sydney     => 'australia/sydney',
               );

if @citymapping
{
  for @citymapping -> $line
  {
    my ($city, $url) = $line.split(":");
    %city2url{$city} = $url;
  }
}

Running it:

$ raku daylight-turbo2 --left=Ulaanbaatar:2019:12 Ulaanbaatar:mongolia/ulaanbaatar
    Ulaanbaatar     London
Day December   December   Difference
        2019       2019
 1   8:42:17    8:11:53      -30:24
 2   8:40:33    8:09:53      -30:40
 3   8:38:53    8:07:57      -30:56
 4   8:37:18    8:06:07      -31:11
 5   8:35:47    8:04:22      -31:25
 6   8:34:21    8:02:42      -31:39
 7   8:33:00    8:01:08      -31:52
 8   8:31:44    7:59:40      -32:04
 9   8:30:32    7:58:17      -32:15
10   8:29:26    7:57:00      -32:26
11   8:28:24    7:55:50      -32:34
12   8:27:28    7:54:45      -32:43
13   8:26:37    7:53:46      -32:51
14   8:25:51    7:52:54      -32:57
15   8:25:10    7:52:07      -33:03
16   8:24:35    7:51:27      -33:08
17   8:24:05    7:50:54      -33:11
18   8:23:40    7:50:27      -33:13
19   8:23:21    7:50:06      -33:15
20   8:23:07    7:49:52      -33:15
21   8:22:59    7:49:44      -33:15
22   8:22:57    7:49:43      -33:14
23   8:23:00    7:49:48      -33:12
24   8:23:08    7:50:00      -33:08
25   8:23:22    7:50:19      -33:03
26   8:23:41    7:50:44      -32:57
27   8:24:06    7:51:15      -32:51
28   8:24:37    7:51:53      -32:44
29   8:25:12    7:52:37      -32:35
30   8:25:53    7:53:27      -32:26
31   8:26:40    7:54:24      -32:16

Note that «London» is too far right in the header, as «Ulaanbaatar» is too long.

Note the missing error checking in the parsing of new cities (we should allow letters and "-" only - and insist on a single "/" in the middle of the URL part), and that it is possible to do naughty things like this: «Oxford:spain/madrid«.

Also note that the caching functionality will clutter the current directory with html files, e.g.:

$ ls -l
total 1020
-rw-r--r-- 1 arne arne 72137 des.   4 21:38 2019-dec.html
-rw-r--r-- 1 arne arne 71544 des.   4 21:15 2019-nov.html
-rw-r--r-- 1 arne arne 70483 des.   7 11:48 Berlin:2009:02.html
-rw-r--r-- 1 arne arne 69832 des.   7 11:49 Berlin:2019:02.html
-rw-r--r-- 1 arne arne 70654 des.   7 12:11 Berlin:2119:02.html
-rwxr-xr-x 1 arne arne  1800 des.   4 22:11 daylight
-rwxr-xr-x 1 arne arne  2694 des.   6 23:03 daylight-turbo
-rwxr-xr-x 1 arne arne  3024 des.   7 12:24 daylight-turbo2
-rw-r--r-- 1 arne arne 70017 des.   6 22:37 London:2019:02.html
-rw-r--r-- 1 arne arne 71457 des.   6 22:18 London:2019:11.html
-rw-r--r-- 1 arne arne 72112 des.   6 22:18 London:2019:12.html
-rw-r--r-- 1 arne arne 69989 des.   6 22:37 Paris:2019:02.html
-rw-r--r-- 1 arne arne 72325 des.   6 22:57 Paris:2019:12.html
-rw-r--r-- 1 arne arne 72145 des.   6 22:57 Paris:2019:22.html
-rw-r--r-- 1 arne arne 73184 des.   6 23:03 Sydney:2019:12.html
-rw-r--r-- 1 arne arne 72611 des.   7 12:21 Ulaanbaatar:2019:12.html
-rwxr-xr-x 1 arne arne   475 des.   2 13:06 weekdays

And that's it.