revised_holidays 9.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (382) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/ruby.yml +25 -0
  3. data/CHANGELOG.md +446 -0
  4. data/CODE_OF_CONDUCT.md +74 -0
  5. data/Gemfile +3 -0
  6. data/LICENSE +21 -0
  7. data/Makefile +45 -0
  8. data/README.md +337 -0
  9. data/Rakefile +109 -0
  10. data/bin/console +7 -0
  11. data/bin/setup +6 -0
  12. data/definitions/.github/workflows/ruby.yml +25 -0
  13. data/definitions/.gitignore +5 -0
  14. data/definitions/CHANGELOG.md +352 -0
  15. data/definitions/Gemfile +6 -0
  16. data/definitions/LICENSE +21 -0
  17. data/definitions/METHODS.yml +26 -0
  18. data/definitions/Makefile +9 -0
  19. data/definitions/README.md +22 -0
  20. data/definitions/ar.yaml +268 -0
  21. data/definitions/at.yaml +116 -0
  22. data/definitions/au.yaml +907 -0
  23. data/definitions/be_fr.yaml +153 -0
  24. data/definitions/be_nl.yaml +153 -0
  25. data/definitions/bg.yaml +186 -0
  26. data/definitions/br.yaml +178 -0
  27. data/definitions/ca.yaml +969 -0
  28. data/definitions/ch.yaml +277 -0
  29. data/definitions/cl.yaml +294 -0
  30. data/definitions/co.yaml +437 -0
  31. data/definitions/cr.yaml +100 -0
  32. data/definitions/cz.yaml +137 -0
  33. data/definitions/de.yaml +401 -0
  34. data/definitions/dk.yaml +220 -0
  35. data/definitions/doc/CONTRIBUTING.md +44 -0
  36. data/definitions/doc/MAINTAINERS.md +39 -0
  37. data/definitions/doc/SYNTAX.md +435 -0
  38. data/definitions/doc/architecture/README.md +15 -0
  39. data/definitions/doc/architecture/adr-001.md +86 -0
  40. data/definitions/doc/architecture/adr-002.md +64 -0
  41. data/definitions/ecbtarget.yaml +74 -0
  42. data/definitions/ee.yaml +123 -0
  43. data/definitions/es.yaml +495 -0
  44. data/definitions/federalreserve.yaml +389 -0
  45. data/definitions/federalreservebanks.yaml +821 -0
  46. data/definitions/fedex.yaml +102 -0
  47. data/definitions/fi.yaml +234 -0
  48. data/definitions/fr.yaml +157 -0
  49. data/definitions/gb.yaml +565 -0
  50. data/definitions/ge.yaml +158 -0
  51. data/definitions/gr.yaml +158 -0
  52. data/definitions/hk.yaml +287 -0
  53. data/definitions/hr.yaml +171 -0
  54. data/definitions/hu.yaml +156 -0
  55. data/definitions/ie.yaml +172 -0
  56. data/definitions/index.yaml +80 -0
  57. data/definitions/is.yaml +247 -0
  58. data/definitions/it.yaml +246 -0
  59. data/definitions/jp.yaml +761 -0
  60. data/definitions/ke.yaml +107 -0
  61. data/definitions/kr.yaml +166 -0
  62. data/definitions/kz.yaml +128 -0
  63. data/definitions/li.yaml +154 -0
  64. data/definitions/lib/validation/custom_method_validator.rb +38 -0
  65. data/definitions/lib/validation/definition_validator.rb +35 -0
  66. data/definitions/lib/validation/error.rb +11 -0
  67. data/definitions/lib/validation/month_validator.rb +58 -0
  68. data/definitions/lib/validation/run.rb +66 -0
  69. data/definitions/lib/validation/test_validator.rb +83 -0
  70. data/definitions/lt.yaml +198 -0
  71. data/definitions/lu.yaml +123 -0
  72. data/definitions/lv.yaml +229 -0
  73. data/definitions/ma.yaml +96 -0
  74. data/definitions/mt_en.yaml +131 -0
  75. data/definitions/mt_mt.yaml +131 -0
  76. data/definitions/mx.yaml +160 -0
  77. data/definitions/my.yaml +79 -0
  78. data/definitions/nerc.yaml +94 -0
  79. data/definitions/ng.yaml +97 -0
  80. data/definitions/nl.yaml +127 -0
  81. data/definitions/no.yaml +169 -0
  82. data/definitions/northamericainformal.yaml +105 -0
  83. data/definitions/nyse.yaml +137 -0
  84. data/definitions/nz.yaml +346 -0
  85. data/definitions/pe.yaml +208 -0
  86. data/definitions/ph.yaml +130 -0
  87. data/definitions/pl.yaml +796 -0
  88. data/definitions/pt.yaml +187 -0
  89. data/definitions/ro.yaml +240 -0
  90. data/definitions/rs_cyrl.yaml +129 -0
  91. data/definitions/rs_la.yaml +129 -0
  92. data/definitions/ru.yaml +108 -0
  93. data/definitions/se.yaml +238 -0
  94. data/definitions/sg.yaml +89 -0
  95. data/definitions/si.yaml +162 -0
  96. data/definitions/sk.yaml +154 -0
  97. data/definitions/spec/coverage_report.rb +7 -0
  98. data/definitions/spec/data/invalid/months/malformed/bad.yaml +15 -0
  99. data/definitions/spec/data/invalid/months/missing/no_months.yaml +9 -0
  100. data/definitions/spec/data/valid/simple.yaml +15 -0
  101. data/definitions/spec/spec_helper.rb +7 -0
  102. data/definitions/spec/validation/custom_method_validator_spec.rb +60 -0
  103. data/definitions/spec/validation/definition_validator_spec.rb +43 -0
  104. data/definitions/spec/validation/month_validator_spec.rb +175 -0
  105. data/definitions/spec/validation/test_validator_spec.rb +169 -0
  106. data/definitions/th.yaml +111 -0
  107. data/definitions/tn.yaml +83 -0
  108. data/definitions/tr.yaml +174 -0
  109. data/definitions/ua.yaml +161 -0
  110. data/definitions/unitednations.yaml +189 -0
  111. data/definitions/ups.yaml +102 -0
  112. data/definitions/us.yaml +965 -0
  113. data/definitions/ve.yaml +118 -0
  114. data/definitions/vi.yaml +54 -0
  115. data/definitions/za.yaml +139 -0
  116. data/doc/CONTRIBUTING.md +72 -0
  117. data/doc/MAINTAINERS.md +81 -0
  118. data/doc/REFERENCES +19 -0
  119. data/lib/generated_definitions/MANIFEST +83 -0
  120. data/lib/generated_definitions/REGIONS.rb +6 -0
  121. data/lib/generated_definitions/ar.rb +60 -0
  122. data/lib/generated_definitions/at.rb +37 -0
  123. data/lib/generated_definitions/au.rb +177 -0
  124. data/lib/generated_definitions/be.rb +42 -0
  125. data/lib/generated_definitions/be_fr.rb +36 -0
  126. data/lib/generated_definitions/be_nl.rb +36 -0
  127. data/lib/generated_definitions/bg.rb +53 -0
  128. data/lib/generated_definitions/br.rb +37 -0
  129. data/lib/generated_definitions/ca.rb +88 -0
  130. data/lib/generated_definitions/ch.rb +95 -0
  131. data/lib/generated_definitions/cl.rb +71 -0
  132. data/lib/generated_definitions/co.rb +121 -0
  133. data/lib/generated_definitions/cr.rb +35 -0
  134. data/lib/generated_definitions/cz.rb +37 -0
  135. data/lib/generated_definitions/de.rb +65 -0
  136. data/lib/generated_definitions/dk.rb +48 -0
  137. data/lib/generated_definitions/ecbtarget.rb +30 -0
  138. data/lib/generated_definitions/ee.rb +36 -0
  139. data/lib/generated_definitions/el.rb +38 -0
  140. data/lib/generated_definitions/es.rb +56 -0
  141. data/lib/generated_definitions/europe.rb +638 -0
  142. data/lib/generated_definitions/federalreserve.rb +35 -0
  143. data/lib/generated_definitions/federalreservebanks.rb +35 -0
  144. data/lib/generated_definitions/fedex.rb +36 -0
  145. data/lib/generated_definitions/fi.rb +61 -0
  146. data/lib/generated_definitions/fr.rb +39 -0
  147. data/lib/generated_definitions/gb.rb +51 -0
  148. data/lib/generated_definitions/ge.rb +41 -0
  149. data/lib/generated_definitions/gr.rb +38 -0
  150. data/lib/generated_definitions/hk.rb +106 -0
  151. data/lib/generated_definitions/hr.rb +40 -0
  152. data/lib/generated_definitions/hu.rb +35 -0
  153. data/lib/generated_definitions/ie.rb +33 -0
  154. data/lib/generated_definitions/is.rb +60 -0
  155. data/lib/generated_definitions/it.rb +45 -0
  156. data/lib/generated_definitions/jp.rb +166 -0
  157. data/lib/generated_definitions/ke.rb +34 -0
  158. data/lib/generated_definitions/kr.rb +40 -0
  159. data/lib/generated_definitions/kz.rb +38 -0
  160. data/lib/generated_definitions/li.rb +44 -0
  161. data/lib/generated_definitions/lt.rb +38 -0
  162. data/lib/generated_definitions/lu.rb +35 -0
  163. data/lib/generated_definitions/lv.rb +56 -0
  164. data/lib/generated_definitions/ma.rb +33 -0
  165. data/lib/generated_definitions/mt_en.rb +38 -0
  166. data/lib/generated_definitions/mt_mt.rb +38 -0
  167. data/lib/generated_definitions/mx.rb +54 -0
  168. data/lib/generated_definitions/my.rb +30 -0
  169. data/lib/generated_definitions/nerc.rb +30 -0
  170. data/lib/generated_definitions/ng.rb +33 -0
  171. data/lib/generated_definitions/nl.rb +37 -0
  172. data/lib/generated_definitions/no.rb +40 -0
  173. data/lib/generated_definitions/northamerica.rb +229 -0
  174. data/lib/generated_definitions/nyse.rb +34 -0
  175. data/lib/generated_definitions/nz.rb +105 -0
  176. data/lib/generated_definitions/pe.rb +43 -0
  177. data/lib/generated_definitions/ph.rb +50 -0
  178. data/lib/generated_definitions/pl.rb +73 -0
  179. data/lib/generated_definitions/pt.rb +40 -0
  180. data/lib/generated_definitions/ro.rb +39 -0
  181. data/lib/generated_definitions/rs_cyrl.rb +39 -0
  182. data/lib/generated_definitions/rs_la.rb +39 -0
  183. data/lib/generated_definitions/ru.rb +37 -0
  184. data/lib/generated_definitions/scandinavia.rb +166 -0
  185. data/lib/generated_definitions/se.rb +53 -0
  186. data/lib/generated_definitions/sg.rb +31 -0
  187. data/lib/generated_definitions/si.rb +39 -0
  188. data/lib/generated_definitions/sk.rb +39 -0
  189. data/lib/generated_definitions/southamerica.rb +248 -0
  190. data/lib/generated_definitions/th.rb +36 -0
  191. data/lib/generated_definitions/tn.rb +32 -0
  192. data/lib/generated_definitions/tr.rb +64 -0
  193. data/lib/generated_definitions/ua.rb +37 -0
  194. data/lib/generated_definitions/unitednations.rb +81 -0
  195. data/lib/generated_definitions/ups.rb +36 -0
  196. data/lib/generated_definitions/us.rb +153 -0
  197. data/lib/generated_definitions/ve.rb +36 -0
  198. data/lib/generated_definitions/vi.rb +29 -0
  199. data/lib/generated_definitions/za.rb +36 -0
  200. data/lib/holidays/core_extensions/date.rb +57 -0
  201. data/lib/holidays/core_extensions/time.rb +23 -0
  202. data/lib/holidays/date_calculator/day_of_month.rb +68 -0
  203. data/lib/holidays/date_calculator/easter.rb +91 -0
  204. data/lib/holidays/date_calculator/lunar_date.rb +371 -0
  205. data/lib/holidays/date_calculator/weekend_modifier.rb +80 -0
  206. data/lib/holidays/definition/context/function_processor.rb +91 -0
  207. data/lib/holidays/definition/context/generator.rb +209 -0
  208. data/lib/holidays/definition/context/load.rb +29 -0
  209. data/lib/holidays/definition/context/merger.rb +22 -0
  210. data/lib/holidays/definition/decorator/custom_method_proc.rb +28 -0
  211. data/lib/holidays/definition/decorator/custom_method_source.rb +30 -0
  212. data/lib/holidays/definition/decorator/test.rb +37 -0
  213. data/lib/holidays/definition/entity/custom_method.rb +11 -0
  214. data/lib/holidays/definition/entity/test.rb +11 -0
  215. data/lib/holidays/definition/generator/module.rb +54 -0
  216. data/lib/holidays/definition/generator/regions.rb +55 -0
  217. data/lib/holidays/definition/generator/test.rb +51 -0
  218. data/lib/holidays/definition/parser/custom_method.rb +67 -0
  219. data/lib/holidays/definition/parser/test.rb +86 -0
  220. data/lib/holidays/definition/repository/cache.rb +47 -0
  221. data/lib/holidays/definition/repository/custom_methods.rb +27 -0
  222. data/lib/holidays/definition/repository/holidays_by_month.rb +57 -0
  223. data/lib/holidays/definition/repository/proc_result_cache.rb +51 -0
  224. data/lib/holidays/definition/repository/regions.rb +46 -0
  225. data/lib/holidays/definition/validator/custom_method.rb +31 -0
  226. data/lib/holidays/definition/validator/region.rb +36 -0
  227. data/lib/holidays/definition/validator/test.rb +71 -0
  228. data/lib/holidays/errors.rb +11 -0
  229. data/lib/holidays/factory/date_calculator.rb +42 -0
  230. data/lib/holidays/factory/definition.rb +143 -0
  231. data/lib/holidays/factory/finder.rb +70 -0
  232. data/lib/holidays/finder/context/between.rb +45 -0
  233. data/lib/holidays/finder/context/dates_driver_builder.rb +64 -0
  234. data/lib/holidays/finder/context/next_holiday.rb +57 -0
  235. data/lib/holidays/finder/context/parse_options.rb +104 -0
  236. data/lib/holidays/finder/context/search.rb +111 -0
  237. data/lib/holidays/finder/context/year_holiday.rb +57 -0
  238. data/lib/holidays/finder/rules/in_region.rb +31 -0
  239. data/lib/holidays/finder/rules/year_range.rb +58 -0
  240. data/lib/holidays/load_all_definitions.rb +56 -0
  241. data/lib/holidays/version.rb +3 -0
  242. data/lib/holidays.rb +130 -0
  243. data/lib/revised_holidays.rb +1 -0
  244. data/revised_holidays.gemspec +31 -0
  245. data/test/coverage_report.rb +26 -0
  246. data/test/data/test_custom_govt_holiday_defs.yaml +5 -0
  247. data/test/data/test_custom_informal_holidays_defs.yaml +11 -0
  248. data/test/data/test_custom_year_range_holiday_defs.yaml +31 -0
  249. data/test/data/test_invalid_region.rb +15 -0
  250. data/test/data/test_multiple_custom_holiday_defs.yaml +12 -0
  251. data/test/data/test_multiple_regions_with_conflicts_region_1.yaml +38 -0
  252. data/test/data/test_multiple_regions_with_conflicts_region_2.yaml +38 -0
  253. data/test/data/test_region.rb +15 -0
  254. data/test/data/test_single_custom_holiday_defs.yaml +12 -0
  255. data/test/data/test_single_custom_holiday_with_custom_procs.yaml +28 -0
  256. data/test/defs/test_defs_ar.rb +69 -0
  257. data/test/defs/test_defs_at.rb +31 -0
  258. data/test/defs/test_defs_au.rb +233 -0
  259. data/test/defs/test_defs_be_fr.rb +45 -0
  260. data/test/defs/test_defs_be_nl.rb +45 -0
  261. data/test/defs/test_defs_bg.rb +41 -0
  262. data/test/defs/test_defs_br.rb +49 -0
  263. data/test/defs/test_defs_ca.rb +289 -0
  264. data/test/defs/test_defs_ch.rb +51 -0
  265. data/test/defs/test_defs_cl.rb +69 -0
  266. data/test/defs/test_defs_co.rb +113 -0
  267. data/test/defs/test_defs_cr.rb +29 -0
  268. data/test/defs/test_defs_cz.rb +37 -0
  269. data/test/defs/test_defs_de.rb +89 -0
  270. data/test/defs/test_defs_dk.rb +47 -0
  271. data/test/defs/test_defs_ecbtarget.rb +27 -0
  272. data/test/defs/test_defs_ee.rb +41 -0
  273. data/test/defs/test_defs_es.rb +137 -0
  274. data/test/defs/test_defs_europe.rb +1522 -0
  275. data/test/defs/test_defs_fed_ex.rb +24 -0
  276. data/test/defs/test_defs_federalreserve.rb +119 -0
  277. data/test/defs/test_defs_federalreservebanks.rb +251 -0
  278. data/test/defs/test_defs_fedex.rb +31 -0
  279. data/test/defs/test_defs_fi.rb +59 -0
  280. data/test/defs/test_defs_fr.rb +43 -0
  281. data/test/defs/test_defs_gb.rb +159 -0
  282. data/test/defs/test_defs_ge.rb +53 -0
  283. data/test/defs/test_defs_gr.rb +41 -0
  284. data/test/defs/test_defs_hk.rb +59 -0
  285. data/test/defs/test_defs_hr.rb +45 -0
  286. data/test/defs/test_defs_hu.rb +47 -0
  287. data/test/defs/test_defs_ie.rb +53 -0
  288. data/test/defs/test_defs_is.rb +51 -0
  289. data/test/defs/test_defs_it.rb +55 -0
  290. data/test/defs/test_defs_jp.rb +159 -0
  291. data/test/defs/test_defs_ke.rb +31 -0
  292. data/test/defs/test_defs_kr.rb +37 -0
  293. data/test/defs/test_defs_kz.rb +39 -0
  294. data/test/defs/test_defs_li.rb +35 -0
  295. data/test/defs/test_defs_lt.rb +65 -0
  296. data/test/defs/test_defs_lu.rb +35 -0
  297. data/test/defs/test_defs_lv.rb +98 -0
  298. data/test/defs/test_defs_ma.rb +29 -0
  299. data/test/defs/test_defs_mt_en.rb +41 -0
  300. data/test/defs/test_defs_mt_mt.rb +41 -0
  301. data/test/defs/test_defs_mx.rb +49 -0
  302. data/test/defs/test_defs_my.rb +23 -0
  303. data/test/defs/test_defs_nerc.rb +29 -0
  304. data/test/defs/test_defs_ng.rb +29 -0
  305. data/test/defs/test_defs_nl.rb +33 -0
  306. data/test/defs/test_defs_no.rb +43 -0
  307. data/test/defs/test_defs_northamerica.rb +667 -0
  308. data/test/defs/test_defs_nyse.rb +46 -0
  309. data/test/defs/test_defs_nz.rb +67 -0
  310. data/test/defs/test_defs_pe.rb +47 -0
  311. data/test/defs/test_defs_ph.rb +29 -0
  312. data/test/defs/test_defs_pl.rb +229 -0
  313. data/test/defs/test_defs_pt.rb +47 -0
  314. data/test/defs/test_defs_ro.rb +65 -0
  315. data/test/defs/test_defs_rs_cyrl.rb +46 -0
  316. data/test/defs/test_defs_rs_la.rb +46 -0
  317. data/test/defs/test_defs_ru.rb +34 -0
  318. data/test/defs/test_defs_scandinavia.rb +215 -0
  319. data/test/defs/test_defs_se.rb +59 -0
  320. data/test/defs/test_defs_sg.rb +25 -0
  321. data/test/defs/test_defs_si.rb +105 -0
  322. data/test/defs/test_defs_sk.rb +41 -0
  323. data/test/defs/test_defs_southamerica.rb +327 -0
  324. data/test/defs/test_defs_th.rb +33 -0
  325. data/test/defs/test_defs_tn.rb +27 -0
  326. data/test/defs/test_defs_tr.rb +60 -0
  327. data/test/defs/test_defs_ua.rb +41 -0
  328. data/test/defs/test_defs_unitednations.rb +11 -0
  329. data/test/defs/test_defs_ups.rb +31 -0
  330. data/test/defs/test_defs_us.rb +387 -0
  331. data/test/defs/test_defs_ve.rb +35 -0
  332. data/test/defs/test_defs_vi.rb +22 -0
  333. data/test/defs/test_defs_za.rb +35 -0
  334. data/test/holidays/core_extensions/test_date.rb +122 -0
  335. data/test/holidays/core_extensions/test_date_time.rb +60 -0
  336. data/test/holidays/date_calculator/test_day_of_month.rb +27 -0
  337. data/test/holidays/date_calculator/test_easter_gregorian.rb +30 -0
  338. data/test/holidays/date_calculator/test_easter_julian.rb +36 -0
  339. data/test/holidays/date_calculator/test_lunar_date.rb +89 -0
  340. data/test/holidays/date_calculator/test_weekend_modifier.rb +54 -0
  341. data/test/holidays/definition/context/test_function_processor.rb +199 -0
  342. data/test/holidays/definition/context/test_generator.rb +226 -0
  343. data/test/holidays/definition/context/test_load.rb +37 -0
  344. data/test/holidays/definition/context/test_merger.rb +25 -0
  345. data/test/holidays/definition/decorator/test_custom_method_proc.rb +113 -0
  346. data/test/holidays/definition/decorator/test_custom_method_source.rb +96 -0
  347. data/test/holidays/definition/decorator/test_test.rb +123 -0
  348. data/test/holidays/definition/generator/test_module.rb +268 -0
  349. data/test/holidays/definition/generator/test_regions.rb +97 -0
  350. data/test/holidays/definition/generator/test_test.rb +113 -0
  351. data/test/holidays/definition/parser/test_custom_method.rb +79 -0
  352. data/test/holidays/definition/parser/test_test.rb +142 -0
  353. data/test/holidays/definition/repository/test_cache.rb +123 -0
  354. data/test/holidays/definition/repository/test_custom_methods.rb +43 -0
  355. data/test/holidays/definition/repository/test_holidays_by_month.rb +275 -0
  356. data/test/holidays/definition/repository/test_proc_result_cache.rb +91 -0
  357. data/test/holidays/definition/repository/test_regions.rb +104 -0
  358. data/test/holidays/definition/validator/test_custom_method.rb +94 -0
  359. data/test/holidays/definition/validator/test_region.rb +54 -0
  360. data/test/holidays/definition/validator/test_test.rb +60 -0
  361. data/test/holidays/finder/context/test_between.rb +172 -0
  362. data/test/holidays/finder/context/test_dates_driver_builder.rb +91 -0
  363. data/test/holidays/finder/context/test_next_holiday.rb +156 -0
  364. data/test/holidays/finder/context/test_parse_options.rb +141 -0
  365. data/test/holidays/finder/context/test_search.rb +232 -0
  366. data/test/holidays/finder/context/test_year_holiday.rb +202 -0
  367. data/test/holidays/finder/rules/test_in_region.rb +42 -0
  368. data/test/holidays/finder/rules/test_year_range.rb +166 -0
  369. data/test/integration/README.md +9 -0
  370. data/test/integration/test_all_regions.rb +49 -0
  371. data/test/integration/test_any_holidays_during_work_week.rb +90 -0
  372. data/test/integration/test_available_regions.rb +23 -0
  373. data/test/integration/test_custom_holidays.rb +41 -0
  374. data/test/integration/test_custom_informal_holidays.rb +15 -0
  375. data/test/integration/test_custom_year_range_holidays.rb +35 -0
  376. data/test/integration/test_holidays.rb +243 -0
  377. data/test/integration/test_holidays_between.rb +87 -0
  378. data/test/integration/test_multiple_regions.rb +71 -0
  379. data/test/integration/test_multiple_regions_with_conflict.rb +29 -0
  380. data/test/integration/test_nonstandard_regions.rb +25 -0
  381. data/test/test_helper.rb +37 -0
  382. metadata +649 -0
@@ -0,0 +1,209 @@
1
+ require 'yaml'
2
+
3
+ #FIXME This whole file is my next refactor target. We do wayyyyy too much by
4
+ # convention here. We need hard and fast rules and explicit errors when you
5
+ # try to parse something that isn't allowed. So if you are a dev recognize
6
+ # that a lot of the guard statements in here are to codify existing legacy
7
+ # logic. The fact is that we already require these guards, we just don't
8
+ # enforce it explicitly. Now we will. And by doing so things will begin
9
+ # to look very, very messy.
10
+ module Holidays
11
+ module Definition
12
+ module Context
13
+ class Generator
14
+ def initialize(custom_method_parser, custom_method_source_decorator, custom_methods_repository, test_parser, test_source_generator, module_source_generator)
15
+ @custom_method_parser = custom_method_parser
16
+ @custom_method_source_decorator = custom_method_source_decorator
17
+ @custom_methods_repository = custom_methods_repository
18
+ @test_parser = test_parser
19
+ @test_source_generator = test_source_generator
20
+ @module_source_generator = module_source_generator
21
+ end
22
+
23
+ def parse_definition_files(files)
24
+ raise ArgumentError, "Must have at least one file to parse" if files.nil? || files.empty?
25
+
26
+ all_regions = []
27
+ all_rules_by_month = {}
28
+ all_custom_methods = {}
29
+ all_tests = []
30
+
31
+ files.flatten!
32
+
33
+ files.each do |file|
34
+ definition_file = YAML.load_file(file)
35
+
36
+ custom_methods = @custom_method_parser.call(definition_file['methods'])
37
+
38
+ regions, rules_by_month = parse_month_definitions(definition_file['months'], custom_methods)
39
+
40
+ all_regions << regions.flatten
41
+
42
+ all_rules_by_month.merge!(rules_by_month) { |month, existing, new|
43
+ existing << new
44
+ existing.flatten!
45
+ }
46
+
47
+ #FIXME This is a problem. We will have a 'global' list of methods. That's always bad. What effects will this have?
48
+ # This is an existing problem (just so we are clear). An issue would be extremely rare because we are generally parsing
49
+ # single files/custom files. But it IS possible that we would parse a bunch of things at the same time and step
50
+ # on each other so we need a solution.
51
+ all_custom_methods.merge!(custom_methods)
52
+
53
+ all_tests += @test_parser.call(definition_file['tests'])
54
+ end
55
+
56
+ all_regions.flatten!.uniq!
57
+
58
+ [all_regions, all_rules_by_month, all_custom_methods, all_tests]
59
+ end
60
+
61
+ def generate_definition_source(module_name, files, regions, rules_by_month, custom_methods, tests)
62
+ month_strings = generate_month_definition_strings(rules_by_month, custom_methods)
63
+
64
+ # Build the custom methods string
65
+ custom_method_string = ''
66
+ custom_methods.each do |key, code|
67
+ custom_method_string << @custom_method_source_decorator.call(code) + ",\n\n"
68
+ end
69
+
70
+ module_src = @module_source_generator.call(module_name, files, regions, month_strings, custom_method_string)
71
+ test_src = @test_source_generator.call(module_name, files, tests)
72
+
73
+ return module_src, test_src || ''
74
+ end
75
+
76
+ private
77
+
78
+ #FIXME This should be a 'month_definitions_parser' like the above parser
79
+ def parse_month_definitions(month_definitions, parsed_custom_methods)
80
+ regions = []
81
+ rules_by_month = {}
82
+
83
+ if month_definitions
84
+ month_definitions.each do |month, definitions|
85
+ rules_by_month[month] = [] unless rules_by_month[month]
86
+ definitions.each do |definition|
87
+ rule = {}
88
+
89
+ definition.each do |key, val|
90
+ # Ruby 2.4 doesn't have the `transform_keys` method. Once we drop 2.4 support we can
91
+ # use `val.transform_keys!(&:to_sym) if val.is_a?(Hash)` instead of this `if` statement.
92
+ if val.is_a?(Hash)
93
+ val = val.keys.each_with_object({}) do |k, result|
94
+ result[k.to_sym] = val[k]
95
+ end
96
+ end
97
+
98
+ rule[key.to_sym] = val
99
+ end
100
+
101
+ if rule[:year_ranges] && rule[:year_ranges].key?(:between)
102
+ start_year = rule[:year_ranges][:between]["start"].to_i
103
+ end_year = rule[:year_ranges][:between]["end"].to_i
104
+
105
+ rule[:year_ranges][:between] = Range.new(start_year, end_year)
106
+ end
107
+
108
+ rule[:regions] = rule[:regions].collect { |r| r.to_sym }
109
+ regions << rule[:regions]
110
+
111
+ exists = false
112
+ rules_by_month[month].each do |ex|
113
+ if ex[:name] == rule[:name] and ex[:wday] == rule[:wday] and ex[:mday] == rule[:mday] and ex[:week] == rule[:week] and ex[:type] == rule[:type] and ex[:function] == rule[:function] and ex[:observed] == rule[:observed] and ex[:year_ranges] == rule[:year_ranges]
114
+ ex[:regions] << rule[:regions].flatten
115
+ exists = true
116
+ end
117
+ end
118
+
119
+ unless exists
120
+ # This will add in the custom method arguments so they are immediately
121
+ # available for 'on the fly' def loading.
122
+ if rule[:function]
123
+ rule[:function_arguments] = get_function_arguments(rule[:function], parsed_custom_methods)
124
+ end
125
+
126
+ rules_by_month[month] << rule
127
+ end
128
+ end
129
+ end
130
+ end
131
+
132
+ [regions, rules_by_month]
133
+ end
134
+
135
+ #FIXME This should really be split out and tested with its own unit tests.
136
+ def generate_month_definition_strings(rules_by_month, parsed_custom_methods)
137
+ month_strings = []
138
+
139
+ rules_by_month.each do |month, rules|
140
+ month_string = " #{month.to_s} => ["
141
+ rule_strings = []
142
+ rules.each do |rule|
143
+ string = '{'
144
+ if rule[:mday]
145
+ string << ":mday => #{rule[:mday]}, "
146
+ end
147
+
148
+ if rule[:function]
149
+ string << ":function => \"#{rule[:function].to_s}\", "
150
+
151
+ # We need to add in the arguments so we can know what to send in when calling the custom proc during holiday lookups.
152
+ # NOTE: the allowed arguments are enforced in the custom methods parser.
153
+ string << ":function_arguments => #{get_function_arguments(rule[:function], parsed_custom_methods)}, "
154
+
155
+ if rule[:function_modifier]
156
+ string << ":function_modifier => #{rule[:function_modifier].to_s}, "
157
+ end
158
+ end
159
+
160
+ # This is the 'else'. It is possible for mday AND function
161
+ # to be set but this is the fallback. This whole area
162
+ # needs to be reworked!
163
+ if string == '{'
164
+ string << ":wday => #{rule[:wday]}, :week => #{rule[:week]}, "
165
+ end
166
+
167
+ if rule[:year_ranges] && rule[:year_ranges].is_a?(Hash)
168
+ selector = rule[:year_ranges].keys.first
169
+ value = rule[:year_ranges][selector]
170
+
171
+ string << ":year_ranges => { :#{selector} => #{value} },"
172
+ end
173
+
174
+ if rule[:observed]
175
+ string << ":observed => \"#{rule[:observed].to_s}\", "
176
+ string << ":observed_arguments => #{get_function_arguments(rule[:observed], parsed_custom_methods)}, "
177
+ end
178
+
179
+ if rule[:type]
180
+ string << ":type => :#{rule[:type]}, "
181
+ end
182
+
183
+ # shouldn't allow the same region twice
184
+ string << ":name => \"#{rule[:name]}\", :regions => [:" + rule[:regions].uniq.join(', :') + "]}"
185
+ rule_strings << string
186
+ end
187
+ month_string << rule_strings.join(",\n ") + "]"
188
+ month_strings << month_string
189
+ end
190
+
191
+ return month_strings
192
+ end
193
+
194
+ # This method sucks. The issue here is that the custom methods repo has the 'general' methods (like easter)
195
+ # but the 'parsed_custom_methods' have the recently parsed stuff. We don't load those until they are needed later.
196
+ # This entire file is a refactor target so I am adding some tech debt to get me over the hump.
197
+ # What we should do is ensure that all custom methods are loaded into the repo as soon as they are parsed
198
+ # so we only have one place to look.
199
+ def get_function_arguments(function_id, parsed_custom_methods)
200
+ if method = @custom_methods_repository.find(function_id)
201
+ method.parameters.collect { |arg| arg[1] }
202
+ elsif method = parsed_custom_methods[function_id]
203
+ method.arguments.collect { |arg| arg.to_sym }
204
+ end
205
+ end
206
+ end
207
+ end
208
+ end
209
+ end
@@ -0,0 +1,29 @@
1
+ module Holidays
2
+ module Definition
3
+ module Context
4
+ class Load
5
+ def initialize(definition_merger, full_definitions_path)
6
+ @definition_merger = definition_merger
7
+ @full_definitions_path = full_definitions_path
8
+ end
9
+
10
+ def call(region)
11
+ region_definition_file = "#{@full_definitions_path}/#{region}"
12
+ require region_definition_file
13
+
14
+ target_region_module = Module.const_get("Holidays").const_get(region.upcase)
15
+
16
+ @definition_merger.call(
17
+ region,
18
+ target_region_module.holidays_by_month,
19
+ target_region_module.custom_methods,
20
+ )
21
+
22
+ target_region_module.defined_regions
23
+ rescue NameError, LoadError => e
24
+ raise UnknownRegionError.new(e), "Could not load region prefix: #{region.to_s}"
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,22 @@
1
+ module Holidays
2
+ module Definition
3
+ module Context
4
+ # Merge a new set of definitions into the Holidays module.
5
+ class Merger
6
+ def initialize(holidays_by_month_repo, regions_repo, custom_methods_repo)
7
+ @holidays_repo = holidays_by_month_repo
8
+ @regions_repo = regions_repo
9
+ @custom_methods_repo = custom_methods_repo
10
+ end
11
+
12
+ def call(target_regions, target_holidays, target_custom_methods)
13
+ #FIXME Does this need to come in this exact order? God I hope not.
14
+ # If not then we should swap the order so it matches the init.
15
+ @regions_repo.add(target_regions)
16
+ @holidays_repo.add(target_holidays)
17
+ @custom_methods_repo.add(target_custom_methods)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,28 @@
1
+ module Holidays
2
+ module Definition
3
+ module Decorator
4
+ class CustomMethodProc
5
+ def call(proc)
6
+ validate!(proc)
7
+
8
+ eval("Proc.new { |#{parse_arguments(proc.arguments)}|
9
+ #{proc.source}
10
+ }")
11
+ end
12
+
13
+ private
14
+
15
+ def validate!(proc)
16
+ raise ArgumentError if proc.name.nil? || proc.name.empty?
17
+ raise ArgumentError if proc.arguments.nil? || proc.arguments.empty?
18
+ raise ArgumentError if proc.source.nil? || proc.source.empty?
19
+ end
20
+
21
+ def parse_arguments(args)
22
+ a = args.join(", ")
23
+ a[0..-1]
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,30 @@
1
+ module Holidays
2
+ module Definition
3
+ module Decorator
4
+ class CustomMethodSource
5
+ def call(proc)
6
+ validate!(proc)
7
+
8
+ method_name = proc.name
9
+ args = args_string(proc.arguments)
10
+ source = proc.source
11
+
12
+ "\"#{method_name.to_s}(#{args})\" => Proc.new { |#{args}|\n#{source}}"
13
+ end
14
+
15
+ private
16
+
17
+ def validate!(proc)
18
+ raise ArgumentError if proc.name.nil? || proc.name == ""
19
+ raise ArgumentError if proc.arguments.nil? || !proc.arguments.is_a?(Array) || proc.arguments.empty?
20
+ raise ArgumentError if proc.source.nil? || proc.source == ""
21
+ end
22
+
23
+ def args_string(args)
24
+ a = args.join(", ")
25
+ a[0..-1]
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,37 @@
1
+ module Holidays
2
+ module Definition
3
+ module Decorator
4
+ class Test
5
+ def call(t)
6
+ src = ""
7
+
8
+ t.dates.each do |d|
9
+ date = "Date.civil(#{d.year}, #{d.month}, #{d.day})"
10
+
11
+ holiday_call = "Holidays.on(#{date}, #{t.regions}"
12
+
13
+ if t.options
14
+ holiday_call += ", #{decorate_options(t.options)}"
15
+ end
16
+
17
+ if t.holiday?
18
+ src += "assert_equal \"#{t.name}\", (#{holiday_call})[0] || {})[:name]\n"
19
+ else
20
+ src += "assert_nil (#{holiday_call})[0] || {})[:name]\n"
21
+ end
22
+ end
23
+
24
+ src
25
+ end
26
+
27
+ private
28
+
29
+ def decorate_options(options)
30
+ options.map do |o|
31
+ o.to_sym
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,11 @@
1
+ module Holidays
2
+ module Definition
3
+ module Entity
4
+ CustomMethod = Struct.new(:name, :arguments, :source) do
5
+ def initialize(fields = {})
6
+ super(*fields.values_at(*members))
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module Holidays
2
+ module Definition
3
+ module Entity
4
+ Test = Struct.new(:dates, :regions, :options, :name, :holiday?) do
5
+ def initialize(fields = {})
6
+ super(*fields.values_at(*members))
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,54 @@
1
+ require 'holidays/errors'
2
+
3
+ module Holidays
4
+ module Definition
5
+ module Generator
6
+ class Module
7
+ def call(module_name, files, regions, month_strings, custom_methods)
8
+ raise ArgumentError.new("module name cannot be nil") if module_name.nil?
9
+ raise ArgumentError.new("module name cannot be blank") if module_name.empty?
10
+
11
+ raise ArgumentError.new("files cannot be nil") if files.nil?
12
+ raise ArgumentError.new("files cannot be empty") if files.empty?
13
+ raise ArgumentError.new("files must all be strings") unless files.all? { |f| f.is_a?(String) }
14
+
15
+ raise ArgumentError.new("regions cannot be nil") if regions.nil?
16
+ raise ArgumentError.new("regions cannot be empty") if regions.empty?
17
+
18
+ raise ArgumentError.new("month strings cannot be nil") if month_strings.nil?
19
+ raise ArgumentError.new("month strings cannot be empty") if month_strings.empty?
20
+
21
+ module_src =<<-EOM
22
+ # encoding: utf-8
23
+ module Holidays
24
+ # This file is generated by the Ruby Holidays gem.
25
+ #
26
+ # Definitions loaded: #{files.join(', ')}
27
+ #
28
+ # All the definitions are available at https://github.com/holidays/holidays
29
+ module #{module_name.to_s.upcase} # :nodoc:
30
+ def self.defined_regions
31
+ [:#{regions.join(', :')}]
32
+ end
33
+
34
+ def self.holidays_by_month
35
+ {
36
+ #{month_strings.join(",\n")}
37
+ }
38
+ end
39
+
40
+ def self.custom_methods
41
+ {
42
+ #{custom_methods}
43
+ }
44
+ end
45
+ end
46
+ end
47
+ EOM
48
+
49
+ module_src
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,55 @@
1
+ module Holidays
2
+ module Definition
3
+ module Generator
4
+ class Regions
5
+ # The "ca", "mx", and "us" holiday definitions include the "northamericainformal"
6
+ # holiday definitions, but that does not make these countries subregions of one another.
7
+ NORTH_AMERICA_REGIONS = %i[ca mx us].freeze
8
+
9
+ def call(regions)
10
+ validate!(regions)
11
+
12
+ <<-EOF
13
+ # encoding: utf-8
14
+ module Holidays
15
+ REGIONS = #{to_array(regions)}
16
+
17
+ PARENT_REGION_LOOKUP = #{generate_parent_lookup(regions)}
18
+ end
19
+ EOF
20
+ end
21
+
22
+ private
23
+
24
+ def validate!(regions)
25
+ raise ArgumentError.new("regions cannot be missing") if regions.nil?
26
+ raise ArgumentError.new("regions must be a hash") unless regions.is_a?(Hash)
27
+ raise ArgumentError.new("regions cannot be empty") if regions.empty?
28
+ end
29
+
30
+ def to_array(regions)
31
+ all_regions = []
32
+
33
+ regions.each do |region, subregions|
34
+ all_regions << subregions
35
+ end
36
+
37
+ all_regions.flatten.uniq
38
+ end
39
+
40
+ def generate_parent_lookup(regions)
41
+ lookup = {}
42
+
43
+ regions.each do |region, subregions|
44
+ subregions.each do |subregion|
45
+ parent_region = NORTH_AMERICA_REGIONS.include?(subregion) ? subregion : region
46
+ lookup[subregion] = parent_region unless lookup.has_key?(subregion)
47
+ end
48
+ end
49
+
50
+ lookup
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,51 @@
1
+ require 'holidays/errors'
2
+
3
+ module Holidays
4
+ module Definition
5
+ module Generator
6
+ class Test
7
+ def initialize(decorator)
8
+ @decorator = decorator
9
+ end
10
+
11
+ def call(module_name, file_names, tests)
12
+ validate!(module_name, file_names, tests)
13
+
14
+ test_src =<<-EndOfTests
15
+ # encoding: utf-8
16
+ require File.expand_path(File.dirname(__FILE__)) + '/../test_helper'
17
+
18
+ # This file is generated by the Ruby Holiday gem.
19
+ #
20
+ # Definitions loaded: #{file_names.join(', ')}
21
+ class #{module_name.to_s.capitalize}DefinitionTests < Test::Unit::TestCase # :nodoc:
22
+
23
+ def test_#{module_name.to_s.downcase}#{decorate(tests)}
24
+ end
25
+ end
26
+ EndOfTests
27
+
28
+ test_src
29
+ end
30
+
31
+ private
32
+
33
+ def validate!(module_name, file_names, tests)
34
+ raise ArgumentError.new("module_name cannot be missing") if module_name.nil? || module_name.empty?
35
+ raise ArgumentError.new("file_names for '#{module_name}' cannot be missing") if file_names.nil? || file_names.empty?
36
+ raise ArgumentError.new("tests for '#{module_name}' cannot be missing") if tests.nil?
37
+ end
38
+
39
+ def decorate(tests)
40
+ out = ""
41
+
42
+ tests.each do |t|
43
+ out << "\n " + @decorator.call(t)
44
+ end
45
+
46
+ out
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,67 @@
1
+ require 'holidays/definition/entity/custom_method'
2
+
3
+ module Holidays
4
+ module Definition
5
+ module Parser
6
+ class CustomMethod
7
+ def initialize(validator)
8
+ @validator = validator
9
+ end
10
+
11
+ def call(methods)
12
+ return {} if methods.nil? || methods.empty?
13
+
14
+ validate!(methods)
15
+
16
+ custom_methods = {}
17
+
18
+ methods.each do |name, pieces|
19
+ arguments = parse_arguments!(pieces["arguments"])
20
+
21
+ custom_methods[method_key(name, arguments)] = Entity::CustomMethod.new({
22
+ name: name,
23
+ arguments: arguments,
24
+ source: pieces["ruby"],
25
+ })
26
+ end
27
+
28
+ custom_methods
29
+ end
30
+
31
+ private
32
+
33
+ def validate!(methods)
34
+ raise ArgumentError unless methods.all? do |name, pieces|
35
+ @validator.valid?(
36
+ {
37
+ :name => name,
38
+ :arguments => pieces["arguments"],
39
+ :source => pieces["ruby"]
40
+ }
41
+ )
42
+ end
43
+ end
44
+
45
+ def parse_arguments!(arguments)
46
+ splitArgs = arguments.split(",")
47
+ parsedArgs = []
48
+
49
+ splitArgs.each do |arg|
50
+ parsedArgs << arg.strip
51
+ end
52
+
53
+ parsedArgs
54
+ end
55
+
56
+ def method_key(name, args)
57
+ "#{name.to_s}(#{args_string(args)})"
58
+ end
59
+
60
+ def args_string(args)
61
+ a = args.join(", ")
62
+ a[0..-1]
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end