#!/usr/bin/perl -w # -*- CPerl -*- # # swedish_holidays.pl -- calculate public holidays ("röda dagar") and # holidays specific to Agero # (c) 2001-03-22 Gustaf Erikson, Agero Creation # # CHANGES # 2002-09-10: Version 1.3 # New name: swedish_holidays.pl # Locale-agnostic version # # 2002-07-06: Version 1.2 # User-defined holidays possible # Locale support # # 2002-07-03: Version 1.1 # Redesign # Add support for writing CSV files # # $Id: swedish_holidays.pl,v 1.2 2002/09/10 11:13:08 ger Exp $ # # USAGE: swedish_holidays.pl [-A] [-o filename] [-h] start_year [end_year] # # This will produce plaintext to standard output, and three files in # the same directory as the executable. One of these files contains # SQL commands for the Agero time reporting system Atrapp (if the -A # flag is set). The other is a tab-separated file for import into M$ # Outlook. The third is a comma-separated file (.csv) for other # applications. # # COPYING: please use whatever you need from this code without # restrictions. # # REFERENCES: "Tioårskalendern 2001" Almanackförlaget. # # FIXME: check CSV format to comply w/ Yahoo sync util # Instructions in Swedish ############################################################################## # PACKAGES ################################################################### use strict; # use the Data::Calc module from CPAN. use Date::Calc qw(Day_of_Week Add_Delta_YMD Easter_Sunday Today_and_Now); # use the Getopt::Std to process command line arguments use Getopt::Std; # USER DEFINED VARS ######################################################### # You can add your own holidays here. # The example below is specific to my company, Agero. # Use this var to mark your holidays. # It will follow the holidays you specify in brackets. my $company_tag = "Agero"; # The canonical Swedish holidays are: # Fixed dates: # Nyårsdagen, Trettondagen, Första maj, Juldagen, Annandag jul, # Nyårsafton. # Variable dates: # Långfredagen, Påskdagen, Annandag påsk, Kristi Himmelsfärdsdagen, # Pingstdagen, Annandag pingst, Midsommardagen, Alla helgons dag # Specify your holidays in this list. # Format: "date: description" (the colon is important!) # If date is in the format MM-DD, it will be added every year. # Specify full date to limit to a specific year. # Specify variable dates in relation to the canonical variable dates listed # above, using "plus " or "minus "01-01", "Trettondagen" => "01-06", "Första maj" => "05-01", "Juldagen" => "12-25", "Annandag jul" => "12-26"); my @ms_dag = (); my @ah_dag = (); my ($paskdagen, $langfredagen, $annandag_pask, $kristi_himmelsfardsdagen, $pingstdagen, $annandag_pingst, $midsommardagen, $alla_helgons_dag); my %variable_holidays = (); # SUBS ####################################################################### sub print_usage() { #prints a usage message and exit. print "\nUsage: $0 [-o filename] [-A] [-h] start_year [end_year] Options: -o filename: if specified, output will be written to the files filename.sql (if the -A flag is set), filename.txt, and filename.csv. -A : if specified, custom holidays will be included. -h : this message. Användning: $0 [-o filnamn] [-A] [-h] startår [slutår] Alternativ: -o filnamn : tre filer kommer att genereras: filnamn.sql (tillsammans med -A flaggan), filnamn.txt (tabseparerade värden) och filnamn.csv. -A : användardefinierade helger kommer att skrivas ut. -h : detta meddelande \n"; exit 0; } # Constants ################################################################## # years where this program works # Date::Calc's Easter calculation routine fails otherwise my $MIN_YEAR = 1583; my $MAX_YEAR = 2299; # date ranges for "midsommar", "alla helgons dag". See a Swedish # almanack for details my $MS_MIN = "06-20"; my $AH_MIN = "10-31"; # output file stuff my $author = "Gustaf Erikson, Agero Creation AB\n"; my $timestamp = sprintf( "Genererad av programmet \'$program_name\' på $date_format %02d:%02d:%02d\n\n", Today_and_Now()); my ($sql_head, $sql_foot) = ("update tb_date set scheduled_hours=0 where (date_id = \'", "\' and scheduled_hours <> 0);"); my $tsvheader = "Aktivitet\tDatum\tVisa tid som\r\n"; # What Outlook should show the time as my $show_time_as = 2; # INIT ####################################################################### # declare vars my ($start_year, $end_year, $title, $year_range, $holiday, $date, $year, $month, $day, $swedish_weekday, $sqlfile, $tsvfile, $csvfile, $desc, $kompdag); ############################################################################## # command line option handling die print_usage() unless @ARGV >= 1; # stores output filename in $opt_o; # flags $opt_A and $opt_h are set if given getopts('Aho:'); print_usage() if $opt_h; # check validity of command line options # FIXME: handle empty arg to "-o" and invalid switches if (($opt_A || $opt_o) && @ARGV < 1) { print_usage(); } elsif (@ARGV < 1) { print_usage(); } # get years # FIXME: make current year default if no year given? if (@ARGV == 1) { $start_year = $ARGV[0]; $end_year = $start_year; $year_range = "$start_year"; } else { $start_year = $ARGV[0]; $end_year = $ARGV[1]; $year_range = "$start_year -- $end_year"; } # is $end_year >= $start_year? die "end_year must be greater than start_year!\n" unless $end_year >= $start_year; # check input dates for compliance with Date::Calc's Easter # calculation routine if (($start_year < $MIN_YEAR || $start_year > $MAX_YEAR) || ($end_year < $MIN_YEAR || $end_year > $MAX_YEAR)) { die "Programmet fungerar inte för år utanför $MIN_YEAR - $MAX_YEAR.\n"; } # headers if ($opt_A) { $title = "Helgdagar för $company_tag $year_range\n"; } else { $title = "Helgdagar för $year_range\n"; } # print headers of files print "## " . $title . "## " . $author . "## " . $timestamp; # init files for output if ($opt_o) { if ($opt_A) { # only Agero cares about the Atrapp DB $sqlfile = "$opt_o.sql"; # SQL output for "Atrapp" database open (SQL, "> $sqlfile") || die "Kunde inte öppna $sqlfile: $!"; chomp $timestamp; print SQL "-- " . $title . "-- " . $author . "-- " . $timestamp; print SQL "-- Mata in i Atrapp-databasen med hjälp av TOAD eller SQL*Plus\n\n"; } $tsvfile = "$opt_o.txt"; # tab-separated values for MS Outlook $csvfile = "$opt_o.csv"; # comma-separated file for mostly anything open (TSV, "> $tsvfile") || die "Kunde inte öppna $tsvfile: $!"; open (CSV, "> $csvfile") || die "Kunde inte öppna $csvfile: $!"; print TSV $tsvheader; } # MAIN LOOP ################################################################## for ($year = $start_year; $year <= $end_year; $year++) { # array for the holidays my @holidays = (); # Fixed dates while (($holiday, $date) = each %fixed_holidays) { ($month, $day) = split("-", $date); $swedish_weekday = $swedish_weekdays[Day_of_Week($year,$month,$day) - 1]; # push the line containg the holidays into a list of holidays for # this year. push @holidays, "$year-$date: $holiday ($swedish_weekday)\n"; } # Variable holidays # Easter related stuff $paskdagen = sprintf($date_format, Easter_Sunday($year)); # Friday, Sunday, Monday in Holy Week are holidays $langfredagen = sprintf($date_format, Add_Delta_YMD(split("-", $paskdagen), 0, 0, -2)); $annandag_pask = sprintf($date_format, Add_Delta_YMD(split("-", $paskdagen), 0, 0, 1)); # "Kristi Himmelsfärdsdag" is 39 days after Easter Sunday. $kristi_himmelsfardsdagen = sprintf($date_format, Add_Delta_YMD(split("-", $paskdagen), 0, 0, 39)); # Pentecost is 7 Sundays after Easter Sunday. "Annandag pingst" # is 50 days after E.S. $pingstdagen = sprintf($date_format, Add_Delta_YMD(split("-", $paskdagen), 0, 0, 49)); $annandag_pingst = sprintf($date_format, Add_Delta_YMD(split("-", $paskdagen), 0, 0, 50)); # "Midsommardagen" is the Saturday after $MS_MIN. @ms_dag = ($year, split("-", $MS_MIN)); while (Day_of_Week(@ms_dag) != 6 ) { @ms_dag = Add_Delta_YMD(@ms_dag, 0, 0, 1); } $midsommardagen = sprintf($date_format, @ms_dag); # "Alla helgons dag" is the Saturday after $AH_MIN. @ah_dag = ($year, split("-", $AH_MIN)); while (Day_of_Week(@ah_dag) != 6) { @ah_dag = Add_Delta_YMD(@ah_dag, 0, 0, 1); } $alla_helgons_dag = sprintf($date_format, @ah_dag); # fill the hash %variable_holidays = ( "Påskdagen" => $paskdagen, "Långfredagen" => $langfredagen, "Annandag påsk" => $annandag_pask, "Kristi Himmelsfärdsdagen" => $kristi_himmelsfardsdagen, "Pingstdagen" => $pingstdagen, "Annandag pingst" => $annandag_pingst, "Midsommardagen" => $midsommardagen, "Alla helgons dag" => $alla_helgons_dag); while (($holiday, $date) = each %variable_holidays) { push @holidays, "$date: $holiday\n"; } # Company-specific holidays if ($opt_A) { foreach (@company_specific_holidays) { ($date, $holiday) = split(": ", $_); my $delta; if ($date =~ /^(\d{4})-(\d{2})-(\d{2})$/ && $1 == $year) { # the date is fixed and specific to this year $swedish_weekday = $swedish_weekdays[Day_of_Week($year, $2, $3) - 1]; push @holidays, "$year-$2-$3: $holiday ($swedish_weekday)\n"; } elsif ($date =~ /^(\d{2})-(\d{2})$/) { # the date occurs every year $swedish_weekday = $swedish_weekdays[Day_of_Week($year, split("-", $date)) - 1]; push @holidays, "$year-$date: $holiday ($swedish_weekday)\n"; } elsif ($date =~ /^(.*) (plus|minus) (\d+)/ ) { # the date is relative a canonical date if ($2 eq "minus") { $delta = -$3; } else { $delta = $3; } # ugly swich statement if we can't use locales my $swedish_holiday_name = $1; my $canonical_holiday; SWITCH: for ($swedish_holiday_name) { /L.ngfredagen/ && do {$canonical_holiday = $langfredagen}; /P.skdagen/ && do {$canonical_holiday = $paskdagen}; /Annandag p.sk/ && do {$canonical_holiday = $annandag_pask}; /Kristi Himmelsf.rdsdagen/ && do {$canonical_holiday = $kristi_himmelsfardsdagen}; /Pingstdagen/ && do {$canonical_holiday = $pingstdagen}; /Annandag pingst/ && do {$canonical_holiday = $annandag_pingst}; /Midsommardagen/ && do {$canonical_holiday = $midsommardagen}; /Alla helgons dag/ && do {$canonical_holiday = $alla_helgons_dag}; } push @holidays, sprintf("$date_format: $holiday\n", Add_Delta_YMD(split("-", $canonical_holiday), 0, 0, $delta)); } } } # sort the dates, and output @holidays = sort (@holidays); print "== [ Helgdagar för $year ] ===================================\n\n"; foreach $holiday (@holidays) { # print to STDOUT for us Unix types print $holiday; # mangle the dates for the files if ($opt_o) { ($date, $desc) = split ": ", $holiday; chomp $desc; if ($opt_A) { print SQL $sql_head . $date . $sql_foot . "\n"; } # specific for outlook file $desc =~ s/\(.*$//; # Braindead outlook doesn't accept newline as line separator print TSV $desc . "\t" . $date . "\t" . $show_time_as . "\r\n"; print CSV $date . "," . $desc . "\r\n"; } } # footer print "\n"; } # CLEANUP #################################################################### if ($opt_o) { close SQL if $opt_A; close TSV; close CSV; }