french_republican_calendar.sf 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. #!/usr/bin/ruby
  2. #
  3. ## https://rosettacode.org/wiki/French_Republican_calendar
  4. #
  5. require('DateTime')
  6. var month_names = %w(
  7. Vendémiaire Brumaire Frimaire Nivôse Pluviôse Ventôse
  8. Germinal Floréal Prairial Messidor Thermidor Fructidor
  9. )
  10. var intercalary = [
  11. 'Fête de la vertu',
  12. 'Fête du génie',
  13. 'Fête du travail',
  14. "Fête de l'opinion",
  15. 'Fête des récompenses',
  16. 'Fête de la Révolution',
  17. ]
  18. var i_cal_month = 13
  19. var epoch = %O<DateTime>.new(year => 1792, month => 9, day => 22)
  20. var month_nums = Hash(month_names.kv.map {|p| [p[1], p[0]+1] }.flat...)
  21. var i_cal_nums = Hash(intercalary.kv.map {|p| [p[1], p[0]+1] }.flat...)
  22. func is_republican_leap_year(Number year) -> Bool {
  23. var y = (year + 1)
  24. !!(4.divides(y) && (!100.divides(y) || 400.divides(y)))
  25. }
  26. func Republican_to_Gregorian(String rep_date) -> String {
  27. static months = month_names.map { .escape }.join('|')
  28. static intercal = intercalary.map { .escape }.join('|')
  29. static re = Regex("^
  30. \\s* (?:
  31. (?<ic> #{intercal})
  32. | (?<day> \\d+) \\s+ (?<month> #{months})
  33. )
  34. \\s+ (?<year> \\d+)
  35. \\s*
  36. \\z", 'x')
  37. var m = (rep_date =~ re)
  38. m || die "Republican date not recognized: '#{rep_date}'"
  39. var ncap = m.named_captures
  40. var day1 = Number(ncap{:ic} ? i_cal_nums{ncap{:ic}} : ncap{:day})
  41. var month1 = Number(ncap{:month} ? month_nums{ncap{:month}} : i_cal_month)
  42. var year1 = Number(ncap{:year})
  43. var days_since_epoch = (365*(year1 - 1) + 30*(month1 - 1) + (day1 - 1))
  44. var leap_days = (1 ..^ year1 -> grep { is_republican_leap_year(_) })
  45. epoch.clone.add(days => (days_since_epoch + leap_days)).strftime("%Y-%m-%d")
  46. }
  47. func Gregorian_to_Republican(String greg_date) -> String {
  48. var m = (greg_date =~ /^(\d{4})-(\d{2})-(\d{2})\z/)
  49. m || die "Gregorian date not recognized: '#{greg_date}'"
  50. var g = %O<DateTime>.new(year => m[0], month => m[1], day => m[2])
  51. var days_since_epoch = epoch.delta_days(g).in_units('days')
  52. days_since_epoch < 0 && die "unexpected error"
  53. var (year, days) = (1, days_since_epoch)
  54. loop {
  55. var year_length = (365 + (is_republican_leap_year(year) ? 1 : 0))
  56. days < year_length && break
  57. days -= year_length
  58. year += 1;
  59. }
  60. var day0 = (days % 30)
  61. var month0 = (days - day0)/30
  62. var (day1, month1) = (day0 + 1, month0 + 1)
  63. (month1 == i_cal_month
  64. ? "#{intercalary[day0]} #{year}"
  65. : "#{day1} #{month_names[month0]} #{year}")
  66. }
  67. for line in DATA {
  68. line.sub!(/\s*\#.+\R?\z/, '')
  69. var m = (line =~ /^(\d{4})-(\d{2})-(\d{2})\s+(\S.+?\S)\s*$/)
  70. m || die "error for: #{line.dump}"
  71. var g = "#{m[0]}-#{m[1]}-#{m[2]}"
  72. var r = m[3]
  73. var r2g = Republican_to_Gregorian(r)
  74. var g2r = Gregorian_to_Republican(g)
  75. #say "#{r} -> #{r2g}"
  76. #say "#{g} -> #{g2r}"
  77. if ((g2r != r) || (r2g != g)) {
  78. die "1-way error"
  79. }
  80. if ((Gregorian_to_Republican(r2g) != r) ||
  81. (Republican_to_Gregorian(g2r) != g)
  82. ) {
  83. die "2-way error"
  84. }
  85. }
  86. say 'All tests successful.'
  87. __DATA__
  88. 1792-09-22 1 Vendémiaire 1
  89. 1795-05-20 1 Prairial 3
  90. 1799-07-15 27 Messidor 7
  91. 1803-09-23 Fête de la Révolution 11
  92. 1805-12-31 10 Nivôse 14
  93. 1871-03-18 27 Ventôse 79
  94. 1944-08-25 7 Fructidor 152
  95. 2016-09-19 Fête du travail 224
  96. 1871-05-06 16 Floréal 79 # Paris Commune begins
  97. 1871-05-23 3 Prairial 79 # Paris Commune ends
  98. 1799-11-09 18 Brumaire 8 # Revolution ends by Napoléon coup
  99. 1804-12-02 11 Frimaire 13 # Republic ends by Napoléon coronation
  100. 1794-10-30 9 Brumaire 3 # École Normale Supérieure established
  101. 1794-07-27 9 Thermidor 2 # Robespierre falls
  102. 1799-05-27 8 Prairial 7 # Fromental Halévy born
  103. 1792-09-22 1 Vendémiaire 1
  104. 1793-09-22 1 Vendémiaire 2
  105. 1794-09-22 1 Vendémiaire 3
  106. 1795-09-23 1 Vendémiaire 4
  107. 1796-09-22 1 Vendémiaire 5
  108. 1797-09-22 1 Vendémiaire 6
  109. 1798-09-22 1 Vendémiaire 7
  110. 1799-09-23 1 Vendémiaire 8
  111. 1800-09-23 1 Vendémiaire 9
  112. 1801-09-23 1 Vendémiaire 10
  113. 1802-09-23 1 Vendémiaire 11
  114. 1803-09-24 1 Vendémiaire 12
  115. 1804-09-23 1 Vendémiaire 13
  116. 1805-09-23 1 Vendémiaire 14
  117. 1806-09-23 1 Vendémiaire 15
  118. 1807-09-24 1 Vendémiaire 16
  119. 1808-09-23 1 Vendémiaire 17
  120. 1809-09-23 1 Vendémiaire 18
  121. 1810-09-23 1 Vendémiaire 19
  122. 1811-09-24 1 Vendémiaire 20
  123. 2015-09-23 1 Vendémiaire 224
  124. 2016-09-22 1 Vendémiaire 225
  125. 2017-09-22 1 Vendémiaire 226