French Republican calendar: Difference between revisions

From Rosetta Code
Content added Content deleted
(→‎{{header|Perl 6}}: Added Perl 6 solution)
(→‎{{header|Perl}}: Added Perl 5 solution)
Line 195: Line 195:
> 19 September 2016
> 19 September 2016
Fete du travail 224</pre>
Fete du travail 224</pre>

=={{header|Perl}}==
<lang perl>
use Modern::Perl;
use DateTime;
my @month_names = qw{
Vendémiaire Brumaire Frimaire Nivôse Pluviôse Ventôse
Germinal Floréal Prairial Messidor Thermidor Fructidor
};
my @intercalary = (
'Fête de la vertu', 'Fête du génie', 'Fête du travail',
"Fête de l'opinion", 'Fête des récompenses', 'Fête de la Révolution',
);
my %month_nums = map { $month_names[$_] => $_+1 } 0 .. $#month_names;
my %i_cal_nums = map { $intercalary[$_] => $_+1 } 0 .. $#intercalary;
my $i_cal_month = 13;
my $epoch = DateTime->new( year => 1792, month => 9, day => 22 );

sub is_republican_leap_year {
my $y = $_[0] + 1;
return !!( ($y % 4)==0 and (($y % 100)!=0 or ($y % 400)==0) );
}

sub Republican_to_Gregorian {
my ($rep_date) = @_;
state $months = join '|', map { quotemeta } @month_names;
state $intercal = join '|', map { quotemeta } @intercalary;
state $re = qr{
\A
\s* (?:
(?<ic> $intercal)
| (?<day> \d+) \s+ (?<month> $months)
)
\s+ (?<year> \d+)
\s*
\z
}msx;

$rep_date =~ /$re/
or die "Republican date not recognized: '$rep_date'";

my $day1 = $+{ic} ? $i_cal_nums{$+{ic}} : $+{day};
my $month1 = $+{month} ? $month_nums{$+{month}} : $i_cal_month;
my $year1 = $+{year};

my $days_since_epoch = ($year1-1) * 365 + ($month1-1) * 30 + ($day1-1);

my $leap_days = grep { is_republican_leap_year($_) } 1 .. $year1-1;
return $epoch->clone->add( days => ($days_since_epoch + $leap_days) );
}

sub Gregorian_to_Republican {
my ($greg_date) = @_;

my $days_since_epoch = $epoch->delta_days($greg_date)->in_units('days');
die if $days_since_epoch < 0;
my ( $year, $days ) = ( 1, $days_since_epoch );
while (1) {
my $year_length = 365 + ( is_republican_leap_year($year) ? 1 : 0 );
last if $days < $year_length;
$days -= $year_length;
$year += 1;
}
my $day0 = $days % 30;
my $month0 = ($days - $day0) / 30;

my ( $day1, $month1 ) = ( $day0 + 1, $month0 + 1 );

return $month1 == $i_cal_month
? "$intercalary[$day0 ] $year"
: "$day1 $month_names[$month0] $year";
}

while (<DATA>) {
s{\s*\#.+\n?\z}{};
/^(\d{4})-(\d{2})-(\d{2})\s+(\S.+?\S)\s*$/ or die;

my $g = DateTime->new( year => $1, month => $2, day => $3 );
my $r = $4;

die if Republican_to_Gregorian($r) != $g
or Gregorian_to_Republican($g) ne $r;

die if Gregorian_to_Republican(Republican_to_Gregorian($r)) ne $r
or Republican_to_Gregorian(Gregorian_to_Republican($g)) != $g;
}
say 'All tests successful.';

__DATA__
1792-09-22 1 Vendémiaire 1
1795-05-20 1 Prairial 3
1799-07-15 27 Messidor 7
1803-09-23 Fête de la Révolution 11
1805-12-31 10 Nivôse 14
1871-03-18 27 Ventôse 79
1944-08-25 7 Fructidor 152
2016-09-19 Fête du travail 224
1871-05-06 16 Floréal 79 # Paris Commune begins
1871-05-23 3 Prairial 79 # Paris Commune ends
1799-11-09 18 Brumaire 8 # Revolution ends by Napoléon coup
1804-12-02 11 Frimaire 13 # Republic ends by Napoléon coronation
1794-10-30 9 Brumaire 3 # École Normale Supérieure established
1794-07-27 9 Thermidor 2 # Robespierre falls
1799-05-27 8 Prairial 7 # Fromental Halévy born
1792-09-22 1 Vendémiaire 1
1793-09-22 1 Vendémiaire 2
1794-09-22 1 Vendémiaire 3
1795-09-23 1 Vendémiaire 4
1796-09-22 1 Vendémiaire 5
1797-09-22 1 Vendémiaire 6
1798-09-22 1 Vendémiaire 7
1799-09-23 1 Vendémiaire 8
1800-09-23 1 Vendémiaire 9
1801-09-23 1 Vendémiaire 10
1802-09-23 1 Vendémiaire 11
1803-09-24 1 Vendémiaire 12
1804-09-23 1 Vendémiaire 13
1805-09-23 1 Vendémiaire 14
1806-09-23 1 Vendémiaire 15
1807-09-24 1 Vendémiaire 16
1808-09-23 1 Vendémiaire 17
1809-09-23 1 Vendémiaire 18
1810-09-23 1 Vendémiaire 19
1811-09-24 1 Vendémiaire 20
2015-09-23 1 Vendémiaire 224
2016-09-22 1 Vendémiaire 225
2017-09-22 1 Vendémiaire 226
</lang>
{{Out}}
<pre>All tests successful.</pre>


=={{header|Perl 6}}==
=={{header|Perl 6}}==

Revision as of 05:35, 18 January 2017

French Republican calendar is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.

Write a program to convert dates between the Gregorian calendar and the French Republican calendar.

The year 1 of the Republican calendar began on 22 September 1792. There were twelve months (Vendémiaire, Brumaire, Frimaire, Nivôse, Pluviôse, Ventôse, Germinal, Floréal, Prairial, Messidor, Thermidor, and Fructidor) of 30 days each, followed by five intercalary days or Sansculottides (Fête de la vertu / Virtue Day, Fête du génie / Talent Day, Fête du travail / Labour Day, Fête de l'opinion / Opinion Day, and Fête des récompenses / Honours Day). In leap years (the years 3, 7, and 11) a sixth Sansculottide was added: Fête de la Révolution / Revolution Day.

As a minimum, your program should give correct results for dates in the range from 1 Vendémiaire 1 = 22 September 1792 to 10 Nivôse 14 = 31 December 1805 (the last day when the Republican calendar was officially in use). If you choose to accept later dates, be aware that there are several different methods (described on the Wikipedia page) about how to determine leap years after the year 14. You should indicate which method you are using. (Because of these different methods, correct programs may sometimes give different results for dates after 1805.)

Test your program by converting the following dates both from Gregorian to Republican and from Republican to Gregorian:

• 1 Vendémiaire 1 = 22 September 1792

• 1 Prairial 3 = 20 May 1795

• 27 Messidor 7 = 15 July 1799 (Rosetta Stone discovered)

• Fête de la Révolution 11 = 23 September 1803

• 10 Nivôse 14 = 31 December 1805


BBC BASIC

Computes leap years using the "continuous" method: a year in the Republican calendar is a leap year if and only if the number of the following year is divisible by 4 but not by 100 unless also by 400. No attempt is made to deal with ill-formed or invalid input dates. <lang bbcbasic>REM >frrepcal

DIM gregorian$(11) DIM gregorian%(11) DIM republican$(11) DIM sansculottides$(5) gregorian$() = "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" gregorian%() = 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 REM 7-bit ASCII encoding, so no accents on French words republican$() = "Vendemiaire", "Brumaire", "Frimaire", "Nivose", "Pluviose", "Ventose", "Germinal", "Floreal", "Prairial", "Messidor", "Thermidor", "Fructidor" sansculottides$() = "Fete de la vertu", "Fete du genie", "Fete du travail", "Fete de l'opinion", "Fete des recompenses", "Fete de la Revolution"

PRINT "*** French Republican ***" PRINT "*** calendar converter ***" PRINT "Enter a date to convert, in the format 'day month year'" PRINT "e.g.: 1 Prairial 3," PRINT " 20 May 1795." PRINT "For Sansculottides, use 'day year'" PRINT "e.g.: Fete de l'opinion 9." PRINT "Or just press 'RETURN' to exit the program." PRINT REPEAT

 INPUT LINE "> " src$
 IF src$ <> "" THEN
   PROC_split(src$, day%, month%, year%)
   REM for simplicity, we assume that years up to 1791 are Republican
   REM and years from 1792 onwards are Gregorian
   IF year% < 1792 THEN
     REM convert Republican date to number of days elapsed
     REM since 21 September 1792, then convert that number
     REM to the Gregorian date
     PROC_day_to_gre(FN_rep_to_day(day%, month%, year%), day%, month%, year%)
     PRINT; day%; " "; gregorian$(month% - 1); " " year%
   ELSE
     REM convert Gregorian date to Republican, via
     REM number of days elapsed since 21 September 1792
     PROC_day_to_rep(FN_gre_to_day(day%, month%, year%), day%, month%, year%)
     IF month% = 13 THEN
       PRINT sansculottides$(day% - 1); " "; year%
     ELSE
       PRINT; day%; " "; republican$(month% - 1); " "; year%
     ENDIF
   ENDIF
 ENDIF

UNTIL src$ = "" END

DEF PROC_split(s$, RETURN d%, RETURN m%, RETURN y%) LOCAL month_and_year$, month$, months$(), i% DIM months$(11) IF LEFT$(s$, 4) = "Fete" THEN

 m% = 13
 FOR i% = 0 TO 5
   IF LEFT$(s$, LEN sansculottides$(i%)) = sansculottides$(i%) THEN
     d% = i% + 1
     y% = VAL(RIGHT$(s$, LEN s$ - LEN sansculottides$(i%) - 1))
   ENDIF
 NEXT

ELSE

 d% = VAL(LEFT$(s$, INSTR(s$, " ") - 1))
 month_and_year$ = MID$(s$, INSTR(s$, " ") + 1)
 month$ = LEFT$(month_and_year$, INSTR(month_and_year$, " ") - 1)
 y% = VAL(MID$(month_and_year$, INSTR(month_and_year$, " ") + 1))
 IF y% < 1792 THEN months$() = republican$() ELSE months$() = gregorian$()
 FOR i% = 0 TO 11
   IF months$(i%) = month$ THEN m% = i% + 1
 NEXT

ENDIF ENDPROC

DEF FN_gre_to_day(d%, m%, y%) REM modified & repurposed from code given at REM https://www.staff.science.uu.nl/~gent0113/calendar/isocalendar_text5.htm IF m% < 3 THEN

 y% -= 1
 m% += 12

ENDIF = INT(365.25 * y%) - INT(y% / 100) + INT(y% / 400) + INT(30.6 * (m% + 1)) + d% - 654842

DEF FN_rep_to_day(d%, m%, y%) REM assume that a year is a leap year iff the _following_ year is REM divisible by 4, but not by 100 unless also by 400 REM REM other methods for computing Republican leap years exist IF m% = 13 THEN

 m% -= 1
 d% += 30

ENDIF IF FN_rep_leap(y%) THEN d% -= 1 = 365 * y% + INT((y% + 1) / 4) - INT((y% + 1) / 100) + INT((y% + 1) / 400) + 30 * m% + d% - 395

DEF PROC_day_to_gre(day%, RETURN d%, RETURN m%, RETURN y%) y% = INT(day% / 365.25) d% = day% - INT(365.25 * y%) + 21 y% += 1792 d% += INT(y% / 100) - INT(y% / 400) - 13 m% = 8 WHILE d% > gregorian%(m%)

 d% -= gregorian%(m%)
 m% += 1
 IF m% = 12 THEN
   m% = 0
   y% += 1
   IF FN_gre_leap(y%) THEN gregorian%(1) = 29 ELSE gregorian%(1) = 28
 ENDIF

ENDWHILE m% += 1 ENDPROC

DEF PROC_day_to_rep(day%, RETURN d%, RETURN m%, RETURN y%) LOCAL sansculottides% y% = INT(day% / 365.25) IF FN_rep_leap(y%) THEN y% -= 1 d% = day% - INT(365.25 * y%) + INT((y% + 1) / 100) - INT((y% + 1) / 400) y% += 1 m% = 1 IF FN_rep_leap(y%) THEN sansculottides% = 6 ELSE sansculottides% = 5 WHILE d% > 30

 d% -= 30
 m% += 1
 IF m% = 13 THEN
   IF d% > sansculottides% THEN
     d% -= sansculottides%
     m% = 1
     y% += 1
   ENDIF
 ENDIF

ENDWHILE ENDPROC

DEF FN_rep_leap(year%) REM see comment at the beginning of FN_rep_to_day = ((year% + 1) MOD 4 = 0 AND ((year% + 1) MOD 100 <> 0 OR (year% + 1) MOD 400 = 0))

DEF FN_gre_leap(year%) = (year% MOD 4 = 0 AND (year% MOD 100 <> 0 OR year% MOD 400 = 0))</lang> Output for the test dates:

*** French  Republican ***
*** calendar converter ***
Enter a date to convert, in the format 'day month year'
e.g.: 1 Prairial 3,
      20 May 1795.
For Sansculottides, use 'day year'
e.g.: Fete de l'opinion 9.
Or just press 'RETURN' to exit the program.

> 1 Vendemiaire 1
22 September 1792
> 22 September 1792
1 Vendemiaire 1
> 1 Prairial 3
20 May 1795
> 20 May 1795
1 Prairial 3
> 27 Messidor 7
15 July 1799
> 15 July 1799
27 Messidor 7
> Fete de la Revolution 11
23 September 1803
> 23 September 1803
Fete de la Revolution 11
> 10 Nivose 14
31 December 1805
> 31 December 1805
10 Nivose 14

Output for a few subsequent dates:

> 18 March 1871
27 Ventose 79
> 25 August 1944
7 Fructidor 152
> 19 September 2016
Fete du travail 224

Perl

<lang perl> use Modern::Perl; use DateTime; my @month_names = qw{

   Vendémiaire Brumaire Frimaire  Nivôse   Pluviôse  Ventôse
   Germinal    Floréal  Prairial  Messidor Thermidor Fructidor

}; my @intercalary = (

   'Fête de la vertu',  'Fête du génie',        'Fête du travail',
   "Fête de l'opinion", 'Fête des récompenses', 'Fête de la Révolution',

); my %month_nums = map { $month_names[$_] => $_+1 } 0 .. $#month_names; my %i_cal_nums = map { $intercalary[$_] => $_+1 } 0 .. $#intercalary; my $i_cal_month = 13; my $epoch = DateTime->new( year => 1792, month => 9, day => 22 );

sub is_republican_leap_year {

   my $y = $_[0] + 1;
   return !!( ($y % 4)==0 and (($y % 100)!=0 or ($y % 400)==0) );

}

sub Republican_to_Gregorian {

   my ($rep_date) = @_;
   state $months   = join '|', map { quotemeta } @month_names;
   state $intercal = join '|', map { quotemeta } @intercalary;
   state $re = qr{
     \A
       \s* (?:
               (?<ic> $intercal)
             | (?<day> \d+) \s+ (?<month> $months)
           )
       \s+ (?<year> \d+)
       \s*
     \z
   }msx;
   $rep_date =~ /$re/
       or die "Republican date not recognized: '$rep_date'";
   my $day1   = $+{ic}    ? $i_cal_nums{$+{ic}}    : $+{day};
   my $month1 = $+{month} ? $month_nums{$+{month}} : $i_cal_month;
   my $year1  = $+{year};
   my $days_since_epoch = ($year1-1) * 365 + ($month1-1) * 30 + ($day1-1);
   my $leap_days = grep { is_republican_leap_year($_) } 1 .. $year1-1;
   return $epoch->clone->add( days => ($days_since_epoch + $leap_days) );

}

sub Gregorian_to_Republican {

   my ($greg_date) = @_;
   my $days_since_epoch = $epoch->delta_days($greg_date)->in_units('days');
   die if $days_since_epoch < 0;
   my ( $year, $days ) = ( 1, $days_since_epoch );
   while (1) {
       my $year_length = 365 + ( is_republican_leap_year($year) ? 1 : 0 );
       last if $days < $year_length;
       $days -= $year_length;
       $year += 1;
   }
   my $day0   = $days % 30;
   my $month0 = ($days - $day0) / 30;
   my ( $day1, $month1 ) = ( $day0 + 1, $month0 + 1 );
   return $month1 == $i_cal_month
       ?       "$intercalary[$day0  ] $year"
       : "$day1 $month_names[$month0] $year";

}

while () {

   s{\s*\#.+\n?\z}{};
   /^(\d{4})-(\d{2})-(\d{2})\s+(\S.+?\S)\s*$/ or die;
   my $g = DateTime->new( year => $1, month => $2, day => $3 );
   my $r = $4;
   die if Republican_to_Gregorian($r) != $g
       or Gregorian_to_Republican($g) ne $r;
   die if Gregorian_to_Republican(Republican_to_Gregorian($r)) ne $r
       or Republican_to_Gregorian(Gregorian_to_Republican($g)) != $g;

} say 'All tests successful.';

__DATA__ 1792-09-22 1 Vendémiaire 1 1795-05-20 1 Prairial 3 1799-07-15 27 Messidor 7 1803-09-23 Fête de la Révolution 11 1805-12-31 10 Nivôse 14 1871-03-18 27 Ventôse 79 1944-08-25 7 Fructidor 152 2016-09-19 Fête du travail 224 1871-05-06 16 Floréal 79 # Paris Commune begins 1871-05-23 3 Prairial 79 # Paris Commune ends 1799-11-09 18 Brumaire 8 # Revolution ends by Napoléon coup 1804-12-02 11 Frimaire 13 # Republic ends by Napoléon coronation 1794-10-30 9 Brumaire 3 # École Normale Supérieure established 1794-07-27 9 Thermidor 2 # Robespierre falls 1799-05-27 8 Prairial 7 # Fromental Halévy born 1792-09-22 1 Vendémiaire 1 1793-09-22 1 Vendémiaire 2 1794-09-22 1 Vendémiaire 3 1795-09-23 1 Vendémiaire 4 1796-09-22 1 Vendémiaire 5 1797-09-22 1 Vendémiaire 6 1798-09-22 1 Vendémiaire 7 1799-09-23 1 Vendémiaire 8 1800-09-23 1 Vendémiaire 9 1801-09-23 1 Vendémiaire 10 1802-09-23 1 Vendémiaire 11 1803-09-24 1 Vendémiaire 12 1804-09-23 1 Vendémiaire 13 1805-09-23 1 Vendémiaire 14 1806-09-23 1 Vendémiaire 15 1807-09-24 1 Vendémiaire 16 1808-09-23 1 Vendémiaire 17 1809-09-23 1 Vendémiaire 18 1810-09-23 1 Vendémiaire 19 1811-09-24 1 Vendémiaire 20 2015-09-23 1 Vendémiaire 224 2016-09-22 1 Vendémiaire 225 2017-09-22 1 Vendémiaire 226 </lang>

Output:
All tests successful.

Perl 6

<lang perl6>use v6; constant @month_names = <

   Vendémiaire Brumaire Frimaire  Nivôse   Pluviôse  Ventôse
   Germinal    Floréal  Prairial  Messidor Thermidor Fructidor

>; constant @intercalary =

   'Fête de la vertu',  'Fête du génie',        'Fête du travail',
   "Fête de l'opinion", 'Fête des récompenses', 'Fête de la Révolution',

constant %month_nums = %( @month_names Z=> 1..12 ); constant %i_cal_nums = %( @intercalary Z=> 1.. 6 ); constant $i_cal_month = 13; constant $epoch = Date.new: '1792-09-22';

sub is_republican_leap_year ( Int:D $year --> Bool ) {

   my $y := $year + 1;
   return ?( $y %% 4 and ($y !%% 100 or $y %% 400) );

}

sub Republican_to_Gregorian ( Str:D $rep_date --> Date ) {

   grammar Republican_date_text {
       token day   { \d+ }
       token year  { \d+ }
       token ic    { @intercalary }
       token month { @month_names }
       rule TOP    { ^ [ <ic> | <day> <month> ] <year> $ }
   }
   
   Republican_date_text.parse($rep_date)
       orelse die "Republican date not recognized: '$rep_date'";
   my $day1   := $/<ic>    ?? %i_cal_nums{~$/<ic>}    !! +$/<day>;
   my $month1 := $/<month> ?? %month_nums{~$/<month>} !! $i_cal_month;
   my @ymd0 := ($/<year>, $month1, $day1) »-» 1;
   my $days_since_epoch := [+] @ymd0 Z* (365, 30, 1);
   my $leap_days := +grep &is_republican_leap_year, 1 ..^ $/<year>;
   return $epoch + $days_since_epoch + $leap_days;

}

sub Gregorian_to_Republican ( Date:D $greg_date --> Str ) {

   my $days_since_epoch := $greg_date - $epoch;
   die if $days_since_epoch < 0;
   my ( $year, $days ) = 1, $days_since_epoch;
   loop {
       my $year_length = 365 + (1 if $year.&is_republican_leap_year);
       last if $days < $year_length;
       $days -= $year_length;
       $year += 1;
   }
   my ( $day0, $month0 ) = $days.polymod( 30 );
   my ( $day1, $month1 ) = ($day0, $month0) X+ 1;
   return $month1 == $i_cal_month
       ??       "@intercalary[$day0  ] $year"
       !! "$day1 @month_names[$month0] $year";

}

my @test_data =

   ( '1792-09-22', '1 Vendémiaire 1' ),
   ( '1795-05-20', '1 Prairial 3' ),
   ( '1799-07-15', '27 Messidor 7' ),
   ( '1803-09-23', 'Fête de la Révolution 11' ),
   ( '1805-12-31', '10 Nivôse 14' ),
   ( '1871-03-18', '27 Ventôse 79' ),
   ( '1944-08-25', '7 Fructidor 152' ),
   ( '2016-09-19', 'Fête du travail 224' ),
   ( '1871-05-06', '16 Floréal 79'  ), # Paris Commune begins
   ( '1871-05-23', '3 Prairial 79'  ), # Paris Commune ends
   ( '1799-11-09', '18 Brumaire 8'  ), # Revolution ends by Napoléon coup
   ( '1804-12-02', '11 Frimaire 13' ), # Republic   ends by Napoléon coronation
   ( '1794-10-30', '9 Brumaire 3'   ), # École Normale Supérieure established
   ( '1794-07-27', '9 Thermidor 2'  ), # Robespierre falls
   ( '1799-05-27', '8 Prairial 7'   ), # Fromental Halévy born
   ( '1792-09-22', '1 Vendémiaire 1'   ),
   ( '1793-09-22', '1 Vendémiaire 2'   ),
   ( '1794-09-22', '1 Vendémiaire 3'   ),
   ( '1795-09-23', '1 Vendémiaire 4'   ),
   ( '1796-09-22', '1 Vendémiaire 5'   ),
   ( '1797-09-22', '1 Vendémiaire 6'   ),
   ( '1798-09-22', '1 Vendémiaire 7'   ),
   ( '1799-09-23', '1 Vendémiaire 8'   ),
   ( '1800-09-23', '1 Vendémiaire 9'   ),
   ( '1801-09-23', '1 Vendémiaire 10'  ),
   ( '1802-09-23', '1 Vendémiaire 11'  ),
   ( '1803-09-24', '1 Vendémiaire 12'  ),
   ( '1804-09-23', '1 Vendémiaire 13'  ),
   ( '1805-09-23', '1 Vendémiaire 14'  ),
   ( '1806-09-23', '1 Vendémiaire 15'  ),
   ( '1807-09-24', '1 Vendémiaire 16'  ),
   ( '1808-09-23', '1 Vendémiaire 17'  ),
   ( '1809-09-23', '1 Vendémiaire 18'  ),
   ( '1810-09-23', '1 Vendémiaire 19'  ),
   ( '1811-09-24', '1 Vendémiaire 20'  ),
   ( '2015-09-23', '1 Vendémiaire 224' ),
   ( '2016-09-22', '1 Vendémiaire 225' ),
   ( '2017-09-22', '1 Vendémiaire 226' ),

for @test_data -> ( $g_text, $r ) {

   my $g = Date.new: $g_text;
   die if Republican_to_Gregorian($r) != $g
       or Gregorian_to_Republican($g) ne $r;
   die if Gregorian_to_Republican(Republican_to_Gregorian($r)) ne $r
       or Republican_to_Gregorian(Gregorian_to_Republican($g)) != $g;

} say 'All tests successful.';</lang>

Output:
All tests successful.