package Date::Convert::French_Rev; use strict; use vars qw($VERSION @ISA @EXPORT @EXPORT_OK); use Date::Convert; use Carp; use Roman; require Exporter; @ISA = qw(Date::Convert Exporter); # Do not export methods, therefore export nothing @EXPORT = qw( ); $VERSION = '0.05'; use constant REV_BEGINNING => 2375840; # 1 Vendémiaire I in the Revolutionary calendar my @MONTHS_SHORT = qw ( Vnd Bru Fri Niv Plu Vnt Ger Flo Pra Mes The Fru S-C); my @ADD_DAYS_SHORT= qw ( Vertu Génie Trav Raison Récomp Révol); my @MONTHS = qw(Vendémiaire Brumaire Frimaire Nivôse Pluviôse Ventôse Germinal Floréal Prairial Messidor Thermidor Fructidor); push @MONTHS, "jour complémentaire"; # The day numer 10 is counterintuitively placed in the 0-th element # because the modulus operator and the Perl arrays are 0-based. # It works. Do not report a bug. my @DECADE_DAYS = qw ( Décadi Primidi Duodi Tridi Quartidi Quintidi Sextidi Septidi Octidi Nonidi); my @DECADE_DAYS_SHORT = qw ( Déc Pri Duo Tri Qua Qui Sex Sep Oct Non); # When initializing an array with lists within lists, it means one of two things: # Either it is a newbie who does not know how to make multi-dimensional arrays, # Or it is a (at least mildly) experienced Perl-coder who, for some reason, # wants to initialize a flat array with the concatenation of lists. # I am a (at least mildly) experienced programmer who wants to use qw() and yet insert # comments in some places. my @DAYS = ( # Vendémiaire qw( 0raisin 0safran 1châtaigne 1colchique 0cheval 1balsamine 1carotte 2amarante 0panais 1cuve 1pomme_de_terre 2immortelle 0potiron 0réséda 2âne 1belle_de_nuit 1citrouille 0sarrasin 0tournesol 0pressoir 0chanvre 1pèche 0navet 2amaryllis 0boeuf 2aubergine 0piment 1tomate 2orge 0tonneau ), # Brumaire qw( 1pomme 0céleri 1poire 1betterave 2oie 2héliotrope 1figue 1scorsonère 2alisier 1charrue 0salsifis 1macre 0topinambour 2endive 0dindon 4chervis 0cresson 1dentelaire 1grenade 1herse 5bacchante 2azerole 1garance 2orange 0faisan 1pistache 4macjon 0coing 0cormier 0rouleau ), # Frimaire qw( 1raiponce 0turneps 1chicorée 1nèfle 0cochon 1mâche 0chou-fleur 0miel 0genièvre 1pioche 1cire 0raifort 0cèdre 0sapin 0chevreuil 2ajonc 0cyprès 0lierre 1sabine 0hoyau 2érable-sucre 1bruyère 0roseau 2oseille 0grillon 0pignon 0liège 1truffe 2olive 1pelle ), # Nivôse qw( 1tourbe 1houille 0bitume 0soufre 0chien 1lave 1terre_végétale 0fumier 0salpêtre 0fléau 0granit 2argile 2ardoise 0grès 0lapin 0silex 1marne 1pierre_à_chaux 0marbre 0van 1pierre_à_plâtre 0sel 0fer 0cuivre 0chat 2étain 0plomb 0zinc 0mercure 0crible ), # Pluviôse qw( 5lauréole 1mousse 0fragon 0perce-neige 0taureau 0laurier-thym 2amadouvier 4mézéréon 0peuplier 1cognée 2ellébore 0brocoli 0laurier 2avelinier 1vache 0buis 0lichen 2if 1pulmonaire 1serpette 0thlaspi 4thymelé 0chiendent 5trainasse 0lièvre 1guède 0noisetier 0cyclamen 1chélidoine 0traîneau ), # Ventôse qw( 0tussilage 0cornouiller 0violier 0troène 0bouc 2asaret 2alaterne 1violette 0marsault 1bêche 0narcisse 2orme 1fumeterre 0vélar 1chèvre 2épinard 0doronic 0mouron 0cerfeuil 0cordeau 1mandragore 0persil 0cochléaria 1pâquerette 0thon 0pissenlit 1sylvie 0capillaire 0frêne 0plantoir ), # Germinal qw( 1primevère 0platane 2asperge 1tulipe 1poule 1blette 0bouleau 1jonquille 2aulne 0couvoir 1pervenche 0charme 1morille 0hêtre 2abeille 1laitue 0mélèze 1ciguë 0radis 1ruche 0gainier 1romaine 0marronnier 1roquette 0pigeon 0lilas 2anémone 1pensée 1myrtille 0greffoir ), # Floréal qw( 1rose 0chêne 1fougère 2aubépine 0rossignol 2ancolie 0muguet 0champignon 1jacinthe 0rateau 1rhubarbe 0sainfoin 0bâton-d'or 4chamérisier 0ver_à_soie 1consoude 1pimprenelle 1corbeille-d'or 2arroche 0sarcloir 0statice 1fritillaire 1bourrache 1valériane 1carpe 0fusain 1civette 1buglosse 0sénevé 1houlette ), # Prairial qw( 1luzerne 6hémérocalle 0trèfle 2angélique 0canard 1mélisse 0fromental 0martagon 0serpolet 1faux 1fraise 1bétoine 0pois 2acacia 1caille 2oeillet 0sureau 0pavot 0tilleul 1fourche 0barbeau 1camomille 0chèvrefeuille 0caille-lait 1tanche 0jasmin 1verveine 0thym 1pivoine 0chariot ), # Messidor qw( 0seigle 2avoine 2oignon 1véronique 0mulet 0romarin 0concombre 2échalotte 2absinthe 1faucille 0coriandre 2artichaut 1giroflée 1lavande 0chamois 0tabac 1groseille 1gesse 1cerise 0parc 1menthe 0cumin 0haricot 2orcanète 1pintade 1sauge 2ail 1vesce 0blé 5chalémie ), # Thermidor qw( 2épautre 0bouillon-blanc 0melon 2ivraie 0bélier 1prèle 2armoise 0carthame 1mûre 2arrosoir 4panis 4salicor 2abricot 0basilic 1brebis 1guimauve 0lin 2amande 1gentiane 2écluse 1carline 0câprier 1lentille 2aunée 1loutre 1myrte 0colza 0lupin 0coton 0moulin ), # Fructidor qw( 1prune 0millet 0lycoperdon 2escourgeon 0saumon 1tubéreuse 4sucrion 2apocyn 1réglisse 2échelle 1pastèque 0fenouil 2épine-vinette 1noix 1truite 0citron 1cardère 0nerprun 0tagette 1hotte 2églantier 1noisette 0houblon 0sorgho 2écrevisse 5bagarade 1verge-d'or 0maïs 0marron 0panier ), # Jours complémentaires qw( 1vertu 0génie 0travail 2opinion 3récompenses 1révolution )); my @PREFIXES = ('jour du ', 'jour de la ', "jour de l'", 'jour des '); use constant NORMAL_YEAR => 365; use constant LEAP_YEAR => 366; use constant FOUR_YEARS => 4 * NORMAL_YEAR + 1; # one leap year every four years use constant CENTURY => 25 * FOUR_YEARS - 1; # centuries aren't leap years... use constant FOUR_CENTURIES => 4 * CENTURY + 1; # ...except every four centuries that are. use constant FOUR_MILLENIA => 10 * FOUR_CENTURIES - 1; # ...except every four millenia that are not. # number of days between the start of the revolutionary calendar, and the # beginning of year n - 1 my @YEARS_BEGINS= (0, 365, 730, 1096, 1461, 1826, 2191, 2557, 2922, 3287, 3652, 4018, 4383, 4748, 5113, 5479, 5844); sub initialize { my $self = shift; my ($year, $month, $day) = @_; if (not defined($year) and not defined($month) and not defined($day)) { return Date::Convert::initialize } if (not defined($year) or not defined($month) or not defined($day)) { croak "Date::Convert::French_Rev::initialize needs more args" } confess "These routines don't work well for French_Rev before year 1" if $year < 1; my $absol = REV_BEGINNING; $$self{'year'} = $year; $$self{'month'} = $month; $$self{'day'} = $day; my $is_leap = is_leap Date::Convert::French_Rev $year; croak "month $month out of range" if $month > 13 or $month <= 0; croak "standard day number $day out of range" if $day <= 0 and $month <= 12; croak "standard day number $day out of range" if $day > 30 and $month <= 12; croak "additional day $day out of range" if ($month == 13) and ($day <= 0); croak "additional day $day out of range" if ($month == 13) and ($day > 5) and !$is_leap; croak "additional day $day out of range" if ($month == 13) and ($day > 6) and $is_leap; $year --; #get years *before* this year. Makes math easier. :) # first, convert year into days. . . if ($year >= 16) # Romme rule in effect, or nearly so { $absol += int($year/4000) * FOUR_MILLENIA; $year %= 4000; $absol += int($year/400) * FOUR_CENTURIES; $year %= 400; $absol += int($year/100) * CENTURY; $year %= 100; $absol += int($year/4)* FOUR_YEARS; $year %= 4; $absol += $year * NORMAL_YEAR; } else # table look-up for the programmer-hostile equinox rule { $absol += $YEARS_BEGINS[$year] } # now, month into days. $absol += 30 * ($month - 1) + $day - 1; $$self{absol}=$absol; } sub year { my $self = shift; return $$self{year} if exists $$self{year}; # no point recalculating. my $days; my $year; # note: years and days are initially days *before* today, rather than # today's date. This is because of fenceposts. :) $days = $$self{absol} - REV_BEGINNING; if ($days < $YEARS_BEGINS[16]) { $year = scalar grep { $_ <= $days } @YEARS_BEGINS; $days -= $YEARS_BEGINS[$year - 1]; $days++; } else { #$days --; my $x; $x = int ($days / FOUR_MILLENIA); $year += $x * 4000; $days -= $x * FOUR_MILLENIA; $x = int ($days / FOUR_CENTURIES); $year += $x * 400; $days -= $x * FOUR_CENTURIES; $x = int ($days / CENTURY); $x = 3 if $x == 4; # last day of the 400-year period $year += $x * 100; $days -= $x * CENTURY; $x = int ($days / FOUR_YEARS); $year += $x * 4; $days -= $x * FOUR_YEARS; $x = int ($days / NORMAL_YEAR); $x = 3 if $x == 4; # last day of the 4-year period $year += $x; $days -= $x * NORMAL_YEAR; ++$year; # because of 0-based mathematics vs 1-based chronology ++$days; } $$self{year} = $year; $$self{days_into_year} = $days; return $year; } sub month { my $self = shift; return $$self{month} if exists $$self{month}; my $year = $self -> year; my $days = $$self{days_into_year} - 1; my $day = $days % 30; $days -= $day; my $month = $days / 30 + 1; $$self{month} = $month; $$self{day} = $day + 1; return $month; } sub day { my $self = shift; return $$self{day} if exists $$self{day}; $self->month; # calculates day as a side-effect return $$self{day}; } sub date { my $self = shift; return ($self->year, $self->month, $self->day); } sub is_leap { my $self = shift; my $year = shift || $self->year; # so is_leap can be static or method # Autumn equinox from I to XIX return 1 if ($year == 3) or ($year == 7) or ($year == 11) or ($year == 15); return 0 if ($year < 20); # Romme rule from XX on return 0 if $year % 4; # not a multiple of 4 -> normal year return 1 if $year % 100; # a multiple of 4 but not of 100 -> leap year return 0 if $year % 400; # a multiple of 100 but not of 400 -> normal year return 1 if $year % 4000; # a multiple of 400 but not of 4000 -> leap return 0; # multiple of 4000 -> normal year } sub field { my ($self, $spec) = @_; my $decade_day = $self->day % 10; # below, a switch statement, more or less, as described in perlfaq7 $spec eq '%d' && do { return sprintf "%02d", $self->day }; $spec eq '%j' && do { return sprintf "%03d", 30 * $self->month + $self->day - 30 }; $spec eq '%e' && do { return sprintf "%2d", $self->day }; $spec eq '%m' && do { return sprintf "%02d", $self->month }; $spec eq '%f' && do { return sprintf "%2d", $self->month }; $spec =~ /\%[YGL]/ && do { return sprintf "%04d", $self->year }; $spec =~ /\%B/ && do { return $MONTHS[$self->month - 1] }; $spec =~ /\%[bh]/ && do { return $MONTHS_SHORT[$self->month - 1] }; $spec eq '%y' && do { return sprintf "%02d", $self->year % 100 }; $spec eq '%n' && do { return "\n" }; $spec eq '%t' && do { return "\t" }; $spec eq '%+' && do { return '+' }; $spec eq '%%' && do { return '%' }; $spec eq '%a' && do { return $DECADE_DAYS_SHORT[$decade_day] }; $spec eq '%A' && do { return $DECADE_DAYS[$decade_day] }; $spec eq '%w' && do { return sprintf("%2d", $decade_day || 10) }; $spec eq '%EY' && do { return Roman $self->year }; $spec eq '%Ey' && do { return roman $self->year }; $spec eq '%Ej' && do { my $jj = 30 * $self->month + $self->day - 31; # %j is 1..366, but $jj is 0..365 my $lb = $DAYS[$jj]; $lb =~ s/_/ /g; $lb =~ s/^(\d)/$PREFIXES[$1 % 4]/; return $lb; }; $spec eq '%EJ' && do { my $jj = 30 * $self->month + $self->day - 31; # %j is 1..366, but $jj is 0..365 my $lb = $DAYS[$jj]; $lb =~ s/_/ /g; # Using a capitalized prefix, and capitalizing the first letter $lb =~ s/^(\d)(.)/\u$PREFIXES[$1 % 4]\u$2/; return $lb; }; $spec eq '%Oj' && do { my $jj = 30 * $self->month + $self->day - 31; # %j is 1..366, but $jj is 0..365 my $lb = substr $DAYS[$jj], 1; $lb =~ s/_/ /g; return $lb; }; return $spec; } sub date_string { my ($self, $format) = @_; # Default value when not provided. I do not test true / false, because # some adventurous mind could think that "0" is a valid format, even if false. $format = "%e %B %EY" if (! defined $format or $format eq ''); my $year = roman($self->year); # possibly to trigger the side effect my $month = $self->month; $format =~ s/( # start of $1 \% # percent sign (?: # start of alternative (?:O.) # extended field specifier: O with a second char | # or (?:E.) # other extended field specifier: E with a second char | # or . # basic field specifier: single char )) # end of alternative and end of $1 /'$self->field($1)'/eegx; # is there a simpler way to do it? return $format; } # A module must return a true value. Traditionally, a module returns 1. # But this module is a revolutionary one, so it discards all old traditions. "Liberté, égalité, fraternité ou la mort !"; __END__ =head1 NAME Date::Convert::French_Rev - Convert from / to French Revolutionary Calendar =head1 SYNOPSIS use Date::Convert::French_Rev; Converting from Gregorian (or other) to Revolutionary $date=new Date::Convert::Gregorian(1799, 11, 9); convert Date::Convert::French_Rev $date; print $date->date_string, "\n"; print $date->date_string($format), "\n"; Converting from Revolutionary to Gregorian (or other) $date=new Date::Convert::French_Rev(8, 2, 18); convert Date::Convert::Gregorian $date; print $date->date_string, "\n"; =head1 REQUIRES Date::Convert, Roman =head1 EXPORTS Nothing. =head1 DESCRIPTION This module is obsolete. It is replaced by a module compatible with DateTime.pm: DateTime::Calendar::FrenchRevolutionary The following methods are available: =over 4 =item new Create a new Revolutinary date object with the specified start parameters, ie. C<$date = new Date::Convert::French_Rev(8, 2, 18)> for 18 Brumaire VIII. =item date Extract a list consisting of the year, the month and the day. The end-of-year additional days are assigned to the virtual 13th month. =item year Return just the year element of date. =item month Return the month element of date, or 13 if the date is an additional day at the end of the year. =item day Just like year and month. =item is_leap Boolean. =item convert Change the date to a new format. =item date_string Return the date in a pretty format. You can give an string parameter to adjust the date format to your preferences. =back The format parameter to C is a string consisting of any characters (letters, digits, whitespace, other) with %-prefixed field descriptors, inspired from the Unix standard C command. The following field descriptors are recognized: =over 4 =item %y year - 00 to 99 =item %Y, %G, %L year - 0001 to 9999. There is no difference between these three variants. This is because in the Revolutionary calendar, the beginning of a year is always aligned with the beginning of a décade, while in the Gregorian calendar, the beginning of a year is usually not aligned with the beginning of a week. =item %EY, %Ey year as a Roman number - I to MMM =item %m month of year - 01 to 12, or 13 for the end-of-year additional days. =item %f month of year - " 1" to "12", or "13" for the end-of-year additional days. =item %b, %h month abbreviation - Ven to Fru, or S-C for the end-of-year additional days (called I). =item %B month full name - Vendémiaire to Fructidor, or "jour complémentaire" for the end-of-year additional days. =item %d day of month - 01 to 30 =item %e day of month - " 1" to "30" =item %A day of décade - "Primidi" to "Décadi". =item %a abbreviated day of décade - "Pri" to "Déc". Beware: do not confuse Sep, Oct and Déc with Gregorian calendar months =item %w day of décade - " 1" to "10" (" 1" for Primidi, " 2" for Duodi, etc) =item %j day of the year - "001" to "366". =item %Ej full name of the day of the year. Instead of assigning a saint to each day, the creators of the calendar decided to assign a plant, an animal or a tool. =item %EJ same as %Ej, but significant words are capitalized. =item %Oj simple name of the day of the year. Same as %Ej, without the prefix. =item %n, %t, %%, %+ replaced by a newline, tab, percent and plus character respectively =back The time-related field specifiers are irrelevant. Therefore, they are copied "as is" into the result string. These fields are: %h, %k, %i, %I, %p, %M, %S, %s, %o, %Z, %z Neither are the composite field specifiers supported: %c, %C, %u, %g, %D, %x, %l, %r, %R, %T, %X, %V, %Q, %q, %P, %F, %J, %K =for diagnostics start description =head1 DIAGNOSTICS These are the diagnostics which can be produced by C. The explanations can be displayed when the error is produced if you have installed C, version 1.2. Just insert one of the following lines in your script: use diagnostics qw(-m=Date::Convert::French_Rev); use diagnostics qw(-m=Date::Convert::French_Rev -m=perl); =for diagnostics start items =item month %s out of range The French Revolutionary calendar has 12 months, plus 5 or 6 additional days that do not belong to a month. So the month number must be in the 1-12 range for normal days, or 13 for additional days =item additional day %s out of range The day number for the end-of-year additional days is a number in the 1-5 range (or the 1-6 range for leap years). =item Date::Convert::French_Rev::initialize needs more args You must provide a year, a month number and a day number to C. =item standard day number %s out of range The day number for any normal month is in the 1-30 range. =item These routines don't work well for French_Rev before year 1 If you ask a negative (Revolutionary) year, zero included, the module may produce various errors, such as off-by-one conditions. =for diagnostics stop items =head1 KNOWN BUGS AND CAVEATS Not many bugs, but many caveats. Some day names correspond to little known plants. Therefore, I am not sure if they use the masculine gender or the feminine gender. I have found some in various dictionaries, but there still remain a few of them. Note: they are tagged with a 4, 5 or 6 code. My sources disagree about the 4th additional day. One says "jour de l'opinion", the other says "jour de la raison". Another disagreement is that some sources ignore the Romme rule, and use only the equinox rule. So, a 1- or 2-day difference can happen. This module inherits its user interface from Mordechai Abzug's C, which is, according to its author, "in pre-alpha state". Therefore, my module's user interface is also subject to changes. I have checked the manpage for C in two flavors of Unix: Linux and AIX. In the best case, the extended field descriptors C<%Ex> and C<%Oy> are poorly documented, but usually they are not documented. =head1 HISTORICAL NOTES The Revolutionary calendar was in use in France from 24 November 1793 (4 Frimaire II) to 31 December 1805 (10 Nivôse XIV). An attempt to use the decimal rule (the basis of the metric system) to the calendar. Therefore, the week disappeared, replaced by the décade (10 days, totally different from the English word "decade", 10 years). In addition, all months have exactly 3 decades, no more, no less. At first, the year was beginning on the equinox of autumn, for two reasons. First, the republic had been established on 22 September 1792, which happened to be the equinox, and second, the equinox was the symbol of equality, the day and the night lasting exactly 12 hours each. It was therefore in tune with the republic's motto "Liberty, Equality, Fraternity". But it was not practical, so Romme proposed a leap year rule similar to the Gregorian calendar rule. In his book I, the 19th century writer Thomas Carlyle proposes these translations for the month names: =over 4 =item Vendémiaire -> Vintagearious =item Brumaire -> Fogarious =item Frimaire -> Frostarious =item Nivôse -> Snowous =item Pluviôse -> Rainous =item Ventôse -> Windous =item Germinal -> Buddal =item Floréal -> Floweral =item Prairial -> Meadowal =item Messidor -> Reapidor =item Thermidor -> Heatidor =item Fructidor -> Fruitidor =back =head1 AUTHOR Jean Forget based on Mordechai T. Abzug's work The development of this module is hosted by I, L. =head1 SEE ALSO =head2 Software date(1), perl(1), Date::DateCalc(3), Date::Convert(3) calendar/cal-french.el in emacs-21.2 or xemacs 21.1.8 =head2 books Quid 2001, M and D Frémy, publ. Robert Laffont Agenda Républicain 197 (1988/89), publ. Syros Alternatives Any French schoolbook about the French Revolution The French Revolution, Thomas Carlyle, Oxford University Press =head2 Internet http://www.faqs.org/faqs/calendars/faq/part3/ =head1 LICENSE STUFF Copyright (c) 2001, 2002 Jean Forget. All rights reserved. This program is free software. You can distribute, modify, and otherwise mangle Date::Convert::French_Rev under the same terms as perl. =cut