Peter
Peter Campbell Smith

Sundays and totients

Weekly challenge 175 — 25 July 2022

Week 175 - 25 Jul 2022

Task 1

Task — Last Sunday

Write a script to list the last Sunday of every month in a given $year.

Examples


Example 1:
Year: 2022
2022-01-30
2022-02-27
2022-03-27
2022-04-24
2022-05-29
2022-06-26
2022-07-31
2022-08-28
2022-09-25
2022-10-30
2022-11-27
2022-12-25

Analysis

It's a slightly awkward calculation. There can be 4 or 5 Sundays in a month, and that depends on the day of the week the month starts on and how many days there are in the month. So I hit on the idea of looking at the 12 months starting with the February of the given year. If we take the 1st of each of these 12 months, we can construct the Unix time of noon like this:

$time = timelocal_posix(0, 0, 12, 1, $month, 
     $year - 1900);

and then use timelocal to get the day of the week as $t[6] in
@t = localtime($time)

Then, to get the last Sunday of the preceding month, if the 1st of the month is a Sunday, ie $t[6] == 0, we need to move back 7 days, or otherwise we just move back $t[6] days. To do that we subtract 86400 seconds for each day we're moving back, and then a final localtime gives us the desired date.

The Posix version can cover (at least) 1753 - 3999 in the Gregorian calendar.

Try it 

Try running the script with any input:



example: 2000

Script


#!/usr/bin/perl

# Peter Campbell Smith - 2022-07-25
# PWC 175 task 1

use v5.28;
use utf8;
use warnings;
binmode(STDOUT, ':utf8');
use Time::Local 'timelocal_posix';

my (@years, $year, $m, $month, $time, @t, $back, $y, $sundays);

@years = (2022, 1753, 3999);

for $y (@years) {
        
    # loop over months Feb to next Jan, and move back to the Sunday preceding the 1st of each month
    $year = $y;
    say qq[\nInput:  $y];
    $sundays = '';
    for $m (1 .. 12) {
        
        # last time reset year and month
        $year ++ if $m == 12;
        $month = $m % 12;
        
        # unix time on 1st of month
        $time = timelocal_posix(0, 0, 12, 1, $month, $year - 1900);
        @t = localtime($time);
        
        # move back 7 days if Sunday, 6 if Saturday ...
        $back = $t[6] == 0 ? 7 : $t[6];
        $time -= $back * 86400;
        
        # and get the date
        @t = localtime($time);
        $sundays .= sprintf('%04d-%02d-%02d, ', $t[5] + 1900, $t[4] + 1, $t[3]);
        $sundays = substr($sundays, 0, -2) . qq[\n] if $m =~ m/^(4|8|12)$/;
    }
    print qq[Output:\n$sundays];
}

Output


Input:  2022
Output:
2022-01-30, 2022-02-27, 2022-03-27, 2022-04-24
2022-05-29, 2022-06-26, 2022-07-31, 2022-08-28
2022-09-25, 2022-10-30, 2022-11-27, 2022-12-25

Input:  1753
Output:
1753-01-28, 1753-02-25, 1753-03-25, 1753-04-29
1753-05-27, 1753-06-24, 1753-07-29, 1753-08-26
1753-09-30, 1753-10-28, 1753-11-25, 1753-12-30

Input:  3999
Output:
3999-01-31, 3999-02-28, 3999-03-28, 3999-04-25
3999-05-30, 3999-06-27, 3999-07-25, 3999-08-29
3999-09-26, 3999-10-31, 3999-11-28, 3999-12-26