forked from HPR/hpr-tools
		
	
		
			
	
	
		
			1579 lines
		
	
	
		
			44 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
		
		
			
		
	
	
			1579 lines
		
	
	
		
			44 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
| 
								 | 
							
								#!/usr/bin/env perl
							 | 
						||
| 
								 | 
							
								#===============================================================================
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								#         FILE: make_email
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								#        USAGE: ./make_email [-debug=N] [-month=DATE] [-from=ADDRESS]
							 | 
						||
| 
								 | 
							
								#               [-to=ADDRESS] [-[no]mail] [-date=DATE] [-start=START_TIME]
							 | 
						||
| 
								 | 
							
								#               [-end=END_TIME] [-config=FILE] [-dbconfig=FILE]
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								#  DESCRIPTION: Make and send an invitation email for the next Community News
							 | 
						||
| 
								 | 
							
								#               with times per timezone.
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								#               The configuration file (.make_email.cfg) defines the name of
							 | 
						||
| 
								 | 
							
								#               the email template and the defaults used when generating the
							 | 
						||
| 
								 | 
							
								#               message. The date of the recording is computed from the
							 | 
						||
| 
								 | 
							
								#               current month (Saturday before the first Monday of the month
							 | 
						||
| 
								 | 
							
								#               when the show will be posted). It can also be specified
							 | 
						||
| 
								 | 
							
								#               through the -date=DATE option.
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								#               The month the email relates to can be changed through the
							 | 
						||
| 
								 | 
							
								#               -month=DATA option, though this is rarely used. Use a date of
							 | 
						||
| 
								 | 
							
								#               the format 'YYYY-MM-DD' here. The day is ignored but the year
							 | 
						||
| 
								 | 
							
								#               and month are used in the computation. The month specified
							 | 
						||
| 
								 | 
							
								#               must be in the future.
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								#               The database configuration file defines the database to be
							 | 
						||
| 
								 | 
							
								#               used to compute the date and show number. Use .hpr_db.cfg for
							 | 
						||
| 
								 | 
							
								#               the local MariaDB copy (for testing) and .hpr_livedb.cfg for
							 | 
						||
| 
								 | 
							
								#               the live database (over the ssh tunnel, which must have been
							 | 
						||
| 
								 | 
							
								#               opened already).
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								#      OPTIONS: ---
							 | 
						||
| 
								 | 
							
								# 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
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								#       AUTHOR: Dave Morriss (djm), Dave.Morriss@gmail.com
							 | 
						||
| 
								 | 
							
								#      VERSION: 0.2.7
							 | 
						||
| 
								 | 
							
								#      CREATED: 2013-10-28 20:35:22
							 | 
						||
| 
								 | 
							
								#     REVISION: 2024-05-24 18:53:17
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								#===============================================================================
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								use 5.010;
							 | 
						||
| 
								 | 
							
								use strict;
							 | 
						||
| 
								 | 
							
								use warnings;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								use Getopt::Long;
							 | 
						||
| 
								 | 
							
								use Pod::Usage;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								use Config::General;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								use Date::Parse;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								use DateTime;
							 | 
						||
| 
								 | 
							
								use DateTime::TimeZone;
							 | 
						||
| 
								 | 
							
								use DateTime::Format::Duration;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								use Date::Calc qw{:all};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								use Template;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								use Mail::Mailer;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								use DBI;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								use Data::Dumper;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Version number (manually incremented)
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								our $VERSION = '0.2.7';
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Script name
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								( my $PROG = $0 ) =~ s|.*/||mx;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#-------------------------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								# Declarations
							 | 
						||
| 
								 | 
							
								#-------------------------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Constants and other declarations
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								my $basedir    = "$ENV{HOME}/HPR/Community_News";
							 | 
						||
| 
								 | 
							
								my $configfile1 = "$basedir/.${PROG}.cfg";
							 | 
						||
| 
								 | 
							
								my $configfile2 = "$basedir/.hpr_db.cfg";
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								my ( $dbh, $sth1, $sth2, $sth3, $h1, $h2, $rv );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# The timezones we want to report. These were generated with
							 | 
						||
| 
								 | 
							
								# DateTime::TimeZone->all_names(). Just uncomment the desired elements.
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								my @zones = (
							 | 
						||
| 
								 | 
							
								    #{{{
							 | 
						||
| 
								 | 
							
								    #'Africa/Abidjan',
							 | 
						||
| 
								 | 
							
								    #'Africa/Accra',
							 | 
						||
| 
								 | 
							
								    #'Africa/Addis_Ababa',
							 | 
						||
| 
								 | 
							
								    #'Africa/Algiers',
							 | 
						||
| 
								 | 
							
								    #'Africa/Asmara',
							 | 
						||
| 
								 | 
							
								    #'Africa/Bamako',
							 | 
						||
| 
								 | 
							
								    #'Africa/Bangui',
							 | 
						||
| 
								 | 
							
								    #'Africa/Banjul',
							 | 
						||
| 
								 | 
							
								    #'Africa/Bissau',
							 | 
						||
| 
								 | 
							
								    #'Africa/Blantyre',
							 | 
						||
| 
								 | 
							
								    #'Africa/Brazzaville',
							 | 
						||
| 
								 | 
							
								    #'Africa/Bujumbura',
							 | 
						||
| 
								 | 
							
								    #'Africa/Cairo',
							 | 
						||
| 
								 | 
							
								    #'Africa/Casablanca',
							 | 
						||
| 
								 | 
							
								    #'Africa/Ceuta',
							 | 
						||
| 
								 | 
							
								    #'Africa/Conakry',
							 | 
						||
| 
								 | 
							
								    #'Africa/Dakar',
							 | 
						||
| 
								 | 
							
								    #'Africa/Dar_es_Salaam',
							 | 
						||
| 
								 | 
							
								    #'Africa/Djibouti',
							 | 
						||
| 
								 | 
							
								    #'Africa/Douala',
							 | 
						||
| 
								 | 
							
								    #'Africa/El_Aaiun',
							 | 
						||
| 
								 | 
							
								    #'Africa/Freetown',
							 | 
						||
| 
								 | 
							
								    #'Africa/Gaborone',
							 | 
						||
| 
								 | 
							
								    #'Africa/Harare',
							 | 
						||
| 
								 | 
							
								    #'Africa/Johannesburg',
							 | 
						||
| 
								 | 
							
								    #'Africa/Kampala',
							 | 
						||
| 
								 | 
							
								    #'Africa/Khartoum',
							 | 
						||
| 
								 | 
							
								    #'Africa/Kigali',
							 | 
						||
| 
								 | 
							
								    #'Africa/Kinshasa',
							 | 
						||
| 
								 | 
							
								    #'Africa/Lagos',
							 | 
						||
| 
								 | 
							
								    #'Africa/Libreville',
							 | 
						||
| 
								 | 
							
								    #'Africa/Lome',
							 | 
						||
| 
								 | 
							
								    #'Africa/Luanda',
							 | 
						||
| 
								 | 
							
								    #'Africa/Lubumbashi',
							 | 
						||
| 
								 | 
							
								    #'Africa/Lusaka',
							 | 
						||
| 
								 | 
							
								    #'Africa/Malabo',
							 | 
						||
| 
								 | 
							
								    #'Africa/Maputo',
							 | 
						||
| 
								 | 
							
								    #'Africa/Maseru',
							 | 
						||
| 
								 | 
							
								    #'Africa/Mbabane',
							 | 
						||
| 
								 | 
							
								    #'Africa/Mogadishu',
							 | 
						||
| 
								 | 
							
								    #'Africa/Monrovia',
							 | 
						||
| 
								 | 
							
								    #'Africa/Nairobi',
							 | 
						||
| 
								 | 
							
								    #'Africa/Ndjamena',
							 | 
						||
| 
								 | 
							
								    #'Africa/Niamey',
							 | 
						||
| 
								 | 
							
								    #'Africa/Nouakchott',
							 | 
						||
| 
								 | 
							
								    #'Africa/Ouagadougou',
							 | 
						||
| 
								 | 
							
								    #'Africa/Porto-Novo',
							 | 
						||
| 
								 | 
							
								    #'Africa/Sao_Tome',
							 | 
						||
| 
								 | 
							
								    #'Africa/Tripoli',
							 | 
						||
| 
								 | 
							
								    #'Africa/Tunis',
							 | 
						||
| 
								 | 
							
								    #'Africa/Windhoek',
							 | 
						||
| 
								 | 
							
								    #'America/Adak',
							 | 
						||
| 
								 | 
							
								    #'America/Anchorage',
							 | 
						||
| 
								 | 
							
								    #'America/Antigua',
							 | 
						||
| 
								 | 
							
								    #'America/Araguaina',
							 | 
						||
| 
								 | 
							
								    #'America/Argentina/Buenos_Aires',
							 | 
						||
| 
								 | 
							
								    #'America/Argentina/Catamarca',
							 | 
						||
| 
								 | 
							
								    #'America/Argentina/Cordoba',
							 | 
						||
| 
								 | 
							
								    #'America/Argentina/Jujuy',
							 | 
						||
| 
								 | 
							
								    #'America/Argentina/La_Rioja',
							 | 
						||
| 
								 | 
							
								    #'America/Argentina/Mendoza',
							 | 
						||
| 
								 | 
							
								    #'America/Argentina/Rio_Gallegos',
							 | 
						||
| 
								 | 
							
								    #'America/Argentina/Salta',
							 | 
						||
| 
								 | 
							
								    #'America/Argentina/San_Juan',
							 | 
						||
| 
								 | 
							
								    #'America/Argentina/San_Luis',
							 | 
						||
| 
								 | 
							
								    #'America/Argentina/Tucuman',
							 | 
						||
| 
								 | 
							
								    #'America/Argentina/Ushuaia',
							 | 
						||
| 
								 | 
							
								    #'America/Asuncion',
							 | 
						||
| 
								 | 
							
								    #'America/Atikokan',
							 | 
						||
| 
								 | 
							
								    #'America/Bahia',
							 | 
						||
| 
								 | 
							
								    #'America/Bahia_Banderas',
							 | 
						||
| 
								 | 
							
								    #'America/Barbados',
							 | 
						||
| 
								 | 
							
								    #'America/Belem',
							 | 
						||
| 
								 | 
							
								    #'America/Belize',
							 | 
						||
| 
								 | 
							
								    #'America/Blanc-Sablon',
							 | 
						||
| 
								 | 
							
								    #'America/Boa_Vista',
							 | 
						||
| 
								 | 
							
								    #'America/Bogota',
							 | 
						||
| 
								 | 
							
								    #'America/Boise',
							 | 
						||
| 
								 | 
							
								    #'America/Cambridge_Bay',
							 | 
						||
| 
								 | 
							
								    #'America/Campo_Grande',
							 | 
						||
| 
								 | 
							
								    #'America/Cancun',
							 | 
						||
| 
								 | 
							
								    #'America/Caracas',
							 | 
						||
| 
								 | 
							
								    #'America/Cayenne',
							 | 
						||
| 
								 | 
							
								    #'America/Cayman',
							 | 
						||
| 
								 | 
							
								    'America/Chicago',
							 | 
						||
| 
								 | 
							
								    #'America/Chihuahua',
							 | 
						||
| 
								 | 
							
								    #'America/Costa_Rica',
							 | 
						||
| 
								 | 
							
								    #'America/Creston',
							 | 
						||
| 
								 | 
							
								    #'America/Cuiaba',
							 | 
						||
| 
								 | 
							
								    #'America/Curacao',
							 | 
						||
| 
								 | 
							
								    #'America/Danmarkshavn',
							 | 
						||
| 
								 | 
							
								    #'America/Dawson',
							 | 
						||
| 
								 | 
							
								    #'America/Dawson_Creek',
							 | 
						||
| 
								 | 
							
								    #'America/Denver',
							 | 
						||
| 
								 | 
							
								    #'America/Detroit',
							 | 
						||
| 
								 | 
							
								    #'America/Edmonton',
							 | 
						||
| 
								 | 
							
								    #'America/Eirunepe',
							 | 
						||
| 
								 | 
							
								    #'America/El_Salvador',
							 | 
						||
| 
								 | 
							
								    #'America/Fortaleza',
							 | 
						||
| 
								 | 
							
								    #'America/Glace_Bay',
							 | 
						||
| 
								 | 
							
								    #'America/Godthab',
							 | 
						||
| 
								 | 
							
								    #'America/Goose_Bay',
							 | 
						||
| 
								 | 
							
								    #'America/Grand_Turk',
							 | 
						||
| 
								 | 
							
								    #'America/Guatemala',
							 | 
						||
| 
								 | 
							
								    #'America/Guayaquil',
							 | 
						||
| 
								 | 
							
								    #'America/Guyana',
							 | 
						||
| 
								 | 
							
								    #'America/Halifax',
							 | 
						||
| 
								 | 
							
								    #'America/Havana',
							 | 
						||
| 
								 | 
							
								    #'America/Hermosillo',
							 | 
						||
| 
								 | 
							
								    #'America/Indiana/Indianapolis',
							 | 
						||
| 
								 | 
							
								    #'America/Indiana/Knox',
							 | 
						||
| 
								 | 
							
								    #'America/Indiana/Marengo',
							 | 
						||
| 
								 | 
							
								    #'America/Indiana/Petersburg',
							 | 
						||
| 
								 | 
							
								    #'America/Indiana/Tell_City',
							 | 
						||
| 
								 | 
							
								    #'America/Indiana/Vevay',
							 | 
						||
| 
								 | 
							
								    #'America/Indiana/Vincennes',
							 | 
						||
| 
								 | 
							
								    #'America/Indiana/Winamac',
							 | 
						||
| 
								 | 
							
								    #'America/Inuvik',
							 | 
						||
| 
								 | 
							
								    #'America/Iqaluit',
							 | 
						||
| 
								 | 
							
								    #'America/Jamaica',
							 | 
						||
| 
								 | 
							
								    #'America/Juneau',
							 | 
						||
| 
								 | 
							
								    #'America/Kentucky/Louisville',
							 | 
						||
| 
								 | 
							
								    #'America/Kentucky/Monticello',
							 | 
						||
| 
								 | 
							
								    #'America/La_Paz',
							 | 
						||
| 
								 | 
							
								    #'America/Lima',
							 | 
						||
| 
								 | 
							
								    'America/Los_Angeles',
							 | 
						||
| 
								 | 
							
								    #'America/Maceio',
							 | 
						||
| 
								 | 
							
								    #'America/Managua',
							 | 
						||
| 
								 | 
							
								    #'America/Manaus',
							 | 
						||
| 
								 | 
							
								    #'America/Martinique',
							 | 
						||
| 
								 | 
							
								    #'America/Matamoros',
							 | 
						||
| 
								 | 
							
								    #'America/Mazatlan',
							 | 
						||
| 
								 | 
							
								    #'America/Menominee',
							 | 
						||
| 
								 | 
							
								    #'America/Merida',
							 | 
						||
| 
								 | 
							
								    #'America/Metlakatla',
							 | 
						||
| 
								 | 
							
								    #'America/Mexico_City',
							 | 
						||
| 
								 | 
							
								    #'America/Miquelon',
							 | 
						||
| 
								 | 
							
								    #'America/Moncton',
							 | 
						||
| 
								 | 
							
								    #'America/Monterrey',
							 | 
						||
| 
								 | 
							
								    #'America/Montevideo',
							 | 
						||
| 
								 | 
							
								    #'America/Montreal',
							 | 
						||
| 
								 | 
							
								    #'America/Nassau',
							 | 
						||
| 
								 | 
							
								    'America/New_York',
							 | 
						||
| 
								 | 
							
								    #'America/Nipigon',
							 | 
						||
| 
								 | 
							
								    #'America/Nome',
							 | 
						||
| 
								 | 
							
								    #'America/Noronha',
							 | 
						||
| 
								 | 
							
								    #'America/North_Dakota/Beulah',
							 | 
						||
| 
								 | 
							
								    #'America/North_Dakota/Center',
							 | 
						||
| 
								 | 
							
								    #'America/North_Dakota/New_Salem',
							 | 
						||
| 
								 | 
							
								    #'America/Ojinaga',
							 | 
						||
| 
								 | 
							
								    #'America/Panama',
							 | 
						||
| 
								 | 
							
								    #'America/Pangnirtung',
							 | 
						||
| 
								 | 
							
								    #'America/Paramaribo',
							 | 
						||
| 
								 | 
							
								    #'America/Phoenix',
							 | 
						||
| 
								 | 
							
								    #'America/Port-au-Prince',
							 | 
						||
| 
								 | 
							
								    #'America/Port_of_Spain',
							 | 
						||
| 
								 | 
							
								    #'America/Porto_Velho',
							 | 
						||
| 
								 | 
							
								    #'America/Puerto_Rico',
							 | 
						||
| 
								 | 
							
								    #'America/Rainy_River',
							 | 
						||
| 
								 | 
							
								    #'America/Rankin_Inlet',
							 | 
						||
| 
								 | 
							
								    #'America/Recife',
							 | 
						||
| 
								 | 
							
								    #'America/Regina',
							 | 
						||
| 
								 | 
							
								    #'America/Resolute',
							 | 
						||
| 
								 | 
							
								    #'America/Rio_Branco',
							 | 
						||
| 
								 | 
							
								    #'America/Santa_Isabel',
							 | 
						||
| 
								 | 
							
								    #'America/Santarem',
							 | 
						||
| 
								 | 
							
								    #'America/Santiago',
							 | 
						||
| 
								 | 
							
								    #'America/Santo_Domingo',
							 | 
						||
| 
								 | 
							
								    #'America/Sao_Paulo',
							 | 
						||
| 
								 | 
							
								    #'America/Scoresbysund',
							 | 
						||
| 
								 | 
							
								    #'America/Sitka',
							 | 
						||
| 
								 | 
							
								    #'America/St_Johns',
							 | 
						||
| 
								 | 
							
								    #'America/Swift_Current',
							 | 
						||
| 
								 | 
							
								    #'America/Tegucigalpa',
							 | 
						||
| 
								 | 
							
								    #'America/Thule',
							 | 
						||
| 
								 | 
							
								    #'America/Thunder_Bay',
							 | 
						||
| 
								 | 
							
								    #'America/Tijuana',
							 | 
						||
| 
								 | 
							
								    #'America/Toronto',
							 | 
						||
| 
								 | 
							
								    #'America/Vancouver',
							 | 
						||
| 
								 | 
							
								    #'America/Whitehorse',
							 | 
						||
| 
								 | 
							
								    #'America/Winnipeg',
							 | 
						||
| 
								 | 
							
								    #'America/Yakutat',
							 | 
						||
| 
								 | 
							
								    #'America/Yellowknife',
							 | 
						||
| 
								 | 
							
								    #'Antarctica/Casey',
							 | 
						||
| 
								 | 
							
								    #'Antarctica/Davis',
							 | 
						||
| 
								 | 
							
								    #'Antarctica/DumontDUrville',
							 | 
						||
| 
								 | 
							
								    #'Antarctica/Macquarie',
							 | 
						||
| 
								 | 
							
								    #'Antarctica/Mawson',
							 | 
						||
| 
								 | 
							
								    #'Antarctica/Palmer',
							 | 
						||
| 
								 | 
							
								    #'Antarctica/Rothera',
							 | 
						||
| 
								 | 
							
								    #'Antarctica/Syowa',
							 | 
						||
| 
								 | 
							
								    #'Antarctica/Vostok',
							 | 
						||
| 
								 | 
							
								    #'Asia/Aden',
							 | 
						||
| 
								 | 
							
								    #'Asia/Almaty',
							 | 
						||
| 
								 | 
							
								    #'Asia/Amman',
							 | 
						||
| 
								 | 
							
								    #'Asia/Anadyr',
							 | 
						||
| 
								 | 
							
								    #'Asia/Aqtau',
							 | 
						||
| 
								 | 
							
								    #'Asia/Aqtobe',
							 | 
						||
| 
								 | 
							
								    #'Asia/Ashgabat',
							 | 
						||
| 
								 | 
							
								    #'Asia/Baghdad',
							 | 
						||
| 
								 | 
							
								    #'Asia/Bahrain',
							 | 
						||
| 
								 | 
							
								    #'Asia/Baku',
							 | 
						||
| 
								 | 
							
								    #'Asia/Bangkok',
							 | 
						||
| 
								 | 
							
								    #'Asia/Beirut',
							 | 
						||
| 
								 | 
							
								    #'Asia/Bishkek',
							 | 
						||
| 
								 | 
							
								    #'Asia/Brunei',
							 | 
						||
| 
								 | 
							
								    #'Asia/Choibalsan',
							 | 
						||
| 
								 | 
							
								    #'Asia/Chongqing',
							 | 
						||
| 
								 | 
							
								    #'Asia/Colombo',
							 | 
						||
| 
								 | 
							
								    #'Asia/Damascus',
							 | 
						||
| 
								 | 
							
								    #'Asia/Dhaka',
							 | 
						||
| 
								 | 
							
								    #'Asia/Dili',
							 | 
						||
| 
								 | 
							
								    #'Asia/Dubai',
							 | 
						||
| 
								 | 
							
								    #'Asia/Dushanbe',
							 | 
						||
| 
								 | 
							
								    #'Asia/Gaza',
							 | 
						||
| 
								 | 
							
								    #'Asia/Harbin',
							 | 
						||
| 
								 | 
							
								    #'Asia/Hebron',
							 | 
						||
| 
								 | 
							
								    #'Asia/Ho_Chi_Minh',
							 | 
						||
| 
								 | 
							
								    'Asia/Hong_Kong',
							 | 
						||
| 
								 | 
							
								    #'Asia/Hovd',
							 | 
						||
| 
								 | 
							
								    #'Asia/Irkutsk',
							 | 
						||
| 
								 | 
							
								    #'Asia/Jakarta',
							 | 
						||
| 
								 | 
							
								    #'Asia/Jayapura',
							 | 
						||
| 
								 | 
							
								    #'Asia/Jerusalem',
							 | 
						||
| 
								 | 
							
								    #'Asia/Kabul',
							 | 
						||
| 
								 | 
							
								    #'Asia/Kamchatka',
							 | 
						||
| 
								 | 
							
								    #'Asia/Karachi',
							 | 
						||
| 
								 | 
							
								    #'Asia/Kashgar',
							 | 
						||
| 
								 | 
							
								    #'Asia/Kathmandu',
							 | 
						||
| 
								 | 
							
								    #'Asia/Khandyga',
							 | 
						||
| 
								 | 
							
								    #'Asia/Kolkata',
							 | 
						||
| 
								 | 
							
								    #'Asia/Krasnoyarsk',
							 | 
						||
| 
								 | 
							
								    #'Asia/Kuala_Lumpur',
							 | 
						||
| 
								 | 
							
								    #'Asia/Kuching',
							 | 
						||
| 
								 | 
							
								    #'Asia/Kuwait',
							 | 
						||
| 
								 | 
							
								    #'Asia/Macau',
							 | 
						||
| 
								 | 
							
								    #'Asia/Magadan',
							 | 
						||
| 
								 | 
							
								    #'Asia/Makassar',
							 | 
						||
| 
								 | 
							
								    #'Asia/Manila',
							 | 
						||
| 
								 | 
							
								    #'Asia/Muscat',
							 | 
						||
| 
								 | 
							
								    #'Asia/Nicosia',
							 | 
						||
| 
								 | 
							
								    #'Asia/Novokuznetsk',
							 | 
						||
| 
								 | 
							
								    #'Asia/Novosibirsk',
							 | 
						||
| 
								 | 
							
								    #'Asia/Omsk',
							 | 
						||
| 
								 | 
							
								    #'Asia/Oral',
							 | 
						||
| 
								 | 
							
								    #'Asia/Phnom_Penh',
							 | 
						||
| 
								 | 
							
								    #'Asia/Pontianak',
							 | 
						||
| 
								 | 
							
								    #'Asia/Pyongyang',
							 | 
						||
| 
								 | 
							
								    #'Asia/Qatar',
							 | 
						||
| 
								 | 
							
								    #'Asia/Qyzylorda',
							 | 
						||
| 
								 | 
							
								    #'Asia/Rangoon',
							 | 
						||
| 
								 | 
							
								    #'Asia/Riyadh',
							 | 
						||
| 
								 | 
							
								    #'Asia/Sakhalin',
							 | 
						||
| 
								 | 
							
								    #'Asia/Samarkand',
							 | 
						||
| 
								 | 
							
								    #'Asia/Seoul',
							 | 
						||
| 
								 | 
							
								    #'Asia/Shanghai',
							 | 
						||
| 
								 | 
							
								    #'Asia/Singapore',
							 | 
						||
| 
								 | 
							
								    #'Asia/Taipei',
							 | 
						||
| 
								 | 
							
								    #'Asia/Tashkent',
							 | 
						||
| 
								 | 
							
								    #'Asia/Tbilisi',
							 | 
						||
| 
								 | 
							
								    #'Asia/Tehran',
							 | 
						||
| 
								 | 
							
								    #'Asia/Thimphu',
							 | 
						||
| 
								 | 
							
								    #'Asia/Tokyo',
							 | 
						||
| 
								 | 
							
								    #'Asia/Ulaanbaatar',
							 | 
						||
| 
								 | 
							
								    #'Asia/Urumqi',
							 | 
						||
| 
								 | 
							
								    #'Asia/Ust-Nera',
							 | 
						||
| 
								 | 
							
								    #'Asia/Vientiane',
							 | 
						||
| 
								 | 
							
								    #'Asia/Vladivostok',
							 | 
						||
| 
								 | 
							
								    #'Asia/Yakutsk',
							 | 
						||
| 
								 | 
							
								    #'Asia/Yekaterinburg',
							 | 
						||
| 
								 | 
							
								    #'Asia/Yerevan',
							 | 
						||
| 
								 | 
							
								    #'Atlantic/Azores',
							 | 
						||
| 
								 | 
							
								    #'Atlantic/Bermuda',
							 | 
						||
| 
								 | 
							
								    #'Atlantic/Canary',
							 | 
						||
| 
								 | 
							
								    #'Atlantic/Cape_Verde',
							 | 
						||
| 
								 | 
							
								    #'Atlantic/Faroe',
							 | 
						||
| 
								 | 
							
								    #'Atlantic/Madeira',
							 | 
						||
| 
								 | 
							
								    #'Atlantic/Reykjavik',
							 | 
						||
| 
								 | 
							
								    #'Atlantic/South_Georgia',
							 | 
						||
| 
								 | 
							
								    #'Atlantic/St_Helena',
							 | 
						||
| 
								 | 
							
								    #'Atlantic/Stanley',
							 | 
						||
| 
								 | 
							
								    #'Australia/Adelaide',
							 | 
						||
| 
								 | 
							
								    'Australia/Brisbane',
							 | 
						||
| 
								 | 
							
								    #'Australia/Broken_Hill',
							 | 
						||
| 
								 | 
							
								    #'Australia/Currie',
							 | 
						||
| 
								 | 
							
								    #'Australia/Darwin',
							 | 
						||
| 
								 | 
							
								    #'Australia/Eucla',
							 | 
						||
| 
								 | 
							
								    #'Australia/Hobart',
							 | 
						||
| 
								 | 
							
								    #'Australia/Lindeman',
							 | 
						||
| 
								 | 
							
								    #'Australia/Lord_Howe',
							 | 
						||
| 
								 | 
							
								    #'Australia/Melbourne',
							 | 
						||
| 
								 | 
							
								    'Australia/Perth',
							 | 
						||
| 
								 | 
							
								    'Australia/Sydney',
							 | 
						||
| 
								 | 
							
								    #'CET',
							 | 
						||
| 
								 | 
							
								    #'CST6CDT',
							 | 
						||
| 
								 | 
							
								    #'EET',
							 | 
						||
| 
								 | 
							
								    #'EST',
							 | 
						||
| 
								 | 
							
								    #'EST5EDT',
							 | 
						||
| 
								 | 
							
								    'Europe/Amsterdam',
							 | 
						||
| 
								 | 
							
								    #'Europe/Andorra',
							 | 
						||
| 
								 | 
							
								    #'Europe/Athens',
							 | 
						||
| 
								 | 
							
								    #'Europe/Belgrade',
							 | 
						||
| 
								 | 
							
								    #'Europe/Berlin',
							 | 
						||
| 
								 | 
							
								    #'Europe/Brussels',
							 | 
						||
| 
								 | 
							
								    #'Europe/Bucharest',
							 | 
						||
| 
								 | 
							
								    #'Europe/Budapest',
							 | 
						||
| 
								 | 
							
								    #'Europe/Chisinau',
							 | 
						||
| 
								 | 
							
								    #'Europe/Copenhagen',
							 | 
						||
| 
								 | 
							
								    #'Europe/Dublin',
							 | 
						||
| 
								 | 
							
								    #'Europe/Gibraltar',
							 | 
						||
| 
								 | 
							
								    #'Europe/Helsinki',
							 | 
						||
| 
								 | 
							
								    #'Europe/Istanbul',
							 | 
						||
| 
								 | 
							
								    #'Europe/Kaliningrad',
							 | 
						||
| 
								 | 
							
								    #'Europe/Kiev',
							 | 
						||
| 
								 | 
							
								    #'Europe/Lisbon',
							 | 
						||
| 
								 | 
							
								    'Europe/London',
							 | 
						||
| 
								 | 
							
								    #'Europe/Luxembourg',
							 | 
						||
| 
								 | 
							
								    #'Europe/Madrid',
							 | 
						||
| 
								 | 
							
								    #'Europe/Malta',
							 | 
						||
| 
								 | 
							
								    #'Europe/Minsk',
							 | 
						||
| 
								 | 
							
								    #'Europe/Monaco',
							 | 
						||
| 
								 | 
							
								    #'Europe/Moscow',
							 | 
						||
| 
								 | 
							
								    #'Europe/Oslo',
							 | 
						||
| 
								 | 
							
								    #'Europe/Paris',
							 | 
						||
| 
								 | 
							
								    #'Europe/Prague',
							 | 
						||
| 
								 | 
							
								    #'Europe/Riga',
							 | 
						||
| 
								 | 
							
								    #'Europe/Rome',
							 | 
						||
| 
								 | 
							
								    #'Europe/Samara',
							 | 
						||
| 
								 | 
							
								    #'Europe/Simferopol',
							 | 
						||
| 
								 | 
							
								    #'Europe/Sofia',
							 | 
						||
| 
								 | 
							
								    #'Europe/Stockholm',
							 | 
						||
| 
								 | 
							
								    #'Europe/Tallinn',
							 | 
						||
| 
								 | 
							
								    #'Europe/Tirane',
							 | 
						||
| 
								 | 
							
								    #'Europe/Uzhgorod',
							 | 
						||
| 
								 | 
							
								    #'Europe/Vienna',
							 | 
						||
| 
								 | 
							
								    #'Europe/Vilnius',
							 | 
						||
| 
								 | 
							
								    #'Europe/Volgograd',
							 | 
						||
| 
								 | 
							
								    #'Europe/Warsaw',
							 | 
						||
| 
								 | 
							
								    #'Europe/Zaporozhye',
							 | 
						||
| 
								 | 
							
								    'Europe/Zurich',
							 | 
						||
| 
								 | 
							
								    #'HST',
							 | 
						||
| 
								 | 
							
								    #'Indian/Antananarivo',
							 | 
						||
| 
								 | 
							
								    #'Indian/Chagos',
							 | 
						||
| 
								 | 
							
								    #'Indian/Christmas',
							 | 
						||
| 
								 | 
							
								    #'Indian/Cocos',
							 | 
						||
| 
								 | 
							
								    #'Indian/Comoro',
							 | 
						||
| 
								 | 
							
								    #'Indian/Kerguelen',
							 | 
						||
| 
								 | 
							
								    #'Indian/Mahe',
							 | 
						||
| 
								 | 
							
								    #'Indian/Maldives',
							 | 
						||
| 
								 | 
							
								    #'Indian/Mauritius',
							 | 
						||
| 
								 | 
							
								    #'Indian/Mayotte',
							 | 
						||
| 
								 | 
							
								    #'Indian/Reunion',
							 | 
						||
| 
								 | 
							
								    #'MET',
							 | 
						||
| 
								 | 
							
								    #'MST',
							 | 
						||
| 
								 | 
							
								    #'MST7MDT',
							 | 
						||
| 
								 | 
							
								    #'PST8PDT',
							 | 
						||
| 
								 | 
							
								    #'Pacific/Apia',
							 | 
						||
| 
								 | 
							
								    'Pacific/Auckland',
							 | 
						||
| 
								 | 
							
								    #'Pacific/Chatham',
							 | 
						||
| 
								 | 
							
								    #'Pacific/Chuuk',
							 | 
						||
| 
								 | 
							
								    #'Pacific/Easter',
							 | 
						||
| 
								 | 
							
								    #'Pacific/Efate',
							 | 
						||
| 
								 | 
							
								    #'Pacific/Enderbury',
							 | 
						||
| 
								 | 
							
								    #'Pacific/Fakaofo',
							 | 
						||
| 
								 | 
							
								    #'Pacific/Fiji',
							 | 
						||
| 
								 | 
							
								    #'Pacific/Funafuti',
							 | 
						||
| 
								 | 
							
								    #'Pacific/Galapagos',
							 | 
						||
| 
								 | 
							
								    #'Pacific/Gambier',
							 | 
						||
| 
								 | 
							
								    #'Pacific/Guadalcanal',
							 | 
						||
| 
								 | 
							
								    #'Pacific/Guam',
							 | 
						||
| 
								 | 
							
								    #'Pacific/Honolulu',
							 | 
						||
| 
								 | 
							
								    #'Pacific/Kiritimati',
							 | 
						||
| 
								 | 
							
								    #'Pacific/Kosrae',
							 | 
						||
| 
								 | 
							
								    #'Pacific/Kwajalein',
							 | 
						||
| 
								 | 
							
								    #'Pacific/Majuro',
							 | 
						||
| 
								 | 
							
								    #'Pacific/Marquesas',
							 | 
						||
| 
								 | 
							
								    #'Pacific/Midway',
							 | 
						||
| 
								 | 
							
								    #'Pacific/Nauru',
							 | 
						||
| 
								 | 
							
								    #'Pacific/Niue',
							 | 
						||
| 
								 | 
							
								    #'Pacific/Norfolk',
							 | 
						||
| 
								 | 
							
								    #'Pacific/Noumea',
							 | 
						||
| 
								 | 
							
								    #'Pacific/Pago_Pago',
							 | 
						||
| 
								 | 
							
								    #'Pacific/Palau',
							 | 
						||
| 
								 | 
							
								    #'Pacific/Pitcairn',
							 | 
						||
| 
								 | 
							
								    #'Pacific/Pohnpei',
							 | 
						||
| 
								 | 
							
								    #'Pacific/Port_Moresby',
							 | 
						||
| 
								 | 
							
								    #'Pacific/Rarotonga',
							 | 
						||
| 
								 | 
							
								    #'Pacific/Saipan',
							 | 
						||
| 
								 | 
							
								    #'Pacific/Tahiti',
							 | 
						||
| 
								 | 
							
								    #'Pacific/Tarawa',
							 | 
						||
| 
								 | 
							
								    #'Pacific/Tongatapu',
							 | 
						||
| 
								 | 
							
								    #'Pacific/Wake',
							 | 
						||
| 
								 | 
							
								    #'Pacific/Wallis',
							 | 
						||
| 
								 | 
							
								    'UTC',
							 | 
						||
| 
								 | 
							
								    #'WET',
							 | 
						||
| 
								 | 
							
								    #}}}
							 | 
						||
| 
								 | 
							
								);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Enable Unicode mode
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								binmode STDOUT, ":encoding(UTF-8)";
							 | 
						||
| 
								 | 
							
								binmode STDERR, ":encoding(UTF-8)";
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Defaults for options
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								my $DEF_DEBUG = 0;
							 | 
						||
| 
								 | 
							
								my $DEF_FROM  = 'Dave.Morriss@gmail.com';
							 | 
						||
| 
								 | 
							
								my $DEF_TO    = 'perloid@autistici.org';
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Options and arguments
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								my %options;
							 | 
						||
| 
								 | 
							
								Options( \%options );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Default help
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								pod2usage( -msg => "$PROG version $VERSION\n", -verbose => 0, -exitval => 1 )
							 | 
						||
| 
								 | 
							
								    if ( $options{'help'} );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Full documentation if requested with -doc
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								pod2usage(
							 | 
						||
| 
								 | 
							
								    -msg       => "$PROG version $VERSION\n",
							 | 
						||
| 
								 | 
							
								    -verbose   => 2,
							 | 
						||
| 
								 | 
							
								    -exitval   => 1,
							 | 
						||
| 
								 | 
							
								#    -noperldoc => 0,
							 | 
						||
| 
								 | 
							
								) if ( $options{'documentation'} );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Collect options
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								my $DEBUG = ( defined( $options{debug} ) ? $options{debug} : $DEF_DEBUG );
							 | 
						||
| 
								 | 
							
								my $month = $options{month};
							 | 
						||
| 
								 | 
							
								my $mail  = ( defined( $options{mail} ) ? $options{mail} : 0 );
							 | 
						||
| 
								 | 
							
								my $from_address = (
							 | 
						||
| 
								 | 
							
								    defined( $options{fromaddress} ) ? $options{fromaddress} : $DEF_FROM );
							 | 
						||
| 
								 | 
							
								my $to_address
							 | 
						||
| 
								 | 
							
								    = ( defined( $options{toaddress} ) ? $options{toaddress} : $DEF_TO );
							 | 
						||
| 
								 | 
							
								my $date     = $options{date};
							 | 
						||
| 
								 | 
							
								my $start    = $options{starttime};
							 | 
						||
| 
								 | 
							
								my $end      = $options{endtime};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# This value is in the configuration file and can't be overridden. The planned
							 | 
						||
| 
								 | 
							
								# end time can be specified however.
							 | 
						||
| 
								 | 
							
								#my $duration = $options{duration};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								my $cfgfile
							 | 
						||
| 
								 | 
							
								    = ( defined( $options{config} ) ? $options{config} : $configfile1 );
							 | 
						||
| 
								 | 
							
								my $dbcfgfile
							 | 
						||
| 
								 | 
							
								    = ( defined( $options{dbconfig} ) ? $options{dbconfig} : $configfile2 );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Use the 'testfile' mailer if option -nomail was chosen. This writes the file
							 | 
						||
| 
								 | 
							
								# 'mailer.testfile' and sends no message
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								my $mailertype = ( $mail ? 'sendmail' : 'testfile' );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Sanity checking the options
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								die "Unable to find $cfgfile\n" unless ( -e $cfgfile );
							 | 
						||
| 
								 | 
							
								die "Use only one of -month=MONTH or -date=DATE\n"
							 | 
						||
| 
								 | 
							
								    if (defined($month) && defined($date));
							 | 
						||
| 
								 | 
							
								#die "Use only one of -endtime=TIME or -duration=HOURS\n"
							 | 
						||
| 
								 | 
							
								#    if (defined($end) && defined($duration));
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#-------------------------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								# Load script and database configuration data
							 | 
						||
| 
								 | 
							
								#-------------------------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								my $conf = new Config::General(
							 | 
						||
| 
								 | 
							
								    -ConfigFile      => $cfgfile,
							 | 
						||
| 
								 | 
							
								    -InterPolateVars => 1,
							 | 
						||
| 
								 | 
							
								    -ExtendedAccess  => 1
							 | 
						||
| 
								 | 
							
								);
							 | 
						||
| 
								 | 
							
								my %config = $conf->getall();
							 | 
						||
| 
								 | 
							
								#print Dumper( \%config ), "\n";
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Load database configuration data
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								my $dbconf = new Config::General(
							 | 
						||
| 
								 | 
							
								    -ConfigFile      => $dbcfgfile,
							 | 
						||
| 
								 | 
							
								    -InterPolateVars => 1,
							 | 
						||
| 
								 | 
							
								    -ExtendedAccess  => 1
							 | 
						||
| 
								 | 
							
								);
							 | 
						||
| 
								 | 
							
								my %dbconfig = $dbconf->getall();
							 | 
						||
| 
								 | 
							
								#print Dumper( \%dbconfig ), "\n";
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Configuration file values 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;
							 | 
						||
| 
								 | 
							
								my $dayname  = $config{email}->{dayname}  // 'Sunday';
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# If we had a start time specified then check it and ensure the end time makes
							 | 
						||
| 
								 | 
							
								# sense. Otherwise use the configuration file defaults, but don't check them.
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								if ($start) {
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    # Check start time and add a seconds field if needed
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    $start = validate_time($start);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    # The end time usually needs to be 2 hours from the start if not
							 | 
						||
| 
								 | 
							
								    # specified. The actual duration is specified in the configuration file
							 | 
						||
| 
								 | 
							
								    # (with a default above).
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    unless ($end) {
							 | 
						||
| 
								 | 
							
								        my @end = split( ':', $start );
							 | 
						||
| 
								 | 
							
								        $end[0] += $duration;
							 | 
						||
| 
								 | 
							
								        $end = join(':',@end);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    # Check and add a seconds field if needed
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    $end = validate_time($end);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								else {
							 | 
						||
| 
								 | 
							
								    $start = $config{email}->{starttime};
							 | 
						||
| 
								 | 
							
								    $end = $config{email}->{endtime};
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Start and end times from options or the configuration file
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								my @starttime = split( ':', $start );
							 | 
						||
| 
								 | 
							
								my @endtime   = split( ':', $end );
							 | 
						||
| 
								 | 
							
								die "Missing start/end time(s)\n" unless ( @starttime && @endtime );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								my $template = $config{email}->{template};
							 | 
						||
| 
								 | 
							
								die "Missing template file $template\n" unless (-e $template);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								_debug($DEBUG >= 2,
							 | 
						||
| 
								 | 
							
								    '$start: ' . coalesce($start,''),
							 | 
						||
| 
								 | 
							
								    '$end:   ' . coalesce($end,''),
							 | 
						||
| 
								 | 
							
								    '--'
							 | 
						||
| 
								 | 
							
								);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								if ($DEBUG >= 1) {
							 | 
						||
| 
								 | 
							
								    report_settings();
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#-------------------------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								# Connect to the database
							 | 
						||
| 
								 | 
							
								# 2021-12-24: moved to MariaDB
							 | 
						||
| 
								 | 
							
								# 2022-02-28: the MariaDB driver has gone away apparently. Reverted to MySQL
							 | 
						||
| 
								 | 
							
								# again
							 | 
						||
| 
								 | 
							
								#-------------------------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								my $dbhost = $dbconfig{database}->{host} // '127.0.0.1';
							 | 
						||
| 
								 | 
							
								my $dbport = $dbconfig{database}->{port} // 3306;
							 | 
						||
| 
								 | 
							
								my $dbname = $dbconfig{database}->{name};
							 | 
						||
| 
								 | 
							
								my $dbuser = $dbconfig{database}->{user};
							 | 
						||
| 
								 | 
							
								my $dbpwd  = $dbconfig{database}->{password};
							 | 
						||
| 
								 | 
							
								#$dbh = DBI->connect( "DBI:MariaDB:database=$dbname;host=$dbhost;port=$dbport",
							 | 
						||
| 
								 | 
							
								#    $dbuser, $dbpwd, { AutoCommit => 1 } )
							 | 
						||
| 
								 | 
							
								#    or croak $DBI::errstr;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								$dbh = DBI->connect( "dbi:mysql:host=$dbhost;port=$dbport;database=$dbname",
							 | 
						||
| 
								 | 
							
								    $dbuser, $dbpwd, { AutoCommit => 1 } )
							 | 
						||
| 
								 | 
							
								    or die $DBI::errstr;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Enable client-side UTF8
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								$dbh->{mysql_enable_utf8} = 1;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Date and time values using Date::Calc format
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								my @today = Today();
							 | 
						||
| 
								 | 
							
								my @startdate;
							 | 
						||
| 
								 | 
							
								my @startmonth;
							 | 
						||
| 
								 | 
							
								my @reviewdate;
							 | 
						||
| 
								 | 
							
								my $monday = 1;    # Day of week number 1-7, Monday-Sunday
							 | 
						||
| 
								 | 
							
								my $offset = day_offset($dayname)->{offset};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#-------------------------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								# Work out the start date from the -date=DATE option, the -month=DATE option
							 | 
						||
| 
								 | 
							
								# or the current date.
							 | 
						||
| 
								 | 
							
								#-------------------------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# If there's an argument 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
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    my @parsed = strptime($date);
							 | 
						||
| 
								 | 
							
								    die "Invalid -date=DATE option '$date'\n"
							 | 
						||
| 
								 | 
							
								        unless ( defined( $parsed[3] )
							 | 
						||
| 
								 | 
							
								        && defined( $parsed[4] )
							 | 
						||
| 
								 | 
							
								        && defined( $parsed[5] ) );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    $parsed[5] += 1900;
							 | 
						||
| 
								 | 
							
								    $parsed[4] += 1;
							 | 
						||
| 
								 | 
							
								    @startdate = @parsed[ 5, 4, 3 ];
							 | 
						||
| 
								 | 
							
								    die "Date is in the past '$date'; aborting\n"
							 | 
						||
| 
								 | 
							
								        unless ( Date_to_Days(@startdate) > Date_to_Days(@today) );
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								elsif ( defined($month) ) {
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    # Parse the month out of the -month=DATE argument
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    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 ( Date_to_Days(@startmonth) > Date_to_Days(@today) );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    # Compute the next meeting date from now (by finding the next first Monday
							 | 
						||
| 
								 | 
							
								    # of the month then backing up two days to the Saturday).
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    @startdate = make_date( \@startmonth, $monday, 1, $offset );
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								else {
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    # 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 );
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								_debug($DEBUG >= 2, '@startdate: ' . join(',',@startdate));
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# The month being reviewed is sometimes the same month and sometimes the month
							 | 
						||
| 
								 | 
							
								# before.
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								if ( $startdate[1] eq $today[1] ) {
							 | 
						||
| 
								 | 
							
								    @reviewdate = @startdate;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								else {
							 | 
						||
| 
								 | 
							
								    @reviewdate = Add_Delta_YM( @startdate, 0, -1 );
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								_debug($DEBUG >= 2, '@reviewdate: ' . join(',',@reviewdate));
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Transfer Date::Calc values into hashes for initialising DateTime objects so
							 | 
						||
| 
								 | 
							
								# we can play time zone games
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								my ( %dtargs, $dtstart, $dtend );
							 | 
						||
| 
								 | 
							
								@dtargs{ 'year', 'month', 'day', 'hour', 'minute', 'second', 'time_zone' }
							 | 
						||
| 
								 | 
							
								    = ( @startdate, @starttime, 'UTC' );
							 | 
						||
| 
								 | 
							
								$dtstart = DateTime->new(%dtargs);
							 | 
						||
| 
								 | 
							
								@dtargs{ 'hour', 'minute', 'second' } = (@endtime);
							 | 
						||
| 
								 | 
							
								$dtend = DateTime->new(%dtargs);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Compute the number of days until the recording
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								my $dtnow    = DateTime->now( time_zone => 'UTC' );
							 | 
						||
| 
								 | 
							
								my $dtoffset = $dtstart->delta_days($dtnow);
							 | 
						||
| 
								 | 
							
								my $dtf      = DateTime::Format::Duration->new( pattern => '%e' );
							 | 
						||
| 
								 | 
							
								my $days     = $dtf->format_duration($dtoffset);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Formatted dates for the mail message body
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								my ( $year, $monthname, $nicedate, $starttime, $endtime ) = (
							 | 
						||
| 
								 | 
							
								    $dtstart->strftime("%Y"),           Month_to_Text( $reviewdate[1] ),
							 | 
						||
| 
								 | 
							
								    $dtstart->strftime("%A, %B %d %Y"), $dtstart->strftime("%R (%Z)"),
							 | 
						||
| 
								 | 
							
								    $dtend->strftime("%R (%Z)"),
							 | 
						||
| 
								 | 
							
								);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								_debug($DEBUG >= 2,
							 | 
						||
| 
								 | 
							
								    "\$year: $year",
							 | 
						||
| 
								 | 
							
								    "\$monthname: $monthname",
							 | 
						||
| 
								 | 
							
								    "\$nicedate: $nicedate",
							 | 
						||
| 
								 | 
							
								    "\$starttime: $starttime",
							 | 
						||
| 
								 | 
							
								    "\$endtime: $endtime"
							 | 
						||
| 
								 | 
							
								);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Build the subject line
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								my $waittime = ( $days > 6 ? "in $days days"   : "next %A" );
							 | 
						||
| 
								 | 
							
								my $next     = ( $days > 6 ? ''                : 'next ' );
							 | 
						||
| 
								 | 
							
								my $forspec  = ( $days > 6 ? "for $monthname " : "" );
							 | 
						||
| 
								 | 
							
								my $subject  = $dtstart->strftime(
							 | 
						||
| 
								 | 
							
								    "HPR Community News ${forspec}- $waittime on %FT%TZ");
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								_debug( $DEBUG >= 2, "\$subject: $subject" );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Prepare to send mail
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								my $mailer = Mail::Mailer->new($mailertype);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Generate the headers we need
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								$mailer->open(
							 | 
						||
| 
								 | 
							
								    {   To      => $to_address,
							 | 
						||
| 
								 | 
							
								        From    => $from_address,
							 | 
						||
| 
								 | 
							
								        Subject => $subject,
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Build an array of timezone data for the template
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								my @timezones;
							 | 
						||
| 
								 | 
							
								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.
							 | 
						||
| 
								 | 
							
								#-------------------------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								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->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 $shownotes = $h1->{id};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								_debug( $DEBUG >= 2, "\$shownotes (slot): $shownotes" );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								$sth1->finish;
							 | 
						||
| 
								 | 
							
								$dbh->disconnect;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#-------------------------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								# Fill the template
							 | 
						||
| 
								 | 
							
								#-------------------------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								my $tt = Template->new(
							 | 
						||
| 
								 | 
							
								    {   ABSOLUTE => 1,
							 | 
						||
| 
								 | 
							
								        ENCODING => 'utf8',
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								my $vars = {
							 | 
						||
| 
								 | 
							
								#    subject   => $subject,
							 | 
						||
| 
								 | 
							
								#    from      => $from_address,
							 | 
						||
| 
								 | 
							
								#    to        => $to_address,
							 | 
						||
| 
								 | 
							
								    server    => $server,
							 | 
						||
| 
								 | 
							
								    port      => $port,
							 | 
						||
| 
								 | 
							
								    room      => $room,
							 | 
						||
| 
								 | 
							
								    timezones => \@timezones,
							 | 
						||
| 
								 | 
							
								    utc       => {
							 | 
						||
| 
								 | 
							
								        days  => $days,
							 | 
						||
| 
								 | 
							
								        month => $monthname,
							 | 
						||
| 
								 | 
							
								        year  => $year,
							 | 
						||
| 
								 | 
							
								        date  => $nicedate,
							 | 
						||
| 
								 | 
							
								        start => $starttime,
							 | 
						||
| 
								 | 
							
								        end   => $endtime,
							 | 
						||
| 
								 | 
							
								    },
							 | 
						||
| 
								 | 
							
								    shownotes => $shownotes,
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								my $document;
							 | 
						||
| 
								 | 
							
								$tt->process( $template,
							 | 
						||
| 
								 | 
							
								    $vars, \$document, { binmode => ':utf8' } )
							 | 
						||
| 
								 | 
							
								    || die $tt->error(), "\n";
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Add the template-generated body to the mail message
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								print $mailer $document;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Send the message
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								$mailer->close
							 | 
						||
| 
								 | 
							
								    or die "Couldn't send message: $!\n";
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								unless ($mail) {
							 | 
						||
| 
								 | 
							
								    print "Message was not sent since -nomail was selected (or defaulted).\n";
							 | 
						||
| 
								 | 
							
								    print "Look in 'mailer.testfile' for the output\n";
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								exit;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#===  FUNCTION  ================================================================
							 | 
						||
| 
								 | 
							
								#         NAME: report_settings
							 | 
						||
| 
								 | 
							
								#      PURPOSE: Report settings from options (or defaults)
							 | 
						||
| 
								 | 
							
								#   PARAMETERS: None
							 | 
						||
| 
								 | 
							
								#      RETURNS: Nothing
							 | 
						||
| 
								 | 
							
								#  DESCRIPTION: 
							 | 
						||
| 
								 | 
							
								#       THROWS: No exceptions
							 | 
						||
| 
								 | 
							
								#     COMMENTS: None
							 | 
						||
| 
								 | 
							
								#     SEE ALSO: N/A
							 | 
						||
| 
								 | 
							
								#===============================================================================
							 | 
						||
| 
								 | 
							
								sub report_settings {
							 | 
						||
| 
								 | 
							
								    my $fmt = "D> %-14s = %s\n";
							 | 
						||
| 
								 | 
							
								    print "D> Settings from options or default values:\n";
							 | 
						||
| 
								 | 
							
								    printf $fmt, "Month", coalesce($month,'undef');
							 | 
						||
| 
								 | 
							
								    printf $fmt, "Mail", coalesce($mail,'undef');
							 | 
						||
| 
								 | 
							
								    printf $fmt, "From", coalesce($from_address,'undef');
							 | 
						||
| 
								 | 
							
								    printf $fmt, "To", coalesce($to_address,'undef');
							 | 
						||
| 
								 | 
							
								    printf $fmt, "Meeting date", coalesce($date,'undef');
							 | 
						||
| 
								 | 
							
								    printf $fmt, "Start time", join(':',@starttime);
							 | 
						||
| 
								 | 
							
								    printf $fmt, "End time",  join(':',@endtime);
							 | 
						||
| 
								 | 
							
								    printf $fmt, "Config file", coalesce($cfgfile,'undef');
							 | 
						||
| 
								 | 
							
								    printf $fmt, "DB config file", coalesce($dbcfgfile,'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');
							 | 
						||
| 
								 | 
							
								    print "D> ----\n";
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#===  FUNCTION  ================================================================
							 | 
						||
| 
								 | 
							
								#         NAME: compute_endtime
							 | 
						||
| 
								 | 
							
								#      PURPOSE: Given a start time and a duration computes the end time
							 | 
						||
| 
								 | 
							
								#   PARAMETERS: $rdate          arrayref for the date
							 | 
						||
| 
								 | 
							
								#               $rstime         arrayref for the start time
							 | 
						||
| 
								 | 
							
								#               $rduration      arrayref for the duration [HH,MM,SS]
							 | 
						||
| 
								 | 
							
								#      RETURNS: The end time as a string (HH:MM:SS)
							 | 
						||
| 
								 | 
							
								#  DESCRIPTION: 
							 | 
						||
| 
								 | 
							
								#       THROWS: No exceptions
							 | 
						||
| 
								 | 
							
								#     COMMENTS: Decided not to implement this, may do so in future.
							 | 
						||
| 
								 | 
							
								#     SEE ALSO: N/A
							 | 
						||
| 
								 | 
							
								#===============================================================================
							 | 
						||
| 
								 | 
							
								#sub compute_endtime {
							 | 
						||
| 
								 | 
							
								#}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#===  FUNCTION  ================================================================
							 | 
						||
| 
								 | 
							
								#         NAME: validate_time
							 | 
						||
| 
								 | 
							
								#      PURPOSE: Validates a time in HH:MM:SS format
							 | 
						||
| 
								 | 
							
								#   PARAMETERS: $time   Time string
							 | 
						||
| 
								 | 
							
								#      RETURNS: The input string with any missing fields added in
							 | 
						||
| 
								 | 
							
								#  DESCRIPTION: The input time needs to be in the format HH:MM[:SS] where
							 | 
						||
| 
								 | 
							
								#               a missing seconds value is replaced with '00'. A regex check
							 | 
						||
| 
								 | 
							
								#               on the format is performed, and if that passes a check is made
							 | 
						||
| 
								 | 
							
								#               on the time values (using 'check_time').
							 | 
						||
| 
								 | 
							
								#       THROWS: No exceptions
							 | 
						||
| 
								 | 
							
								#     COMMENTS: None
							 | 
						||
| 
								 | 
							
								#     SEE ALSO: N/A
							 | 
						||
| 
								 | 
							
								#===============================================================================
							 | 
						||
| 
								 | 
							
								sub validate_time {
							 | 
						||
| 
								 | 
							
								    my ($time) = (@_);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if ( defined($time) ) {
							 | 
						||
| 
								 | 
							
								        if ( ( my @fields )
							 | 
						||
| 
								 | 
							
								            = ( $time =~ /(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?/ ) )
							 | 
						||
| 
								 | 
							
								        {
							 | 
						||
| 
								 | 
							
								            @fields = map { defined($_) ? sprintf('%02d',$_) : "00" } @fields;
							 | 
						||
| 
								 | 
							
								            $time   = join( ':', @fields );
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        else {
							 | 
						||
| 
								 | 
							
								            die "Invalid time: $time\n";
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        unless ( check_time(split(':',$time)) ) {
							 | 
						||
| 
								 | 
							
								            die "Invalid time: $time\n";
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return $time;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#===  FUNCTION  ================================================================
							 | 
						||
| 
								 | 
							
								#         NAME: make_date
							 | 
						||
| 
								 | 
							
								#      PURPOSE: Make the event date for recurrence
							 | 
						||
| 
								 | 
							
								#   PARAMETERS: $refdate
							 | 
						||
| 
								 | 
							
								#                       An arrayref to the reference date array (usually
							 | 
						||
| 
								 | 
							
								#                       today's date)
							 | 
						||
| 
								 | 
							
								#               $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
							 | 
						||
| 
								 | 
							
								#  DESCRIPTION: We want to compute a simple date with an offset, such as
							 | 
						||
| 
								 | 
							
								#               "the Saturday 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).
							 | 
						||
| 
								 | 
							
								#       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:
							 | 
						||
| 
								 | 
							
								#===============================================================================
							 | 
						||
| 
								 | 
							
								sub make_date {
							 | 
						||
| 
								 | 
							
								    my ( $refdate, $dow, $n, $offset ) = @_;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    # Compute the required date: the "$n"th day of week "$dow" in the year and
							 | 
						||
| 
								 | 
							
								    # month in @$refdate. This could be a date in the past.
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    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 the day offset
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    @date = Add_Delta_Days( @date, $offset ) if $offset;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    # Return a list
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    return (@date);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#===  FUNCTION  ================================================================
							 | 
						||
| 
								 | 
							
								#         NAME: storeTZ
							 | 
						||
| 
								 | 
							
								#      PURPOSE: Store start/end times for a timezone
							 | 
						||
| 
								 | 
							
								#   PARAMETERS: $start          DateTime object containing the starting
							 | 
						||
| 
								 | 
							
								#                               datetime as UTC
							 | 
						||
| 
								 | 
							
								#               $end            DateTime object containing the ending datetime
							 | 
						||
| 
								 | 
							
								#                               as UTC
							 | 
						||
| 
								 | 
							
								#               $tz             The textual time zone (need this to be valid)
							 | 
						||
| 
								 | 
							
								#      RETURNS: A hash containing the timezone name and start and end times
							 | 
						||
| 
								 | 
							
								#  DESCRIPTION: Relies on DateTime::TimeZone to do the work to turn a UTC time
							 | 
						||
| 
								 | 
							
								#               into a time in a different time zone. Uses the DateTime
							 | 
						||
| 
								 | 
							
								#               strftime method to format the dates and times for printing.
							 | 
						||
| 
								 | 
							
								#       THROWS: No exceptions
							 | 
						||
| 
								 | 
							
								#     COMMENTS: None
							 | 
						||
| 
								 | 
							
								#     SEE ALSO: N/A
							 | 
						||
| 
								 | 
							
								#===============================================================================
							 | 
						||
| 
								 | 
							
								sub storeTZ {
							 | 
						||
| 
								 | 
							
								    my ( $start, $end, $tz ) = @_;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    my %result;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    # Adjust time zone
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    $start->set_time_zone($tz);
							 | 
						||
| 
								 | 
							
								    $end->set_time_zone($tz);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    # Print time zone and start/end times in that zone
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    $result{name}  = "$tz";
							 | 
						||
| 
								 | 
							
								    $result{start} = $start->strftime("%H:%S  %a, %b %d %Y");
							 | 
						||
| 
								 | 
							
								    $result{end}   = $end->strftime("%H:%S  %a, %b %d %Y");
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return \%result;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#===  FUNCTION  ================================================================
							 | 
						||
| 
								 | 
							
								#         NAME: printTZ
							 | 
						||
| 
								 | 
							
								#      PURPOSE: Print start/end times for a timezone
							 | 
						||
| 
								 | 
							
								#   PARAMETERS: $fh             File handle for writing
							 | 
						||
| 
								 | 
							
								#               $start          DateTime object containing the starting
							 | 
						||
| 
								 | 
							
								#                               datetime as UTC
							 | 
						||
| 
								 | 
							
								#               $end            DateTime object containing the ending datetime
							 | 
						||
| 
								 | 
							
								#                               as UTC
							 | 
						||
| 
								 | 
							
								#               $tz             The textual time zone (need this to be valid)
							 | 
						||
| 
								 | 
							
								#      RETURNS: Nothing
							 | 
						||
| 
								 | 
							
								#  DESCRIPTION: Relies on DateTime::TimeZone to do the work to turn a UTC time
							 | 
						||
| 
								 | 
							
								#               into a time in a different time zone. Uses the DateTime
							 | 
						||
| 
								 | 
							
								#               strftime method to format the dates and times for printing.
							 | 
						||
| 
								 | 
							
								#       THROWS: No exceptions
							 | 
						||
| 
								 | 
							
								#     COMMENTS: None
							 | 
						||
| 
								 | 
							
								#     SEE ALSO:
							 | 
						||
| 
								 | 
							
								#===============================================================================
							 | 
						||
| 
								 | 
							
								sub printTZ {
							 | 
						||
| 
								 | 
							
								    my ( $fh, $start, $end, $tz ) = @_;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    # Adjust time zone
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    $start->set_time_zone($tz);
							 | 
						||
| 
								 | 
							
								    $end->set_time_zone($tz);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    # Print time zone and start/end times in that zone
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    print $fh "$tz\n";
							 | 
						||
| 
								 | 
							
								    print $fh $start->strftime("Start: %H:%S  %a, %b %d %Y\n");
							 | 
						||
| 
								 | 
							
								    print $fh $end->strftime("End:   %H:%S  %a, %b %d %Y\n\n");
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#===  FUNCTION  ================================================================
							 | 
						||
| 
								 | 
							
								#         NAME: day_offset
							 | 
						||
| 
								 | 
							
								#      PURPOSE: Given a day name computes day attributes including the
							 | 
						||
| 
								 | 
							
								#               (negative) offset in days from the target Monday to the
							 | 
						||
| 
								 | 
							
								#               recording date.
							 | 
						||
| 
								 | 
							
								#   PARAMETERS: $dayname        Name of a day of the week
							 | 
						||
| 
								 | 
							
								#      RETURNS: Hashref containing the full day name, the weekday number and
							 | 
						||
| 
								 | 
							
								#               the integer 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
							 | 
						||
| 
								 | 
							
								#               regex in turn and if it matches the sub-hash is returned. This
							 | 
						||
| 
								 | 
							
								#               allows the caller to use 'day_offset($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
							 | 
						||
| 
								 | 
							
								#               'undef' is returned.
							 | 
						||
| 
								 | 
							
								#       THROWS: No exceptions
							 | 
						||
| 
								 | 
							
								#     COMMENTS: None
							 | 
						||
| 
								 | 
							
								#     SEE ALSO: N/A
							 | 
						||
| 
								 | 
							
								#===============================================================================
							 | 
						||
| 
								 | 
							
								sub day_offset {
							 | 
						||
| 
								 | 
							
								    my ( $dayname ) = @_;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    my %matches = (
							 | 
						||
| 
								 | 
							
								        qr{^(?i)Sun(day)?$} => {
							 | 
						||
| 
								 | 
							
								            dayname => 'Sunday',
							 | 
						||
| 
								 | 
							
								            wday    => 7,
							 | 
						||
| 
								 | 
							
								            offset  => -1,
							 | 
						||
| 
								 | 
							
								        },
							 | 
						||
| 
								 | 
							
								        qr{^(?i)Sat(urday)?$} => {
							 | 
						||
| 
								 | 
							
								            dayname => 'Saturday',
							 | 
						||
| 
								 | 
							
								            wday    => 6,
							 | 
						||
| 
								 | 
							
								            offset  => -2,
							 | 
						||
| 
								 | 
							
								        },
							 | 
						||
| 
								 | 
							
								        qr{^(?i)Fri(day)?$} => {
							 | 
						||
| 
								 | 
							
								            dayname => 'Friday',
							 | 
						||
| 
								 | 
							
								            wday    => 5,
							 | 
						||
| 
								 | 
							
								            offset  => -3,
							 | 
						||
| 
								 | 
							
								        },
							 | 
						||
| 
								 | 
							
								        qr{^(?i)Thu(rsday)?$} => {
							 | 
						||
| 
								 | 
							
								            dayname => 'Thursday',
							 | 
						||
| 
								 | 
							
								            wday    => 4,
							 | 
						||
| 
								 | 
							
								            offset  => -4,
							 | 
						||
| 
								 | 
							
								        },
							 | 
						||
| 
								 | 
							
								        qr{^(?i)Wed(nesday)?$} => {
							 | 
						||
| 
								 | 
							
								            dayname => 'Wednesday',
							 | 
						||
| 
								 | 
							
								            wday    => 3,
							 | 
						||
| 
								 | 
							
								            offset  => -5,
							 | 
						||
| 
								 | 
							
								        },
							 | 
						||
| 
								 | 
							
								        qr{^(?i)Tue(sday)?$} => {
							 | 
						||
| 
								 | 
							
								            dayname => 'Tuesday',
							 | 
						||
| 
								 | 
							
								            wday    => 2,
							 | 
						||
| 
								 | 
							
								            offset  => -6,
							 | 
						||
| 
								 | 
							
								        },
							 | 
						||
| 
								 | 
							
								        qr{^(?i)Mon(day)?$} => {
							 | 
						||
| 
								 | 
							
								            dayname => 'Monday',
							 | 
						||
| 
								 | 
							
								            wday    => 1,
							 | 
						||
| 
								 | 
							
								            offset  => -7,
							 | 
						||
| 
								 | 
							
								        },
							 | 
						||
| 
								 | 
							
								    );
							 | 
						||
| 
								 | 
							
								    my $match;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    foreach my $re (keys(%matches)) {
							 | 
						||
| 
								 | 
							
								        if ($dayname =~ $re) {
							 | 
						||
| 
								 | 
							
								            $match = $matches{$re};
							 | 
						||
| 
								 | 
							
								            last;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return $match;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#===  FUNCTION  ================================================================
							 | 
						||
| 
								 | 
							
								#         NAME: _debug
							 | 
						||
| 
								 | 
							
								#      PURPOSE: Prints debug reports
							 | 
						||
| 
								 | 
							
								#   PARAMETERS: $active         Boolean: 1 for print, 0 for no print
							 | 
						||
| 
								 | 
							
								#               $messages...    Arbitrary list of messages to print
							 | 
						||
| 
								 | 
							
								#      RETURNS: Nothing
							 | 
						||
| 
								 | 
							
								#  DESCRIPTION: Outputs messages if $active is true. It removes any trailing
							 | 
						||
| 
								 | 
							
								#               newline from each one and then adds one in the 'print' to the
							 | 
						||
| 
								 | 
							
								#               caller doesn't have to bother. Prepends each message with 'D>'
							 | 
						||
| 
								 | 
							
								#               to show it's a debug message.
							 | 
						||
| 
								 | 
							
								#       THROWS: No exceptions
							 | 
						||
| 
								 | 
							
								#     COMMENTS: Differs from other functions of the same name
							 | 
						||
| 
								 | 
							
								#     SEE ALSO: N/A
							 | 
						||
| 
								 | 
							
								#===============================================================================
							 | 
						||
| 
								 | 
							
								sub _debug {
							 | 
						||
| 
								 | 
							
								    my $active = shift;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    my $message;
							 | 
						||
| 
								 | 
							
								    return unless $active;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    while ($message = shift) {
							 | 
						||
| 
								 | 
							
								        chomp($message);
							 | 
						||
| 
								 | 
							
								        print STDERR "D> $message\n";
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#===  FUNCTION  ================================================================
							 | 
						||
| 
								 | 
							
								#         NAME: coalesce
							 | 
						||
| 
								 | 
							
								#      PURPOSE: To find the first defined argument and return it
							 | 
						||
| 
								 | 
							
								#   PARAMETERS: Arbitrary number of arguments
							 | 
						||
| 
								 | 
							
								#      RETURNS: The first defined argument or undef if there are none
							 | 
						||
| 
								 | 
							
								#  DESCRIPTION:
							 | 
						||
| 
								 | 
							
								#       THROWS: No exceptions
							 | 
						||
| 
								 | 
							
								#     COMMENTS: None
							 | 
						||
| 
								 | 
							
								#     SEE ALSO: N/A
							 | 
						||
| 
								 | 
							
								#===============================================================================
							 | 
						||
| 
								 | 
							
								sub coalesce {
							 | 
						||
| 
								 | 
							
								    foreach (@_) {
							 | 
						||
| 
								 | 
							
								        return $_ if defined($_);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    return undef;    ## no critic
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#===  FUNCTION  ================================================================
							 | 
						||
| 
								 | 
							
								#         NAME: Options
							 | 
						||
| 
								 | 
							
								#      PURPOSE: Processes command-line options
							 | 
						||
| 
								 | 
							
								#   PARAMETERS: $optref     Hash reference to hold the options
							 | 
						||
| 
								 | 
							
								#      RETURNS: Undef
							 | 
						||
| 
								 | 
							
								#  DESCRIPTION:
							 | 
						||
| 
								 | 
							
								#       THROWS: no exceptions
							 | 
						||
| 
								 | 
							
								#     COMMENTS: none
							 | 
						||
| 
								 | 
							
								#     SEE ALSO: n/a
							 | 
						||
| 
								 | 
							
								#===============================================================================
							 | 
						||
| 
								 | 
							
								sub Options {
							 | 
						||
| 
								 | 
							
								    my ($optref) = @_;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    my @options = (
							 | 
						||
| 
								 | 
							
								        "help",          "documentation|man",
							 | 
						||
| 
								 | 
							
								        "debug=i",       "mail!",
							 | 
						||
| 
								 | 
							
								        "fromaddress=s", "toaddress=s",
							 | 
						||
| 
								 | 
							
								        "date=s",        "starttime=s",
							 | 
						||
| 
								 | 
							
								        "endtime=s",     "month=s",
							 | 
						||
| 
								 | 
							
								        "config=s",      "dbconfig=s",
							 | 
						||
| 
								 | 
							
								    );
							 | 
						||
| 
								 | 
							
								    # "duration=s",
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if ( !GetOptions( $optref, @options ) ) {
							 | 
						||
| 
								 | 
							
								        pod2usage( -msg => "Version $VERSION\n", -verbose => 0, -exitval => 1 );
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								__END__
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
							 | 
						||
| 
								 | 
							
								#  Application Documentation
							 | 
						||
| 
								 | 
							
								#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
							 | 
						||
| 
								 | 
							
								#{{{
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=head1 NAME
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								make_email - generates an HPR Community News recording invitation email
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=head1 VERSION
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								This documentation refers to make_email version 0.2.7
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=head1 USAGE
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    make_email [-help] [-documentation] [-debug=N] [-month=DATE] [-[no]mail]
							 | 
						||
| 
								 | 
							
								        [-from=FROM_ADDRESS] [-to=TO_ADDRESS] [-date=DATE] [-start=START_TIME]
							 | 
						||
| 
								 | 
							
								        [-end=END_TIME] [-config=FILE] [-dbconfig=FILE]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    ./make_email -dbconf=$HOME/HPR/.hpr_livedb.cfg -date=2022-12-27
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=head1 OPTIONS
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=over 8
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<-help>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Prints a brief help message describing the usage of the program, and then exits.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<-documentation> B<-man>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Prints the entire embedded documentation for the program, then exits.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Another way to see the full documentation use:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								B<perldoc ./make_email>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<-debug=N>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Enables debugging mode when N > 0 (zero is the default, no debugging output).
							 | 
						||
| 
								 | 
							
								The levels are:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Values are:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=over 4
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item 1
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Reports all of the settings taken from the configuration file, the provided
							 | 
						||
| 
								 | 
							
								command line options or their default values. The report is generated early on
							 | 
						||
| 
								 | 
							
								in the processing of these values.  Use B<-debug=2> for information about the
							 | 
						||
| 
								 | 
							
								next stages.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item 2
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Reports the following (as well as the data for level 1):
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=over 4
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item .
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Details of the start date chosen
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item .
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Details of the year, name of month, readable date, and recording start and end
							 | 
						||
| 
								 | 
							
								times.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item .
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The subject line chosen for the email.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item .
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The date of the show being searched for in the database.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item .
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The number of the show found in the database.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=back
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=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<-[no]mail>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								** NOTE ** The sending of mail does not work at present, and B<-nomail> should
							 | 
						||
| 
								 | 
							
								always be used.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Causes mail to be sent (B<-mail>) or not sent (B<-nomail>). If the mail is
							 | 
						||
| 
								 | 
							
								sent then it is sent via the local MTA (in the assumption that there is one).
							 | 
						||
| 
								 | 
							
								If this option is omitted, the default is B<-nomail>, in which case the
							 | 
						||
| 
								 | 
							
								message is appended to the file B<mailer.testfile> in the current directory.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<-from=FROM_ADDRESS>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								** NOTE ** The sending of mail does not work at present.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								This option defines the address from which the message is to be sent. This
							 | 
						||
| 
								 | 
							
								address is used in the message header; the message envelope will contain the
							 | 
						||
| 
								 | 
							
								I<real> sender.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<-to=TO_ADDRESS>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								** NOTE ** The sending of mail does not work at present.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								This option defines the address to which the message is to be sent.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=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<dayname>) 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<START_TIME>
							 | 
						||
| 
								 | 
							
								value must be a valid B<HH:MM> 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<duration>.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=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<END_TIME> value must be a valid B<HH:MM> time specification.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=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<CONFIGURATION AND ENVIRONMENT>.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<-dbconfig=FILE>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								This option defines a database configuration file other than the default
							 | 
						||
| 
								 | 
							
								B<.hpr_db.cfg>. The file must be formatted as described below in the section
							 | 
						||
| 
								 | 
							
								I<CONFIGURATION AND ENVIRONMENT>.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The default file is configured to open a local copy of the HPR database. An
							 | 
						||
| 
								 | 
							
								alternative is B<.hpr_livedb.cfg> which assumes an SSH tunnel to the live
							 | 
						||
| 
								 | 
							
								database and attempts to connect to it. Use the script I<open_tunnel> to open
							 | 
						||
| 
								 | 
							
								the SSH tunnel.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=back
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=head1 DESCRIPTION
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Makes and sends(*) an invitation email for the next Community News with times per
							 | 
						||
| 
								 | 
							
								timezone. The message is structured by a Template Toolkit template, so its
							 | 
						||
| 
								 | 
							
								content 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
							 | 
						||
| 
								 | 
							
								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.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The email generated by the script is sent to the HPR mailing list, usually on
							 | 
						||
| 
								 | 
							
								the Monday prior to the weekend of the recording.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Notes:
							 | 
						||
| 
								 | 
							
								* Mail sending does not work at present.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=head1 DIAGNOSTICS
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=over 8
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<Unable to find ...>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The configuration file specified in B<-config=FILE> (or the default file)
							 | 
						||
| 
								 | 
							
								could not be found.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<Use only one of -month=MONTH or -date=DATE>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								These options are mutually exclusive. See their specifications earlier in this
							 | 
						||
| 
								 | 
							
								document.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<Missing start/end time(s)>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								One or both of the start and end times is missing, either from the configuration file or
							 | 
						||
| 
								 | 
							
								from the command line options.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<Missing template file ...>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The template file specified in the configuration file could not be found.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<Various database messages>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The program can generate warning messages from the database.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<Invalid -date=DATE option '...'>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								An invalid date has been supplied via this option.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<Date is in the past '...'>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The date specified in B<-date=DATE> is in the past.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<Invalid -month=DATE option '...'>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								An invalid date has been supplied via this option.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<Date is in the past '...'>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The month specified in B<-month=DATE> is in the past.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<Various Template Toolkit messages>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The program can generate warning messages from the Template.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<Couldn't send message: ...>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The email mesage has been constructed but could not be sent. See the error
							 | 
						||
| 
								 | 
							
								returned by the mail subsystem for more information.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=back
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=head1 CONFIGURATION AND ENVIRONMENT
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=head2 EMAIL CONFIGURATION
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The program obtains the settings it requires for preparing the email from
							 | 
						||
| 
								 | 
							
								a configuration file, which by default is called B<.make_email.cfg>. This file
							 | 
						||
| 
								 | 
							
								needs to contain the following data:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    <email>
							 | 
						||
| 
								 | 
							
								        server    = MUMBLE_SERVER_NAME
							 | 
						||
| 
								 | 
							
								        port      = MUMBLE_PORT
							 | 
						||
| 
								 | 
							
								        room      = NAME_OF_ROOM
							 | 
						||
| 
								 | 
							
								        dayname   = DAY_OF_WEEK_OF_RECORDING
							 | 
						||
| 
								 | 
							
								        starttime = 18:00:00
							 | 
						||
| 
								 | 
							
								        endtime   = 20:00:00
							 | 
						||
| 
								 | 
							
								        duration  = 02 # hours
							 | 
						||
| 
								 | 
							
								        template  = NAME_OF_TEMPLATE
							 | 
						||
| 
								 | 
							
								    </email>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=head2 DATABASE CONFIGURATION
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The program obtains the credentials it requires for connecting to the HPR
							 | 
						||
| 
								 | 
							
								database by loading them from a configuration file. The default file is called
							 | 
						||
| 
								 | 
							
								B<.hpr_db.cfg> and should contain the following data:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    <database>
							 | 
						||
| 
								 | 
							
								        host = 127.0.0.1
							 | 
						||
| 
								 | 
							
								        port = PORT
							 | 
						||
| 
								 | 
							
								        name = DBNAME
							 | 
						||
| 
								 | 
							
								        user = USER
							 | 
						||
| 
								 | 
							
								        password = PASSWORD
							 | 
						||
| 
								 | 
							
								    </database>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The file B<.hpr_livedb.cfg> should be available to allow access to the
							 | 
						||
| 
								 | 
							
								database over an SSH tunnel which has been previously opened.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=head1 DEPENDENCIES
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    DBI
							 | 
						||
| 
								 | 
							
								    Date::Calc
							 | 
						||
| 
								 | 
							
								    Date::Parse
							 | 
						||
| 
								 | 
							
								    DateTime
							 | 
						||
| 
								 | 
							
								    DateTime::Format::Duration
							 | 
						||
| 
								 | 
							
								    DateTime::TimeZone
							 | 
						||
| 
								 | 
							
								    Getopt::Long
							 | 
						||
| 
								 | 
							
								    Mail::Mailer
							 | 
						||
| 
								 | 
							
								    Pod::Usage
							 | 
						||
| 
								 | 
							
								    Template
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=head1 BUGS AND LIMITATIONS
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								There are no known bugs in this script.
							 | 
						||
| 
								 | 
							
								Please report problems to Dave Morriss (Dave.Morriss@gmail.com)
							 | 
						||
| 
								 | 
							
								Patches are welcome.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=head1 AUTHOR
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Dave Morriss (Dave.Morriss@gmail.com) 2013 - 2024
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=head1 LICENCE AND COPYRIGHT
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Copyright (c) Dave Morriss (Dave.Morriss@gmail.com). All rights reserved.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								This program is free software. You can redistribute it and/or modify it under
							 | 
						||
| 
								 | 
							
								the same terms as perl itself.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=cut
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#}}}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# [zo to open fold, zc to close]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# vim: syntax=perl:ts=8:sw=4:et:ai:tw=78:fo=tcrqn21:fdm=marker
							 |