diff --git a/Community_News/.make_email.cfg b/Community_News/.make_email.cfg
index c4bbd4d..fecf23c 100644
--- a/Community_News/.make_email.cfg
+++ b/Community_News/.make_email.cfg
@@ -2,20 +2,17 @@
server = chatter.skyehaven.net
port = 64738
room = HPR
- # Default day of the week
+ # Default day of the week for the recording
dayname = Friday
# Times are UTC
- starttime = 15:00:00
- endtime = 17:00:00
+ starttime = 16:00:00
+ endtime = 18:00:00
# Usually 2 hours are set aside. This value is used when a different start
# time is provided and no end time.
duration = 02 # hours
# Template is in the current directory
template = make_email_template.tpl
-
- name = hpr.db
-
name = recording_dates.dat
diff --git a/Community_News/make_email b/Community_News/make_email
index 1e4ea9e..d8c175c 100755
--- a/Community_News/make_email
+++ b/Community_News/make_email
@@ -27,17 +27,13 @@
# REQUIREMENTS: ---
# BUGS: ---
#
-# NOTES: Does not send the email at present. Needs work
-# 2022-02-28: DBD::MariaDB has vanished, had to revert to MySQL
-# again.
-# 2025-02-22: Moved to SQLite for the database
-# 2025-02-23: Dropped Mail::Mailer and all related code. The
-# output is now written to STDOUT or an output file.
+# NOTES: Extensive rewrite. No longer needs a database and computes
+# review months and recording dates itself.
#
# AUTHOR: Dave Morriss (djm), Dave.Morriss@gmail.com
-# VERSION: 0.3.3
+# VERSION: 0.3.4
# CREATED: 2013-10-28 20:35:22
-# REVISION: 2025-04-03 14:42:45
+# REVISION: 2025-04-07 18:15:30
#
#===============================================================================
@@ -74,7 +70,7 @@ use Data::Dumper;
#
# Version number (manually incremented)
#
-our $VERSION = '0.3.3';
+our $VERSION = '0.3.4';
#
# Script name
@@ -90,7 +86,6 @@ our $VERSION = '0.3.3';
( my $basedir = abs_path($0) ) =~ s|/?[^/]*$||mx;
my $configfile = "$basedir/.${PROG}.cfg";
-my ( $dbh, $sth1, $h1, $h2, $rv );
my ( %recdates, $rdfh );
#
@@ -545,7 +540,7 @@ my $cfgfile
#
# Sanity checking the options
#
-die "Unable to find $cfgfile\n" unless ( -e $cfgfile );
+die "Unable to find config file '$cfgfile'\n" unless ( -e $cfgfile );
die "Use only one of -month=MONTH or -date=DATE\n"
if (defined($month) && defined($date));
@@ -563,11 +558,11 @@ _debug( $DEBUG >= 2, '%config: ' . Dumper( \%config ) );
#
# Configuration file values for the email text with defaults and/or checks
#
-my $server = $config{email}->{server} // 'chatter.skyehaven.net';
-my $port = $config{email}->{port} // 64738;
-my $room = $config{email}->{room} // 'Hacker Public Radio';
-my $duration = $config{email}->{duration} // 2; # Hours
-my $dayname = $config{email}->{dayname} // 'Friday';
+my $cfg_server = $config{email}->{server} // 'chatter.skyehaven.net';
+my $cfg_port = $config{email}->{port} // 64738;
+my $cfg_room = $config{email}->{room} // 'Hacker Public Radio';
+my $cfg_duration = $config{email}->{duration} // 2; # Hours
+my $cfg_dayname = $config{email}->{dayname} // 'Friday';
#
# If we had a start time specified then check it and ensure the end time makes
@@ -586,7 +581,7 @@ if ($start) {
#
unless ($end) {
my @end = split( ':', $start );
- $end[0] += $duration;
+ $end[0] += $cfg_duration;
$end = join(':',@end);
}
@@ -601,7 +596,7 @@ else {
}
#
-# Start and end times from options or the configuration file
+# Recording start and end times from options or the configuration file
#
my @starttime = split( ':', $start );
my @endtime = split( ':', $end );
@@ -610,28 +605,28 @@ die "Missing start/end time(s)\n" unless ( @starttime && @endtime );
#
# The template from the configuration file
#
-my $template = $config{email}->{template};
-die "Missing template file $template\n" unless (-e $template);
+my $cfg_template = $config{email}->{template};
+die "Missing template file '$cfg_template'\n" unless (-e $cfg_template);
#
# Recording date cache filename in the configuration file
#
-my $recdatefile = $config{recdates}->{name};
-unless ($recdatefile) {
+my $cfg_recdatefile = $config{recdates}->{name};
+unless ($cfg_recdatefile) {
warn "No recording date file defined in configuration";
say STDERR "Continuing without this file";
}
-elsif ( ! -e $recdatefile) {
- warn "Can't find recording date file $recdatefile";
+elsif ( ! -e $cfg_recdatefile) {
+ warn "Can't find recording date file '$cfg_recdatefile'";
say STDERR "Continuing without this file";
- $recdatefile = undef;
+ $cfg_recdatefile = undef;
}
#
# Load the recording dates
#
-if ($recdatefile) {
- %recdates = load_cache($recdatefile, $rdfh);
+if ($cfg_recdatefile) {
+ %recdates = load_cache($cfg_recdatefile, $rdfh);
}
_debug($DEBUG >= 2,
@@ -645,112 +640,87 @@ if ($DEBUG >= 1) {
}
#-------------------------------------------------------------------------------
-# Connect to the database
-# 2021-12-24: moved to MariaDB
-# 2022-02-28: the MariaDB driver has gone away apparently. Reverted to MySQL
-# again
-# 2025-02-22: Converted to SQLite
+# Use -date=DATE, -month=DATE or default to today's date to compute or collect
+# the review month and the recording date. Use Date::Calc format.
+#
+# If there's a -date=DATE option then it'll be an override for the recording
+# date otherwise we'll compute it.
#-------------------------------------------------------------------------------
-my $dbname = $config{database}->{name};
-
-$dbh = DBI->connect( "DBI:SQLite:dbname=$dbname",
- "", "", { AutoCommit => 1 } );
-
-#
-# Enable client-side UTF8
-#
-$dbh->{sqlite_unicode} = 1;
-
-#
-# Date and time values using Date::Calc format
-#
my @today = Today();
-my @startdate;
-my @startmonth;
-#my @reviewdate;
+my @recordingdate;
+my @reviewmonth;
my $monday = 1; # Day of week number 1-7, Monday-Sunday
-my $offset = day_offset($dayname)->{offset};
+my $offset = day_offset($cfg_dayname)->{offset};
-#-------------------------------------------------------------------------------
-# Work out the start date from the -date=DATE option, the -month=DATE option
-# or the current date.
-#-------------------------------------------------------------------------------
-#
-# If there's a -date=DATE option then it'll be an override for the start date
-# otherwise we'll compute it.
-#
+#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
if ( defined($date) ) {
#
- # Parse and perform rudimentary validation on the argument
+ # Parse and perform rudimentary validation on the argument. Don't change
+ # the day in the date. Allow past dates it -force is on.
#
- my @parsed = strptime($date);
- die "Invalid -date=DATE option '$date'\n"
- unless ( defined( $parsed[3] )
- && defined( $parsed[4] )
- && defined( $parsed[5] ) );
+ @recordingdate = convert_date( $date, 0, $force );
- $parsed[5] += 1900;
- $parsed[4] += 1;
- @startdate = @parsed[ 5, 4, 3 ];
- die "Date is in the past '$date'; aborting\n"
- unless ( $force || Date_to_Days(@startdate) > Date_to_Days(@today) );
+ #
+ # Determine the review month
+ #
+ if (week_of_month(@recordingdate) >= 4) {
+ #
+ # Use the month of the date
+ #
+ @reviewmonth = ((@recordingdate)[0,1],1);
+ }
+ else {
+ #
+ # Go backwards a month from the month of the recording date
+ #
+ @reviewmonth = Add_Delta_YM((@recordingdate)[0,1],1,0,-1);
+ }
}
+#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
elsif ( defined($month) ) {
#
- # Parse the month out of the -month=DATE argument
+ # Parse the month out of the -month=DATE option. The day is forced to 1,
+ # but cannot be omitted from the option. This defines the review month.
#
- my @parsed = strptime($month);
- die "Invalid -month=DATE option '$month'\n"
- unless ( defined( $parsed[3] )
- && defined( $parsed[4] )
- && defined( $parsed[5] ) );
-
- $parsed[5] += 1900;
- $parsed[4] += 1;
- @startmonth = @parsed[ 5, 4, 3 ];
- die "Date is in the past '$month'; aborting\n"
- unless ( $force || Date_to_Days(@startmonth) > Date_to_Days(@today) );
+ @reviewmonth = convert_date( $month, 1, $force );
#
# Compute the next meeting date from now (by finding the next first Monday
# of the month then backing up two days to the desired day - default
# Friday).
#
- @startdate = make_date( \@startmonth, $monday, 1, $offset );
+ my @nextmonth = Add_Delta_YM(@reviewmonth,0,1);
+ @recordingdate = make_recording_date( \@nextmonth, $monday, 1, $offset );
}
+#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
else {
+ #
+ # This is equivalent to the -month=DATE option except we use the current
+ # month which we get from @today and save it as @reviewmonth. We add
+ # a month to this to get the next month.
+ #
+ @reviewmonth = ((@today)[0,1],1);
+ my @nextmonth = Add_Delta_YM(@reviewmonth,0,1);
+
#
# Compute the next meeting date from now (by finding the next first Monday
# of the month then backing up a number of days to the required date).
#
- @startdate = make_date( \@today, $monday, 1, $offset );
+ @recordingdate = make_recording_date( \@nextmonth, $monday, 1, $offset );
}
-_debug($DEBUG >= 2, '@startdate: ' . join(',',@startdate));
+_debug($DEBUG >= 2, '$offset: ' . $offset);
+_debug($DEBUG >= 2, '@recordingdate: ' . join(',',@recordingdate));
+_debug($DEBUG >= 2, '@reviewmonth: ' . join(',',@reviewmonth));
-#
-# The month being reviewed is sometimes the same month and sometimes the month
-# before.
-#
-#if ( $startdate[1] eq $today[1] ) {
-# # Same month
-# @reviewdate = @startdate;
-#}
-#else {
-# # Previous month - backup 1 month
-# @reviewdate = Add_Delta_YM( @startdate, 0, -1 );
-#}
-#
-#_debug($DEBUG >= 2, '@reviewdate: ' . join(',',@reviewdate));
-
-#
+#-------------------------------------------------------------------------------
# Transfer Date::Calc values into DateTime objects so
# we can get better formatting.
-#
+#-------------------------------------------------------------------------------
my ( $dtrevmonth, $dtstart, $dtend );
-$dtrevmonth = dc_to_dt( \@startmonth );
-$dtstart = dc_to_dt( [ @startdate, @starttime ] );
-$dtend = dc_to_dt( [ @startdate, @endtime ] );
+$dtrevmonth = dc_to_dt( \@reviewmonth );
+$dtstart = dc_to_dt( [ @recordingdate, @starttime ] );
+$dtend = dc_to_dt( [ @recordingdate, @endtime ] );
#
# Compute the number of days until the recording
@@ -791,15 +761,16 @@ _debug($DEBUG >= 2,
"\$monthname: $monthname",
"\$nicedate: $nicedate",
"\$starttime: $starttime",
- "\$endtime: $endtime"
+ "\$endtime: $endtime",
+ "ISO date: ".$dtstart->ymd
);
#
# Build the subject line (with the recording date)
#
-my $waittime = ( $days > 6 ? "in $days days" : "next %A" );
-my $next = ( $days > 6 ? '' : 'next ' );
-my $forspec = ( $days > 6 ? "for $monthname " : "" );
+my $waittime = ( $days > 6 ? "in $days days" : "next %A" );
+my $next = ( $days > 6 ? '' : 'next ' );
+my $forspec = ( $days > 6 ? "for $revmonthname " : "" );
my $subject = $dtstart->strftime(
"HPR Community News ${forspec}- $waittime on %FT%TZ");
@@ -830,48 +801,6 @@ for my $tz (@zones) {
push( @timezones, storeTZ( $dtstart, $dtend, $tz ) );
}
-#-------------------------------------------------------------------------------
-# Find the number of the show with the notes. Take care because the recording
-# date might not be on the weekend before the show is released.
-# TODO: If this search fails (because in future Community News shows will not
-# be reserved), then the date needs to be computed.
-#-------------------------------------------------------------------------------
-my $isodate = $dtstart->ymd;
-#$sth1 = $dbh->prepare(q{
-# SELECT id FROM eps
-# WHERE date > ?
-# AND date_format(date,"%W") = 'Monday'
-# AND title LIKE 'HPR Community News%'
-# ORDER BY date
-# LIMIT 1
-#});
-$sth1 = $dbh->prepare(q{
- SELECT id FROM eps
- WHERE date > ?
- AND strftime("%u", date) = '1'
- AND title LIKE 'HPR Community News%'
- ORDER BY date
- LIMIT 1
-});
-$sth1->execute($isodate);
-if ( $dbh->err ) {
- warn $dbh->errstr;
-}
-
-_debug( $DEBUG >= 2, "\$isodate: $isodate" );
-
-unless ( $h1 = $sth1->fetchrow_hashref ) {
- warn "Unable to find a reserved show on the specified date - cannot continue\n";
- exit 1;
-}
-
-my $episode = $h1->{id};
-
-_debug( $DEBUG >= 2, "\$episode (slot): $episode" );
-
-$sth1->finish;
-$dbh->disconnect;
-
#-------------------------------------------------------------------------------
# Update the date cache now we have the date and time details we need.
#-------------------------------------------------------------------------------
@@ -887,14 +816,14 @@ if (exists($recdates{$monthkey})) {
# Save the new data (assuming it's correct)
#
$recdates{$monthkey} = $datestamp;
- update_cache( $recdatefile, \%recdates );
+ update_cache( $cfg_recdatefile, \%recdates );
}
}
else {
#
# Add a new record to the end of the cache file
#
- append_cache( $recdatefile, sprintf( "%s,%s", $monthkey, $datestamp ) );
+ append_cache( $cfg_recdatefile, sprintf( "%s,%s", $monthkey, $datestamp ) );
}
#-------------------------------------------------------------------------------
@@ -907,9 +836,9 @@ my $tt = Template->new(
);
my $vars = {
- server => $server,
- port => $port,
- room => $room,
+ server => $cfg_server,
+ port => $cfg_port,
+ room => $cfg_room,
subject => $subject,
timezones => \@timezones,
utc => {
@@ -922,11 +851,10 @@ my $vars = {
start => $starttime,
end => $endtime,
},
- episode => $episode, # show number
};
my $document;
-$tt->process( $template,
+$tt->process( $cfg_template,
$vars, \$document, { binmode => ':utf8' } )
|| die $tt->error(), "\n";
@@ -1035,7 +963,7 @@ sub update_cache {
# Copy the cache file to a backup
#
copy($cache_name,"${cache_name}~")
- or die "Unable to back up $cache_name\n";
+ or die "Unable to back up '$cache_name'\n";
#
# Open the original file in write mode
@@ -1080,11 +1008,11 @@ sub report_settings {
printf $fmt, "Start time", join(':',@starttime);
printf $fmt, "End time", join(':',@endtime);
printf $fmt, "Config file", coalesce($cfgfile,'undef');
- printf $fmt, "Server", coalesce($server,'undef');
- printf $fmt, "Port", coalesce($port,'undef');
- printf $fmt, "Room", coalesce($room,'undef');
- printf $fmt, "Template", coalesce($template,'undef');
- printf $fmt, "Recording date file", coalesce($recdatefile,'undef');
+ printf $fmt, "Server", coalesce($cfg_server,'undef');
+ printf $fmt, "Port", coalesce($cfg_port,'undef');
+ printf $fmt, "Room", coalesce($cfg_room,'undef');
+ printf $fmt, "Template", coalesce($cfg_template,'undef');
+ printf $fmt, "Recording date file", coalesce($cfg_recdatefile,'undef');
print "D> ----\n";
}
@@ -1123,6 +1051,67 @@ sub validate_time {
return $time;
}
+#=== FUNCTION ================================================================
+# NAME: valid_month
+# PURPOSE: Determines if the requested month is valid, meaning the
+# current one or a future one
+# PARAMETERS: $refmonth Arrayref holding the requested month
+# RETURNS: True if a past month, otherwise false
+# DESCRIPTION: The day of the requested month is forced to 1. Four Julian
+# dates are computed: the start of the requested month, the end of the
+# requested month, the current day and the start of the month
+# after this one. The month is a current or future
+# one if today's date is greater than the start of the month, or
+# the end of the month.
+# THROWS: No exceptions
+# COMMENTS: None
+# SEE ALSO: N/A
+#===============================================================================
+sub valid_month {
+ my ($refmonth) = @_;
+
+ my @month = @$refmonth;
+
+ #
+ # Force the date to first of the month
+ #
+ $month[2] = 1;
+
+ #
+ # Make Julian dates:
+ # $jstartmonth - start of the requested month
+ # $jendmonth - end of the requested month
+ # $jtoday - today
+ # $jstartnextmonth - the end of the current month plus 1 day
+ #
+ my $jstartmonth = Date_to_Days(@month);
+ my $jendmonth = Date_to_Days(
+ Add_Delta_Days( @month, Days_in_Month( @month[ 0, 1 ] ) )
+ );
+ my $jtoday = Date_to_Days( Today() );
+ my $jstartnextmonth = Date_to_Days(
+ Add_Delta_Days(
+ ( ( Today() )[ 0, 1 ], 1 ), # Start of this month
+ Days_in_Month( ( Today() )[ 0, 1 ] ) # Days this month
+ )
+ );
+
+ _debug($DEBUG >= 2,
+ "\$jstartmonth: $jstartmonth",
+ "\$jendmonth: $jendmonth",
+ "\$jtoday: $jtoday",
+ "\$jstartnextmonth $jstartnextmonth",
+ "----"
+ );
+
+ #
+ # It's valid if this month or a later one
+ #
+ return ( $jtoday >= $jstartmonth && $jtoday <= $jendmonth ) ||
+ ($jstartmonth >= $jstartnextmonth);
+
+}
+
#=== FUNCTION ================================================================
# NAME: dc_to_dt
# PURPOSE: Converts a Date::Calc datetime into a DateTime equivalent
@@ -1174,27 +1163,121 @@ sub dc_to_dt {
}
#=== FUNCTION ================================================================
-# NAME: make_date
-# PURPOSE: Make the event date for recurrence
+# NAME: week_of_month
+# PURPOSE: Determine the week number of the month a date is in
+# PARAMETERS: $year, $month, $day fields of a Date::Calc date
+# RETURNS: The computed week number within the month, in the range 1-5
+# DESCRIPTION: This is the algorithm provided in example 5 of the RECIPES
+# section of the Date::Calc manpage. It uses the incoming day
+# number, added to the day number of the first day of the month,
+# subtracts 2 and divides by 7 then adds 1 to the result.
+# THROWS: No exceptions
+# COMMENTS: None
+# SEE ALSO: N/A
+#===============================================================================
+sub week_of_month {
+ my ( $year, $month, $day ) = @_;
+
+ return int( ( $day + Day_of_Week( $year, $month, 1 ) - 2 ) / 7 ) + 1;
+}
+
+#=== FUNCTION ================================================================
+# NAME: convert_date
+# PURPOSE: Convert a textual date (ideally YYYY-MM-DD) to a Date::Calc
+# date for the start of the given month.
+# PARAMETERS: $textdate date in text form
+# $ismonth Boolean controlling whether the date is to be
+# forced to the start of the month or not.
+# $force Boolean defining whether to skip validating
+# the date
+# RETURNS: The date or the start of the month from the textual date
+# ($textdate) in Date::Calc format
+# DESCRIPTION: Parses the date string and makes a Date::Calc date from the
+# result making the day part is 1 if $override is true.
+# Optionally checks that the date isn't in the past, though
+# $force = 1 ignores this check.
+# THROWS: No exceptions
+# COMMENTS: Requires Date::Calc and Date::Parse
+# SEE ALSO: N/A
+#===============================================================================
+sub convert_date {
+ my ( $textdate, $ismonth, $allow_past ) = @_;
+
+ my ( @today, @parsed, @dcdate );
+
+ #
+ # Reference date
+ #
+ @today = Today();
+
+ #
+ # Parse and perform rudimentary validation on the $textdate date.
+ # Using Date::Parse routine 'strptime' which returns:
+ # ($ss,$mm,$hh,$day,$month,$year,$zone) = strptime($date)
+ # 0 1 2 3 4 5 6
+ #
+ # The Date::Calc date $startdate[0] gets the returned year or the current
+ # year if no year was parsed, $startdate[1] gets the parsed month or the
+ # current month if no month was parsed, and $startdate[2] gets a day of 1.
+ #
+ @parsed = strptime($textdate);
+ die "Unable to parse date '$textdate'\n" unless @parsed;
+
+ @dcdate = (
+ ( defined( $parsed[5] ) ? $parsed[5] + 1900 : $today[0] ), # year
+ ( defined( $parsed[4] ) ? $parsed[4] + 1 : $today[1] ), # month
+ ( $ismonth ? 1 : $parsed[3] ) # day
+ );
+
+ #
+ # Unless we've overridden then perform checks:
+ # - if we're handling a month call validmonth
+ # - otherwise there should be a positive or zero difference in days
+ # between the target date and today's date
+ # this is to prevent going backwards in time.
+ #
+ unless ($allow_past) {
+ unless (
+ ( $ismonth && ( valid_month( \@dcdate ) ) )
+ ||
+ ( !$ismonth && ( Date_to_Days(@dcdate) > Date_to_Days(@today) ) )
+ )
+ {
+ warn "convert_date: Invalid date $textdate (in the past)\n";
+ die "Use -force to create a back-dated calendar\n";
+ }
+ }
+
+ return @dcdate;
+
+}
+
+#=== FUNCTION ================================================================
+# NAME: make_recording_date
+# PURPOSE: Makes a recording date for a given review month
# PARAMETERS: $refdate
# An arrayref to the reference date array (usually
-# today's date)
+# the start of the target month)
# $dow Day of week for the event date (1-7, 1=Monday)
# $n The nth day of the week in the given month required
# for the event date
# $offset Number of days to offset the computed date
-# RETURNS: The resulting date as a list for Date::Calc
+# RETURNS: The resulting date as a Date::Calc list
# DESCRIPTION: We want to compute a simple date with an offset, such as
-# "the Saturday before the first Monday of the month". We do
+# "the Friday before the first Monday of the month". We do
# this by computing a pre-offset date (first Monday of month)
-# then apply the offset (Saturday before).
+# then apply the offset (Friday before).
# THROWS: No exceptions
-# COMMENTS: This function was originally written for my HPR episode on
-# iCalendar.
-# TODO Needs more testing to be considered truly universal
-# SEE ALSO:
+# COMMENTS: Derived from make_date, originally written for my HPR episode on
+# iCalendar. It then got used in reserve_cnews, and was
+# originally used here. It was really for generating a series of
+# dates or reserving a seires of episode slots, and wasn't
+# really suited for this script.
+# Rather than using a date array reference it might be better to
+# use a year, month, date sequence like Date::Calc does.
+# SEE ALSO: N/A
#===============================================================================
-sub make_date {
+sub make_recording_date {
my ( $refdate, $dow, $n, $offset ) = @_;
#
@@ -1203,18 +1286,6 @@ sub make_date {
#
my @date = Nth_Weekday_of_Month_Year( @$refdate[ 0, 1 ], $dow, $n );
- #
- # If the computed date plus the offset is before the base date advance
- # a month
- #
- if ( Day_of_Year(@date) + $offset < Day_of_Year(@$refdate) ) {
- #
- # Add a month and recompute
- #
- @date = Add_Delta_YM( @date, 0, 1 );
- @date = Nth_Weekday_of_Month_Year( @date[ 0, 1 ], $dow, $n );
- }
-
#
# Apply any day offset
#
@@ -1274,11 +1345,11 @@ sub storeTZ {
# number (wday) and the integer offset (offset) from Monday to
# the recording day, or undef.
# DESCRIPTION: Uses the hash '%matches' keyed by regular expressions matching
-# day names. The argument '$dayname' is matched against each
+# day names. The argument '$cfg_dayname' is matched against each
# regex in turn and if it matches the sub-hash is returned. This
-# allows the caller to use 'day_offset($dayname)->{dayname}' to
+# allows the caller to use 'day_offset($cfg_dayname)->{dayname}' to
# get the full name of the day if needed, or the offset as
-# 'day_offset($dayname)->{offset}'. If there is no match
+# 'day_offset($cfg_dayname)->{offset}'. If there is no match
# 'undef' is returned.
# THROWS: No exceptions
# COMMENTS: None
@@ -1422,25 +1493,14 @@ make_email - generates the text of an HPR Community News recording invitation
=head1 VERSION
-This documentation refers to make_email version 0.3.3
+This documentation refers to make_email version 0.3.4
=head1 USAGE
- make_email [-help] [-documentation] [-debug=N] [-month=DATE] [-date=DATE]
- [-start=START_TIME] [-end=END_TIME] [-[no]force] [-config=FILE]
-
- 1. ./make_email -date=2022-12-27
- Generate email for a specific date (in the future)
-
- 2. ./make_email -force -month=2025-03-01 -start=16:00 -out=HPR_email_%s.txt
- Assume this is run in early April 2025. Normally, generating email for
- March would not be allowed since it's in the past, so -force is needed
- to override the checks. The start day is the default Friday, and the
- start time is set to 16:00. The message (email body) is written to
- HPR_email_2025-03.txt. The date cache (recording_dates.dat) will be
- updated with the line:
- 2025-03-01,2025-04-04 16:00:00
+ make_email [-help] [-documentation|-man]
+ [-month=DATE] [-date=DATE] [-start=START_TIME] [-end=END_TIME]
+ [-[no]force] [-config=FILE] [-debug=N]
=head1 OPTIONS
@@ -1458,6 +1518,82 @@ Another way to see the full documentation use:
perldoc ./make_email
+Run this in the directory where B is kept.
+
+=item B<-month=DATE>
+
+Defines the month to be reviewed in the Community News recording for which the
+email is being generated. Normally (without this option) the current month is
+chosen and the date of recording computed within it. The month specified here
+is provided as a ISO8601 date such as 2014-03-08 (meaning March 2014) or
+1-Jan-2017 (meaning January 2017). Only the year and month parts are used but
+a valid day must be present.
+
+=item B<-date=DATE>
+
+This option is a way of providing a non-default date for the recording.
+Normally the script computes the next scheduled date based on the algorithm
+"DAY_OF_WEEK before the first Monday of the next month" (where DAY_OF_WEEK is
+the value defined in the configuration file as B) starting from the
+current date or the start of the month given in the B<-month=DATE> option. If
+for any reason a different date is required then this may be specified via
+this option.
+
+The recording date should be given as an ISO8601 date (such as 2014-03-08).
+
+=item B<-start=START_TIME>
+
+The default start time is defined in the configuration file, but if it is
+necessary to change it, this option can be used to do it. The B
+value must be a valid B time specification.
+
+A change to the start time in the configuration file also implies that the end
+time should change. If the B<-start=START_TIME> option is present but
+B<-end=END_TIME> is not, then the end time is computed by adding a number of
+hours to the start time this number is defined in the configuration file as
+B.
+
+=item B<-end=END_TIME>
+
+The default end time is defined in the configuration file, but if it is
+necessary to change it temporarily, this option can be used to do it. The
+B value must be a valid B time specification.
+
+=item B<-output=FILE>
+
+This option defines an output file to receive the mail message text. If the option is
+omitted the notes are written to STDOUT, allowing them to be redirected if
+required.
+
+The output file name may contain one instance of the characters 'B<%s>'. This
+denotes the point at which the year and the review month in the format
+B are inserted. For example if the script is being run for reviewing
+February 2025 the option:
+
+ -out=HPR_email_%s.txt
+
+will cause the generation of the file:
+
+ HPR_email_2025-02.txt
+
+=item B<-[no]force>
+
+Sometimes the recording of the Community News episode for a month takes place
+on the first Friday of the next month. If an attempt is made to run this
+script to make the email for such a recording in the month after the review
+month, safety checks will prevent it. This option, which is normally off, will
+allow the script to run and generate the mail message.
+
+Only use this if an apparent anomaly with the dates is detected. This may
+happen if the recording is to be made in the month after the month being
+reviewed.
+
+=item B<-config=FILE>
+
+This option defines a configuration file other than the default
+B<.make_email.cfg>. The file must be formatted as described below in the
+section I.
+
=item B<-debug=N>
Enables debugging mode when N > 0 (zero is the default, no debugging output).
@@ -1505,104 +1641,36 @@ The number of the show found in the database.
=back
-=item B<-month=DATE>
-
-Defines the month for which the email will be generated using a date in that
-month. Normally (without this option) the current month is chosen and the date
-of recording computed within it. The month specified here is provided as
-a ISO8601 date such as 2014-03-08 (meaning March 2014) or 1-Jan-2017 (meaning
-January 2017). Only the year and month parts are used but a valid day must be
-present.
-
-=item B<-date=DATE>
-
-This is an option provides a non-default date for the recording. Normally the
-script computes the next scheduled date based on the algorithm "DAY_OF_WEEK
-before the first Monday of the next month" (where DAY_OF_WEEK is the value
-defined in the configuration file as B) starting from the current
-date or the start of the month given in the B<-month=DATE> option. If for any
-reason a different date is required then this may be specified via this
-option.
-
-The recording date should be given as an ISO8601 date (such as 2014-03-08).
-
-=item B<-start=START_TIME>
-
-The default start time is defined in the configuration file, but if it is
-necessary to change it, this option can be used to do it. The B
-value must be a valid B time specification.
-
-A change to the start time in the configuration file also implies that the end
-time should change. If the B<-start=START_TIME> option is present but
-B<-end=END_TIME> is not, then the end time is computed by adding a number of
-hours to the start time this number is defined in the configuration file as
-B.
-
-=item B<-end=END_TIME>
-
-The default end time is defined in the configuration file, but if it is
-necessary to change it temporarily, this option can be used to do it. The
-B value must be a valid B time specification.
-
-=item B<-output=FILE>
-
-This option defines an output file to receive the mail message text. If the option is
-omitted the notes are written to STDOUT, allowing them to be redirected if
-required.
-
-The output file name may contain the characters 'B<%s>'. This denotes the
-point at which the year and the review month in the format B are
-inserted. For example if the script is being run for February 2025 the option:
-
- -out=HPR_email_%s.txt
-
-will cause the generation of the file:
-
- HPR_email_2025-02.txt
-
-=item B<-[no]force>
-
-Sometimes the recording of the Community News episode for a month takes place
-on the first Friday of the next month. If an attempt is made to run this
-script to make the email for such a recording in the month after the review
-month, safety checks will prevent it. This option, which is normally off, will
-allow the script to run and generate the mail message.
-
-=item B<-config=FILE>
-
-This option defines a configuration file other than the default
-B<.make_email.cfg>. The file must be formatted as described below in the
-section I.
=back
=head1 DESCRIPTION
-Makes an invitation email for the next Community News with times per timezone.
-The message is structured by a Template Toolkit template, so its contents can
-be adjusted without changing this script.
+The script makes an invitation email for the next Community News recording
+with times per timezone. The message is structured by a Template Toolkit
+template, so its contents can be adjusted without changing this script.
In normal operation the script computes the date of the next recording using
-the algorithm "Saturday before the first Monday of the next month" starting
+the algorithm "DAY before the first Monday of the next month" starting
from the current date or the start of the month (and year) given in the
B<-month=DATE> option.
-It uses the recording date (B<-date=DATE> option) to access the MySQL database
-to find the date on which the show will be released. It does that so the notes
-on that show can be viewed by the volunteers recording the show. These notes
-are expanded to be usable during the recording, with comments relating to
-earlier shows being displayed in full, and any comments missed in the last
-recording highlighted. Comments made to shows during the past month can be
-seen as the shows are visited and discussed.
+Alternatively it can use the B<-date=DATE> option to define a non-default
+recording date (and time if B<-start=START_TIME> is provided). This allows the
+defaults in the configuration file to be overridden.
-The email generated by the script is sent to the HPR mailing list, usually on
-the Monday prior to the weekend of the recording.
+Only one of B<-month=DATE> and B<-date=DATE> is allowed.
+
+The script creates the text of the body of an email message. It is necessary
+to use it with an email client to construct the message to be sent to the HPR
+mailing list, usually on the Monday prior to the weekend of the recording. The
+script does not send the message itself.
=head1 DIAGNOSTICS
=over 8
-=item B
+=item B
The configuration file specified in B<-config=FILE> (or the default file)
could not be found.
@@ -1617,29 +1685,87 @@ document.
One or both of the start and end times is missing, either from the configuration file or
from the command line options.
-=item B
+=item B
The template file specified in the configuration file could not be found.
-=item B
+=item B
-The program can generate warning messages from the database.
+The script reads and writes a file, usually called B,
+which is defined in the configuration file. This is used to record the date
+and time of the recording for a given month for use by othger scripts. This
+warning is reporting that no such file specification has been found in the
+configuration file. The script will continue without it.
+
+=item B
+
+The file for holding recording dates, which is defined in the configuration
+file, cannot be found. The script will continue without it.
+
+=item B
+
+The script cannot open the nominated output file for writing.
+
+=item B
+
+No output file has been nominated, so the script witll write to STDOUT. It has
+failed to open this channel however.
+
+=item B<...: failed to open '...': ...>
+
+The date cache file could not be opened.
+
+=item B<...: failed to close '...': ...>
+
+Type: warning
+
+The date cache file could not be closed.
+
+=item B
+
+The script needs to append to the date cache and it is trying to make a backup
+of it before it does so. This process has failed.
=item B
-An invalid date has been supplied via this option.
-
-=item B
-
-The date specified in B<-date=DATE> is in the past.
-
-=item B
+Type: fatal
An invalid date has been supplied via this option.
-=item B
+=item B<...: failed to truncate '...': ...> or
+B<...: failed to seek in '...': ...>
-The month specified in B<-month=DATE> is in the past.
+Type: fatal
+
+The script is attempting to update a line in the date cache, but has failed to
+perform an operation on it.
+
+=item B
+
+Type: fatal
+
+The script is checking one of the times provided and has found it to be
+invalid.
+
+=item B
+
+Type: fatal
+
+The script has failed to validate a date and time value (built from options
+such as B<-date=DATE> and B<-start=TIME>).
+
+=item B
+
+Type: fatal
+
+The script has failed to parse a date from B<-date=DATE> or B<-month=DATE>.
+
+=item B<"convert_date: Invalid date ... (in the past)>
+
+Type: fatal
+
+The date specified in B<-date=DATE> or B<-month=DATE> is in the past. There is
+a supplementary message: I