# RogerBW's Blog

 Perl Weekly Challenge 37: weekdays and day lengths 11 December 2019 I’ve been doing the Perl Weekly Challenges. Last week's was about counting weekdays and working out total daylight. Write a script to calculate the total number of weekdays (Mon-Fri) in each month of the year 2019. There are obviously lots of ways to do this, so I thought I'd indulge myself. In Perl5, I just iterated through the year, working out for each day whether it was a weekday and if so adding it to that month's total. It's also a good excuse to use a C-style `for` loop rather than the `foreach` that I find much more common in my day-to-day Perl. ``````use Time::Local; use POSIX qw(strftime); my \$y=2019; my \$s=timegm(0,0,12,1,0,\$y); my \$e=timegm(0,0,12,1,0,\$y+1); my @o; my @m; `````` Loop from the first day of the year, terminating just before the first day of next year. ``````for (my \$t=\$s;\$t<\$e;\$t+=86400) { my @d=gmtime(\$t); `````` The only bits we care about are the month (element 4) and the day of the week (element 6). Yes, this system has Sunday as day 0. Work out the short name of the month while we're at it, if we don't already have one. `````` if (\$d>0 && \$d<6) { \$o[\$d]++; unless (defined \$m[\$d]) { \$m[\$d]=strftime('%b',@d); } } } foreach my \$i (0..\$#o) { print "\$m[\$i]: \$o[\$i] days\n"; } `````` For Perl6, rather than just copy the Perl5 program and bash it until it worked, I took a completely different approach. I do like Perl6's Date object (particularly the fact that it exists separately from DateTime, so that when I only care about day-level resolution I don't need to muck about with faking a time of day as I did above). ``````my \$y=2019; for (1..12) -> \$m { `````` It would be really handy just to be able to say "the first day of month 13" and then step back one to get the last day of month 12, but no, that's an "error" apparently. Pathetic humans and their calendars. (Actually, I love calendrical calculations, and I recommend the book of that name by Reingold and Dershowitz to anyone who does too. Even if it does use LISP for its code examples. Linked at the bottom of this post.) `````` my \$mm=\$m+1; my \$yy=\$y; if (\$mm>12) { \$mm-=12; \$yy++; } my \$d=Date.new(\$yy,\$mm,1).earlier(:1day); `````` So now we have \$d set to the last day of the month, and can work back from it, counting down until we hit the 28th. (The total number of weekdays in days 1-28 of any month is always 20, so we don't bother to calculate those individually.) `````` my \$wd=20; while (\$d.day>28) { if (\$d.day-of-week < 6) { \$wd++; } \$d=\$d.earlier(:1day); } `````` There doesn't appear to be any direct access to strftime in Perl6, so we just dump month numbers instead of names. `````` say "\$m: \$wd days"; } `````` Another approach would be to work out the day number and day of week of the last day of the month, then calculate from that how many weekdays must fall in the span `(29 .. last-day)`. 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 can, but I don't have to, because I've already written my handy astronomical calendar and as a result dug into the fiddliness that is the Earth-Centered Inertial coordinate model. `Astro::Coord::ECI` is mostly intended for tracking artificial satellites, but it's the only module I've found that will let me readily calculate moon rise and set times (not that I need them for this example). Latitude, longitude and altitude of London are taken from the Wikipedia page. ``````use Astro::Coord::ECI; use Astro::Coord::ECI::Sun; use Astro::Coord::ECI::Utils qw{deg2rad}; use Time::Local; my \$year=2019; my (\$lat,\$lon,\$alt)=(deg2rad(51.507222),deg2rad(-0.1275),11); my @dtime; my \$sun=Astro::Coord::ECI::Sun->new; foreach my \$month (11,12) { my \$ts=timelocal(0,0,0,1,\$month-1,\$year); `````` Once more, it would be really handy just to be able to say "the first day of month 13". `````` my \$te; { my \$mm=\$month+1; my \$yy=\$year; if (\$mm>12) { \$mm-=12; \$yy++; } \$te=timelocal(0,0,0,1,\$mm-1,\$yy); } my \$sta=Astro::Coord::ECI-> universal(\$ts)-> geodetic(\$lat,\$lon,\$alt); my \$ds=0; my \$ls=0; `````` \$ls is the latest sunrise time. Note that we assume midnight is always dark; if this calculation were being done for somewhere significantly east or west of London, there might be partial daylight spanning midnight at the start or end of the month, which would need additional code to check for those cases; but the specification was for London, and one of the things I'm trying to do here is get the code working quickly. Then it's just a matter of iterating through the almanac that the module will generate for us; "horizon" events are sunrise/sunset. Add up the total day lengths for each month, then finally compare the two months. `````` foreach my \$event (\$sun->almanac(\$sta,\$ts,\$te)) { if (\$event-> eq 'horizon') { my \$t=localtime(\$event->); if (\$event-> == 1) { \$ls=\$event->; } else { if (\$ls) { \$ds+=\$event->-\$ls; } } } } push @dtime,\$ds; } print 'delta ',\$dtime-\$dtime," s\n"; `````` For Perl6 the ECI module isn't available, so I did use those pages from timeanddate.com; for convenience, I saved them to HTML files and found a parser that converts HTML into XML documents. ``````use Gumbo; my @dtime; for ('2019-11-london.html','2019-12-london.html') -> \$file { my \$dlt=0; my \$fh=open :r,\$file; my \$text=''; for \$fh.lines { \$text ~= \$_; } close \$fh; my \$xml=parse-html(\$text); `````` There's only one table in the document, so jump straight to that. `````` my \$tab=\$xml.root.elements(:TAG, :RECURSE); `````` Then iterate through each row, picking the ones with twelve elements since those are the ones with data in them. Element 2 is the day length (hours:minutes:seconds), so grab that and parse it. This is obviously not at all robust against changes in the site design. `````` for \$tab.elements(:TAG