# PERL WEEKLY CHALLENGE – 037

This is my seventh week participating into the weekly challenge.

## Easy Challenge

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

In perl 5, I used Datetime and DateTime::Event::Recurrence to do the heavy lifting for this task, looping through each month and have DateTime::Event::Recurrence calculate the weekdays.

https://metacpan.org/pod/DateTime

https://metacpan.org/pod/DateTime::Event::Recurrence

In Raku I just looped through all the days in a year and stored the value in a hash using the Date objects `day-of-week` method to find if the day is a weekday.

#### Perl 5 solution

``````#!/usr/bin/perl
# Test: ./ch1.pl 2019
use strict;
use warnings;
use feature qw /say/;
use DateTime;
use DateTime::Event::Recurrence;

show_weekdays_per_year(\$ARGV[0]);

sub show_weekdays_per_year {
my \$year = shift || 2019;
for my \$month (1..12) {
show_weekdays_per_month(\$month, \$year);
}
}

sub show_weekdays_per_month {
my (\$month, \$year) = @_;

my \$working_days =
DateTime::Event::Recurrence->weekly(
days => [1 .. 5]
);

# Start of the month
my \$start = DateTime->new(
year  => \$year,
month => \$month,
day   => 1
);

# End of the month
my \$end = \$start->clone;
->subtract( days => 1 );

my \$num_days = \$working_days->as_list(
start => \$start,
end => \$end
);

say \$start->month_abbr() . ": \$num_days days";
}
``````

Output

``````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``````

#### Raku solution

``````# Test: perl6 ch1.p6 2019
use v6.d;

sub MAIN(Int \$year = 2019) {
show-weekdays-per-year(\$year);
}

# Weekdays per year
sub show-weekdays-per-year(Int \$year) {
my \$current = Date.new(\$year, 1, 1);
my %months{Int};

my @mon = (
'Jan', 'Feb', 'Mar', 'Apr',
'May', 'Jun', 'Jul', 'Aug',
'Sep', 'Oct', 'Nov', 'Dec'
);

while (\$current.year == \$year) {
%months{\$current.month}++
if (\$current.day-of-week == (1 .. 5).any);
\$current++;
}

for %months.keys.sort -> \$key {
say @mon[\$key - 1] ~ ': ' ~
%months{\$key} ~ ' days';
}
}``````

Output

``````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``````

## Hard Challenge

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.

For the perl5 solution I used DateTime to model the two months and Web::Scraper to scrape the daylight data for each of these month. Then I converted the daylight data from hh::mm::ss into seconds and calculated the difference between the two months. Then I used Time::Seconds to output the data in a readable format.

The reason I decided to scrape the data was that I didn’t want to bother with the api, not figuring out how to cut and paste the data out of the table.

The Raku solution is basically the same, except I don’t Scrape or use Time::Piece.

EDIT: I fixed a bug where the convert_time_to seconds wasn’t calculated correct. It was adding 60 rather than the number of seconds in the hh:mm:ss string.

#### Perl 5 solution

``````#!/usr/bin/perl
# Test: ./ch2.pl
use strict;
use warnings;
use URI;
use Web::Scraper;
use feature qw /say/;
use String::Util qw /trim/;
use DateTime;
use Time::Seconds;

my \$nov = DateTime->new(
year  => 2019,
month => 11,
day   => 1
);

my \$dec = DateTime->new(
year  => 2019,
month => 12,
day   => 1
);

compare_lengths(\$nov, \$dec);

# Compare the daylight lengths of 2 months
sub compare_lengths {
my (\$date1, \$date2) = @_;

# Get the data from the web
my \$date1_data =
get_data(\$date1->month, \$date1->year);
my \$date2_data =
get_data(\$date2->month, \$date2->year);

# Calculate totals
my \$date1_total = calculate_daylight_total(\$date1_data);
my \$date2_total = calculate_daylight_total(\$date2_data);
my \$difference = \$date1_total - \$date2_total;

# Print the output
say \$date1->month_abbr . ' ' . \$date1->year . ' has ' .
Time::Seconds->new(\$date1_total)->pretty .
' of daylight.';

say \$date2->month_abbr . ' ' . \$date2->year . ' has ' .
Time::Seconds->new(\$date2_total)->pretty .
' of daylight.';

say "The difference is: " .
Time::Seconds->new(\$difference)->pretty . '.';
}

# Calculates the total daylight hours from data
sub calculate_daylight_total {
my (\$data) = @_;
my \$total = 0;

for my \$time_string (@{\$data}) {
\$total += convert_time_to_seconds(\$time_string);
}

return \$total;
}

# Convert hh::mm::ss to seconds
sub convert_time_to_seconds {
my (\$hh, \$mm, \$ss) = split(':', shift);
return \$hh * 3600 + \$mm * 60 + \$ss;
}

# Get the data from the web
sub get_data {
my (\$month, \$year) = @_;
my @data;

# Scrape the date
my \$url = 'https://www.timeanddate.com/sun/uk/london?' .
"month=\$month&year=\$year";

my \$times = scraper {
process 'table[id="as-monthsun"] td', "times[]" => {
td_text => 'TEXT',
}
};

my \$res = \$times->scrape( URI->new( \$url ) );

# Parse the times
for my \$time (@{\$res->{times}}) {
my \$e_time = trim(\$time->{td_text});
push @data, \$e_time
if (\$e_time =~ /^(\d)+\:(\d)+\:(\d)+\$/);
}

# Output the data
return \@data;
}
``````

Output

Nov 2019 has 11 days, 3 hours, 0 minutes, 40 seconds of daylight.
Dec 2019 has 10 days, 5 hours, 45 minutes, 1 second of daylight.
The difference is: 21 hours, 15 minutes, 39 seconds.

#### Raku solution

``````# Test: perl6 ch2.p6
use v6.d;

# Box configurations
sub MAIN () {
my \$date1 = Date.new(2019,11,1);
my \$date2 = Date.new(2019,12,1);
compare_lengths(\$date1, \$date2);
}

sub compare_lengths(Date \$date1, Date \$date2) {
# Months
my @mon = (
'Jan', 'Feb', 'Mar', 'Apr',
'May', 'Jun', 'Jul', 'Aug',
'Sep', 'Oct', 'Nov', 'Dec'
);

# Get the data from the web
my @date1_data =
get-data(\$date1);
my @date2_data =
get-data(\$date2);

# Calculate totals
my \$date1_total = calculate-daylight-total(@date1_data);
my \$date2_total = calculate-daylight-total(@date2_data);
my \$difference = \$date1_total - \$date2_total;

# Print the output
say @mon[\$date1.month - 1] ~ ' ' ~ \$date1.year ~ ' has ' ~
convert-seconds-to-string(\$date1_total) ~
' of daylight.';

# Print the output
say @mon[\$date2.month - 1] ~ ' ' ~ \$date2.year ~ ' has ' ~
convert-seconds-to-string(\$date2_total) ~
' of daylight.';

say "The difference is: " ~
convert-seconds-to-string(\$difference) ~ '.';
}

# Calculates the total daylight hours from data
sub calculate-daylight-total(@data) {
my \$total = 0;
for (@data) -> \$daylight {
\$total += convert-time-to-seconds(\$daylight).Int;
}
return \$total
}

# Convert seconds to readable string
sub convert-seconds-to-string (Int \$seconds) {
return ( \$seconds.polymod(60, 60, 24) Z
('seconds', 'minutes', 'hours', 'days')
).reverse.join(", ");
}

# Convert hh::mm::ss to seconds
sub convert-time-to-seconds(Str \$time_string) {
my (\$hh, \$mm, \$ss) = \$time_string.split(':');
return \$hh * 3600 + \$mm * 60 + \$ss;
}

# A bit of a cheat, I built the Scraper
# in perl5 so don't reinvent the wheel
# Gets the daytime data
sub get-data(Date \$date) {
my %data = (
'2019-11-01' => (
'9:40:44', '9:37:10', '9:33:37', '9:30:07',
'9:26:38', '9:23:11', '9:19:45', '9:16:22',
'9:13:01', '9:09:42', '9:06:25', '9:03:11',
'8:59:59', '8:56:50', '8:53:44', '8:50:40',
'8:47:39', '8:44:42', '8:41:48', '8:38:57',
'8:36:09', '8:33:25', '8:30:45', '8:28:09',
'8:25:36', '8:23:08', '8:20:44', '8:18:24',
'8:16:09', '8:13:59'
),
'2019-12-01' => (
'8:11:53', '8:09:53', '8:07:57', '8:06:07',
'8:04:22', '8:02:42', '8:01:08', '7:59:40',
'7:58:17', '7:57:00', '7:55:50', '7:54:45',
'7:53:46', '7:52:54', '7:52:07', '7:51:27',
'7:50:54', '7:50:27', '7:50:06', '7:49:52',
'7:49:44', '7:49:43', '7:49:48', '7:50:00',
'7:50:19', '7:50:44', '7:51:15', '7:51:53',
'7:52:37', '7:53:27', '7:54:24'
),
);

return @(%data.{\$date});
}
``````

Output

Nov 2019 has 11 days, 3 hours, 0 minutes, 40 seconds of daylight.
Dec 2019 has 10 days, 5 hours, 45 minutes, 1 seconds of daylight.
The difference is: 0 days, 21 hours, 15 minutes, 39 seconds.