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

with javascript_semantics
constant gregorians = {"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},
         republicans = {"Vendémiaire", "Brumaire", "Frimaire", "Nivôse", "Pluviôse", 
                        "Ventôse", "Germinal", "Floréal", "Prairial", "Messidor", 
                        "Thermidor", "Fructidor"},
         sansculottides = {"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"}
 
function rep_leap(integer year)
    return mod(year+1,4)==0 and (mod(year+1,100)!=0 or mod(year+1,400)==0)
end function
 
function rep_to_day(sequence dmy)
    integer {d, m, y} = dmy
    if m == 13 then
        m -= 1
        d += 30
    end if
    if rep_leap(y) then
        d -= 1
    end if
    integer res = 365*(y-1) + floor((y+1)/4) - floor((y+1)/100) + floor((y+1)/400) + 30*(m-1) + d
    return res
end function
 
function gre_leap(integer year)
    return mod(year,4)==0 and (mod(year,100)!=0 or mod(year,400)==0)
end function
 
function gre_to_day(sequence dmy)
    integer {d, m, y} = dmy
    if m < 3 then
        y -= 1
        m += 12
    end if
    integer res =  floor(y*365.25) - floor(y/100) + floor(y/400)
                 + floor(30.6*(m+1)) + d - 654842
    return res
end function
 
function day_to_rep(integer day)
    integer y = floor((day-1)/365.25)
    if not rep_leap(y) then
        y += 1
    end if
    integer d = day - floor(y*365.25) + 365 + floor(y/100) - floor(y/400),
            m = 1,
            sansculottide = 5+rep_leap(y)
    while d>30 do
        d -= 30
        m += 1
        if m == 13 then
            if d > sansculottide then
                d -= sansculottide
                m = 1
                y += 1
                sansculottide = 5 + rep_leap(y)
            end if
        end if
    end while
    return {d,m,y}
end function
 
function day_to_gre(integer day)
    integer y = floor(day/365.25),
            d = day - floor(y*365.25) + 21,
            m = 9
    y += 1792
    d += floor(y/100) - floor(y/400) - 13
    sequence gregoriam = deep_copy(gregorian)  -- (modifiable copy)
    while d>gregoriam[m] do
        d -= gregoriam[m]
        m += 1
        if m == 13 then
            m = 1
            y += 1
            gregoriam[2] = 28 + gre_leap(y)
        end if
    end while
    return {d,m,y}
end function
 
function greg_to_frep(string greg)
    {integer day, string months, integer year} = scanf(greg,"%d %s %d")[1]
    integer month = find(months,gregorians)
    {day,month,year} = day_to_rep(gre_to_day({day,month,year}))
    string frep = iff(month=13?sprintf("%s %d",{sansculottides[day], year})
                              :sprintf("%d %s %d",{day, republicans[month], year}))
    return frep
end function    
 
function frep_to_greg(string frep)
    integer day, month, year
    string months, days
    if frep[1]<='9' then
        {day, months, year} = scanf(frep,"%d %s %d")[1]
        month = find(months,republicans)
    else
        {days, year} = scanf(frep,"%s %d")[1]
        day = find(days,sansculottides)
        month = 13
    end if
    {day,month,year} = day_to_gre(rep_to_day({day, month, year}))
    string greg = sprintf("%02d %s %d",{day, gregorians[month], year})
    return greg
end function
 
constant test_data = {
                      { "22 September 1792", "1 Vendémiaire 1" },
                      { "20 May 1795", "1 Prairial 3" },
                      { "15 July 1799", "27 Messidor 7" },
                      { "23 September 1803", "Fête de la Révolution 11" },
                      { "31 December 1805", "10 Nivôse 14" },
 
                      { "18 March 1871", "27 Ventôse 79" },
                      { "25 August 1944", "7 Fructidor 152" },
                      { "19 September 2016", "Fête du travail 224" },
 
                      { "06 May 1871", "16 Floréal 79" },       -- Paris Commune begins
                      { "23 May 1871", "3 Prairial 79" },       -- Paris Commune ends
                      { "09 November 1799", "18 Brumaire 8" },  -- Revolution ends by Napoléon coup
                      { "02 December 1804", "11 Frimaire 13" }, -- Republic ends by Napoléon coronation
                      { "30 October 1794", "9 Brumaire 3" },    -- École Normale Supérieure established
                      { "27 July 1794", "9 Thermidor 2" },      -- Robespierre falls
                      { "27 May 1799", "8 Prairial 7" },        -- Fromental Halévy born
 
                      { "22 September 1792", "1 Vendémiaire 1" },
                      { "22 September 1793", "1 Vendémiaire 2" },
                      { "22 September 1794", "1 Vendémiaire 3" },
                      { "23 September 1795", "1 Vendémiaire 4" },
                      { "22 September 1796", "1 Vendémiaire 5" },
                      { "22 September 1797", "1 Vendémiaire 6" },
                      { "22 September 1798", "1 Vendémiaire 7" },
                      { "23 September 1799", "1 Vendémiaire 8" },
                      { "23 September 1800", "1 Vendémiaire 9" },
                      { "23 September 1801", "1 Vendémiaire 10" },
                      { "23 September 1802", "1 Vendémiaire 11" },
                      { "24 September 1803", "1 Vendémiaire 12" },
                      { "23 September 1804", "1 Vendémiaire 13" },
                      { "23 September 1805", "1 Vendémiaire 14" },
                      { "23 September 1806", "1 Vendémiaire 15" },
                      { "24 September 1807", "1 Vendémiaire 16" },
                      { "23 September 1808", "1 Vendémiaire 17" },
                      { "23 September 1809", "1 Vendémiaire 18" },
                      { "23 September 1810", "1 Vendémiaire 19" },
                      { "24 September 1811", "1 Vendémiaire 20" },
                      { "23 September 2015", "1 Vendémiaire 224" },
                      { "21 September 2016", "Fête des récompenses 224" },
                      { "22 September 2016", "1 Vendémiaire 225" },
                      { "23 September 2016", "2 Vendémiaire 225" },
                      { "22 September 2017", "1 Vendémiaire 226" },
                      { "28 September 2017", "7 Vendémiaire 226" } }
 
for i=1 to length(test_data) do
    string {greg, frep} = test_data[i],
           frep2 = greg_to_frep(greg),
           greg2 = frep_to_greg(frep),
           ok = iff(frep=frep2 and greg=greg2?"ok":"**** ERROR ****")
    if platform()=WINDOWS then
        -- the windows console does not handle 
        -- non-basic-latin-ascii characters well...
        frep = substitute_all(frep,"éêéô","eeeo")
    end if
    printf(1,"%18s <==> %-25s %s\n",{greg,frep,ok})
end for
 
--sanity test:
for i=1 to 150000 do -- (years 1792..~2203)
    if rep_to_day(day_to_rep(i))!=i then ?9/0 end if
    if gre_to_day(day_to_gre(i))!=i then ?9/0 end if
end for
