File Coverage

blib/lib/Date/Convert/French_Rev.pm
Criterion Covered Total %
statement 196 196 100.0
branch 94 94 100.0
condition 44 44 100.0
subroutine 23 23 100.0
pod 8 9 88.8
total 365 366 99.7


line stmt bran cond sub pod time code
1             # -*- encoding: utf-8; indent-tabs-mode: nil -*-
2             #
3             # Perl Date::Convert extension to convert dates from/to the French Revolutionary calendar
4             # Copyright (C) 2001-2003, 2013, 2015 Jean Forget
5             #
6             # See the license in the embedded documentation below.
7             #
8             package Date::Convert::French_Rev;
9              
10 12     12   4104769 use utf8;
  12         77  
  12         57  
11 12     12   357 use strict;
  12         19  
  12         248  
12 12     12   45 use warnings;
  12         21  
  12         374  
13 12     12   45 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
  12         19  
  12         868  
14 12     12   7074 use Date::Convert;
  12         36691  
  12         331  
15 12     12   81 use Carp;
  12         20  
  12         685  
16 12     12   6092 use Roman;
  12         8155  
  12         1088  
17              
18             require Exporter;
19              
20             @ISA = qw(Date::Convert Exporter);
21             # Do not export methods, therefore export nothing
22             @EXPORT = qw(
23            
24             );
25             $VERSION = '0.08';
26              
27 12     12   86 use constant REV_BEGINNING => 2375840; # 1 Vendémiaire I in the Revolutionary calendar
  12         15  
  12         4859  
28             my @MONTHS_SHORT = qw ( Vnd Bru Fri Niv Plu Vnt Ger Flo Pra Mes The Fru S-C);
29             my @MONTHS = qw(Vendémiaire Brumaire Frimaire
30             Nivôse Pluviôse Ventôse
31             Germinal Floréal Prairial
32             Messidor Thermidor Fructidor);
33             push @MONTHS, "jour complémentaire"; # Incompatible with qw(), because of embedded space
34              
35             # The day numer 10 is counterintuitively placed in the 0-th element
36             # because the modulus operator and the Perl arrays are 0-based.
37             # It works. Do not report a bug.
38             my @DECADE_DAYS = qw ( Décadi Primidi Duodi Tridi Quartidi Quintidi Sextidi Septidi Octidi Nonidi);
39             my @DECADE_DAYS_SHORT = qw ( Déc Pri Duo Tri Qua Qui Sex Sep Oct Non);
40              
41             # When initializing an array with lists within lists, it means one of two things:
42             # Either it is a newbie who does not know how to make multi-dimensional arrays,
43             # Or it is a (at least mildly) experienced Perl-coder who, for some reason,
44             # wants to initialize a flat array with the concatenation of lists.
45             # I am a (at least mildly) experienced programmer who wants to use qw() and yet insert
46             # comments in some places.
47             my @DAYS = (
48             # Vendémiaire
49             qw(
50             0raisin 0safran 1châtaigne 1colchique 0cheval
51             1balsamine 1carotte 2amarante 0panais 1cuve
52             1pomme_de_terre 2immortelle 0potiron 0réséda 2âne
53             1belle_de_nuit 1citrouille 0sarrasin 0tournesol 0pressoir
54             0chanvre 1pêche 0navet 2amaryllis 0bœuf
55             2aubergine 0piment 1tomate 2orge 0tonneau
56             ),
57             # Brumaire
58             qw(
59             1pomme 0céleri 1poire 1betterave 2oie
60             2héliotrope 1figue 1scorsonère 2alisier 1charrue
61             0salsifis 1macre 0topinambour 2endive 0dindon
62             0chervis 0cresson 1dentelaire 1grenade 1herse
63             1bacchante 2azerole 1garance 2orange 0faisan
64             1pistache 0macjon 0coing 0cormier 0rouleau
65             ),
66             # Frimaire
67             qw(
68             1raiponce 0turneps 1chicorée 1nèfle 0cochon
69             1mâche 0chou-fleur 0miel 0genièvre 1pioche
70             1cire 0raifort 0cèdre 0sapin 0chevreuil
71             2ajonc 0cyprès 0lierre 1sabine 0hoyau
72             2érable-sucre 1bruyère 0roseau 2oseille 0grillon
73             0pignon 0liège 1truffe 2olive 1pelle
74             ),
75             # Nivôse
76             qw(
77             1tourbe 1houille 0bitume 0soufre 0chien
78             1lave 1terre_végétale 0fumier 0salpêtre 0fléau
79             0granit 2argile 2ardoise 0grès 0lapin
80             0silex 1marne 1pierre_à_chaux 0marbre 0van
81             1pierre_à_plâtre 0sel 0fer 0cuivre 0chat
82             2étain 0plomb 0zinc 0mercure 0crible
83             ),
84             # Pluviôse
85             qw(
86             1lauréole 1mousse 0fragon 0perce-neige 0taureau
87             0laurier-thym 2amadouvier 0mézéréon 0peuplier 1cognée
88             2ellébore 0brocoli 0laurier 2avelinier 1vache
89             0buis 0lichen 2if 1pulmonaire 1serpette
90             0thlaspi 0thymelé 0chiendent 1traînasse 0lièvre
91             1guède 0noisetier 0cyclamen 1chélidoine 0traîneau
92             ),
93             # Ventôse
94             qw(
95             0tussilage 0cornouiller 0violier 0troène 0bouc
96             2asaret 2alaterne 1violette 0marsault 1bêche
97             0narcisse 2orme 1fumeterre 0vélar 1chèvre
98             2épinard 0doronic 0mouron 0cerfeuil 0cordeau
99             1mandragore 0persil 0cochléaria 1pâquerette 0thon
100             0pissenlit 1sylvie 0capillaire 0frêne 0plantoir
101             ),
102             # Germinal
103             qw(
104             1primevère 0platane 2asperge 1tulipe 1poule
105             1blette 0bouleau 1jonquille 2aulne 0couvoir
106             1pervenche 0charme 1morille 0hêtre 2abeille
107             1laitue 0mélèze 1ciguë 0radis 1ruche
108             0gainier 1romaine 0marronnier 1roquette 0pigeon
109             0lilas 2anémone 1pensée 1myrtille 0greffoir
110             ),
111             # Floréal
112             qw(
113             1rose 0chêne 1fougère 2aubépine 0rossignol
114             2ancolie 0muguet 0champignon 1jacinthe 0rateau
115             1rhubarbe 0sainfoin 0bâton-d'or 0chamérisier 0ver_à_soie
116             1consoude 1pimprenelle 1corbeille-d'or 2arroche 0sarcloir
117             0statice 1fritillaire 1bourrache 1valériane 1carpe
118             0fusain 1civette 1buglosse 0sénevé 1houlette
119             ),
120             # Prairial
121             qw(
122             1luzerne 2hémérocalle 0trèfle 2angélique 0canard
123             1mélisse 0fromental 0martagon 0serpolet 1faux
124             1fraise 1bétoine 0pois 2acacia 1caille
125             2œillet 0sureau 0pavot 0tilleul 1fourche
126             0barbeau 1camomille 0chèvrefeuille 0caille-lait 1tanche
127             0jasmin 1verveine 0thym 1pivoine 0chariot
128             ),
129             # Messidor
130             qw(
131             0seigle 2avoine 2oignon 1véronique 0mulet
132             0romarin 0concombre 2échalotte 2absinthe 1faucille
133             1coriandre 2artichaut 1giroflée 1lavande 0chamois
134             0tabac 1groseille 1gesse 1cerise 0parc
135             1menthe 0cumin 0haricot 2orcanète 1pintade
136             1sauge 2ail 1vesce 0blé 1chalémie
137             ),
138             # Thermidor
139             qw(
140             2épautre 0bouillon-blanc 0melon 2ivraie 0bélier
141             1prèle 2armoise 0carthame 1mûre 2arrosoir
142             0panis 0salicor 2abricot 0basilic 1brebis
143             1guimauve 0lin 2amande 1gentiane 2écluse
144             1carline 0câprier 1lentille 2aunée 1loutre
145             1myrte 0colza 0lupin 0coton 0moulin
146             ),
147             # Fructidor
148             qw(
149             1prune 0millet 0lycoperdon 2escourgeon 0saumon
150             1tubéreuse 0sucrion 2apocyn 1réglisse 2échelle
151             1pastèque 0fenouil 2épine-vinette 1noix 1truite
152             0citron 1cardère 0nerprun 0tagette 1hotte
153             2églantier 1noisette 0houblon 0sorgho 2écrevisse
154             1bagarade 1verge-d'or 0maïs 0marron 0panier
155             ),
156             # Jours complémentaires
157             qw(
158             1vertu 0génie 0travail 2opinion 3récompenses
159             1révolution
160             ));
161              
162             my @PREFIXES = ('jour du ', 'jour de la ', "jour de l'", 'jour des ');
163              
164 12     12   64 use constant NORMAL_YEAR => 365;
  12         20  
  12         635  
165 12     12   153 use constant LEAP_YEAR => 366;
  12         14  
  12         638  
166 12     12   53 use constant FOUR_YEARS => 4 * NORMAL_YEAR + 1; # one leap year every four years
  12         16  
  12         630  
167 12     12   46 use constant CENTURY => 25 * FOUR_YEARS - 1; # centuries aren't leap years...
  12         15  
  12         637  
168 12     12   50 use constant FOUR_CENTURIES => 4 * CENTURY + 1; # ...except every four centuries that are.
  12         17  
  12         652  
169 12     12   61 use constant FOUR_MILLENIA => 10 * FOUR_CENTURIES - 1; # ...except every four millenia that are not.
  12         13  
  12         18437  
170              
171             # number of days between the start of the revolutionary calendar, and the
172             # beginning of year n - 1
173             my @YEARS_BEGINS= (0, 365, 730, 1096, 1461, 1826, 2191, 2557, 2922, 3287, 3652,
174             4018, 4383, 4748, 5113, 5479, 5844);
175              
176             # This method shoudl be in the master class, but for the moment, it is only available here
177             sub change_to {
178 35 100   35 1 194 croak "Need to specify the new calendar"
179             if @_ <= 1;
180 34         33 my ($self, $new_cal) = @_;
181 34         78 $new_cal->convert($self);
182             }
183              
184             sub initialize {
185 173     173 1 99390 my $self = shift;
186 173         245 my ($year, $month, $day) = @_;
187 173 100 100     1029 unless (defined($year) and defined($month) and defined($day))
      100        
188 4         46 { croak "Date::Convert::French_Rev::initialize needs more args" }
189 169         274 my $absol = REV_BEGINNING;
190 169         341 $$self{'year'} = $year;
191 169         194 $$self{'month'} = $month;
192 169         188 $$self{'day'} = $day;
193              
194 169         296 my $is_leap = Date::Convert::French_Rev->is_leap($year);
195 169 100       318 croak "year $year out of range" if $year <= 0;
196 167 100 100     551 croak "month $month out of range" if $month > 13 or $month <= 0;
197 165 100 100     315 croak "standard day number $day out of range" if $day <= 0 and $month <= 12;
198 164 100 100     334 croak "standard day number $day out of range" if $day > 30 and $month <= 12;
199 163 100 100     358 croak "additional day $day out of range" if ($month == 13) and ($day <= 0);
200 161 100 100     381 croak "additional day $day out of range" if ($month == 13) and ($day > 5) and !$is_leap;
      100        
201 160 100 100     325 croak "additional day $day out of range" if ($month == 13) and ($day > 6); # implying "and $is_leap" other cases already discarded
202              
203 158         152 $year --; #get years *before* this year. Makes math easier. :)
204             # first, convert year into days. . .
205 158 100       242 if ($year >= 16) # Romme rule in effect, or nearly so
206             {
207 98         223 $absol += int($year/4000) * FOUR_MILLENIA;
208 98         86 $year %= 4000;
209 98         98 $absol += int($year/400) * FOUR_CENTURIES;
210 98         70 $year %= 400;
211 98         95 $absol += int($year/100) * CENTURY;
212 98         82 $year %= 100;
213 98         88 $absol += int($year/4)* FOUR_YEARS;
214 98         69 $year %= 4;
215 98         96 $absol += $year * NORMAL_YEAR;
216             }
217             else # table look-up for the programmer-hostile equinox rule
218 60         78 { $absol += $YEARS_BEGINS[$year] }
219              
220             # now, month into days.
221 158         194 $absol += 30 * ($month - 1) + $day - 1;
222              
223 158         365 $$self{absol} = $absol;
224             }
225              
226             sub year {
227 572     572 1 530 my $self = shift;
228 572 100       1473 return $$self{year} if exists $$self{year}; # no point recalculating.
229 153         122 my $days;
230             my $year;
231             # note: years and days are initially days *before* today, rather than
232             # today's date. This is because of fenceposts. :)
233 153         148 $days = $$self{absol} - REV_BEGINNING;
234 153 100       253 if ($days < $YEARS_BEGINS[16]) {
235 64         88 $year = scalar grep { $_ <= $days } @YEARS_BEGINS;
  1088         1063  
236 64         70 $days -= $YEARS_BEGINS[$year - 1];
237 64         48 $days++;
238             }
239             else {
240 89         71 my $x;
241 89         177 $x = int ($days / FOUR_MILLENIA);
242 89         86 $year += $x * 4000;
243 89         81 $days -= $x * FOUR_MILLENIA;
244              
245 89         84 $x = int ($days / FOUR_CENTURIES);
246 89         70 $year += $x * 400;
247 89         68 $days -= $x * FOUR_CENTURIES;
248              
249 89         79 $x = int ($days / CENTURY);
250 89 100       135 $x = 3 if $x == 4; # last day of the 400-year period
251 89         82 $year += $x * 100;
252 89         70 $days -= $x * CENTURY;
253              
254 89         78 $x = int ($days / FOUR_YEARS);
255 89         73 $year += $x * 4;
256 89         60 $days -= $x * FOUR_YEARS;
257              
258 89         74 $x = int ($days / NORMAL_YEAR);
259 89 100       132 $x = 3 if $x == 4; # last day of the 4-year period
260 89         60 $year += $x;
261 89         61 $days -= $x * NORMAL_YEAR;
262              
263 89         61 ++$year; # because of 0-based mathematics vs 1-based chronology
264 89         84 ++$days;
265             }
266 153         182 $$self{year} = $year;
267 153         198 $$self{days_into_year} = $days;
268 153         191 return $year;
269             }
270              
271             sub month {
272 329     329 1 336 my $self = shift;
273 329 100       1265 return $$self{month} if exists $$self{month};
274 153         195 my $year = $self -> year;
275 153         156 my $days = $$self{days_into_year} - 1;
276 153         132 my $day = $days % 30;
277 153         130 $days -= $day;
278 153         188 my $month = $days / 30 + 1;
279 153         155 $$self{month} = $month;
280 153         158 $$self{day} = $day + 1;
281 153         157 return $month;
282             }
283              
284             sub day {
285 558     558 1 10169 my $self = shift;
286 558 100       2081 return $$self{day} if exists $$self{day};
287 25         39 $self->month; # calculates day as a side-effect
288 25         33 return $$self{day};
289             }
290              
291             sub date {
292 25     25 1 9086 my $self = shift;
293 25         41 return ($self->year, $self->month, $self->day);
294             }
295              
296             sub is_leap {
297 261     261 1 19050 my ($self, $year) = @_;
298 261 100       510 if (@_ == 1) {
299 46         62 $year = $self->year; # so is_leap can be static or method
300             }
301              
302             # Autumn equinox from I to XIX
303 261 100 100     1702 return 1 if ($year == 3) or ($year == 7) or ($year == 11) or ($year == 15);
      100        
      100        
304 240 100       458 return 0 if ($year < 20);
305              
306             # Romme rule from XX on
307 156 100       330 return 0 if $year % 4; # not a multiple of 4 -> normal year
308 90 100       156 return 1 if $year % 100; # a multiple of 4 but not of 100 -> leap year
309 71 100       160 return 0 if $year % 400; # a multiple of 100 but not of 400 -> normal year
310 32 100       84 return 1 if $year % 4000; # a multiple of 400 but not of 4000 -> leap
311 8         17 return 0; # multiple of 4000 -> normal year
312             }
313              
314             sub field {
315 378     378 0 579 my ($self, $spec) = @_;
316 378         515 my $decade_day = $self->day % 10;
317             # below, a switch statement, more or less, as described in perlfaq7
318              
319 378 100       637 $spec eq '%d' && do { return sprintf "%02d", $self->day };
  13         16  
320 365 100       539 $spec eq '%j' && do { return sprintf "%03d", 30 * $self->month + $self->day - 30 };
  2         4  
321 363 100       512 $spec eq '%e' && do { return sprintf "%2d", $self->day };
  66         89  
322 297 100       411 $spec eq '%m' && do { return sprintf "%02d", $self->month };
  2         4  
323 295 100       393 $spec eq '%f' && do { return sprintf "%2d", $self->month };
  2         4  
324 293 100       706 $spec =~ /\%[YGL]/ && do { return sprintf "%04d", $self->year };
  9         13  
325 284 100       437 $spec =~ /\%B/ && do { return $MONTHS[$self->month - 1] };
  75         128  
326 209 100       336 $spec =~ /\%[bh]/ && do { return $MONTHS_SHORT[$self->month - 1] };
  6         11  
327 203 100       270 $spec eq '%y' && do { return sprintf "%02d", $self->year % 100 };
  2         6  
328 201 100       260 $spec eq '%n' && do { return "\n" };
  1         6  
329 200 100       267 $spec eq '%t' && do { return "\t" };
  1         8  
330 199 100       270 $spec eq '%+' && do { return '+' };
  1         7  
331 198 100       254 $spec eq '%%' && do { return '%' };
  4         25  
332 194 100       267 $spec eq '%a' && do { return $DECADE_DAYS_SHORT[$decade_day] };
  4         37  
333 190 100       261 $spec eq '%A' && do { return $DECADE_DAYS[$decade_day] };
  11         90  
334 179 100 100     237 $spec eq '%w' && do { return sprintf("%2d", $decade_day || 10) };
  3         30  
335 176 100       246 $spec eq '%EY' && do { return $self->year < 4000 ? Roman($self->year) : $self->year };
  91 100       145  
336 85 100       111 $spec eq '%Ey' && do { return $self->year < 4000 ? roman($self->year) : $self->year };
  4 100       7  
337             ($spec eq '%Ej' || $spec eq '%*')
338             && do
339 81 100 100     258 {
340 11         22 my $jj = 30 * $self->month + $self->day - 31; # %j is 1..366, but $jj is 0..365
341 11         18 my $lb = $DAYS[$jj];
342 11         65 $lb =~ s/_/ /g;
343 11         61 $lb =~ s/^(\d)/$PREFIXES[$1]/;
344 11         80 return $lb;
345             };
346             $spec eq '%EJ' && do
347 70 100       94 {
348 30         44 my $jj = 30 * $self->month + $self->day - 31; # %j is 1..366, but $jj is 0..365
349 30         37 my $lb = $DAYS[$jj];
350 30         41 $lb =~ s/_/ /g;
351             # Using a capitalized prefix, and capitalizing the first letter
352 30         212 $lb =~ s/^(\d)(.)/\u$PREFIXES[$1]\u$2/;
353 30         266 return $lb;
354             };
355             $spec eq '%Oj' && do
356 40 100       48 {
357 4         8 my $jj = 30 * $self->month + $self->day - 31; # %j is 1..366, but $jj is 0..365
358 4         14 my $lb = substr $DAYS[$jj], 1;
359 4         9 $lb =~ s/_/ /g;
360 4         28 return $lb;
361             };
362 36         267 return $spec;
363             }
364              
365             sub date_string {
366 118     118 1 46378 my ($self, $format) = @_;
367              
368             # Default value when not provided. I do not test true / false, because
369             # some adventurous mind could think that "0" is a valid format, even if false.
370 118 100 100     467 $format = "%e %B %EY" if (! defined $format or $format eq '');
371              
372 118         198 my $year = $self->year; # possibly to trigger the side effect
373 118         186 my $month = $self->month;
374 118         615 $format =~ s/( # start of $1
375             \% # percent sign
376             (?: # start of alternative
377             (?:O.) # extended field specifier: O with a second char
378             | # or
379             (?:E.) # other extended field specifier: E with a second char
380             | # or
381             . # basic field specifier: single char
382             )) # end of alternative and end of $1
383 378         19031 /'$self->field($1)'/eegx; # is there a simpler way to do it?
384              
385 118         3451 return $format;
386             }
387              
388             # A module must return a true value. Traditionally, a module returns 1.
389             # But this module is a revolutionary one, so it discards all old traditions.
390             "Liberté, égalité, fraternité
391             ou la mort !";
392              
393             __END__