HydPy 6.2.dev1__cp313-cp313-win_amd64.whl

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 (890) hide show
  1. hydpy/__init__.py +275 -0
  2. hydpy/aliases.py +2554 -0
  3. hydpy/auxs/__init__.py +0 -0
  4. hydpy/auxs/anntools.py +1305 -0
  5. hydpy/auxs/armatools.py +883 -0
  6. hydpy/auxs/calibtools.py +3337 -0
  7. hydpy/auxs/interptools.py +1094 -0
  8. hydpy/auxs/iuhtools.py +543 -0
  9. hydpy/auxs/networktools.py +597 -0
  10. hydpy/auxs/ppolytools.py +809 -0
  11. hydpy/auxs/quadtools.py +61 -0
  12. hydpy/auxs/roottools.py +228 -0
  13. hydpy/auxs/smoothtools.py +273 -0
  14. hydpy/auxs/statstools.py +2125 -0
  15. hydpy/auxs/validtools.py +81 -0
  16. hydpy/conf/HydPyConfigBase.xsd +68637 -0
  17. hydpy/conf/HydPyConfigBase.xsdt +358 -0
  18. hydpy/conf/HydPyConfigMultipleRuns.xsd +25 -0
  19. hydpy/conf/HydPyConfigSingleRun.xsd +24 -0
  20. hydpy/conf/__init__.py +0 -0
  21. hydpy/conf/a_coefficients_explicit_lobatto_sequence.npy +0 -0
  22. hydpy/conf/support_points_for_smoothpar_logistic2.npy +0 -0
  23. hydpy/config.py +42 -0
  24. hydpy/core/__init__.py +0 -0
  25. hydpy/core/aliastools.py +214 -0
  26. hydpy/core/autodoctools.py +1947 -0
  27. hydpy/core/auxfiletools.py +1169 -0
  28. hydpy/core/devicetools.py +3810 -0
  29. hydpy/core/exceptiontools.py +269 -0
  30. hydpy/core/filetools.py +1985 -0
  31. hydpy/core/hydpytools.py +3089 -0
  32. hydpy/core/importtools.py +1398 -0
  33. hydpy/core/indextools.py +345 -0
  34. hydpy/core/itemtools.py +1849 -0
  35. hydpy/core/masktools.py +460 -0
  36. hydpy/core/modeltools.py +4868 -0
  37. hydpy/core/netcdftools.py +2683 -0
  38. hydpy/core/objecttools.py +2023 -0
  39. hydpy/core/optiontools.py +1045 -0
  40. hydpy/core/parametertools.py +4674 -0
  41. hydpy/core/printtools.py +222 -0
  42. hydpy/core/propertytools.py +643 -0
  43. hydpy/core/pubtools.py +254 -0
  44. hydpy/core/selectiontools.py +1571 -0
  45. hydpy/core/sequencetools.py +4476 -0
  46. hydpy/core/seriestools.py +339 -0
  47. hydpy/core/testtools.py +2483 -0
  48. hydpy/core/timetools.py +3567 -0
  49. hydpy/core/typingtools.py +333 -0
  50. hydpy/core/variabletools.py +2615 -0
  51. hydpy/cythons/__init__.py +24 -0
  52. hydpy/cythons/annutils.pxd +33 -0
  53. hydpy/cythons/annutils.pyi +25 -0
  54. hydpy/cythons/annutils.pyx +120 -0
  55. hydpy/cythons/autogen/__init__.py +0 -0
  56. hydpy/cythons/autogen/annutils.cp313-win_amd64.pyd +0 -0
  57. hydpy/cythons/autogen/annutils.pxd +42 -0
  58. hydpy/cythons/autogen/annutils.pyx +129 -0
  59. hydpy/cythons/autogen/c_arma.cp313-win_amd64.pyd +0 -0
  60. hydpy/cythons/autogen/c_arma.pxd +179 -0
  61. hydpy/cythons/autogen/c_arma.pyx +356 -0
  62. hydpy/cythons/autogen/c_arma_rimorido.cp313-win_amd64.pyd +0 -0
  63. hydpy/cythons/autogen/c_arma_rimorido.pxd +179 -0
  64. hydpy/cythons/autogen/c_arma_rimorido.pyx +356 -0
  65. hydpy/cythons/autogen/c_conv.cp313-win_amd64.pyd +0 -0
  66. hydpy/cythons/autogen/c_conv.pxd +198 -0
  67. hydpy/cythons/autogen/c_conv.pyx +491 -0
  68. hydpy/cythons/autogen/c_conv_idw.cp313-win_amd64.pyd +0 -0
  69. hydpy/cythons/autogen/c_conv_idw.pxd +124 -0
  70. hydpy/cythons/autogen/c_conv_idw.pyx +264 -0
  71. hydpy/cythons/autogen/c_conv_idw_ed.cp313-win_amd64.pyd +0 -0
  72. hydpy/cythons/autogen/c_conv_idw_ed.pxd +197 -0
  73. hydpy/cythons/autogen/c_conv_idw_ed.pyx +481 -0
  74. hydpy/cythons/autogen/c_conv_nn.cp313-win_amd64.pyd +0 -0
  75. hydpy/cythons/autogen/c_conv_nn.pxd +120 -0
  76. hydpy/cythons/autogen/c_conv_nn.pyx +224 -0
  77. hydpy/cythons/autogen/c_dam.cp313-win_amd64.pyd +0 -0
  78. hydpy/cythons/autogen/c_dam.pxd +805 -0
  79. hydpy/cythons/autogen/c_dam.pyx +1477 -0
  80. hydpy/cythons/autogen/c_dam_llake.cp313-win_amd64.pyd +0 -0
  81. hydpy/cythons/autogen/c_dam_llake.pxd +364 -0
  82. hydpy/cythons/autogen/c_dam_llake.pyx +705 -0
  83. hydpy/cythons/autogen/c_dam_lreservoir.cp313-win_amd64.pyd +0 -0
  84. hydpy/cythons/autogen/c_dam_lreservoir.pxd +365 -0
  85. hydpy/cythons/autogen/c_dam_lreservoir.pyx +708 -0
  86. hydpy/cythons/autogen/c_dam_lretention.cp313-win_amd64.pyd +0 -0
  87. hydpy/cythons/autogen/c_dam_lretention.pxd +340 -0
  88. hydpy/cythons/autogen/c_dam_lretention.pyx +625 -0
  89. hydpy/cythons/autogen/c_dam_pump.cp313-win_amd64.pyd +0 -0
  90. hydpy/cythons/autogen/c_dam_pump.pxd +402 -0
  91. hydpy/cythons/autogen/c_dam_pump.pyx +724 -0
  92. hydpy/cythons/autogen/c_dam_pump_sluice.cp313-win_amd64.pyd +0 -0
  93. hydpy/cythons/autogen/c_dam_pump_sluice.pxd +452 -0
  94. hydpy/cythons/autogen/c_dam_pump_sluice.pyx +829 -0
  95. hydpy/cythons/autogen/c_dam_sluice.cp313-win_amd64.pyd +0 -0
  96. hydpy/cythons/autogen/c_dam_sluice.pxd +404 -0
  97. hydpy/cythons/autogen/c_dam_sluice.pyx +726 -0
  98. hydpy/cythons/autogen/c_dam_v001.cp313-win_amd64.pyd +0 -0
  99. hydpy/cythons/autogen/c_dam_v001.pxd +452 -0
  100. hydpy/cythons/autogen/c_dam_v001.pyx +816 -0
  101. hydpy/cythons/autogen/c_dam_v002.cp313-win_amd64.pyd +0 -0
  102. hydpy/cythons/autogen/c_dam_v002.pxd +394 -0
  103. hydpy/cythons/autogen/c_dam_v002.pyx +703 -0
  104. hydpy/cythons/autogen/c_dam_v003.cp313-win_amd64.pyd +0 -0
  105. hydpy/cythons/autogen/c_dam_v003.pxd +417 -0
  106. hydpy/cythons/autogen/c_dam_v003.pyx +744 -0
  107. hydpy/cythons/autogen/c_dam_v004.cp313-win_amd64.pyd +0 -0
  108. hydpy/cythons/autogen/c_dam_v004.pxd +486 -0
  109. hydpy/cythons/autogen/c_dam_v004.pyx +891 -0
  110. hydpy/cythons/autogen/c_dam_v005.cp313-win_amd64.pyd +0 -0
  111. hydpy/cythons/autogen/c_dam_v005.pxd +524 -0
  112. hydpy/cythons/autogen/c_dam_v005.pyx +928 -0
  113. hydpy/cythons/autogen/c_dummy.cp313-win_amd64.pyd +0 -0
  114. hydpy/cythons/autogen/c_dummy.pxd +151 -0
  115. hydpy/cythons/autogen/c_dummy.pyx +263 -0
  116. hydpy/cythons/autogen/c_dummy_interceptedwater.cp313-win_amd64.pyd +0 -0
  117. hydpy/cythons/autogen/c_dummy_interceptedwater.pxd +69 -0
  118. hydpy/cythons/autogen/c_dummy_interceptedwater.pyx +104 -0
  119. hydpy/cythons/autogen/c_dummy_node2node.cp313-win_amd64.pyd +0 -0
  120. hydpy/cythons/autogen/c_dummy_node2node.pxd +89 -0
  121. hydpy/cythons/autogen/c_dummy_node2node.pyx +148 -0
  122. hydpy/cythons/autogen/c_dummy_snowalbedo.cp313-win_amd64.pyd +0 -0
  123. hydpy/cythons/autogen/c_dummy_snowalbedo.pxd +69 -0
  124. hydpy/cythons/autogen/c_dummy_snowalbedo.pyx +104 -0
  125. hydpy/cythons/autogen/c_dummy_snowcover.cp313-win_amd64.pyd +0 -0
  126. hydpy/cythons/autogen/c_dummy_snowcover.pxd +69 -0
  127. hydpy/cythons/autogen/c_dummy_snowcover.pyx +104 -0
  128. hydpy/cythons/autogen/c_dummy_snowycanopy.cp313-win_amd64.pyd +0 -0
  129. hydpy/cythons/autogen/c_dummy_snowycanopy.pxd +69 -0
  130. hydpy/cythons/autogen/c_dummy_snowycanopy.pyx +104 -0
  131. hydpy/cythons/autogen/c_dummy_soilwater.cp313-win_amd64.pyd +0 -0
  132. hydpy/cythons/autogen/c_dummy_soilwater.pxd +69 -0
  133. hydpy/cythons/autogen/c_dummy_soilwater.pyx +104 -0
  134. hydpy/cythons/autogen/c_evap.cp313-win_amd64.pyd +0 -0
  135. hydpy/cythons/autogen/c_evap.pxd +1029 -0
  136. hydpy/cythons/autogen/c_evap.pyx +2601 -0
  137. hydpy/cythons/autogen/c_evap_aet_hbv96.cp313-win_amd64.pyd +0 -0
  138. hydpy/cythons/autogen/c_evap_aet_hbv96.pxd +227 -0
  139. hydpy/cythons/autogen/c_evap_aet_hbv96.pyx +584 -0
  140. hydpy/cythons/autogen/c_evap_aet_minhas.cp313-win_amd64.pyd +0 -0
  141. hydpy/cythons/autogen/c_evap_aet_minhas.pxd +193 -0
  142. hydpy/cythons/autogen/c_evap_aet_minhas.pyx +478 -0
  143. hydpy/cythons/autogen/c_evap_aet_morsim.cp313-win_amd64.pyd +0 -0
  144. hydpy/cythons/autogen/c_evap_aet_morsim.pxd +681 -0
  145. hydpy/cythons/autogen/c_evap_aet_morsim.pyx +1642 -0
  146. hydpy/cythons/autogen/c_evap_pet_ambav1.cp313-win_amd64.pyd +0 -0
  147. hydpy/cythons/autogen/c_evap_pet_ambav1.pxd +532 -0
  148. hydpy/cythons/autogen/c_evap_pet_ambav1.pyx +1296 -0
  149. hydpy/cythons/autogen/c_evap_pet_hbv96.cp313-win_amd64.pyd +0 -0
  150. hydpy/cythons/autogen/c_evap_pet_hbv96.pxd +179 -0
  151. hydpy/cythons/autogen/c_evap_pet_hbv96.pyx +328 -0
  152. hydpy/cythons/autogen/c_evap_pet_m.cp313-win_amd64.pyd +0 -0
  153. hydpy/cythons/autogen/c_evap_pet_m.pxd +124 -0
  154. hydpy/cythons/autogen/c_evap_pet_m.pyx +214 -0
  155. hydpy/cythons/autogen/c_evap_pet_mlc.cp313-win_amd64.pyd +0 -0
  156. hydpy/cythons/autogen/c_evap_pet_mlc.pxd +126 -0
  157. hydpy/cythons/autogen/c_evap_pet_mlc.pyx +214 -0
  158. hydpy/cythons/autogen/c_evap_ret_fao56.cp313-win_amd64.pyd +0 -0
  159. hydpy/cythons/autogen/c_evap_ret_fao56.pxd +305 -0
  160. hydpy/cythons/autogen/c_evap_ret_fao56.pyx +624 -0
  161. hydpy/cythons/autogen/c_evap_ret_io.cp313-win_amd64.pyd +0 -0
  162. hydpy/cythons/autogen/c_evap_ret_io.pxd +112 -0
  163. hydpy/cythons/autogen/c_evap_ret_io.pyx +176 -0
  164. hydpy/cythons/autogen/c_evap_ret_tw2002.cp313-win_amd64.pyd +0 -0
  165. hydpy/cythons/autogen/c_evap_ret_tw2002.pxd +139 -0
  166. hydpy/cythons/autogen/c_evap_ret_tw2002.pyx +273 -0
  167. hydpy/cythons/autogen/c_exch.cp313-win_amd64.pyd +0 -0
  168. hydpy/cythons/autogen/c_exch.pxd +230 -0
  169. hydpy/cythons/autogen/c_exch.pyx +462 -0
  170. hydpy/cythons/autogen/c_exch_branch_hbv96.cp313-win_amd64.pyd +0 -0
  171. hydpy/cythons/autogen/c_exch_branch_hbv96.pxd +134 -0
  172. hydpy/cythons/autogen/c_exch_branch_hbv96.pyx +255 -0
  173. hydpy/cythons/autogen/c_exch_waterlevel.cp313-win_amd64.pyd +0 -0
  174. hydpy/cythons/autogen/c_exch_waterlevel.pxd +54 -0
  175. hydpy/cythons/autogen/c_exch_waterlevel.pyx +78 -0
  176. hydpy/cythons/autogen/c_exch_weir_hbv96.cp313-win_amd64.pyd +0 -0
  177. hydpy/cythons/autogen/c_exch_weir_hbv96.pxd +156 -0
  178. hydpy/cythons/autogen/c_exch_weir_hbv96.pyx +282 -0
  179. hydpy/cythons/autogen/c_ga.cp313-win_amd64.pyd +0 -0
  180. hydpy/cythons/autogen/c_ga.pxd +353 -0
  181. hydpy/cythons/autogen/c_ga.pyx +1204 -0
  182. hydpy/cythons/autogen/c_ga_garto.cp313-win_amd64.pyd +0 -0
  183. hydpy/cythons/autogen/c_ga_garto.pxd +330 -0
  184. hydpy/cythons/autogen/c_ga_garto.pyx +1105 -0
  185. hydpy/cythons/autogen/c_ga_garto_submodel1.cp313-win_amd64.pyd +0 -0
  186. hydpy/cythons/autogen/c_ga_garto_submodel1.pxd +236 -0
  187. hydpy/cythons/autogen/c_ga_garto_submodel1.pyx +944 -0
  188. hydpy/cythons/autogen/c_gland.cp313-win_amd64.pyd +0 -0
  189. hydpy/cythons/autogen/c_gland.pxd +437 -0
  190. hydpy/cythons/autogen/c_gland.pyx +726 -0
  191. hydpy/cythons/autogen/c_gland_gr4.cp313-win_amd64.pyd +0 -0
  192. hydpy/cythons/autogen/c_gland_gr4.pxd +382 -0
  193. hydpy/cythons/autogen/c_gland_gr4.pyx +605 -0
  194. hydpy/cythons/autogen/c_gland_gr5.cp313-win_amd64.pyd +0 -0
  195. hydpy/cythons/autogen/c_gland_gr5.pxd +368 -0
  196. hydpy/cythons/autogen/c_gland_gr5.pyx +568 -0
  197. hydpy/cythons/autogen/c_gland_gr6.cp313-win_amd64.pyd +0 -0
  198. hydpy/cythons/autogen/c_gland_gr6.pxd +420 -0
  199. hydpy/cythons/autogen/c_gland_gr6.pyx +673 -0
  200. hydpy/cythons/autogen/c_hland.cp313-win_amd64.pyd +0 -0
  201. hydpy/cythons/autogen/c_hland.pxd +855 -0
  202. hydpy/cythons/autogen/c_hland.pyx +2486 -0
  203. hydpy/cythons/autogen/c_hland_96.cp313-win_amd64.pyd +0 -0
  204. hydpy/cythons/autogen/c_hland_96.pxd +631 -0
  205. hydpy/cythons/autogen/c_hland_96.pyx +1724 -0
  206. hydpy/cythons/autogen/c_hland_96c.cp313-win_amd64.pyd +0 -0
  207. hydpy/cythons/autogen/c_hland_96c.pxd +621 -0
  208. hydpy/cythons/autogen/c_hland_96c.pyx +1822 -0
  209. hydpy/cythons/autogen/c_hland_96p.cp313-win_amd64.pyd +0 -0
  210. hydpy/cythons/autogen/c_hland_96p.pxd +683 -0
  211. hydpy/cythons/autogen/c_hland_96p.pyx +1911 -0
  212. hydpy/cythons/autogen/c_kinw.cp313-win_amd64.pyd +0 -0
  213. hydpy/cythons/autogen/c_kinw.pxd +509 -0
  214. hydpy/cythons/autogen/c_kinw.pyx +965 -0
  215. hydpy/cythons/autogen/c_kinw_williams.cp313-win_amd64.pyd +0 -0
  216. hydpy/cythons/autogen/c_kinw_williams.pxd +409 -0
  217. hydpy/cythons/autogen/c_kinw_williams.pyx +763 -0
  218. hydpy/cythons/autogen/c_kinw_williams_ext.cp313-win_amd64.pyd +0 -0
  219. hydpy/cythons/autogen/c_kinw_williams_ext.pxd +220 -0
  220. hydpy/cythons/autogen/c_kinw_williams_ext.pyx +440 -0
  221. hydpy/cythons/autogen/c_lland.cp313-win_amd64.pyd +0 -0
  222. hydpy/cythons/autogen/c_lland.pxd +1386 -0
  223. hydpy/cythons/autogen/c_lland.pyx +3679 -0
  224. hydpy/cythons/autogen/c_lland_dd.cp313-win_amd64.pyd +0 -0
  225. hydpy/cythons/autogen/c_lland_dd.pxd +679 -0
  226. hydpy/cythons/autogen/c_lland_dd.pyx +1719 -0
  227. hydpy/cythons/autogen/c_lland_knauf.cp313-win_amd64.pyd +0 -0
  228. hydpy/cythons/autogen/c_lland_knauf.pxd +1096 -0
  229. hydpy/cythons/autogen/c_lland_knauf.pyx +2784 -0
  230. hydpy/cythons/autogen/c_lland_knauf_ic.cp313-win_amd64.pyd +0 -0
  231. hydpy/cythons/autogen/c_lland_knauf_ic.pxd +1369 -0
  232. hydpy/cythons/autogen/c_lland_knauf_ic.pyx +3625 -0
  233. hydpy/cythons/autogen/c_meteo.cp313-win_amd64.pyd +0 -0
  234. hydpy/cythons/autogen/c_meteo.pxd +469 -0
  235. hydpy/cythons/autogen/c_meteo.pyx +879 -0
  236. hydpy/cythons/autogen/c_meteo_clear_glob_io.cp313-win_amd64.pyd +0 -0
  237. hydpy/cythons/autogen/c_meteo_clear_glob_io.pxd +75 -0
  238. hydpy/cythons/autogen/c_meteo_clear_glob_io.pyx +107 -0
  239. hydpy/cythons/autogen/c_meteo_glob_fao56.cp313-win_amd64.pyd +0 -0
  240. hydpy/cythons/autogen/c_meteo_glob_fao56.pxd +209 -0
  241. hydpy/cythons/autogen/c_meteo_glob_fao56.pyx +339 -0
  242. hydpy/cythons/autogen/c_meteo_glob_io.cp313-win_amd64.pyd +0 -0
  243. hydpy/cythons/autogen/c_meteo_glob_io.pxd +63 -0
  244. hydpy/cythons/autogen/c_meteo_glob_io.pyx +91 -0
  245. hydpy/cythons/autogen/c_meteo_glob_morsim.cp313-win_amd64.pyd +0 -0
  246. hydpy/cythons/autogen/c_meteo_glob_morsim.pxd +289 -0
  247. hydpy/cythons/autogen/c_meteo_glob_morsim.pyx +527 -0
  248. hydpy/cythons/autogen/c_meteo_precip_io.cp313-win_amd64.pyd +0 -0
  249. hydpy/cythons/autogen/c_meteo_precip_io.pxd +112 -0
  250. hydpy/cythons/autogen/c_meteo_precip_io.pyx +176 -0
  251. hydpy/cythons/autogen/c_meteo_psun_sun_glob_io.cp313-win_amd64.pyd +0 -0
  252. hydpy/cythons/autogen/c_meteo_psun_sun_glob_io.pxd +87 -0
  253. hydpy/cythons/autogen/c_meteo_psun_sun_glob_io.pyx +123 -0
  254. hydpy/cythons/autogen/c_meteo_sun_fao56.cp313-win_amd64.pyd +0 -0
  255. hydpy/cythons/autogen/c_meteo_sun_fao56.pxd +209 -0
  256. hydpy/cythons/autogen/c_meteo_sun_fao56.pyx +343 -0
  257. hydpy/cythons/autogen/c_meteo_sun_morsim.cp313-win_amd64.pyd +0 -0
  258. hydpy/cythons/autogen/c_meteo_sun_morsim.pxd +286 -0
  259. hydpy/cythons/autogen/c_meteo_sun_morsim.pyx +519 -0
  260. hydpy/cythons/autogen/c_meteo_temp_io.cp313-win_amd64.pyd +0 -0
  261. hydpy/cythons/autogen/c_meteo_temp_io.pxd +112 -0
  262. hydpy/cythons/autogen/c_meteo_temp_io.pyx +176 -0
  263. hydpy/cythons/autogen/c_musk.cp313-win_amd64.pyd +0 -0
  264. hydpy/cythons/autogen/c_musk.pxd +282 -0
  265. hydpy/cythons/autogen/c_musk.pyx +605 -0
  266. hydpy/cythons/autogen/c_musk_classic.cp313-win_amd64.pyd +0 -0
  267. hydpy/cythons/autogen/c_musk_classic.pxd +138 -0
  268. hydpy/cythons/autogen/c_musk_classic.pyx +226 -0
  269. hydpy/cythons/autogen/c_musk_mct.cp313-win_amd64.pyd +0 -0
  270. hydpy/cythons/autogen/c_musk_mct.pxd +282 -0
  271. hydpy/cythons/autogen/c_musk_mct.pyx +609 -0
  272. hydpy/cythons/autogen/c_rconc.cp313-win_amd64.pyd +0 -0
  273. hydpy/cythons/autogen/c_rconc.pxd +119 -0
  274. hydpy/cythons/autogen/c_rconc.pyx +174 -0
  275. hydpy/cythons/autogen/c_rconc_nash.cp313-win_amd64.pyd +0 -0
  276. hydpy/cythons/autogen/c_rconc_nash.pxd +111 -0
  277. hydpy/cythons/autogen/c_rconc_nash.pyx +185 -0
  278. hydpy/cythons/autogen/c_rconc_uh.cp313-win_amd64.pyd +0 -0
  279. hydpy/cythons/autogen/c_rconc_uh.pxd +92 -0
  280. hydpy/cythons/autogen/c_rconc_uh.pyx +125 -0
  281. hydpy/cythons/autogen/c_sw1d.cp313-win_amd64.pyd +0 -0
  282. hydpy/cythons/autogen/c_sw1d.pxd +511 -0
  283. hydpy/cythons/autogen/c_sw1d.pyx +1263 -0
  284. hydpy/cythons/autogen/c_sw1d_channel.cp313-win_amd64.pyd +0 -0
  285. hydpy/cythons/autogen/c_sw1d_channel.pxd +119 -0
  286. hydpy/cythons/autogen/c_sw1d_channel.pyx +300 -0
  287. hydpy/cythons/autogen/c_sw1d_gate_out.cp313-win_amd64.pyd +0 -0
  288. hydpy/cythons/autogen/c_sw1d_gate_out.pxd +240 -0
  289. hydpy/cythons/autogen/c_sw1d_gate_out.pyx +476 -0
  290. hydpy/cythons/autogen/c_sw1d_lias.cp313-win_amd64.pyd +0 -0
  291. hydpy/cythons/autogen/c_sw1d_lias.pxd +320 -0
  292. hydpy/cythons/autogen/c_sw1d_lias.pyx +619 -0
  293. hydpy/cythons/autogen/c_sw1d_lias_sluice.cp313-win_amd64.pyd +0 -0
  294. hydpy/cythons/autogen/c_sw1d_lias_sluice.pxd +325 -0
  295. hydpy/cythons/autogen/c_sw1d_lias_sluice.pyx +644 -0
  296. hydpy/cythons/autogen/c_sw1d_network.cp313-win_amd64.pyd +0 -0
  297. hydpy/cythons/autogen/c_sw1d_network.pxd +90 -0
  298. hydpy/cythons/autogen/c_sw1d_network.pyx +246 -0
  299. hydpy/cythons/autogen/c_sw1d_pump.cp313-win_amd64.pyd +0 -0
  300. hydpy/cythons/autogen/c_sw1d_pump.pxd +256 -0
  301. hydpy/cythons/autogen/c_sw1d_pump.pyx +502 -0
  302. hydpy/cythons/autogen/c_sw1d_q_in.cp313-win_amd64.pyd +0 -0
  303. hydpy/cythons/autogen/c_sw1d_q_in.pxd +224 -0
  304. hydpy/cythons/autogen/c_sw1d_q_in.pyx +383 -0
  305. hydpy/cythons/autogen/c_sw1d_q_out.cp313-win_amd64.pyd +0 -0
  306. hydpy/cythons/autogen/c_sw1d_q_out.pxd +224 -0
  307. hydpy/cythons/autogen/c_sw1d_q_out.pyx +383 -0
  308. hydpy/cythons/autogen/c_sw1d_storage.cp313-win_amd64.pyd +0 -0
  309. hydpy/cythons/autogen/c_sw1d_storage.pxd +193 -0
  310. hydpy/cythons/autogen/c_sw1d_storage.pyx +349 -0
  311. hydpy/cythons/autogen/c_sw1d_weir_out.cp313-win_amd64.pyd +0 -0
  312. hydpy/cythons/autogen/c_sw1d_weir_out.pxd +212 -0
  313. hydpy/cythons/autogen/c_sw1d_weir_out.pyx +404 -0
  314. hydpy/cythons/autogen/c_test.cp313-win_amd64.pyd +0 -0
  315. hydpy/cythons/autogen/c_test.pxd +175 -0
  316. hydpy/cythons/autogen/c_test.pyx +348 -0
  317. hydpy/cythons/autogen/c_test_discontinous.cp313-win_amd64.pyd +0 -0
  318. hydpy/cythons/autogen/c_test_discontinous.pxd +146 -0
  319. hydpy/cythons/autogen/c_test_discontinous.pyx +256 -0
  320. hydpy/cythons/autogen/c_test_stiff0d.cp313-win_amd64.pyd +0 -0
  321. hydpy/cythons/autogen/c_test_stiff0d.pxd +146 -0
  322. hydpy/cythons/autogen/c_test_stiff0d.pyx +250 -0
  323. hydpy/cythons/autogen/c_test_stiff1d.cp313-win_amd64.pyd +0 -0
  324. hydpy/cythons/autogen/c_test_stiff1d.pxd +145 -0
  325. hydpy/cythons/autogen/c_test_stiff1d.pyx +294 -0
  326. hydpy/cythons/autogen/c_whmod.cp313-win_amd64.pyd +0 -0
  327. hydpy/cythons/autogen/c_whmod.pxd +482 -0
  328. hydpy/cythons/autogen/c_whmod.pyx +1156 -0
  329. hydpy/cythons/autogen/c_whmod_rural.cp313-win_amd64.pyd +0 -0
  330. hydpy/cythons/autogen/c_whmod_rural.pxd +411 -0
  331. hydpy/cythons/autogen/c_whmod_rural.pyx +982 -0
  332. hydpy/cythons/autogen/c_whmod_urban.cp313-win_amd64.pyd +0 -0
  333. hydpy/cythons/autogen/c_whmod_urban.pxd +482 -0
  334. hydpy/cythons/autogen/c_whmod_urban.pyx +1155 -0
  335. hydpy/cythons/autogen/c_wland.cp313-win_amd64.pyd +0 -0
  336. hydpy/cythons/autogen/c_wland.pxd +842 -0
  337. hydpy/cythons/autogen/c_wland.pyx +1890 -0
  338. hydpy/cythons/autogen/c_wland_gd.cp313-win_amd64.pyd +0 -0
  339. hydpy/cythons/autogen/c_wland_gd.pxd +829 -0
  340. hydpy/cythons/autogen/c_wland_gd.pyx +1847 -0
  341. hydpy/cythons/autogen/c_wland_wag.cp313-win_amd64.pyd +0 -0
  342. hydpy/cythons/autogen/c_wland_wag.pxd +810 -0
  343. hydpy/cythons/autogen/c_wland_wag.pyx +1780 -0
  344. hydpy/cythons/autogen/c_wq.cp313-win_amd64.pyd +0 -0
  345. hydpy/cythons/autogen/c_wq.pxd +275 -0
  346. hydpy/cythons/autogen/c_wq.pyx +652 -0
  347. hydpy/cythons/autogen/c_wq_trapeze.cp313-win_amd64.pyd +0 -0
  348. hydpy/cythons/autogen/c_wq_trapeze.pxd +170 -0
  349. hydpy/cythons/autogen/c_wq_trapeze.pyx +400 -0
  350. hydpy/cythons/autogen/c_wq_trapeze_strickler.cp313-win_amd64.pyd +0 -0
  351. hydpy/cythons/autogen/c_wq_trapeze_strickler.pxd +243 -0
  352. hydpy/cythons/autogen/c_wq_trapeze_strickler.pyx +578 -0
  353. hydpy/cythons/autogen/c_wq_walrus.cp313-win_amd64.pyd +0 -0
  354. hydpy/cythons/autogen/c_wq_walrus.pxd +61 -0
  355. hydpy/cythons/autogen/c_wq_walrus.pyx +82 -0
  356. hydpy/cythons/autogen/configutils.cp313-win_amd64.pyd +0 -0
  357. hydpy/cythons/autogen/configutils.pxd +17 -0
  358. hydpy/cythons/autogen/configutils.pyx +119 -0
  359. hydpy/cythons/autogen/interfaceutils.cp313-win_amd64.pyd +0 -0
  360. hydpy/cythons/autogen/interfaceutils.pxd +31 -0
  361. hydpy/cythons/autogen/interfaceutils.pyx +82 -0
  362. hydpy/cythons/autogen/interputils.cp313-win_amd64.pyd +0 -0
  363. hydpy/cythons/autogen/interputils.pxd +42 -0
  364. hydpy/cythons/autogen/interputils.pyx +118 -0
  365. hydpy/cythons/autogen/masterinterface.cp313-win_amd64.pyd +0 -0
  366. hydpy/cythons/autogen/masterinterface.pxd +153 -0
  367. hydpy/cythons/autogen/masterinterface.pyx +222 -0
  368. hydpy/cythons/autogen/pointerutils.cp313-win_amd64.pyd +0 -0
  369. hydpy/cythons/autogen/pointerutils.pxd +31 -0
  370. hydpy/cythons/autogen/pointerutils.pyx +650 -0
  371. hydpy/cythons/autogen/ppolyutils.cp313-win_amd64.pyd +0 -0
  372. hydpy/cythons/autogen/ppolyutils.pxd +35 -0
  373. hydpy/cythons/autogen/ppolyutils.pyx +59 -0
  374. hydpy/cythons/autogen/quadutils.cp313-win_amd64.pyd +0 -0
  375. hydpy/cythons/autogen/quadutils.pxd +26 -0
  376. hydpy/cythons/autogen/quadutils.pyx +973 -0
  377. hydpy/cythons/autogen/rootutils.cp313-win_amd64.pyd +0 -0
  378. hydpy/cythons/autogen/rootutils.pxd +28 -0
  379. hydpy/cythons/autogen/rootutils.pyx +109 -0
  380. hydpy/cythons/autogen/sequenceutils.cp313-win_amd64.pyd +0 -0
  381. hydpy/cythons/autogen/sequenceutils.pxd +45 -0
  382. hydpy/cythons/autogen/sequenceutils.pyx +101 -0
  383. hydpy/cythons/autogen/smoothutils.cp313-win_amd64.pyd +0 -0
  384. hydpy/cythons/autogen/smoothutils.pxd +29 -0
  385. hydpy/cythons/autogen/smoothutils.pyx +833 -0
  386. hydpy/cythons/configutils.pxd +8 -0
  387. hydpy/cythons/configutils.pyi +5 -0
  388. hydpy/cythons/configutils.pyx +110 -0
  389. hydpy/cythons/interfaceutils.pxd +22 -0
  390. hydpy/cythons/interfaceutils.pyi +15 -0
  391. hydpy/cythons/interfaceutils.pyx +73 -0
  392. hydpy/cythons/interputils.pxd +33 -0
  393. hydpy/cythons/interputils.pyi +32 -0
  394. hydpy/cythons/interputils.pyx +109 -0
  395. hydpy/cythons/modelutils.py +2990 -0
  396. hydpy/cythons/pointerutils.pxd +22 -0
  397. hydpy/cythons/pointerutils.pyi +89 -0
  398. hydpy/cythons/pointerutils.pyx +641 -0
  399. hydpy/cythons/ppolyutils.pxd +26 -0
  400. hydpy/cythons/ppolyutils.pyi +21 -0
  401. hydpy/cythons/ppolyutils.pyx +50 -0
  402. hydpy/cythons/quadutils.pxd +17 -0
  403. hydpy/cythons/quadutils.pyi +13 -0
  404. hydpy/cythons/quadutils.pyx +964 -0
  405. hydpy/cythons/rootutils.pxd +19 -0
  406. hydpy/cythons/rootutils.pyi +21 -0
  407. hydpy/cythons/rootutils.pyx +100 -0
  408. hydpy/cythons/sequenceutils.pxd +36 -0
  409. hydpy/cythons/sequenceutils.pyi +7 -0
  410. hydpy/cythons/sequenceutils.pyx +92 -0
  411. hydpy/cythons/smoothutils.pxd +20 -0
  412. hydpy/cythons/smoothutils.pyi +15 -0
  413. hydpy/cythons/smoothutils.pyx +824 -0
  414. hydpy/data/HydPy-H-Lahn/conditions/init_1996_01_01_00_00_00/land_dill_assl.py +13 -0
  415. hydpy/data/HydPy-H-Lahn/conditions/init_1996_01_01_00_00_00/land_lahn_kalk.py +13 -0
  416. hydpy/data/HydPy-H-Lahn/conditions/init_1996_01_01_00_00_00/land_lahn_leun.py +14 -0
  417. hydpy/data/HydPy-H-Lahn/conditions/init_1996_01_01_00_00_00/land_lahn_marb.py +13 -0
  418. hydpy/data/HydPy-H-Lahn/conditions/init_1996_01_01_00_00_00/stream_dill_assl_lahn_leun.py +5 -0
  419. hydpy/data/HydPy-H-Lahn/conditions/init_1996_01_01_00_00_00/stream_lahn_leun_lahn_kalk.py +5 -0
  420. hydpy/data/HydPy-H-Lahn/conditions/init_1996_01_01_00_00_00/stream_lahn_marb_lahn_leun.py +5 -0
  421. hydpy/data/HydPy-H-Lahn/control/default/land.py +9 -0
  422. hydpy/data/HydPy-H-Lahn/control/default/land_dill_assl.py +57 -0
  423. hydpy/data/HydPy-H-Lahn/control/default/land_lahn_kalk.py +57 -0
  424. hydpy/data/HydPy-H-Lahn/control/default/land_lahn_leun.py +56 -0
  425. hydpy/data/HydPy-H-Lahn/control/default/land_lahn_marb.py +57 -0
  426. hydpy/data/HydPy-H-Lahn/control/default/stream_dill_assl_lahn_leun.py +7 -0
  427. hydpy/data/HydPy-H-Lahn/control/default/stream_lahn_leun_lahn_kalk.py +7 -0
  428. hydpy/data/HydPy-H-Lahn/control/default/stream_lahn_marb_lahn_leun.py +7 -0
  429. hydpy/data/HydPy-H-Lahn/multiple_runs.xml +309 -0
  430. hydpy/data/HydPy-H-Lahn/multiple_runs_alpha.xml +71 -0
  431. hydpy/data/HydPy-H-Lahn/network/default/headwaters.py +11 -0
  432. hydpy/data/HydPy-H-Lahn/network/default/nonheadwaters.py +11 -0
  433. hydpy/data/HydPy-H-Lahn/network/default/streams.py +8 -0
  434. hydpy/data/HydPy-H-Lahn/series/default/dill_assl_obs_q.asc +11387 -0
  435. hydpy/data/HydPy-H-Lahn/series/default/evap_pet_hbv96_input_normalairtemperature.nc +0 -0
  436. hydpy/data/HydPy-H-Lahn/series/default/evap_pet_hbv96_input_normalevapotranspiration.nc +0 -0
  437. hydpy/data/HydPy-H-Lahn/series/default/hland_96_input_p.nc +0 -0
  438. hydpy/data/HydPy-H-Lahn/series/default/hland_96_input_t.nc +0 -0
  439. hydpy/data/HydPy-H-Lahn/series/default/lahn_kalk_obs_q.asc +11387 -0
  440. hydpy/data/HydPy-H-Lahn/series/default/lahn_leun_obs_q.asc +11387 -0
  441. hydpy/data/HydPy-H-Lahn/series/default/lahn_marb_obs_q.asc +11387 -0
  442. hydpy/data/HydPy-H-Lahn/series/default/land_dill_assl_evap_pet_hbv96_input_normalairtemperature.asc +11387 -0
  443. hydpy/data/HydPy-H-Lahn/series/default/land_dill_assl_evap_pet_hbv96_input_normalevapotranspiration.asc +11387 -0
  444. hydpy/data/HydPy-H-Lahn/series/default/land_dill_assl_hland_96_input_p.asc +11387 -0
  445. hydpy/data/HydPy-H-Lahn/series/default/land_dill_assl_hland_96_input_t.asc +11387 -0
  446. hydpy/data/HydPy-H-Lahn/series/default/land_lahn_kalk_evap_pet_hbv96_input_normalairtemperature.asc +11387 -0
  447. hydpy/data/HydPy-H-Lahn/series/default/land_lahn_kalk_evap_pet_hbv96_input_normalevapotranspiration.asc +11387 -0
  448. hydpy/data/HydPy-H-Lahn/series/default/land_lahn_kalk_hland_96_input_p.asc +11387 -0
  449. hydpy/data/HydPy-H-Lahn/series/default/land_lahn_kalk_hland_96_input_t.asc +11387 -0
  450. hydpy/data/HydPy-H-Lahn/series/default/land_lahn_leun_evap_pet_hbv96_input_normalairtemperature.asc +11387 -0
  451. hydpy/data/HydPy-H-Lahn/series/default/land_lahn_leun_evap_pet_hbv96_input_normalevapotranspiration.asc +11387 -0
  452. hydpy/data/HydPy-H-Lahn/series/default/land_lahn_leun_hland_96_input_p.asc +11387 -0
  453. hydpy/data/HydPy-H-Lahn/series/default/land_lahn_leun_hland_96_input_t.asc +11387 -0
  454. hydpy/data/HydPy-H-Lahn/series/default/land_lahn_marb_evap_pet_hbv96_input_normalairtemperature.asc +11387 -0
  455. hydpy/data/HydPy-H-Lahn/series/default/land_lahn_marb_evap_pet_hbv96_input_normalevapotranspiration.asc +11387 -0
  456. hydpy/data/HydPy-H-Lahn/series/default/land_lahn_marb_hland_96_input_p.asc +11387 -0
  457. hydpy/data/HydPy-H-Lahn/series/default/land_lahn_marb_hland_96_input_t.asc +11387 -0
  458. hydpy/data/HydPy-H-Lahn/series/default/obs_q.nc +0 -0
  459. hydpy/data/HydPy-H-Lahn/single_run.xml +152 -0
  460. hydpy/data/HydPy-H-Lahn/single_run.xmlt +143 -0
  461. hydpy/data/__init__.py +17 -0
  462. hydpy/docs/__init__.py +0 -0
  463. hydpy/docs/autofigs/__init__.py +0 -0
  464. hydpy/docs/bib/__init__.py +0 -0
  465. hydpy/docs/bib/refs.bib +566 -0
  466. hydpy/docs/combine_docversions.py +133 -0
  467. hydpy/docs/draw_model_sketches.py +1301 -0
  468. hydpy/docs/enable_autodoc.py +7 -0
  469. hydpy/docs/figs/HydPy-G-GR4.png +0 -0
  470. hydpy/docs/figs/HydPy-G-GR5.png +0 -0
  471. hydpy/docs/figs/HydPy-G-GR6.png +0 -0
  472. hydpy/docs/figs/HydPy-H-HBV96-COSERO.png +0 -0
  473. hydpy/docs/figs/HydPy-H-HBV96-PREVAH.png +0 -0
  474. hydpy/docs/figs/HydPy-H-HBV96.png +0 -0
  475. hydpy/docs/figs/HydPy-H-Lahn.png +0 -0
  476. hydpy/docs/figs/HydPy-KinW-Williams.png +0 -0
  477. hydpy/docs/figs/HydPy-L-DD.png +0 -0
  478. hydpy/docs/figs/HydPy-W-Wag.png +0 -0
  479. hydpy/docs/figs/HydPy_Logo.png +0 -0
  480. hydpy/docs/figs/HydPy_Logo_Text.png +0 -0
  481. hydpy/docs/figs/IDLE-editor.png +0 -0
  482. hydpy/docs/figs/IDLE-shell.png +0 -0
  483. hydpy/docs/figs/LAWA_river-basin-bumbers.png +0 -0
  484. hydpy/docs/figs/__init__.py +0 -0
  485. hydpy/docs/html_/__init__.py +0 -0
  486. hydpy/docs/polish_html.py +57 -0
  487. hydpy/docs/prepare.py +224 -0
  488. hydpy/docs/publish_docs.py +53 -0
  489. hydpy/docs/rst/HydPy-ARMA.rst +27 -0
  490. hydpy/docs/rst/HydPy-Conv.rst +22 -0
  491. hydpy/docs/rst/HydPy-Dam.rst +79 -0
  492. hydpy/docs/rst/HydPy-Dummy.rst +21 -0
  493. hydpy/docs/rst/HydPy-Evap.rst +26 -0
  494. hydpy/docs/rst/HydPy-Exch.rst +36 -0
  495. hydpy/docs/rst/HydPy-G.rst +40 -0
  496. hydpy/docs/rst/HydPy-GA.rst +34 -0
  497. hydpy/docs/rst/HydPy-H.rst +24 -0
  498. hydpy/docs/rst/HydPy-KinW.rst +32 -0
  499. hydpy/docs/rst/HydPy-L.rst +42 -0
  500. hydpy/docs/rst/HydPy-Meteo.rst +28 -0
  501. hydpy/docs/rst/HydPy-Musk.rst +21 -0
  502. hydpy/docs/rst/HydPy-Rconc.rst +17 -0
  503. hydpy/docs/rst/HydPy-SW1D.rst +49 -0
  504. hydpy/docs/rst/HydPy-Test.rst +19 -0
  505. hydpy/docs/rst/HydPy-W.rst +20 -0
  506. hydpy/docs/rst/HydPy-WHMod.rst +19 -0
  507. hydpy/docs/rst/HydPy-WQ.rst +20 -0
  508. hydpy/docs/rst/__init__.py +0 -0
  509. hydpy/docs/rst/additional_repositories.rst +40 -0
  510. hydpy/docs/rst/auxiliaries.rst +31 -0
  511. hydpy/docs/rst/continuous_integration.rst +75 -0
  512. hydpy/docs/rst/core.rst +75 -0
  513. hydpy/docs/rst/cythons.rst +47 -0
  514. hydpy/docs/rst/definitions.rst +506 -0
  515. hydpy/docs/rst/developer_guide.rst +54 -0
  516. hydpy/docs/rst/example_projects.rst +40 -0
  517. hydpy/docs/rst/execution.rst +22 -0
  518. hydpy/docs/rst/framework_tools.rst +56 -0
  519. hydpy/docs/rst/how_to_read_the_reference_manual.rst +156 -0
  520. hydpy/docs/rst/hydpydependencies.rst +55 -0
  521. hydpy/docs/rst/index.rst +125 -0
  522. hydpy/docs/rst/installation.rst +155 -0
  523. hydpy/docs/rst/model_families.rst +79 -0
  524. hydpy/docs/rst/model_overview.rst +291 -0
  525. hydpy/docs/rst/modelimports.rst +10 -0
  526. hydpy/docs/rst/options.rst +119 -0
  527. hydpy/docs/rst/programming_style.rst +572 -0
  528. hydpy/docs/rst/project_structure.rst +520 -0
  529. hydpy/docs/rst/quickstart.rst +304 -0
  530. hydpy/docs/rst/reference_manual.rst +29 -0
  531. hydpy/docs/rst/required_tools.rst +50 -0
  532. hydpy/docs/rst/simulation.rst +514 -0
  533. hydpy/docs/rst/submodel_interfaces.rst +32 -0
  534. hydpy/docs/rst/tests_and_documentation.rst +85 -0
  535. hydpy/docs/rst/user_guide.rst +38 -0
  536. hydpy/docs/rst/version_control.rst +116 -0
  537. hydpy/docs/rst/zbibliography.rst +8 -0
  538. hydpy/docs/sphinx/__init__.py +0 -0
  539. hydpy/docs/sphinx/_themes/basic_hydpy/changes/frameset.html +11 -0
  540. hydpy/docs/sphinx/_themes/basic_hydpy/changes/rstsource.html +15 -0
  541. hydpy/docs/sphinx/_themes/basic_hydpy/changes/versionchanges.html +33 -0
  542. hydpy/docs/sphinx/_themes/basic_hydpy/defindex.html +35 -0
  543. hydpy/docs/sphinx/_themes/basic_hydpy/domainindex.html +56 -0
  544. hydpy/docs/sphinx/_themes/basic_hydpy/genindex-single.html +63 -0
  545. hydpy/docs/sphinx/_themes/basic_hydpy/genindex-split.html +41 -0
  546. hydpy/docs/sphinx/_themes/basic_hydpy/genindex.html +76 -0
  547. hydpy/docs/sphinx/_themes/basic_hydpy/globaltoc.html +11 -0
  548. hydpy/docs/sphinx/_themes/basic_hydpy/layout.html +221 -0
  549. hydpy/docs/sphinx/_themes/basic_hydpy/localtoc.html +15 -0
  550. hydpy/docs/sphinx/_themes/basic_hydpy/opensearch.xml +13 -0
  551. hydpy/docs/sphinx/_themes/basic_hydpy/page.html +13 -0
  552. hydpy/docs/sphinx/_themes/basic_hydpy/relations.html +23 -0
  553. hydpy/docs/sphinx/_themes/basic_hydpy/search.html +65 -0
  554. hydpy/docs/sphinx/_themes/basic_hydpy/searchbox.html +21 -0
  555. hydpy/docs/sphinx/_themes/basic_hydpy/searchfield.html +23 -0
  556. hydpy/docs/sphinx/_themes/basic_hydpy/sourcelink.html +18 -0
  557. hydpy/docs/sphinx/_themes/basic_hydpy/static/basic.css_t +925 -0
  558. hydpy/docs/sphinx/_themes/basic_hydpy/static/doctools.js +156 -0
  559. hydpy/docs/sphinx/_themes/basic_hydpy/static/documentation_options.js_t +13 -0
  560. hydpy/docs/sphinx/_themes/basic_hydpy/static/file.png +0 -0
  561. hydpy/docs/sphinx/_themes/basic_hydpy/static/language_data.js_t +26 -0
  562. hydpy/docs/sphinx/_themes/basic_hydpy/static/minus.png +0 -0
  563. hydpy/docs/sphinx/_themes/basic_hydpy/static/plus.png +0 -0
  564. hydpy/docs/sphinx/_themes/basic_hydpy/static/searchtools.js +574 -0
  565. hydpy/docs/sphinx/_themes/basic_hydpy/static/sphinx_highlight.js +154 -0
  566. hydpy/docs/sphinx/_themes/basic_hydpy/theme.conf +16 -0
  567. hydpy/docs/sphinx/_themes/classic_hydpy/layout.html +23 -0
  568. hydpy/docs/sphinx/_themes/classic_hydpy/static/classic.css_t +358 -0
  569. hydpy/docs/sphinx/_themes/classic_hydpy/static/sidebar.js_t +72 -0
  570. hydpy/docs/sphinx/_themes/classic_hydpy/theme.conf +32 -0
  571. hydpy/docs/sphinx/conf.py +398 -0
  572. hydpy/docs/sphinx/defaultlinks_extension.py +36 -0
  573. hydpy/docs/sphinx/integrationtest_extension.py +104 -0
  574. hydpy/docs/sphinx/projectstructure_extension.py +58 -0
  575. hydpy/docs/sphinx/submodelgraph_extension.py +53 -0
  576. hydpy/exe/__init__.py +0 -0
  577. hydpy/exe/commandtools.py +651 -0
  578. hydpy/exe/hyd.py +277 -0
  579. hydpy/exe/modelimports.py +41 -0
  580. hydpy/exe/replacetools.py +216 -0
  581. hydpy/exe/servertools.py +2348 -0
  582. hydpy/exe/xmltools.py +3280 -0
  583. hydpy/interfaces/__init__.py +0 -0
  584. hydpy/interfaces/aetinterfaces.py +94 -0
  585. hydpy/interfaces/dischargeinterfaces.py +45 -0
  586. hydpy/interfaces/petinterfaces.py +117 -0
  587. hydpy/interfaces/precipinterfaces.py +42 -0
  588. hydpy/interfaces/radiationinterfaces.py +79 -0
  589. hydpy/interfaces/rconcinterfaces.py +30 -0
  590. hydpy/interfaces/routinginterfaces.py +324 -0
  591. hydpy/interfaces/soilinterfaces.py +96 -0
  592. hydpy/interfaces/stateinterfaces.py +98 -0
  593. hydpy/interfaces/tempinterfaces.py +46 -0
  594. hydpy/models/__init__.py +0 -0
  595. hydpy/models/arma/__init__.py +14 -0
  596. hydpy/models/arma/arma_control.py +383 -0
  597. hydpy/models/arma/arma_derived.py +204 -0
  598. hydpy/models/arma/arma_fluxes.py +41 -0
  599. hydpy/models/arma/arma_inlets.py +11 -0
  600. hydpy/models/arma/arma_logs.py +19 -0
  601. hydpy/models/arma/arma_model.py +461 -0
  602. hydpy/models/arma/arma_outlets.py +11 -0
  603. hydpy/models/arma_rimorido.py +381 -0
  604. hydpy/models/conv/__init__.py +12 -0
  605. hydpy/models/conv/conv_control.py +303 -0
  606. hydpy/models/conv/conv_derived.py +271 -0
  607. hydpy/models/conv/conv_fluxes.py +54 -0
  608. hydpy/models/conv/conv_inlets.py +11 -0
  609. hydpy/models/conv/conv_model.py +687 -0
  610. hydpy/models/conv/conv_outlets.py +11 -0
  611. hydpy/models/conv_idw.py +120 -0
  612. hydpy/models/conv_idw_ed.py +184 -0
  613. hydpy/models/conv_nn.py +112 -0
  614. hydpy/models/dam/__init__.py +16 -0
  615. hydpy/models/dam/dam_aides.py +17 -0
  616. hydpy/models/dam/dam_control.py +346 -0
  617. hydpy/models/dam/dam_derived.py +559 -0
  618. hydpy/models/dam/dam_factors.py +46 -0
  619. hydpy/models/dam/dam_fluxes.py +179 -0
  620. hydpy/models/dam/dam_inlets.py +29 -0
  621. hydpy/models/dam/dam_logs.py +52 -0
  622. hydpy/models/dam/dam_model.py +5011 -0
  623. hydpy/models/dam/dam_outlets.py +23 -0
  624. hydpy/models/dam/dam_receivers.py +41 -0
  625. hydpy/models/dam/dam_senders.py +23 -0
  626. hydpy/models/dam/dam_solver.py +75 -0
  627. hydpy/models/dam/dam_states.py +11 -0
  628. hydpy/models/dam_llake.py +499 -0
  629. hydpy/models/dam_lreservoir.py +548 -0
  630. hydpy/models/dam_lretention.py +343 -0
  631. hydpy/models/dam_pump.py +278 -0
  632. hydpy/models/dam_pump_sluice.py +339 -0
  633. hydpy/models/dam_sluice.py +319 -0
  634. hydpy/models/dam_v001.py +1127 -0
  635. hydpy/models/dam_v002.py +381 -0
  636. hydpy/models/dam_v003.py +422 -0
  637. hydpy/models/dam_v004.py +665 -0
  638. hydpy/models/dam_v005.py +479 -0
  639. hydpy/models/dummy/__init__.py +15 -0
  640. hydpy/models/dummy/dummy_control.py +22 -0
  641. hydpy/models/dummy/dummy_fluxes.py +11 -0
  642. hydpy/models/dummy/dummy_inlets.py +11 -0
  643. hydpy/models/dummy/dummy_inputs.py +41 -0
  644. hydpy/models/dummy/dummy_model.py +196 -0
  645. hydpy/models/dummy/dummy_outlets.py +11 -0
  646. hydpy/models/dummy_interceptedwater.py +85 -0
  647. hydpy/models/dummy_node2node.py +83 -0
  648. hydpy/models/dummy_snowalbedo.py +84 -0
  649. hydpy/models/dummy_snowcover.py +84 -0
  650. hydpy/models/dummy_snowycanopy.py +86 -0
  651. hydpy/models/dummy_soilwater.py +85 -0
  652. hydpy/models/evap/__init__.py +13 -0
  653. hydpy/models/evap/evap_control.py +354 -0
  654. hydpy/models/evap/evap_derived.py +236 -0
  655. hydpy/models/evap/evap_factors.py +188 -0
  656. hydpy/models/evap/evap_fixed.py +68 -0
  657. hydpy/models/evap/evap_fluxes.py +150 -0
  658. hydpy/models/evap/evap_inputs.py +54 -0
  659. hydpy/models/evap/evap_logs.py +91 -0
  660. hydpy/models/evap/evap_masks.py +48 -0
  661. hydpy/models/evap/evap_model.py +9170 -0
  662. hydpy/models/evap/evap_parameters.py +149 -0
  663. hydpy/models/evap/evap_sequences.py +32 -0
  664. hydpy/models/evap/evap_states.py +18 -0
  665. hydpy/models/evap_aet_hbv96.py +372 -0
  666. hydpy/models/evap_aet_minhas.py +331 -0
  667. hydpy/models/evap_aet_morsim.py +627 -0
  668. hydpy/models/evap_pet_ambav1.py +483 -0
  669. hydpy/models/evap_pet_hbv96.py +147 -0
  670. hydpy/models/evap_pet_m.py +94 -0
  671. hydpy/models/evap_pet_mlc.py +107 -0
  672. hydpy/models/evap_ret_fao56.py +265 -0
  673. hydpy/models/evap_ret_io.py +74 -0
  674. hydpy/models/evap_ret_tw2002.py +165 -0
  675. hydpy/models/exch/__init__.py +14 -0
  676. hydpy/models/exch/exch_control.py +262 -0
  677. hydpy/models/exch/exch_derived.py +36 -0
  678. hydpy/models/exch/exch_factors.py +26 -0
  679. hydpy/models/exch/exch_fluxes.py +48 -0
  680. hydpy/models/exch/exch_inlets.py +11 -0
  681. hydpy/models/exch/exch_logs.py +12 -0
  682. hydpy/models/exch/exch_model.py +451 -0
  683. hydpy/models/exch/exch_outlets.py +17 -0
  684. hydpy/models/exch/exch_receivers.py +17 -0
  685. hydpy/models/exch_branch_hbv96.py +186 -0
  686. hydpy/models/exch_waterlevel.py +73 -0
  687. hydpy/models/exch_weir_hbv96.py +609 -0
  688. hydpy/models/ga/__init__.py +14 -0
  689. hydpy/models/ga/ga_aides.py +17 -0
  690. hydpy/models/ga/ga_control.py +208 -0
  691. hydpy/models/ga/ga_derived.py +77 -0
  692. hydpy/models/ga/ga_fluxes.py +83 -0
  693. hydpy/models/ga/ga_inputs.py +26 -0
  694. hydpy/models/ga/ga_logs.py +17 -0
  695. hydpy/models/ga/ga_model.py +2952 -0
  696. hydpy/models/ga/ga_states.py +87 -0
  697. hydpy/models/ga_garto.py +1001 -0
  698. hydpy/models/ga_garto_submodel1.py +79 -0
  699. hydpy/models/gland/__init__.py +14 -0
  700. hydpy/models/gland/gland_control.py +90 -0
  701. hydpy/models/gland/gland_derived.py +113 -0
  702. hydpy/models/gland/gland_fluxes.py +137 -0
  703. hydpy/models/gland/gland_inputs.py +12 -0
  704. hydpy/models/gland/gland_model.py +1439 -0
  705. hydpy/models/gland/gland_outlets.py +11 -0
  706. hydpy/models/gland/gland_states.py +90 -0
  707. hydpy/models/gland_gr4.py +501 -0
  708. hydpy/models/gland_gr5.py +463 -0
  709. hydpy/models/gland_gr6.py +487 -0
  710. hydpy/models/hland/__init__.py +20 -0
  711. hydpy/models/hland/hland_aides.py +19 -0
  712. hydpy/models/hland/hland_constants.py +37 -0
  713. hydpy/models/hland/hland_control.py +1530 -0
  714. hydpy/models/hland/hland_derived.py +683 -0
  715. hydpy/models/hland/hland_factors.py +57 -0
  716. hydpy/models/hland/hland_fixed.py +42 -0
  717. hydpy/models/hland/hland_fluxes.py +279 -0
  718. hydpy/models/hland/hland_inputs.py +19 -0
  719. hydpy/models/hland/hland_masks.py +107 -0
  720. hydpy/models/hland/hland_model.py +4664 -0
  721. hydpy/models/hland/hland_outlets.py +11 -0
  722. hydpy/models/hland/hland_parameters.py +227 -0
  723. hydpy/models/hland/hland_sequences.py +382 -0
  724. hydpy/models/hland/hland_states.py +236 -0
  725. hydpy/models/hland_96.py +1812 -0
  726. hydpy/models/hland_96c.py +1196 -0
  727. hydpy/models/hland_96p.py +1204 -0
  728. hydpy/models/kinw/__init__.py +18 -0
  729. hydpy/models/kinw/kinw_aides.py +306 -0
  730. hydpy/models/kinw/kinw_control.py +270 -0
  731. hydpy/models/kinw/kinw_derived.py +197 -0
  732. hydpy/models/kinw/kinw_fixed.py +33 -0
  733. hydpy/models/kinw/kinw_fluxes.py +37 -0
  734. hydpy/models/kinw/kinw_inlets.py +11 -0
  735. hydpy/models/kinw/kinw_model.py +3026 -0
  736. hydpy/models/kinw/kinw_outlets.py +11 -0
  737. hydpy/models/kinw/kinw_solver.py +45 -0
  738. hydpy/models/kinw/kinw_states.py +17 -0
  739. hydpy/models/kinw_williams.py +1299 -0
  740. hydpy/models/kinw_williams_ext.py +768 -0
  741. hydpy/models/lland/__init__.py +42 -0
  742. hydpy/models/lland/lland_aides.py +38 -0
  743. hydpy/models/lland/lland_constants.py +88 -0
  744. hydpy/models/lland/lland_control.py +1329 -0
  745. hydpy/models/lland/lland_derived.py +380 -0
  746. hydpy/models/lland/lland_factors.py +18 -0
  747. hydpy/models/lland/lland_fixed.py +128 -0
  748. hydpy/models/lland/lland_fluxes.py +626 -0
  749. hydpy/models/lland/lland_inlets.py +12 -0
  750. hydpy/models/lland/lland_inputs.py +33 -0
  751. hydpy/models/lland/lland_logs.py +17 -0
  752. hydpy/models/lland/lland_masks.py +212 -0
  753. hydpy/models/lland/lland_model.py +7690 -0
  754. hydpy/models/lland/lland_outlets.py +12 -0
  755. hydpy/models/lland/lland_parameters.py +195 -0
  756. hydpy/models/lland/lland_sequences.py +67 -0
  757. hydpy/models/lland/lland_states.py +280 -0
  758. hydpy/models/lland_dd.py +2270 -0
  759. hydpy/models/lland_knauf.py +2156 -0
  760. hydpy/models/lland_knauf_ic.py +1920 -0
  761. hydpy/models/meteo/__init__.py +12 -0
  762. hydpy/models/meteo/meteo_control.py +154 -0
  763. hydpy/models/meteo/meteo_derived.py +159 -0
  764. hydpy/models/meteo/meteo_factors.py +88 -0
  765. hydpy/models/meteo/meteo_fixed.py +19 -0
  766. hydpy/models/meteo/meteo_fluxes.py +46 -0
  767. hydpy/models/meteo/meteo_inputs.py +47 -0
  768. hydpy/models/meteo/meteo_logs.py +30 -0
  769. hydpy/models/meteo/meteo_model.py +2904 -0
  770. hydpy/models/meteo/meteo_parameters.py +14 -0
  771. hydpy/models/meteo/meteo_sequences.py +22 -0
  772. hydpy/models/meteo_clear_glob_io.py +77 -0
  773. hydpy/models/meteo_glob_fao56.py +217 -0
  774. hydpy/models/meteo_glob_io.py +68 -0
  775. hydpy/models/meteo_glob_morsim.py +444 -0
  776. hydpy/models/meteo_precip_io.py +76 -0
  777. hydpy/models/meteo_psun_sun_glob_io.py +83 -0
  778. hydpy/models/meteo_sun_fao56.py +188 -0
  779. hydpy/models/meteo_sun_morsim.py +466 -0
  780. hydpy/models/meteo_temp_io.py +76 -0
  781. hydpy/models/musk/__init__.py +15 -0
  782. hydpy/models/musk/musk_control.py +328 -0
  783. hydpy/models/musk/musk_derived.py +32 -0
  784. hydpy/models/musk/musk_factors.py +53 -0
  785. hydpy/models/musk/musk_fluxes.py +24 -0
  786. hydpy/models/musk/musk_inlets.py +11 -0
  787. hydpy/models/musk/musk_masks.py +15 -0
  788. hydpy/models/musk/musk_model.py +838 -0
  789. hydpy/models/musk/musk_outlets.py +11 -0
  790. hydpy/models/musk/musk_sequences.py +88 -0
  791. hydpy/models/musk/musk_solver.py +68 -0
  792. hydpy/models/musk/musk_states.py +64 -0
  793. hydpy/models/musk_classic.py +228 -0
  794. hydpy/models/musk_mct.py +1247 -0
  795. hydpy/models/rconc/__init__.py +12 -0
  796. hydpy/models/rconc/rconc_control.py +473 -0
  797. hydpy/models/rconc/rconc_derived.py +76 -0
  798. hydpy/models/rconc/rconc_fluxes.py +19 -0
  799. hydpy/models/rconc/rconc_logs.py +74 -0
  800. hydpy/models/rconc/rconc_model.py +260 -0
  801. hydpy/models/rconc/rconc_states.py +11 -0
  802. hydpy/models/rconc_nash.py +48 -0
  803. hydpy/models/rconc_uh.py +53 -0
  804. hydpy/models/sw1d/__init__.py +17 -0
  805. hydpy/models/sw1d/sw1d_control.py +356 -0
  806. hydpy/models/sw1d/sw1d_derived.py +85 -0
  807. hydpy/models/sw1d/sw1d_factors.py +78 -0
  808. hydpy/models/sw1d/sw1d_fixed.py +12 -0
  809. hydpy/models/sw1d/sw1d_fluxes.py +55 -0
  810. hydpy/models/sw1d/sw1d_inlets.py +17 -0
  811. hydpy/models/sw1d/sw1d_model.py +3385 -0
  812. hydpy/models/sw1d/sw1d_outlets.py +11 -0
  813. hydpy/models/sw1d/sw1d_receivers.py +11 -0
  814. hydpy/models/sw1d/sw1d_senders.py +11 -0
  815. hydpy/models/sw1d/sw1d_states.py +23 -0
  816. hydpy/models/sw1d_channel.py +2051 -0
  817. hydpy/models/sw1d_gate_out.py +599 -0
  818. hydpy/models/sw1d_lias.py +105 -0
  819. hydpy/models/sw1d_lias_sluice.py +531 -0
  820. hydpy/models/sw1d_network.py +1219 -0
  821. hydpy/models/sw1d_pump.py +448 -0
  822. hydpy/models/sw1d_q_in.py +79 -0
  823. hydpy/models/sw1d_q_out.py +81 -0
  824. hydpy/models/sw1d_storage.py +78 -0
  825. hydpy/models/sw1d_weir_out.py +75 -0
  826. hydpy/models/test/__init__.py +14 -0
  827. hydpy/models/test/test_control.py +28 -0
  828. hydpy/models/test/test_fluxes.py +17 -0
  829. hydpy/models/test/test_model.py +201 -0
  830. hydpy/models/test/test_solver.py +48 -0
  831. hydpy/models/test/test_states.py +17 -0
  832. hydpy/models/test_discontinous.py +46 -0
  833. hydpy/models/test_stiff0d.py +47 -0
  834. hydpy/models/test_stiff1d.py +42 -0
  835. hydpy/models/whmod/__init__.py +21 -0
  836. hydpy/models/whmod/whmod_constants.py +77 -0
  837. hydpy/models/whmod/whmod_control.py +333 -0
  838. hydpy/models/whmod/whmod_derived.py +210 -0
  839. hydpy/models/whmod/whmod_factors.py +9 -0
  840. hydpy/models/whmod/whmod_fluxes.py +105 -0
  841. hydpy/models/whmod/whmod_inputs.py +15 -0
  842. hydpy/models/whmod/whmod_masks.py +178 -0
  843. hydpy/models/whmod/whmod_model.py +2091 -0
  844. hydpy/models/whmod/whmod_parameters.py +155 -0
  845. hydpy/models/whmod/whmod_sequences.py +193 -0
  846. hydpy/models/whmod/whmod_states.py +73 -0
  847. hydpy/models/whmod_rural.py +794 -0
  848. hydpy/models/whmod_urban.py +1011 -0
  849. hydpy/models/wland/__init__.py +43 -0
  850. hydpy/models/wland/wland_aides.py +55 -0
  851. hydpy/models/wland/wland_constants.py +103 -0
  852. hydpy/models/wland/wland_control.py +508 -0
  853. hydpy/models/wland/wland_derived.py +330 -0
  854. hydpy/models/wland/wland_factors.py +11 -0
  855. hydpy/models/wland/wland_fixed.py +12 -0
  856. hydpy/models/wland/wland_fluxes.py +166 -0
  857. hydpy/models/wland/wland_inputs.py +33 -0
  858. hydpy/models/wland/wland_masks.py +54 -0
  859. hydpy/models/wland/wland_model.py +3755 -0
  860. hydpy/models/wland/wland_outlets.py +11 -0
  861. hydpy/models/wland/wland_parameters.py +214 -0
  862. hydpy/models/wland/wland_sequences.py +108 -0
  863. hydpy/models/wland/wland_solver.py +45 -0
  864. hydpy/models/wland/wland_states.py +56 -0
  865. hydpy/models/wland_gd.py +888 -0
  866. hydpy/models/wland_wag.py +1244 -0
  867. hydpy/models/wq/__init__.py +14 -0
  868. hydpy/models/wq/wq_control.py +117 -0
  869. hydpy/models/wq/wq_derived.py +182 -0
  870. hydpy/models/wq/wq_factors.py +79 -0
  871. hydpy/models/wq/wq_fluxes.py +17 -0
  872. hydpy/models/wq/wq_model.py +1889 -0
  873. hydpy/models/wq_trapeze.py +168 -0
  874. hydpy/models/wq_trapeze_strickler.py +101 -0
  875. hydpy/models/wq_walrus.py +57 -0
  876. hydpy/py.typed +0 -0
  877. hydpy/tests/.coveragerc +22 -0
  878. hydpy/tests/__init__.py +0 -0
  879. hydpy/tests/check_consistency.py +32 -0
  880. hydpy/tests/hydpydoctestcustomize.pth +1 -0
  881. hydpy/tests/hydpydoctestcustomize.py +15 -0
  882. hydpy/tests/iotesting/__init__.py +0 -0
  883. hydpy/tests/run_doctests.py +233 -0
  884. hydpy-6.2.dev1.data/scripts/hyd.py +277 -0
  885. hydpy-6.2.dev1.dist-info/LICENSE +165 -0
  886. hydpy-6.2.dev1.dist-info/METADATA +163 -0
  887. hydpy-6.2.dev1.dist-info/RECORD +890 -0
  888. hydpy-6.2.dev1.dist-info/WHEEL +5 -0
  889. hydpy-6.2.dev1.dist-info/licenses_hydpy_installer.txt +42 -0
  890. hydpy-6.2.dev1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,3567 @@
1
+ """This module specifies the handling of dates and periods in *HydPy* projects.
2
+
3
+ .. _`Time Coordinate`: http://cfconventions.org/Data/cf-conventions/\
4
+ cf-conventions-1.7/cf-conventions.html#time-coordinate
5
+ """
6
+
7
+ # import...
8
+ # ...from standard library
9
+ from __future__ import annotations
10
+ import calendar
11
+ import collections
12
+ import contextlib
13
+ import copy
14
+ import datetime as datetime_
15
+ import time
16
+
17
+ # ...from third party packages
18
+ import numpy
19
+
20
+ # ...from HydPy
21
+ import hydpy
22
+ from hydpy import config
23
+ from hydpy.core import objecttools
24
+ from hydpy.core import propertytools
25
+ from hydpy.core.typingtools import *
26
+
27
+ # The import of `_strptime` is not thread save. The following call of
28
+ # `strptime` is supposed to prevent possible problems arising from this bug.
29
+ time.strptime("1999", "%Y")
30
+
31
+ DateConstrArg: TypeAlias = Union[datetime_.datetime, str, "Date"]
32
+ PeriodConstrArg: TypeAlias = Union[datetime_.timedelta, str, "Period"]
33
+ TypeDate = TypeVar("TypeDate", bound="Date")
34
+ TypePeriod = TypeVar("TypePeriod", bound="Period")
35
+ TypeTimegrid = TypeVar("TypeTimegrid", bound="Timegrid")
36
+ TypeTOY = TypeVar("TypeTOY", bound="TOY") # pylint: disable=invalid-name
37
+ TypeUnit: TypeAlias = Literal["days", "d", "hours", "h", "minutes", "m", "seconds", "s"]
38
+
39
+
40
+ class Date:
41
+ """Handles a single date.
42
+
43
+ We built class the |Date| on top of the Python module |datetime|. It wraps
44
+ |datetime.datetime| objects and specialise this general class on the needs of
45
+ *HydPy* users.
46
+
47
+ |Date| objects can be initialised via |datetime.datetime| objects directly:
48
+
49
+ >>> import datetime
50
+ >>> date = datetime.datetime(1996, 11, 1, 0, 0, 0)
51
+ >>> from hydpy import Date
52
+ >>> Date(date)
53
+ Date("1996-11-01 00:00:00")
54
+
55
+ |Date| objects do not store time zone information. The |Date| object prepared
56
+ above refers to zero o'clock in the time zone defined by |Options.utcoffset|
57
+ (UTC+01:00 by default). When the initialisation argument provides other time zone
58
+ information, its date information is adjusted, which we show in the following
59
+ examples, where the prepared |datetime.datetime| objects refer to UTC 00:00 and
60
+ UTC-01:00:
61
+
62
+ >>> date = datetime.datetime(1996, 11, 1, 0, 0, 0,
63
+ ... tzinfo=datetime.timezone(datetime.timedelta(0)))
64
+ >>> Date(date)
65
+ Date("1996-11-01 01:00:00")
66
+ >>> date = datetime.datetime(1996, 11, 1, 0, 0, 0,
67
+ ... tzinfo=datetime.timezone(datetime.timedelta(hours=-1)))
68
+ >>> Date(date)
69
+ Date("1996-11-01 02:00:00")
70
+
71
+ One can change |Options.utcoffset|, but this does not affect already existing
72
+ |Date| objects:
73
+
74
+ >>> from hydpy import pub
75
+ >>> pub.options.utcoffset = 0
76
+ >>> temp = Date(date)
77
+ >>> temp
78
+ Date("1996-11-01 01:00:00")
79
+ >>> pub.options.utcoffset = 60
80
+ >>> temp
81
+ Date("1996-11-01 01:00:00")
82
+
83
+ Class |Date| accepts |str| objects as alternative constructor arguments. These are
84
+ often more rapidly defined and allow to set the |Date.style| property by the way
85
+ (see the documentation on method |Date.from_string| for more examples):
86
+
87
+ >>> Date("1996-11-01")
88
+ Date("1996-11-01 00:00:00")
89
+ >>> Date("1996.11.01")
90
+ Date("1996.11.01 00:00:00")
91
+
92
+ Invalid arguments types result in the following error:
93
+
94
+ >>> Date(1)
95
+ Traceback (most recent call last):
96
+ ...
97
+ TypeError: While trying to initialise a `Date` object based on argument `1`, the \
98
+ following error occurred: The supplied argument must be either an instance of `Date`, \
99
+ `datetime.datetime`, or `str`. The given arguments type is `int`.
100
+
101
+ In contrast to class |datetime.datetime|, class |Date| is mutable:
102
+
103
+ >>> date = Date("1996-11-01")
104
+ >>> date.hour = 12
105
+ >>> date
106
+ Date("1996-11-01 12:00:00")
107
+
108
+ Unplausible values assigned to property |Date.hour| and its related properties
109
+ result in error messages like the following:
110
+
111
+ >>> date.hour = 24
112
+ Traceback (most recent call last):
113
+ ...
114
+ ValueError: While trying to change the hour of the current Date object, the \
115
+ following error occurred: hour must be in 0..23
116
+
117
+ You can do some math with |Date| objects. First, you can add |Period| objects to
118
+ shift the date:
119
+
120
+ >>> date = Date("2000.01.01")
121
+ >>> date + "1d"
122
+ Date("2000.01.02 00:00:00")
123
+ >>> date += "12h"
124
+ >>> date
125
+ Date("2000.01.01 12:00:00")
126
+
127
+ Second, you can subtract both |Period| and other |Date| objects to shift the date
128
+ or determine the time delta, respectively:
129
+
130
+ >>> date - "1s"
131
+ Date("2000.01.01 11:59:59")
132
+ >>> date -= "12h"
133
+ >>> date
134
+ Date("2000.01.01 00:00:00")
135
+ >>> date - "2000-01-05"
136
+ Period("-4d")
137
+ >>> "2000.01.01 00:00:30" - date
138
+ Period("30s")
139
+
140
+ To try to subtract objects neither interpretable as a |Date| nor |Period| object
141
+ results in the following error:
142
+
143
+ >>> date - "1"
144
+ Traceback (most recent call last):
145
+ ...
146
+ TypeError: Object `1` of type `str` cannot be substracted from a `Date` instance.
147
+
148
+ The comparison operators work as expected:
149
+
150
+ >>> d1, d2 = Date("2000-1-1"), Date("2001-1-1")
151
+ >>> d1 < d2, d1 < "2000-1-1", "2001-1-2" < d1
152
+ (True, False, False)
153
+ >>> d1 <= d2, d1 <= "2000-1-1", "2001-1-2" <= d1
154
+ (True, True, False)
155
+ >>> d1 == d2, d1 == "2000-1-1", "2001-1-2" == d1, d1 == "1d"
156
+ (False, True, False, False)
157
+ >>> d1 != d2, d1 != "2000-1-1", "2001-1-2" != d1, d1 != "1d"
158
+ (True, False, True, True)
159
+ >>> d1 >= d2, d1 >= "2000-1-1", "2001-1-2" >= d1
160
+ (False, True, True)
161
+ >>> d1 > d2, d1 > "2000-1-1", "2001-1-2" > d1
162
+ (False, False, True)
163
+ """
164
+
165
+ # These are the so far accepted date format strings.
166
+ formatstrings = {
167
+ "os": "%Y_%m_%d_%H_%M_%S",
168
+ "iso2": "%Y-%m-%d %H:%M:%S",
169
+ "iso1": "%Y-%m-%dT%H:%M:%S",
170
+ "din1": "%d.%m.%Y %H:%M:%S",
171
+ "din2": "%Y.%m.%d %H:%M:%S",
172
+ "raw": "%Y%m%d%H%M%S",
173
+ }
174
+ # The first month of the hydrological year (e.g. November in Germany)
175
+ _firstmonth_wateryear = 11
176
+ _lastformatstring = "os", formatstrings["os"]
177
+
178
+ datetime: datetime_.datetime
179
+
180
+ def __new__(cls: type[TypeDate], date: DateConstrArg) -> TypeDate:
181
+ try:
182
+ if isinstance(date, Date):
183
+ return cls.from_date(date)
184
+ if isinstance(date, datetime_.datetime):
185
+ return cls.from_datetime(date)
186
+ if isinstance(date, str):
187
+ return cls.from_string(date)
188
+ raise TypeError(
189
+ f"The supplied argument must be either an instance of `Date`, "
190
+ f"`datetime.datetime`, or `str`. The given arguments type is "
191
+ f"`{type(date).__name__}`."
192
+ )
193
+ except BaseException:
194
+ objecttools.augment_excmessage(
195
+ f"While trying to initialise a `Date` object based on argument `{date}`"
196
+ )
197
+
198
+ @classmethod
199
+ def from_date(cls: type[TypeDate], date: Date) -> TypeDate:
200
+ """Create a new |Date| object based on another |Date| object and return it.
201
+
202
+ Initialisation from other |Date| objects preserves their |Date.style|
203
+ information:
204
+
205
+ >>> from hydpy import Date
206
+ >>> date1 = Date("2000.01.01")
207
+ >>> date2 = Date(date1)
208
+ >>> date1.style = "iso2"
209
+ >>> date3 = Date.from_date(date1)
210
+ >>> date2
211
+ Date("2000.01.01 00:00:00")
212
+ >>> date3
213
+ Date("2000-01-01 00:00:00")
214
+ """
215
+ self = super().__new__(cls)
216
+ self.datetime = date.datetime
217
+ self.style = date.style
218
+ return self
219
+
220
+ @classmethod
221
+ def from_datetime(cls: type[TypeDate], date: datetime_.datetime) -> TypeDate:
222
+ """Create a new |Date| object based on a |datetime.datetime| object and return
223
+ it.
224
+
225
+ Initialisation from |datetime.datetime| does not modify the default
226
+ |Date.style| information:
227
+
228
+ >>> from hydpy import Date
229
+ >>> from datetime import datetime, timedelta, timezone
230
+ >>> Date.from_datetime(datetime(2000, 1, 1))
231
+ Date("2000-01-01 00:00:00")
232
+
233
+ Be aware of the different minimum time resolution of class |datetime.datetime|
234
+ (microseconds) and class |Date| (seconds):
235
+
236
+ >>> Date.from_datetime(datetime(2000, 1, 1, microsecond=2))
237
+ Traceback (most recent call last):
238
+ ...
239
+ ValueError: For `Date` instances, the microsecond must be zero, but for the \
240
+ given `datetime` object it is `2` instead.
241
+
242
+ Due to a different kind of handling time zone information, the time zone
243
+ awareness of |datetime.datetime| objects is removed (see the main documentation
244
+ on class |Date| for further information:
245
+
246
+ >>> date = Date.from_datetime(
247
+ ... datetime(2000, 11, 1, tzinfo=timezone(timedelta(0))))
248
+ >>> date
249
+ Date("2000-11-01 01:00:00")
250
+ >>> date.datetime
251
+ datetime.datetime(2000, 11, 1, 1, 0)
252
+ """
253
+ if date.microsecond != 0:
254
+ raise ValueError(
255
+ f"For `Date` instances, the microsecond must be zero, but for the "
256
+ f"given `datetime` object it is `{date.microsecond:d}` instead."
257
+ )
258
+ self = super().__new__(cls)
259
+ utcoffset = date.utcoffset()
260
+ if utcoffset is not None:
261
+ date = (
262
+ date.replace(tzinfo=None)
263
+ - utcoffset
264
+ + datetime_.timedelta(minutes=hydpy.pub.options.utcoffset)
265
+ )
266
+ self.datetime = date
267
+ return self
268
+
269
+ @classmethod
270
+ def from_string(cls: type[TypeDate], date: str) -> TypeDate:
271
+ """Create a new |Date| object based on a |datetime.datetime| object and return
272
+ it.
273
+
274
+ The given string needs to match one of the following |Date.style| patterns.
275
+
276
+ The `os` style is applied in text files and folder names and does not include
277
+ any empty spaces or colons:
278
+
279
+ >>> Date.from_string("1997_11_01_00_00_00").style
280
+ 'os'
281
+
282
+ The `iso` styles are more legible and come in two flavours. `iso1` follows
283
+ ISO 8601, and `iso2` (which is the default style) omits the `T` between the
284
+ date and the time:
285
+
286
+ >>> Date.from_string("1997-11-01T00:00:00").style
287
+ 'iso1'
288
+ >>> Date.from_string("1997-11-01 00:00:00").style
289
+ 'iso2'
290
+
291
+ The `din` styles rely on points instead of hyphens. The difference between the
292
+ available flavours lies in the order of the date literals (DIN refers to a
293
+ German norm):
294
+
295
+ >>> Date("01.11.1997 00:00:00").style
296
+ 'din1'
297
+ >>> Date("1997.11.01 00:00:00").style
298
+ 'din2'
299
+
300
+ The `raw` style avoids any unnecessary characters:
301
+
302
+ >>> Date("19971101000000").style
303
+ 'raw'
304
+
305
+ You are allowed to abbreviate the input strings:
306
+
307
+ >>> for string in ("1996-11-01 00:00:00",
308
+ ... "1996-11-01 00:00",
309
+ ... "1996-11-01 00",
310
+ ... "1996-11-01"):
311
+ ... print(Date.from_string(string))
312
+ 1996-11-01 00:00:00
313
+ 1996-11-01 00:00:00
314
+ 1996-11-01 00:00:00
315
+ 1996-11-01 00:00:00
316
+
317
+ You can combine all styles with ISO time zone identifiers:
318
+
319
+ >>> Date.from_string("1997-11-01T00:00:00Z")
320
+ Date("1997-11-01T01:00:00")
321
+ >>> Date.from_string("1997-11-01 00:00:00-11:00")
322
+ Date("1997-11-01 12:00:00")
323
+ >>> Date.from_string("1997-11-01 +13")
324
+ Date("1997-10-31 12:00:00")
325
+ >>> Date.from_string("1997-11-01 +1330")
326
+ Date("1997-10-31 11:30:00")
327
+ >>> Date.from_string("01.11.1997 00-500")
328
+ Date("01.11.1997 06:00:00")
329
+
330
+ Poorly formatted date strings result in the following or comparable error
331
+ messages:
332
+
333
+ >>> Date.from_string("1997/11/01")
334
+ Traceback (most recent call last):
335
+ ...
336
+ ValueError: The given string `1997/11/01` does not agree with any of the \
337
+ supported format styles.
338
+
339
+ >>> Date.from_string("1997111")
340
+ Traceback (most recent call last):
341
+ ...
342
+ ValueError: The given string `1997111` does not agree with any of the \
343
+ supported format styles.
344
+
345
+ >>> Date.from_string("1997-11-01 +0000001")
346
+ Traceback (most recent call last):
347
+ ...
348
+ ValueError: While trying to apply the time zone offset defined by string \
349
+ `1997-11-01 +0000001`, the following error occurred: wrong number of offset characters
350
+
351
+ >>> Date.from_string("1997-11-01 +0X:00")
352
+ Traceback (most recent call last):
353
+ ...
354
+ ValueError: While trying to apply the time zone offset defined by string \
355
+ `1997-11-01 +0X:00`, the following error occurred: invalid literal for int() with \
356
+ base 10: '0X'
357
+ """
358
+ self = super().__new__(cls)
359
+ substring, offset = self._extract_offset(date)
360
+ vars(self)["style"], date_ = self._extract_date(substring, date)
361
+ self.datetime = self._modify_date(date_, offset, date)
362
+ return self
363
+
364
+ @staticmethod
365
+ def _extract_offset(string: str) -> tuple[str, str | None]:
366
+ if "Z" in string:
367
+ return string.split("Z")[0].strip(), "+0000"
368
+ if "+" in string:
369
+ idx = string.find("+")
370
+ elif string.count("-") in (1, 3):
371
+ idx = string.rfind("-")
372
+ else:
373
+ return string, None
374
+ return string[:idx].strip(), string[idx:].strip()
375
+
376
+ @classmethod
377
+ def _extract_date(
378
+ cls, substring: str, string: str
379
+ ) -> tuple[str, datetime_.datetime]:
380
+ strptime = datetime_.datetime.strptime
381
+ try:
382
+ style, format_ = cls._lastformatstring
383
+ return style, strptime(substring, format_)
384
+ except ValueError as exc:
385
+ if substring.isdigit():
386
+ format_ = cls.formatstrings["raw"][: len(substring) - 2]
387
+ try:
388
+ datetime = strptime(substring, format_)
389
+ except ValueError:
390
+ raise ValueError(
391
+ f"The given string `{string}` does not agree with any of the "
392
+ f"supported format styles."
393
+ ) from exc
394
+ cls._lastformatstring = "raw", format_
395
+ return "raw", datetime
396
+ for style, format_ in cls.formatstrings.items():
397
+ if style != "raw":
398
+ for _ in range(4):
399
+ try:
400
+ datetime = strptime(substring, format_)
401
+ cls._lastformatstring = style, format_
402
+ return style, datetime
403
+ except ValueError:
404
+ format_ = format_[:-3]
405
+ raise ValueError(
406
+ f"The given string `{string}` does not agree with any of the "
407
+ f"supported format styles."
408
+ ) from exc
409
+
410
+ @staticmethod
411
+ def _modify_date(
412
+ date: datetime_.datetime, offset: str | None, string: str
413
+ ) -> datetime_.datetime:
414
+ try:
415
+ if offset is None:
416
+ return date
417
+ factor = 1 if (offset[0] == "+") else -1
418
+ offset = offset[1:].strip().replace(":", "")
419
+ if len(offset) <= 2:
420
+ minutes = int(offset) * 60
421
+ elif len(offset) <= 4:
422
+ minutes = int(offset[:-2]) * 60 + int(offset[-2:])
423
+ else:
424
+ raise ValueError("wrong number of offset characters")
425
+ delta = datetime_.timedelta(
426
+ minutes=factor * minutes - hydpy.pub.options.utcoffset
427
+ )
428
+ new_date = date - delta
429
+ except BaseException:
430
+ objecttools.augment_excmessage(
431
+ f"While trying to apply the time zone offset defined by string "
432
+ f"`{string}`"
433
+ )
434
+ return new_date
435
+
436
+ @classmethod
437
+ def from_array(cls: type[TypeDate], array: NDArrayFloat) -> TypeDate:
438
+ """Return a |Date| instance based on date information (year, month, day, hour,
439
+ minute, second) stored as the first entries of the successive rows of a
440
+ |numpy.ndarray|.
441
+
442
+ >>> from hydpy import Date
443
+ >>> import numpy
444
+ >>> array1d = numpy.array([1992, 10, 8, 15, 15, 42, 999])
445
+ >>> Date.from_array(array1d)
446
+ Date("1992-10-08 15:15:42")
447
+
448
+ >>> array3d = numpy.zeros((7, 2, 2))
449
+ >>> array3d[:, 0, 0] = array1d
450
+ >>> Date.from_array(array3d)
451
+ Date("1992-10-08 15:15:42")
452
+
453
+ .. note::
454
+
455
+ The date defined by the given |numpy.ndarray| cannot include any time zone
456
+ information and corresponds to |Options.utcoffset|, which defaults to
457
+ UTC+01:00.
458
+ """
459
+ for _ in range(1, array.ndim):
460
+ array = array[:, 0]
461
+ return cls.from_datetime(datetime_.datetime(*array.astype(int)[:6]))
462
+
463
+ def to_array(self) -> numpy.ndarray:
464
+ """Return a 1-dimensional |numpy| |numpy.ndarray| with six entries defining
465
+ the actual date (year, month, day, hour, minute, second).
466
+
467
+ >>> from hydpy import Date, print_vector
468
+ >>> print_vector(Date("1992-10-8 15:15:42").to_array())
469
+ 1992.0, 10.0, 8.0, 15.0, 15.0, 42.0
470
+
471
+ .. note::
472
+
473
+ The date defined by the returned |numpy.ndarray| does not include any time
474
+ zone information and corresponds to |Options.utcoffset|, which defaults to
475
+ UTC+01:00.
476
+ """
477
+ return numpy.array(
478
+ [self.year, self.month, self.day, self.hour, self.minute, self.second],
479
+ dtype=config.NP_FLOAT,
480
+ )
481
+
482
+ @classmethod
483
+ def from_cfunits(cls: type[TypeDate], units: str) -> TypeDate:
484
+ """Return a |Date| object representing the reference date of the given `units`
485
+ string agreeing with the NetCDF-CF conventions.
486
+
487
+ We took the following example string from the `Time Coordinate`_ chapter of the
488
+ NetCDF-CF conventions documentation (modified). Note that method
489
+ |Date.from_cfunits| ignores the first entry (the unit) and assumes UTC+00 for
490
+ strings without time zone identifiers (as opposed to the usual `HydPy`
491
+ convention that dates without time zone identifiers correspond to the local
492
+ time defined by the option |Options.utcoffset|):
493
+
494
+ >>> from hydpy import Date
495
+ >>> Date.from_cfunits("seconds since 1992-10-8 15:15:42 -6:00")
496
+ Date("1992-10-08 22:15:42")
497
+ >>> Date.from_cfunits(" day since 1992-10-8 15:15:00")
498
+ Date("1992-10-08 16:15:00")
499
+ >>> Date.from_cfunits("seconds since 1992-10-8 -6:00")
500
+ Date("1992-10-08 07:00:00")
501
+ >>> Date.from_cfunits("m since 1992-10-8")
502
+ Date("1992-10-08 01:00:00")
503
+
504
+ One can also pass the unmodified example string from `Time Coordinate`_ as long
505
+ as one omits any decimal fractions of a second different from zero:
506
+
507
+ >>> Date.from_cfunits("seconds since 1992-10-8 15:15:42.")
508
+ Date("1992-10-08 16:15:42")
509
+ >>> Date.from_cfunits("seconds since 1992-10-8 15:15:42.00")
510
+ Date("1992-10-08 16:15:42")
511
+ >>> Date.from_cfunits("seconds since 1992-10-8 15:15:42. -6:00")
512
+ Date("1992-10-08 22:15:42")
513
+ >>> Date.from_cfunits("seconds since 1992-10-8 15:15:42.0 -6:00")
514
+ Date("1992-10-08 22:15:42")
515
+ >>> Date.from_cfunits("seconds since 1992-10-8 15:15:42.005 -6:00")
516
+ Traceback (most recent call last):
517
+ ...
518
+ ValueError: While trying to parse the date of the NetCDF-CF "units" string \
519
+ `seconds since 1992-10-8 15:15:42.005 -6:00`, the following error occurred: No other \
520
+ decimal fraction of a second than "0" allowed.
521
+ """
522
+ try:
523
+ string = units[units.find("since") + 6 :]
524
+ idx = string.find(".")
525
+ if idx != -1:
526
+ jdx = -999
527
+ for jdx, char in enumerate(string[idx + 1 :]):
528
+ if not char.isnumeric():
529
+ break
530
+ if char != "0":
531
+ raise ValueError(
532
+ 'No other decimal fraction of a second than "0" allowed.'
533
+ )
534
+ else:
535
+ if jdx == -999:
536
+ jdx = idx + 1
537
+ else:
538
+ jdx += 1
539
+ string = f"{string[:idx]}{string[idx+jdx+1:]}"
540
+ offset = cls._extract_offset(string)[1]
541
+ if offset is None:
542
+ string = f"{string} +00:00"
543
+ return cls.from_string(string)
544
+ except BaseException:
545
+ objecttools.augment_excmessage(
546
+ f'While trying to parse the date of the NetCDF-CF "units" string '
547
+ f"`{units}`"
548
+ )
549
+
550
+ def to_cfunits(self, unit: str = "hours", utcoffset: int | None = None) -> str:
551
+ """Return a `units` string agreeing with the NetCDF-CF conventions.
552
+
553
+ By default, method |Date.to_cfunits| uses `hours` as the time unit and takes
554
+ the value of |Options.utcoffset| as time zone information:
555
+
556
+ >>> from hydpy import Date
557
+ >>> date = Date("1992-10-08 15:15:42")
558
+ >>> date.to_cfunits()
559
+ 'hours since 1992-10-08 15:15:42 +01:00'
560
+
561
+ You can define arbitrary strings to describe the time unit:
562
+
563
+ >>> date.to_cfunits(unit="minutes")
564
+ 'minutes since 1992-10-08 15:15:42 +01:00'
565
+
566
+ For changing the time zone, pass the corresponding offset in minutes:
567
+
568
+ >>> date.to_cfunits(unit="sec", utcoffset=-60)
569
+ 'sec since 1992-10-08 13:15:42 -01:00'
570
+ """
571
+ if utcoffset is None:
572
+ utcoffset = hydpy.pub.options.utcoffset
573
+ string = self.to_string("iso2", utcoffset)
574
+ string = " ".join((string[:-6], string[-6:]))
575
+ return f"{unit} since {string}"
576
+
577
+ @property
578
+ def style(self) -> str:
579
+ """Date format style to be applied in printing.
580
+
581
+ Initially, |Date.style| corresponds to the format style of the string used as
582
+ the initialisation object of a |Date| object:
583
+
584
+ >>> from hydpy import Date
585
+ >>> date = Date("01.11.1997 00:00:00")
586
+ >>> date.style
587
+ 'din1'
588
+ >>> date
589
+ Date("01.11.1997 00:00:00")
590
+
591
+ However, you are allowed to change it:
592
+
593
+ >>> date.style = "iso1"
594
+ >>> date
595
+ Date("1997-11-01T00:00:00")
596
+
597
+ The default style is `iso2`:
598
+
599
+ >>> from datetime import datetime
600
+ >>> date = Date(datetime(2000, 1, 1))
601
+ >>> date
602
+ Date("2000-01-01 00:00:00")
603
+ >>> date.style
604
+ 'iso2'
605
+
606
+ Trying to set a non-existing style results in the following error message:
607
+
608
+ >>> date.style = "iso"
609
+ Traceback (most recent call last):
610
+ ...
611
+ AttributeError: Date format style `iso` is not available.
612
+ """
613
+ return vars(self).get("style", "iso2")
614
+
615
+ @style.setter
616
+ def style(self, style: str) -> None:
617
+ if style in self.formatstrings:
618
+ vars(self)["style"] = style
619
+ else:
620
+ vars(self).pop("style", None)
621
+ raise AttributeError(f"Date format style `{style}` is not available.")
622
+
623
+ def _set_thing(self, thing: str, value: int) -> None:
624
+ """Convenience method for `year.fset`, `month.fset`..."""
625
+ try:
626
+ kwargs = {}
627
+ for unit in ("year", "month", "day", "hour", "minute", "second"):
628
+ kwargs[unit] = getattr(self, unit)
629
+ kwargs[thing] = int(value)
630
+ self.datetime = datetime_.datetime(**kwargs)
631
+ except BaseException:
632
+ objecttools.augment_excmessage(
633
+ f"While trying to change the {thing} " f"of the current Date object"
634
+ )
635
+
636
+ @property
637
+ def second(self) -> int:
638
+ """The actual second.
639
+
640
+ >>> from hydpy import Date
641
+ >>> date = Date("2000-01-01 00:00:00")
642
+ >>> date.second
643
+ 0
644
+ >>> date.second = 30
645
+ >>> date.second
646
+ 30
647
+ """
648
+ return self.datetime.second
649
+
650
+ @second.setter
651
+ def second(self, second: int) -> None:
652
+ self._set_thing("second", second)
653
+
654
+ @property
655
+ def minute(self) -> int:
656
+ """The actual minute.
657
+
658
+ >>> from hydpy import Date
659
+ >>> date = Date("2000-01-01 00:00:00")
660
+ >>> date.minute
661
+ 0
662
+ >>> date.minute = 30
663
+ >>> date.minute
664
+ 30
665
+ """
666
+ return self.datetime.minute
667
+
668
+ @minute.setter
669
+ def minute(self, minute: int) -> None:
670
+ self._set_thing("minute", minute)
671
+
672
+ @property
673
+ def hour(self) -> int:
674
+ """The actual hour.
675
+
676
+ >>> from hydpy import Date
677
+ >>> date = Date("2000-01-01 00:00:00")
678
+ >>> date.hour
679
+ 0
680
+ >>> date.hour = 12
681
+ >>> date.hour
682
+ 12
683
+ """
684
+ return self.datetime.hour
685
+
686
+ @hour.setter
687
+ def hour(self, hour: int) -> None:
688
+ self._set_thing("hour", hour)
689
+
690
+ @property
691
+ def day(self) -> int:
692
+ """The actual day.
693
+
694
+ >>> from hydpy import Date
695
+ >>> date = Date("2000-01-01 00:00:00")
696
+ >>> date.day
697
+ 1
698
+ >>> date.day = 15
699
+ >>> date.day
700
+ 15
701
+ """
702
+ return self.datetime.day
703
+
704
+ @day.setter
705
+ def day(self, day: int) -> None:
706
+ self._set_thing("day", day)
707
+
708
+ @property
709
+ def month(self) -> int:
710
+ """The actual month.
711
+
712
+ >>> from hydpy import Date
713
+ >>> date = Date("2000-01-01 00:00:00")
714
+ >>> date.month
715
+ 1
716
+ >>> date.month = 7
717
+ >>> date.month
718
+ 7
719
+ """
720
+ return self.datetime.month
721
+
722
+ @month.setter
723
+ def month(self, month: int) -> None:
724
+ self._set_thing("month", month)
725
+
726
+ @property
727
+ def year(self) -> int:
728
+ """The actual year.
729
+
730
+ >>> from hydpy import Date
731
+ >>> date = Date("2000-01-01 00:00:00")
732
+ >>> date.year
733
+ 2000
734
+ >>> date.year = 1 # smallest possible value
735
+ >>> date.year
736
+ 1
737
+ >>> date.year = 9999 # highest possible value
738
+ >>> date.year
739
+ 9999
740
+ """
741
+ return self.datetime.year
742
+
743
+ @year.setter
744
+ def year(self, year: int) -> None:
745
+ self._set_thing("year", year)
746
+
747
+ def _get_refmonth(self) -> int:
748
+ """The first month of the hydrological year.
749
+
750
+ The default value is 11 (November which is the German reference month):
751
+
752
+ >>> from hydpy import Date
753
+ >>> date1 = Date("2000-01-01")
754
+ >>> date1.refmonth
755
+ 11
756
+
757
+ Setting it, for example, to 10 (October is another typical reference month in
758
+ different countries) affects all |Date| instances, no matter if already
759
+ existing or created afterwards:
760
+
761
+ >>> date2 = Date("2010-01-01")
762
+ >>> date1.refmonth = 10
763
+ >>> date1.refmonth
764
+ 10
765
+ >>> date2.refmonth
766
+ 10
767
+ >>> Date("2010-01-01").refmonth
768
+ 10
769
+
770
+ Alternatively, you can pass an appropriate string (the first three characters
771
+ count):
772
+
773
+ >>> date1.refmonth = "January"
774
+ >>> date1.refmonth
775
+ 1
776
+ >>> date1.refmonth = "feb"
777
+ >>> date1.refmonth
778
+ 2
779
+
780
+ Wrong arguments result in the following error messages:
781
+
782
+ >>> date1.refmonth = 0
783
+ Traceback (most recent call last):
784
+ ...
785
+ ValueError: The reference month must be a value between one (January) and \
786
+ twelve (December) but `0` is given
787
+
788
+ >>> date1.refmonth = "wrong"
789
+ Traceback (most recent call last):
790
+ ...
791
+ ValueError: The given argument `wrong` cannot be interpreted as a month.
792
+
793
+ >>> date1.refmonth = 11
794
+ """
795
+ return type(self)._firstmonth_wateryear
796
+
797
+ def _set_refmonth(self, value: int | str) -> None:
798
+ try:
799
+ refmonth = int(value)
800
+ except ValueError:
801
+ string = str(value)[:3].lower()
802
+ try:
803
+ months = [
804
+ "jan",
805
+ "feb",
806
+ "mar",
807
+ "apr",
808
+ "may",
809
+ "jun",
810
+ "jul",
811
+ "aug",
812
+ "sew",
813
+ "oct",
814
+ "nov",
815
+ "dec",
816
+ ]
817
+ refmonth = months.index(string) + 1
818
+ except ValueError:
819
+ raise ValueError(
820
+ f"The given argument `{value}` cannot be "
821
+ f"interpreted as a month."
822
+ ) from None
823
+ if not 0 < refmonth < 13:
824
+ raise ValueError(
825
+ f"The reference month must be a value between one (January) and "
826
+ f"twelve (December) but `{value}` is given"
827
+ )
828
+ type(self)._firstmonth_wateryear = refmonth
829
+
830
+ refmonth = propertytools.Property[int | str, int](
831
+ fget=_get_refmonth, fset=_set_refmonth
832
+ )
833
+
834
+ @property
835
+ def wateryear(self) -> int:
836
+ """The actual hydrological year according to the selected reference month.
837
+
838
+ Property |Date.refmonth| defaults to November:
839
+
840
+ >>> october = Date("1996.10.01")
841
+ >>> november = Date("1996.11.01")
842
+ >>> october.wateryear
843
+ 1996
844
+ >>> november.wateryear
845
+ 1997
846
+
847
+ Note that changing |Date.refmonth| affects all |Date| objects:
848
+
849
+ >>> october.refmonth = 10
850
+ >>> october.wateryear
851
+ 1997
852
+ >>> november.wateryear
853
+ 1997
854
+ >>> october.refmonth = "November"
855
+ >>> october.wateryear
856
+ 1996
857
+ >>> november.wateryear
858
+ 1997
859
+ """
860
+ if self.month < self._firstmonth_wateryear:
861
+ return self.year
862
+ return self.year + 1
863
+
864
+ @property
865
+ def dayofyear(self) -> int:
866
+ """The day of the year as an integer value.
867
+
868
+ >>> from hydpy import Date
869
+ >>> Date("2003-03-01").dayofyear
870
+ 60
871
+ >>> Date("2004-03-01").dayofyear
872
+ 61
873
+ """
874
+ return self.datetime.timetuple().tm_yday
875
+
876
+ @property
877
+ def leapyear(self) -> bool:
878
+ """Return whether the actual date falls in a leap year or not.
879
+
880
+ >>> from hydpy import Date
881
+ >>> Date("2003-03-01").leapyear
882
+ False
883
+ >>> Date("2004-03-01").leapyear
884
+ True
885
+ >>> Date("2000-03-01").leapyear
886
+ True
887
+ >>> Date("2100-03-01").leapyear
888
+ False
889
+ """
890
+ year = self.year
891
+ return ((year % 4) == 0) and (((year % 100) != 0) or ((year % 400) == 0))
892
+
893
+ @property
894
+ def beginning_next_month(self) -> Date:
895
+ """The first possible date of the next month after the month of the current
896
+ |Date| object.
897
+
898
+ >>> from hydpy import Date
899
+ >>> Date("2001-01-01 00:00:00").beginning_next_month
900
+ Date("2001-02-01 00:00:00")
901
+ >>> Date("2001-01-31 12:30:30").beginning_next_month
902
+ Date("2001-02-01 00:00:00")
903
+ >>> Date("2001-12-01 00:00:00").beginning_next_month
904
+ Date("2002-01-01 00:00:00")
905
+ >>> Date("2001-12-31 12:30:30").beginning_next_month
906
+ Date("2002-01-01 00:00:00")
907
+ """
908
+ date = Date(self)
909
+ date.day = 1
910
+ date.hour = 0
911
+ date.second = 0
912
+ date.minute = 0
913
+ if date.month == 12:
914
+ date.month = 1
915
+ date.year += 1
916
+ else:
917
+ date.month += 1
918
+ return date
919
+
920
+ @property
921
+ def beginning_next_year(self) -> Date:
922
+ """The first possible date of the next calendar year after the year of the
923
+ current |Date| object.
924
+
925
+ >>> from hydpy import Date
926
+ >>> Date("2001-01-01 00:00:00").beginning_next_year
927
+ Date("2002-01-01 00:00:00")
928
+ >>> Date("2001-12-31 12:30:30").beginning_next_year
929
+ Date("2002-01-01 00:00:00")
930
+ """
931
+ date = Date(self)
932
+ date.year += 1
933
+ date.month = 1
934
+ date.day = 1
935
+ date.hour = 0
936
+ date.second = 0
937
+ date.minute = 0
938
+ return date
939
+
940
+ def __add__(self: TypeDate, other: PeriodConstrArg) -> TypeDate:
941
+ new = self.from_datetime(self.datetime + Period(other).timedelta)
942
+ new.style = self.style
943
+ return new
944
+
945
+ def __iadd__(self: TypeDate, other: PeriodConstrArg) -> TypeDate:
946
+ self.datetime += Period(other).timedelta
947
+ return self
948
+
949
+ @overload
950
+ def __sub__(self, other: Date | datetime_.datetime) -> Period:
951
+ """Determine the period between two dates."""
952
+
953
+ @overload
954
+ def __sub__(self: TypeDate, other: Period | datetime_.timedelta) -> TypeDate:
955
+ """Subtract a period from the actual date."""
956
+
957
+ @overload
958
+ def __sub__(self: TypeDate, other: str) -> TypeDate | Period:
959
+ """Result depends on the string."""
960
+
961
+ def __sub__(
962
+ self: TypeDate,
963
+ other: Date | datetime_.datetime | datetime_.timedelta | Period | str,
964
+ ) -> TypeDate | Period:
965
+ if isinstance(other, (Date, datetime_.datetime, str)):
966
+ try:
967
+ return Period(self.datetime - type(self)(other).datetime)
968
+ except BaseException:
969
+ pass
970
+ if isinstance(other, (Period, datetime_.timedelta, str)):
971
+ try:
972
+ new = self.from_datetime(self.datetime - Period(other).timedelta)
973
+ new.style = self.style
974
+ return new
975
+ except BaseException:
976
+ pass
977
+ raise TypeError(
978
+ f"Object `{other}` of type `{type(other).__name__}` cannot be substracted "
979
+ f"from a `Date` instance."
980
+ )
981
+
982
+ def __rsub__(self, other: DateConstrArg) -> Period:
983
+ return Period(type(self)(other).datetime - self.datetime)
984
+
985
+ def __isub__(self: TypeDate, other: PeriodConstrArg) -> TypeDate: # type: ignore
986
+ # without more flexible ways to relate types to string patterns, there is
987
+ # nothing we can do about it (except providing a less flexible interface, of
988
+ # course)
989
+ self.datetime -= Period(other).timedelta
990
+ return self
991
+
992
+ def __lt__(self, other: DateConstrArg) -> bool:
993
+ return self.datetime < type(self)(other).datetime
994
+
995
+ def __le__(self, other: DateConstrArg) -> bool:
996
+ return self.datetime <= type(self)(other).datetime
997
+
998
+ def __eq__(self, other: Any) -> bool:
999
+ try:
1000
+ return self.datetime == type(self)(other).datetime
1001
+ except BaseException:
1002
+ return False
1003
+
1004
+ def __ne__(self, other: Any) -> bool:
1005
+ try:
1006
+ return self.datetime != type(self)(other).datetime
1007
+ except BaseException:
1008
+ return True
1009
+
1010
+ def __gt__(self, other: DateConstrArg) -> bool:
1011
+ return self.datetime > type(self)(other).datetime
1012
+
1013
+ def __ge__(self, other: DateConstrArg) -> bool:
1014
+ return self.datetime >= type(self)(other).datetime
1015
+
1016
+ def __deepcopy__(self: TypeDate, dict_: dict[str, Any]) -> TypeDate:
1017
+ new = type(self).from_date(self)
1018
+ new.datetime = copy.deepcopy(self.datetime)
1019
+ return new
1020
+
1021
+ def to_string(self, style: str | None = None, utcoffset: int | None = None) -> str:
1022
+ """Return a |str| object representing the actual date following the given style
1023
+ and the eventually given UTC offset (in minutes).
1024
+
1025
+ Without any input arguments, the actual |Date.style| is used to return a date
1026
+ string in your local time zone:
1027
+
1028
+ >>> from hydpy import Date
1029
+ >>> date = Date("01.11.1997 00:00:00")
1030
+ >>> date.to_string()
1031
+ '01.11.1997 00:00:00'
1032
+
1033
+ Passing a style string affects the returned |str| object but not the
1034
+ |Date.style| property:
1035
+
1036
+ >>> date.style
1037
+ 'din1'
1038
+ >>> date.to_string(style="iso2")
1039
+ '1997-11-01 00:00:00'
1040
+ >>> date.style
1041
+ 'din1'
1042
+
1043
+ When passing the `utcoffset` in minutes, method |Date.to_string| appends the
1044
+ offset string:
1045
+
1046
+ >>> date.to_string(style="iso2", utcoffset=60)
1047
+ '1997-11-01 00:00:00+01:00'
1048
+
1049
+ If the given offset does not correspond to your local offset defined by
1050
+ |Options.utcoffset| (which defaults to UTC+01:00), the date string is adapted:
1051
+
1052
+ >>> date.to_string(style="iso1", utcoffset=0)
1053
+ '1997-10-31T23:00:00+00:00'
1054
+ """
1055
+ if style is None:
1056
+ style = self.style
1057
+ if utcoffset is None:
1058
+ string = ""
1059
+ date = self.datetime
1060
+ else:
1061
+ sign = "+" if utcoffset >= 0 else "-"
1062
+ hours = abs(utcoffset // 60)
1063
+ minutes = abs(utcoffset % 60)
1064
+ string = f"{sign}{hours:02d}:{minutes:02d}"
1065
+ offset = utcoffset - hydpy.pub.options.utcoffset
1066
+ date = self.datetime + datetime_.timedelta(minutes=offset)
1067
+ return date.strftime(self.formatstrings[style]) + string
1068
+
1069
+ def to_repr(self, style: str | None = None, utcoffset: int | None = None) -> str:
1070
+ """Similar to method |Date.to_string|, but returns a proper string
1071
+ representation instead.
1072
+
1073
+ See method |Date.to_string| for explanations on the following examples:
1074
+
1075
+ >>> from hydpy import Date
1076
+ >>> date = Date("01.11.1997 00:00:00")
1077
+ >>> date.to_repr()
1078
+ 'Date("01.11.1997 00:00:00")'
1079
+ >>> date.to_repr("iso1", utcoffset=0)
1080
+ 'Date("1997-10-31T23:00:00+00:00")'
1081
+ """
1082
+ return f'Date("{self.to_string(style, utcoffset)}")'
1083
+
1084
+ def __str__(self) -> str:
1085
+ return self.to_string(self.style)
1086
+
1087
+ def __repr__(self) -> str:
1088
+ return self.to_repr()
1089
+
1090
+
1091
+ class Period:
1092
+ """Handles a single period.
1093
+
1094
+ We built the class |Period| on top of the Python module |datetime|. It wraps
1095
+ |datetime.timedelta| objects and specialises this general class on the needs of
1096
+ *HydPy* users.
1097
+
1098
+ Be aware of the different minimum time resolution of module |datetime|
1099
+ (microseconds) and module |timetools| (seconds).
1100
+
1101
+ You can initialise |Period| directly via |datetime.timedelta| objects (see the
1102
+ documentation on method |Period.from_timedelta| for more information):
1103
+
1104
+ >>> from hydpy import Period
1105
+ >>> from datetime import timedelta
1106
+ >>> Period(timedelta(1))
1107
+ Period("1d")
1108
+
1109
+ Alternatively, one can initialise from |str| objects. These must consist of some
1110
+ characters defining an integer value followed by a single character defining the
1111
+ unit (see the documentation on method |Period.from_timedelta| for more information):
1112
+
1113
+ >>> Period("30s")
1114
+ Period("30s")
1115
+
1116
+ In case you need an "empty" period object, pass nothing or |None|:
1117
+
1118
+ >>> Period()
1119
+ Period()
1120
+ >>> Period(None)
1121
+ Period()
1122
+
1123
+ All other types result in the following error:
1124
+
1125
+ >>> Period(1)
1126
+ Traceback (most recent call last):
1127
+ ...
1128
+ TypeError: While trying to initialise a `Period` object based argument `1`, the \
1129
+ following error occurred: The supplied argument must be either an instance of \
1130
+ `Period`, `datetime.timedelta`, or `str`, but the given type is `int`.
1131
+
1132
+ Class |Period| supports some mathematical operations. Depending on the operation,
1133
+ the second operand can be either a number or an object interpretable as a date or
1134
+ period.
1135
+
1136
+ First, one can add two |Period| objects or add a |Period| object to an object
1137
+ representing a date:
1138
+
1139
+ >>> period = Period("1m")
1140
+ >>> period + "2m"
1141
+ Period("3m")
1142
+ >>> "30s" + period
1143
+ Period("90s")
1144
+ >>> period += "4m"
1145
+ >>> period
1146
+ Period("5m")
1147
+ >>> "2000-01-01" + period
1148
+ Date("2000-01-01 00:05:00")
1149
+ >>> period + "wrong"
1150
+ Traceback (most recent call last):
1151
+ ...
1152
+ TypeError: Object `wrong` of type `str` cannot be added to a `Period` instance.
1153
+
1154
+ Subtraction works much like addition:
1155
+
1156
+ >>> period = Period("4d")
1157
+ >>> period - "1d"
1158
+ Period("3d")
1159
+ >>> "1d" - period
1160
+ Period("-3d")
1161
+ >>> period -= "2d"
1162
+ >>> period
1163
+ Period("2d")
1164
+ >>> "2000-01-10" - period
1165
+ Date("2000-01-08 00:00:00")
1166
+ >>> "wrong" - period
1167
+ Traceback (most recent call last):
1168
+ ...
1169
+ TypeError: A `Period` instance cannot be subtracted from object `wrong` of type \
1170
+ `str`.
1171
+
1172
+ Use multiplication with a number to change the length of a |Period| object:
1173
+
1174
+ >>> period * 2.0
1175
+ Period("4d")
1176
+ >>> 0.5 * period
1177
+ Period("1d")
1178
+ >>> period *= 1.5
1179
+ >>> period
1180
+ Period("3d")
1181
+
1182
+ Division is possible in combination numbers and objects interpretable as periods:
1183
+
1184
+
1185
+ >>> period / 3.0
1186
+ Period("1d")
1187
+ >>> period / "36h"
1188
+ 2.0
1189
+ >>> "6d" / period
1190
+ 2.0
1191
+ >>> period /= 1.5
1192
+ >>> period
1193
+ Period("2d")
1194
+
1195
+ Floor division and calculation of the remainder are also supported:
1196
+
1197
+ >>> period // "20h"
1198
+ 2
1199
+ >>> period % "20h"
1200
+ Period("8h")
1201
+ >>> "3d" // period
1202
+ 1
1203
+ >>> timedelta(3) % period
1204
+ Period("1d")
1205
+
1206
+ You can change the sign in the following manners:
1207
+
1208
+ >>> period = -period
1209
+ >>> period
1210
+ Period("-2d")
1211
+ >>> +period
1212
+ Period("-2d")
1213
+ >>> abs(period)
1214
+ Period("2d")
1215
+
1216
+ The comparison operators work as expected:
1217
+
1218
+ >>> p1, p3 = Period("1d"), Period("3d")
1219
+ >>> p1 < "2d", p1 < "1d", "2d" < p1
1220
+ (True, False, False)
1221
+ >>> p1 <= p3, p1 <= "1d", "2d" <= p1
1222
+ (True, True, False)
1223
+ >>> p1 == p3, p1 == "1d", "2d" == p1, p1 == "2000-01-01"
1224
+ (False, True, False, False)
1225
+ >>> p1 != p3, p1 != "1d", "2d" != p1, p1 != "2000-01-01"
1226
+ (True, False, True, True)
1227
+ >>> p1 >= p3, p1 >= "1d", "2d" >= p1
1228
+ (False, True, True)
1229
+ >>> p1 > p3, p1 > "1d", "2d" > p1
1230
+ (False, False, True)
1231
+ """
1232
+
1233
+ def __new__(
1234
+ cls: type[TypePeriod], period: PeriodConstrArg | None = None
1235
+ ) -> TypePeriod:
1236
+ try:
1237
+ if isinstance(period, Period):
1238
+ return cls.from_period(period)
1239
+ if isinstance(period, datetime_.timedelta):
1240
+ return cls.from_timedelta(period)
1241
+ if isinstance(period, str):
1242
+ return cls.from_string(period)
1243
+ if period is None:
1244
+ return super().__new__(cls)
1245
+ raise TypeError(
1246
+ f"The supplied argument must be either an instance of `Period`, "
1247
+ f"`datetime.timedelta`, or `str`, but the given type is "
1248
+ f"`{type(period).__name__}`."
1249
+ )
1250
+ except BaseException:
1251
+ objecttools.augment_excmessage(
1252
+ f"While trying to initialise a `Period` object based argument "
1253
+ f"`{period}`"
1254
+ )
1255
+
1256
+ @classmethod
1257
+ def from_period(cls: type[TypePeriod], period: Period) -> TypePeriod:
1258
+ """Create a new |Period| object based on another |Period| object and return it.
1259
+
1260
+ >>> from hydpy import Period
1261
+ >>> p1 = Period("1d")
1262
+ >>> p2 = Period.from_period(p1)
1263
+ >>> p2
1264
+ Period("1d")
1265
+ >>> p1 *= 2
1266
+ >>> p1
1267
+ Period("2d")
1268
+ >>> p2
1269
+ Period("1d")
1270
+ """
1271
+ self = super().__new__(cls)
1272
+ vars(self)["timedelta"] = vars(period).get("timedelta")
1273
+ return self
1274
+
1275
+ @classmethod
1276
+ def from_timedelta(
1277
+ cls: type[TypePeriod], period: datetime_.timedelta
1278
+ ) -> TypePeriod:
1279
+ """Create a new |Period| object based on a |datetime.timedelta| object and
1280
+ return it.
1281
+
1282
+ |datetime.timedelta| objects defining days or seconds are allowed, but
1283
+ |datetime.timedelta| objects defining microseconds are not:
1284
+
1285
+ >>> from hydpy import Period
1286
+ >>> from datetime import timedelta
1287
+ >>> Period.from_timedelta(timedelta(1, 0))
1288
+ Period("1d")
1289
+ >>> Period.from_timedelta(timedelta(0, 1))
1290
+ Period("1s")
1291
+ >>> Period.from_timedelta(timedelta(0, 0, 1))
1292
+ Traceback (most recent call last):
1293
+ ...
1294
+ ValueError: For `Period` instances, microseconds must be zero. \
1295
+ However, for the given `timedelta` object it is `1` instead.
1296
+ """
1297
+ self = super().__new__(cls)
1298
+ vars(self)["timedelta"] = self._check_timedelta(period)
1299
+ return self
1300
+
1301
+ @staticmethod
1302
+ def _check_timedelta(period: datetime_.timedelta) -> datetime_.timedelta:
1303
+ if period.microseconds:
1304
+ raise ValueError(
1305
+ f"For `Period` instances, microseconds must be zero. However, for "
1306
+ f"the given `timedelta` object it is `{period.microseconds}` instead."
1307
+ )
1308
+ return period
1309
+
1310
+ @classmethod
1311
+ def from_string(cls: type[TypePeriod], period: str) -> TypePeriod:
1312
+ """Create a new |Period| object based on a |str| object and return it.
1313
+
1314
+ The string must consist of a leading integer number followed by one of the
1315
+ lower chase characters `s` (seconds), `m` (minutes), `h` (hours), and `d`
1316
+ (days):
1317
+
1318
+ >>> from hydpy import Period
1319
+ >>> Period.from_string("30s")
1320
+ Period("30s")
1321
+ >>> Period.from_string("5m")
1322
+ Period("5m")
1323
+ >>> Period.from_string("6h")
1324
+ Period("6h")
1325
+ >>> Period.from_string("1d")
1326
+ Period("1d")
1327
+
1328
+ Ill-defined strings result in the following errors:
1329
+
1330
+ >>> Period.from_string("oned")
1331
+ Traceback (most recent call last):
1332
+ ...
1333
+ ValueError: All characters of the given period string, except the last one \
1334
+ which represents the unit, need to define an integer number. Instead, these \
1335
+ characters are `one`.
1336
+
1337
+ >>> Period.from_string("1.5d")
1338
+ Traceback (most recent call last):
1339
+ ...
1340
+ ValueError: All characters of the given period string, except the last one \
1341
+ which represents the unit, need to define an integer number. Instead, these \
1342
+ characters are `1.5`.
1343
+
1344
+ >>> Period.from_string("1D")
1345
+ Traceback (most recent call last):
1346
+ ...
1347
+ ValueError: The last character of the given period string needs to be either \
1348
+ `d` (days), `h` (hours), `m` (minutes), or `s` (seconds). Instead, the last \
1349
+ character is `D`.
1350
+ """
1351
+ self = super().__new__(cls)
1352
+ vars(self)["timedelta"] = cls._get_timedelta_from_string(period)
1353
+ return self
1354
+
1355
+ @staticmethod
1356
+ def _get_timedelta_from_string(period: str) -> datetime_.timedelta:
1357
+ try:
1358
+ number = float(period[:-1])
1359
+ if number != int(number):
1360
+ raise ValueError
1361
+ except ValueError:
1362
+ raise ValueError(
1363
+ f"All characters of the given period string, except the last one "
1364
+ f"which represents the unit, need to define an integer number. "
1365
+ f"Instead, these characters are `{period[:-1]}`."
1366
+ ) from None
1367
+ unit = period[-1]
1368
+ if unit == "d":
1369
+ return datetime_.timedelta(number, 0)
1370
+ if unit == "h":
1371
+ return datetime_.timedelta(0, number * 3600)
1372
+ if unit == "m":
1373
+ return datetime_.timedelta(0, number * 60)
1374
+ if unit == "s":
1375
+ return datetime_.timedelta(0, number)
1376
+ raise ValueError(
1377
+ f"The last character of the given period string needs to be either `d` "
1378
+ f"(days), `h` (hours), `m` (minutes), or `s` (seconds). Instead, the "
1379
+ f"last character is `{unit}`."
1380
+ )
1381
+
1382
+ @classmethod
1383
+ def from_seconds(cls: type[TypePeriod], seconds: int) -> TypePeriod:
1384
+ """Create a new |Period| object based on the given integer number of seconds
1385
+ and return it.
1386
+
1387
+ >>> from hydpy import Period
1388
+ >>> Period.from_seconds(120)
1389
+ Period("2m")
1390
+ """
1391
+ return cls.from_timedelta(datetime_.timedelta(0, int(seconds)))
1392
+
1393
+ @classmethod
1394
+ def from_cfunits(cls: type[TypePeriod], units: TypeUnit) -> TypePeriod:
1395
+ """Create a |Period| object representing the time unit of the given `units`
1396
+ string agreeing with the NetCDF-CF conventions and return it.
1397
+
1398
+ We took the following example string from the `Time Coordinate`_ chapter of the
1399
+ NetCDF-CF conventions. Note that the character of the first entry (the actual
1400
+ time unit) is of relevance:
1401
+
1402
+ >>> from hydpy import Period
1403
+ >>> Period.from_cfunits("seconds since 1992-10-8 15:15:42.5 -6:00")
1404
+ Period("1s")
1405
+ >>> Period.from_cfunits(" day since 1992-10-8 15:15:00")
1406
+ Period("1d")
1407
+ >>> Period.from_cfunits("m since 1992-10-8")
1408
+ Period("1m")
1409
+ """
1410
+ return cls.from_string(f"1{units.strip()[0]}")
1411
+
1412
+ def _get_timedelta(self) -> datetime_.timedelta:
1413
+ """The handled |datetime.timedelta| object.
1414
+
1415
+ You are allowed to change and delete the handled |datetime.timedelta| object:
1416
+
1417
+ >>> from hydpy import Period
1418
+ >>> period = Period("1d")
1419
+ >>> period.timedelta.days
1420
+ 1
1421
+ >>> del period.timedelta
1422
+ >>> period.timedelta
1423
+ Traceback (most recent call last):
1424
+ ...
1425
+ AttributeError: The Period object does not handle a timedelta object at the \
1426
+ moment.
1427
+ >>> from datetime import timedelta
1428
+ >>> period.timedelta = timedelta(1)
1429
+ >>> hasattr(period, "timedelta")
1430
+ True
1431
+
1432
+ Property |Period.timedelta| supports the automatic conversion of given |Period|
1433
+ and |str| objects:
1434
+
1435
+ >>> period.timedelta = Period("2d")
1436
+ >>> period.timedelta.days
1437
+ 2
1438
+
1439
+ >>> period.timedelta = "1h"
1440
+ >>> period.timedelta.seconds
1441
+ 3600
1442
+
1443
+ >>> period.timedelta = 1
1444
+ Traceback (most recent call last):
1445
+ ...
1446
+ TypeError: The supplied argument must be either an instance of `Period´, \
1447
+ `datetime.timedelta` or `str`. The given argument's type is `int`.
1448
+ """
1449
+ timedelta = cast(datetime_.timedelta | None, vars(self).get("timedelta"))
1450
+ if timedelta is None:
1451
+ raise AttributeError(
1452
+ "The Period object does not handle a timedelta object at the moment."
1453
+ )
1454
+ return timedelta
1455
+
1456
+ def _set_timedelta(self, period: PeriodConstrArg | None) -> None:
1457
+ if isinstance(period, Period):
1458
+ vars(self)["timedelta"] = vars(period).get("timedelta")
1459
+ elif isinstance(period, datetime_.timedelta):
1460
+ vars(self)["timedelta"] = self._check_timedelta(period)
1461
+ elif isinstance(period, str):
1462
+ vars(self)["timedelta"] = self._get_timedelta_from_string(period)
1463
+ else:
1464
+ raise TypeError(
1465
+ f"The supplied argument must be either an instance of `Period´, "
1466
+ f"`datetime.timedelta` or `str`. The given argument's type is "
1467
+ f"`{type(period).__name__}`."
1468
+ )
1469
+
1470
+ def _del_timedelta(self) -> None:
1471
+ vars(self)["timedelta"] = None
1472
+
1473
+ timedelta = propertytools.Property[PeriodConstrArg | None, datetime_.timedelta](
1474
+ fget=_get_timedelta, fset=_set_timedelta, fdel=_del_timedelta
1475
+ )
1476
+
1477
+ @property
1478
+ def unit(self) -> str:
1479
+ """The (most suitable) unit for the current period.
1480
+
1481
+ |Period.unit| always returns the unit leading to the smallest integer value:
1482
+
1483
+ >>> from hydpy import Period
1484
+ >>> period = Period("1d")
1485
+ >>> period.unit
1486
+ 'd'
1487
+ >>> period /= 2
1488
+ >>> period.unit
1489
+ 'h'
1490
+ >>> Period("120s").unit
1491
+ 'm'
1492
+ >>> Period("90s").unit
1493
+ 's'
1494
+ """
1495
+ if not self.days % 1:
1496
+ return "d"
1497
+ if not self.hours % 1:
1498
+ return "h"
1499
+ if not self.minutes % 1:
1500
+ return "m"
1501
+ return "s"
1502
+
1503
+ @property
1504
+ def seconds(self) -> float:
1505
+ """Period length in seconds.
1506
+
1507
+ >>> from hydpy import Period
1508
+ >>> Period("2d").seconds
1509
+ 172800.0
1510
+ """
1511
+ return self.timedelta.total_seconds()
1512
+
1513
+ @property
1514
+ def minutes(self) -> float:
1515
+ """Period length in minutes.
1516
+
1517
+ >>> from hydpy import Period
1518
+ >>> Period("2d").minutes
1519
+ 2880.0
1520
+ """
1521
+ return self.timedelta.total_seconds() / 60
1522
+
1523
+ @property
1524
+ def hours(self) -> float:
1525
+ """Period length in hours.
1526
+
1527
+ >>> from hydpy import Period
1528
+ >>> Period("2d").hours
1529
+ 48.0
1530
+ """
1531
+ return self.timedelta.total_seconds() / 3600
1532
+
1533
+ @property
1534
+ def days(self) -> float:
1535
+ """Period length in days.
1536
+
1537
+ >>> from hydpy import Period
1538
+ >>> Period("2d").days
1539
+ 2.0
1540
+ """
1541
+ return self.timedelta.total_seconds() / 86400
1542
+
1543
+ def check(self) -> None:
1544
+ """Raise a |RuntimeError| if the step size is undefined at the moment.
1545
+
1546
+ >>> from hydpy import Period
1547
+ >>> Period("1d").check()
1548
+ >>> Period().check()
1549
+ Traceback (most recent call last):
1550
+ ...
1551
+ RuntimeError: No step size defined at the moment.
1552
+ """
1553
+ if not self:
1554
+ raise RuntimeError("No step size defined at the moment.")
1555
+
1556
+ def __bool__(self) -> bool:
1557
+ return bool(getattr(self, "timedelta", None))
1558
+
1559
+ @overload
1560
+ def __add__(self: TypePeriod, other: Date | datetime_.datetime) -> Date:
1561
+ """Add the |Period| object to a |Date| object."""
1562
+
1563
+ @overload
1564
+ def __add__(self: TypePeriod, other: Period | datetime_.timedelta) -> TypePeriod:
1565
+ """Add the |Period| object to another |Period| object."""
1566
+
1567
+ @overload
1568
+ def __add__(self: TypePeriod, other: str) -> Date | TypePeriod:
1569
+ """Result depends on the actual string."""
1570
+
1571
+ def __add__(
1572
+ self: TypePeriod,
1573
+ other: Date | datetime_.datetime | Period | datetime_.timedelta | str,
1574
+ ) -> Date | TypePeriod:
1575
+ if isinstance(other, (Date, datetime_.datetime, str)):
1576
+ try:
1577
+ other = Date(other)
1578
+ new = Date(other.datetime + self.timedelta)
1579
+ new.style = other.style
1580
+ return new
1581
+ except BaseException:
1582
+ pass
1583
+ if isinstance(other, (Period, datetime_.timedelta, str)):
1584
+ try:
1585
+ timedelta = self.timedelta + type(self)(other).timedelta
1586
+ return type(self).from_timedelta(timedelta)
1587
+ except BaseException:
1588
+ pass
1589
+ raise TypeError(
1590
+ f"Object `{other}` of type `{type(other).__name__}` "
1591
+ f"cannot be added to a `Period` instance."
1592
+ )
1593
+
1594
+ @overload
1595
+ def __radd__(self, other: Date | datetime_.datetime) -> Date:
1596
+ """Add the |Period| object to a |Date| object."""
1597
+
1598
+ @overload
1599
+ def __radd__(self: TypePeriod, other: Period | datetime_.timedelta) -> TypePeriod:
1600
+ """Add the |Period| object to another |Period| object."""
1601
+
1602
+ @overload
1603
+ def __radd__(self: TypePeriod, other: str) -> Date | TypePeriod:
1604
+ """Result depends on the string."""
1605
+
1606
+ def __radd__( # type: ignore
1607
+ self: TypePeriod,
1608
+ other: Date | datetime_.datetime | Period | datetime_.timedelta | str,
1609
+ ) -> Date | TypePeriod:
1610
+ # without more flexible ways to relate types to string patterns, there is
1611
+ # nothing we can do about it (except providing a less flexible interface, of
1612
+ # course)
1613
+ return self.__add__(other)
1614
+
1615
+ def __iadd__( # type: ignore
1616
+ self: TypePeriod, other: PeriodConstrArg
1617
+ ) -> TypePeriod:
1618
+ # without more flexible ways to relate types to string patterns, there is
1619
+ # nothing we can do about it (except providing a less flexible interface, of
1620
+ # course)
1621
+ self.timedelta += type(self)(other).timedelta
1622
+ return self
1623
+
1624
+ def __sub__(self: TypePeriod, other: PeriodConstrArg) -> TypePeriod:
1625
+ return type(self).from_timedelta(self.timedelta - type(self)(other).timedelta)
1626
+
1627
+ @overload
1628
+ def __rsub__( # type: ignore
1629
+ self: TypePeriod, other: Date | datetime_.datetime
1630
+ ) -> TypePeriod:
1631
+ # without more flexible ways to relate types to string patterns, there is
1632
+ # nothing we can do about it (except providing a less flexible interface, of
1633
+ # course)
1634
+ """Subtract the |Period| object from a |Date| object."""
1635
+
1636
+ @overload
1637
+ def __rsub__(self, other: Period | datetime_.timedelta) -> Date: # type: ignore
1638
+ # without more flexible ways to relate types to string patterns, there is
1639
+ # nothing we can do about it (except providing a less flexible interface, of
1640
+ # course)
1641
+ """Subtract the |Period| object from another |Period| object."""
1642
+
1643
+ @overload
1644
+ def __rsub__(self: TypePeriod, other: str) -> Date | TypePeriod:
1645
+ """Result depends on string."""
1646
+
1647
+ def __rsub__(
1648
+ self: TypePeriod,
1649
+ other: Date | datetime_.datetime | Period | datetime_.timedelta | str,
1650
+ ) -> Date | TypePeriod:
1651
+ if isinstance(other, (Date, datetime_.datetime, str)):
1652
+ try:
1653
+ other = Date(other)
1654
+ new = Date(other.datetime - self.timedelta)
1655
+ new.style = other.style
1656
+ return new
1657
+ except BaseException:
1658
+ pass
1659
+ if isinstance(other, (Period, datetime_.timedelta, str)):
1660
+ try:
1661
+ timedelta = type(self)(other).timedelta - self.timedelta
1662
+ return type(self).from_timedelta(timedelta)
1663
+ except BaseException:
1664
+ pass
1665
+ raise TypeError(
1666
+ f"A `Period` instance cannot be subtracted from object `{other}` of type "
1667
+ f"`{type(other).__name__}`."
1668
+ )
1669
+
1670
+ def __isub__(self: TypePeriod, other: PeriodConstrArg) -> TypePeriod:
1671
+ self.timedelta -= type(self)(other).timedelta
1672
+ return self
1673
+
1674
+ def __mul__(self: TypePeriod, other: float) -> TypePeriod:
1675
+ return type(self).from_timedelta(self.timedelta * other)
1676
+
1677
+ def __rmul__(self: TypePeriod, other: float) -> TypePeriod:
1678
+ return self.__mul__(other)
1679
+
1680
+ def __imul__(self: TypePeriod, other: float) -> TypePeriod:
1681
+ self.timedelta *= other
1682
+ return self
1683
+
1684
+ @overload
1685
+ def __truediv__(self, other: PeriodConstrArg) -> float:
1686
+ """Divide the |Period| object through another |Period| object."""
1687
+
1688
+ @overload
1689
+ def __truediv__(self: TypePeriod, other: float) -> TypePeriod:
1690
+ """Divide the |Period| object through a number object."""
1691
+
1692
+ def __truediv__(
1693
+ self: TypePeriod, other: PeriodConstrArg | float
1694
+ ) -> TypePeriod | float:
1695
+ if isinstance(other, (float, int)):
1696
+ return type(self).from_timedelta(self.timedelta / other)
1697
+ return self.seconds / type(self)(other).seconds
1698
+
1699
+ def __rtruediv__(self, other: PeriodConstrArg) -> float:
1700
+ return type(self)(other).seconds / self.seconds
1701
+
1702
+ def __itruediv__(self: TypePeriod, other: float) -> TypePeriod: # type: ignore
1703
+ # without more flexible ways to relate types to string patterns, there is
1704
+ # nothing we can do about it (except providing a less flexible interface, of
1705
+ # course)
1706
+ self.timedelta /= other
1707
+ return self
1708
+
1709
+ def __floordiv__(self, other: PeriodConstrArg) -> int:
1710
+ return self.timedelta // type(self)(other).timedelta
1711
+
1712
+ def __rfloordiv__(self, other: PeriodConstrArg) -> int:
1713
+ return type(self)(other).timedelta // self.timedelta
1714
+
1715
+ def __mod__(self: TypePeriod, other: PeriodConstrArg) -> TypePeriod:
1716
+ return type(self).from_timedelta(self.timedelta % type(self)(other).timedelta)
1717
+
1718
+ def __rmod__(self: TypePeriod, other: Period | datetime_.timedelta) -> TypePeriod:
1719
+ return type(self).from_timedelta(type(self)(other).timedelta % self.timedelta)
1720
+
1721
+ def __pos__(self: TypePeriod) -> TypePeriod:
1722
+ return type(self).from_timedelta(self.timedelta)
1723
+
1724
+ def __neg__(self: TypePeriod) -> TypePeriod:
1725
+ return type(self).from_timedelta(-self.timedelta)
1726
+
1727
+ def __abs__(self: TypePeriod) -> TypePeriod:
1728
+ return type(self).from_timedelta(abs(self.timedelta))
1729
+
1730
+ def __lt__(self, other: PeriodConstrArg) -> bool:
1731
+ return self.timedelta < type(self)(other).timedelta
1732
+
1733
+ def __le__(self, other: PeriodConstrArg) -> bool:
1734
+ return self.timedelta <= type(self)(other).timedelta
1735
+
1736
+ def __eq__(self, other: Any) -> bool:
1737
+ try:
1738
+ return self.timedelta == type(self)(other).timedelta
1739
+ except BaseException:
1740
+ return False
1741
+
1742
+ def __ne__(self, other: Any) -> bool:
1743
+ try:
1744
+ return self.timedelta != type(self)(other).timedelta
1745
+ except BaseException:
1746
+ return True
1747
+
1748
+ def __gt__(self, other: PeriodConstrArg) -> bool:
1749
+ return self.timedelta > type(self)(other).timedelta
1750
+
1751
+ def __ge__(self, other: PeriodConstrArg) -> bool:
1752
+ return self.timedelta >= type(self)(other).timedelta
1753
+
1754
+ def __str__(self) -> str:
1755
+ if self.unit == "d":
1756
+ return f"{self.days:.0f}d"
1757
+ if self.unit == "h":
1758
+ return f"{self.hours:.0f}h"
1759
+ if self.unit == "m":
1760
+ return f"{self.minutes:.0f}m"
1761
+ return f"{self.seconds:.0f}s"
1762
+
1763
+ def __repr__(self) -> str:
1764
+ if self:
1765
+ return f'Period("{str(self)}")'
1766
+ return "Period()"
1767
+
1768
+
1769
+ class Timegrid:
1770
+ """Defines an arbitrary number of equidistant dates via the first date, the last
1771
+ date, and the step size between subsequent dates.
1772
+
1773
+ In hydrological modelling, input (and output) data are usually only available with
1774
+ a certain resolution, which also determines the possible resolution of the actual
1775
+ simulation. Class |Timegrid| reflects this situation by representing equidistant
1776
+ dates.
1777
+
1778
+ To initialise a |Timegrid|, pass its first date, its last date and its stepsize as
1779
+ |str| objects, |Date| and |Period| objects, or |datetime.datetime| and
1780
+ |datetime.timedelta| objects (combinations are allowed):
1781
+
1782
+ >>> from hydpy import Date, Period, Timegrid
1783
+ >>> timegrid = Timegrid("2000-01-01", "2001-01-01", "1d")
1784
+ >>> timegrid
1785
+ Timegrid("2000-01-01 00:00:00",
1786
+ "2001-01-01 00:00:00",
1787
+ "1d")
1788
+ >>> timegrid == Timegrid(
1789
+ ... Date("2000-01-01"), Date("2001-01-01"), Period("1d"))
1790
+ True
1791
+ >>> from datetime import datetime, timedelta
1792
+ >>> timegrid == Timegrid(
1793
+ ... datetime(2000, 1, 1), datetime(2001, 1, 1), timedelta(1))
1794
+ True
1795
+
1796
+ Passing unsupported argument types results in errors like the following:
1797
+
1798
+ >>> Timegrid("2000-01-01", "2001-01-01", 1)
1799
+ Traceback (most recent call last):
1800
+ ...
1801
+ TypeError: While trying to prepare a Trimegrid object based on the arguments \
1802
+ `2000-01-01`, `2001-01-01`, and `1`, the following error occurred: While trying to \
1803
+ initialise a `Period` object based argument `1`, the following error occurred: The \
1804
+ supplied argument must be either an instance of `Period`, `datetime.timedelta`, or \
1805
+ `str`, but the given type is `int`.
1806
+
1807
+ You can query indices and the corresponding dates via indexing:
1808
+
1809
+ >>> timegrid[0]
1810
+ Date("2000-01-01 00:00:00")
1811
+ >>> timegrid[5]
1812
+ Date("2000-01-06 00:00:00")
1813
+ >>> timegrid[Date("2000-01-01")]
1814
+ 0
1815
+ >>> timegrid["2000-01-06"]
1816
+ 5
1817
+
1818
+ Indexing beyond the ranges of the actual period is allowed:
1819
+
1820
+ >>> timegrid[-365]
1821
+ Date("1999-01-01 00:00:00")
1822
+ >>> timegrid["2002-01-01"]
1823
+ 731
1824
+
1825
+ However, dates that do not precisely match the defined grid result in the following
1826
+ error:
1827
+
1828
+ >>> timegrid["2001-01-01 12:00"]
1829
+ Traceback (most recent call last):
1830
+ ...
1831
+ ValueError: The given date `2001-01-01 12:00:00` is not properly alligned on the \
1832
+ indexed timegrid `Timegrid("2000-01-01 00:00:00", "2001-01-01 00:00:00", "1d")`.
1833
+
1834
+ You can determine the length of and iterate over |Timegrid| objects:
1835
+
1836
+ >>> len(timegrid)
1837
+ 366
1838
+ >>> for date in timegrid:
1839
+ ... print(date) # doctest: +ELLIPSIS
1840
+ 2000-01-01 00:00:00
1841
+ 2000-01-02 00:00:00
1842
+ ...
1843
+ 2000-12-30 00:00:00
1844
+ 2000-12-31 00:00:00
1845
+
1846
+ By default, iteration yields the left-side timestamps (the start points) of the
1847
+ respective intervals. Set the |Options.timestampleft| option to |False| of you
1848
+ prefer the right-side timestamps:
1849
+
1850
+ >>> from hydpy import pub
1851
+ >>> with pub.options.timestampleft(False):
1852
+ ... for date in timegrid:
1853
+ ... print(date) # doctest: +ELLIPSIS
1854
+ 2000-01-02 00:00:00
1855
+ 2000-01-03 00:00:00
1856
+ ...
1857
+ 2000-12-31 00:00:00
1858
+ 2001-01-01 00:00:00
1859
+
1860
+ You can check |Timegrid| instances for equality:
1861
+
1862
+ >>> timegrid == Timegrid("2000-01-01", "2001-01-01", "1d")
1863
+ True
1864
+ >>> timegrid != Timegrid("2000-01-01", "2001-01-01", "1d")
1865
+ False
1866
+ >>> timegrid == Timegrid("2000-01-02", "2001-01-01", "1d")
1867
+ False
1868
+ >>> timegrid == Timegrid("2000-01-01", "2001-01-02", "1d")
1869
+ False
1870
+ >>> timegrid == Timegrid("2000-01-01", "2001-01-01", "2d")
1871
+ False
1872
+ >>> timegrid == 1
1873
+ False
1874
+
1875
+ Also, you can check if a date or even the whole timegrid lies within a span defined
1876
+ by a |Timegrid| instance (note unaligned dates and time grids with different step
1877
+ sizes are considered unequal):
1878
+
1879
+ >>> Date("2000-01-01") in timegrid
1880
+ True
1881
+ >>> "2001-01-01" in timegrid
1882
+ True
1883
+ >>> "2000-07-01" in timegrid
1884
+ True
1885
+ >>> "1999-12-31" in timegrid
1886
+ False
1887
+ >>> "2001-01-02" in timegrid
1888
+ False
1889
+ >>> "2001-01-02 12:00" in timegrid
1890
+ False
1891
+
1892
+ >>> timegrid in Timegrid("2000-01-01", "2001-01-01", "1d")
1893
+ True
1894
+ >>> timegrid in Timegrid("1999-01-01", "2002-01-01", "1d")
1895
+ True
1896
+ >>> timegrid in Timegrid("2000-01-02", "2001-01-01", "1d")
1897
+ False
1898
+ >>> timegrid in Timegrid("2000-01-01", "2000-12-31", "1d")
1899
+ False
1900
+ >>> timegrid in Timegrid("2000-01-01", "2001-01-01", "2d")
1901
+ False
1902
+
1903
+ For convenience, you can temporarily modify the attributes of a |Timegrid| object
1904
+ by calling it after the `with` statement:
1905
+
1906
+ >>> with timegrid("1999-01-01", "2002-01-01", "1h"):
1907
+ ... print(timegrid)
1908
+ Timegrid("1999-01-01 00:00:00", "2002-01-01 00:00:00", "1h")
1909
+ >>> print(timegrid)
1910
+ Timegrid("2000-01-01 00:00:00", "2001-01-01 00:00:00", "1d")
1911
+
1912
+ You are free to select the attributes you want to change (see method
1913
+ |Timegrid.modify| for further information) or to change specific attributes later
1914
+ within the `with` block:
1915
+
1916
+ >>> with timegrid(lastdate=None) as tg:
1917
+ ... print(timegrid)
1918
+ ... timegrid.firstdate = "1999-01-01"
1919
+ ... tg.lastdate = "2002-01-01"
1920
+ ... tg.stepsize = "1h"
1921
+ ... print(timegrid)
1922
+ Timegrid("2000-01-01 00:00:00", "2001-01-01 00:00:00", "1d")
1923
+ Timegrid("1999-01-01 00:00:00", "2002-01-01 00:00:00", "1h")
1924
+ >>> print(timegrid)
1925
+ Timegrid("2000-01-01 00:00:00", "2001-01-01 00:00:00", "1d")
1926
+ """
1927
+
1928
+ _copy: Timegrid
1929
+
1930
+ def __init__(
1931
+ self,
1932
+ firstdate: DateConstrArg,
1933
+ lastdate: DateConstrArg,
1934
+ stepsize: PeriodConstrArg,
1935
+ ) -> None:
1936
+ try:
1937
+ self.firstdate = firstdate
1938
+ self.lastdate = lastdate
1939
+ self.stepsize = stepsize
1940
+ self.verify()
1941
+ except BaseException:
1942
+ objecttools.augment_excmessage(
1943
+ f"While trying to prepare a Trimegrid object based on the arguments "
1944
+ f"`{firstdate}`, `{lastdate}`, and `{stepsize}`"
1945
+ )
1946
+
1947
+ def _get_firstdate(self) -> Date:
1948
+ """The start date of the relevant period.
1949
+
1950
+ You can query and alter the value of property |Timegrid.firstdate| (call method
1951
+ |Timegrid.verify| afterwards to make sure the |Timegrid| object did not become
1952
+ ill-defined):
1953
+
1954
+ >>> from hydpy import Timegrid
1955
+ >>> timegrid = Timegrid("2000-01-01", "2001-01-01", "1d")
1956
+ >>> timegrid.firstdate
1957
+ Date("2000-01-01 00:00:00")
1958
+ >>> timegrid.firstdate += "1d"
1959
+ >>> timegrid
1960
+ Timegrid("2000-01-02 00:00:00",
1961
+ "2001-01-01 00:00:00",
1962
+ "1d")
1963
+ """
1964
+ return cast(Date, vars(self)["firstdate"])
1965
+
1966
+ def _set_firstdate(self, firstdate: DateConstrArg) -> None:
1967
+ vars(self)["firstdate"] = Date(firstdate)
1968
+
1969
+ firstdate = propertytools.Property[DateConstrArg, Date](
1970
+ fget=_get_firstdate, fset=_set_firstdate
1971
+ )
1972
+
1973
+ def _get_lastdate(self) -> Date:
1974
+ """The end date of the relevant period.
1975
+
1976
+ You can query and alter the value of property |Timegrid.lastdate| (call method
1977
+ |Timegrid.verify| afterwards to make sure the |Timegrid| object did not become
1978
+ ill-defined):
1979
+
1980
+ >>> from hydpy import Timegrid
1981
+ >>> timegrid = Timegrid("2000-01-01", "2001-01-01", "1d")
1982
+ >>> timegrid.lastdate
1983
+ Date("2001-01-01 00:00:00")
1984
+ >>> timegrid.lastdate += "1d"
1985
+ >>> timegrid
1986
+ Timegrid("2000-01-01 00:00:00",
1987
+ "2001-01-02 00:00:00",
1988
+ "1d")
1989
+ """
1990
+ return cast(Date, vars(self)["lastdate"])
1991
+
1992
+ def _set_lastdate(self, lastdate: DateConstrArg) -> None:
1993
+ vars(self)["lastdate"] = Date(lastdate)
1994
+
1995
+ lastdate = propertytools.Property[DateConstrArg, Date](
1996
+ fget=_get_lastdate, fset=_set_lastdate
1997
+ )
1998
+
1999
+ def _get_dates(self) -> tuple[Date, Date]:
2000
+ """Shortcut to get or set both property |Timegrid.firstdate| and property
2001
+ |Timegrid.lastdate| in one step.
2002
+
2003
+ >>> from hydpy import Timegrid
2004
+ >>> timegrid = Timegrid("2000-01-01", "2001-01-01", "1d")
2005
+ >>> timegrid.dates
2006
+ (Date("2000-01-01 00:00:00"), Date("2001-01-01 00:00:00"))
2007
+ >>> timegrid.dates = "2002-01-01", "2003-01-01"
2008
+ >>> timegrid.firstdate
2009
+ Date("2002-01-01 00:00:00")
2010
+ >>> timegrid.lastdate
2011
+ Date("2003-01-01 00:00:00")
2012
+ """
2013
+ return self.firstdate, self.lastdate
2014
+
2015
+ def _set_dates(self, dates: tuple[DateConstrArg, DateConstrArg]) -> None:
2016
+ self.firstdate = dates[0]
2017
+ self.lastdate = dates[1]
2018
+
2019
+ dates = propertytools.Property[
2020
+ tuple[DateConstrArg, DateConstrArg], tuple[Date, Date]
2021
+ ](fget=_get_dates, fset=_set_dates)
2022
+
2023
+ def _get_stepsize(self) -> Period:
2024
+ """The time series data and simulation step size.
2025
+
2026
+ You can query and alter the value of property |Timegrid.stepsize| (call method
2027
+ |Timegrid.verify| afterwards to make sure the |Timegrid| object did not become
2028
+ ill-defined):
2029
+
2030
+ >>> from hydpy import Timegrid
2031
+ >>> timegrid = Timegrid("2000-01-01", "2001-01-01", "1d")
2032
+ >>> timegrid.stepsize
2033
+ Period("1d")
2034
+ >>> timegrid.stepsize += "1d"
2035
+ >>> timegrid
2036
+ Timegrid("2000-01-01 00:00:00",
2037
+ "2001-01-01 00:00:00",
2038
+ "2d")
2039
+ """
2040
+ return cast(Period, vars(self)["stepsize"])
2041
+
2042
+ def _set_stepsize(self, stepsize: PeriodConstrArg) -> None:
2043
+ vars(self)["stepsize"] = Period(stepsize)
2044
+
2045
+ stepsize = propertytools.Property[PeriodConstrArg, Period](
2046
+ _get_stepsize, _set_stepsize
2047
+ )
2048
+
2049
+ @classmethod
2050
+ def from_array(cls: type[TypeTimegrid], array: NDArrayFloat) -> TypeTimegrid:
2051
+ """Create a |Timegrid| instance based on information stored in the first 13
2052
+ rows of a |numpy.ndarray| object and return it.
2053
+
2054
+ In *HydPy*, external time series files do define the time-related reference of
2055
+ their data on their own. For the |numpy| `npy` binary format, we achieve this
2056
+ by reserving the first six entries for the first date of the period, the next
2057
+ six entries for the last date of the period, and the last entry for the step
2058
+ size (in seconds):
2059
+
2060
+ >>> from numpy import array
2061
+ >>> array_ = array([2000, 1, 1, 0, 0, 0, # first date
2062
+ ... 2000, 1, 1, 7, 0, 0, # second date
2063
+ ... 3600, # step size (in seconds)
2064
+ ... 1, 2, 3, 4, 5, 6, 7]) # data
2065
+
2066
+ Use method |Timegrid.from_array| to extract the time information:
2067
+
2068
+ >>> from hydpy import Timegrid
2069
+ >>> timegrid = Timegrid.from_array(array_)
2070
+ >>> timegrid
2071
+ Timegrid("2000-01-01 00:00:00",
2072
+ "2000-01-01 07:00:00",
2073
+ "1h")
2074
+
2075
+ Too little information results in the following error message:
2076
+
2077
+ >>> Timegrid.from_array(array_[:12])
2078
+ Traceback (most recent call last):
2079
+ ...
2080
+ IndexError: To define a Timegrid instance via an array, 13 numbers are \
2081
+ required, but the given array consist of 12 entries/rows only.
2082
+
2083
+ The inverse method |Timegrid.to_array| creates a new |numpy| |numpy.ndarray|
2084
+ based on the current |Timegrid| object:
2085
+
2086
+ >>> from hydpy import round_
2087
+ >>> round_(timegrid.to_array())
2088
+ 2000.0, 1.0, 1.0, 0.0, 0.0, 0.0, 2000.0, 1.0, 1.0, 7.0, 0.0, 0.0, 3600.0
2089
+ """
2090
+ try:
2091
+ return cls(
2092
+ Date.from_array(array[:6]),
2093
+ Date.from_array(array[6:12]),
2094
+ Period.from_seconds(array[12].flat[0]),
2095
+ )
2096
+ except IndexError:
2097
+ raise IndexError(
2098
+ f"To define a Timegrid instance via an array, 13 numbers are "
2099
+ f"required, but the given array consist of {len(array)} entries/rows "
2100
+ f"only."
2101
+ ) from None
2102
+
2103
+ def to_array(self) -> NDArrayFloat:
2104
+ """Return a 1-dimensional |numpy| |numpy.ndarray| storing the information of
2105
+ the actual |Timegrid| object.
2106
+
2107
+ See the documentation on method |Timegrid.from_array| for more information.
2108
+ """
2109
+ values = numpy.empty(13, dtype=config.NP_FLOAT)
2110
+ values[:6] = self.firstdate.to_array()
2111
+ values[6:12] = self.lastdate.to_array()
2112
+ values[12] = self.stepsize.seconds
2113
+ return values
2114
+
2115
+ @classmethod
2116
+ def from_timepoints(
2117
+ cls: type[TypeTimegrid],
2118
+ timepoints: Sequence[float],
2119
+ refdate: DateConstrArg,
2120
+ unit: TypeUnit = "hours",
2121
+ ) -> TypeTimegrid:
2122
+ """Return a |Timegrid| object representing the given starting `timepoints`
2123
+ related to the given `refdate`.
2124
+
2125
+ At least two given time points must be increasing and equidistant. By default,
2126
+ |Timegrid.from_timepoints| assumes them as hours elapsed since the given
2127
+ reference date:
2128
+
2129
+ >>> from hydpy import Timegrid
2130
+ >>> Timegrid.from_timepoints([0.0, 6.0, 12.0, 18.0], "01.01.2000")
2131
+ Timegrid("01.01.2000 00:00:00",
2132
+ "02.01.2000 00:00:00",
2133
+ "6h")
2134
+ >>> Timegrid.from_timepoints([24.0, 30.0, 36.0, 42.0], "1999-12-31")
2135
+ Timegrid("2000-01-01 00:00:00",
2136
+ "2000-01-02 00:00:00",
2137
+ "6h")
2138
+
2139
+ You can pass other time units (`days` or `min`) explicitly (only the first
2140
+ character counts):
2141
+
2142
+ >>> Timegrid.from_timepoints([0.0, 0.25, 0.5, 0.75], "01.01.2000", unit="d")
2143
+ Timegrid("01.01.2000 00:00:00",
2144
+ "02.01.2000 00:00:00",
2145
+ "6h")
2146
+ >>> Timegrid.from_timepoints([1.0, 1.25, 1.5, 1.75], "1999-12-31", unit="days")
2147
+ Timegrid("2000-01-01 00:00:00",
2148
+ "2000-01-02 00:00:00",
2149
+ "6h")
2150
+
2151
+ When setting the |Options.timestampleft| option to |False|,
2152
+ |Timegrid.from_timepoints| assumes each time point to define the right side
2153
+ (the end) of a time interval. Repeating the above examples with this
2154
+ modification shifts the |Timegrid.firstdate| and the |Timegrid.lastdate| of the
2155
+ returned |Timegrid| objects to the left:
2156
+
2157
+ >>> from hydpy import pub
2158
+ >>> with pub.options.timestampleft(False):
2159
+ ... Timegrid.from_timepoints([0.0, 6.0, 12.0, 18.0], "01.01.2000")
2160
+ Timegrid("31.12.1999 18:00:00",
2161
+ "01.01.2000 18:00:00",
2162
+ "6h")
2163
+ >>> with pub.options.timestampleft(False):
2164
+ ... Timegrid.from_timepoints([24.0, 30.0, 36.0, 42.0], "1999-12-31")
2165
+ Timegrid("1999-12-31 18:00:00",
2166
+ "2000-01-01 18:00:00",
2167
+ "6h")
2168
+ >>> with pub.options.timestampleft(False):
2169
+ ... Timegrid.from_timepoints([0.0, 0.25, 0.5, 0.75], "01.01.2000", unit="d")
2170
+ Timegrid("31.12.1999 18:00:00",
2171
+ "01.01.2000 18:00:00",
2172
+ "6h")
2173
+ >>> with pub.options.timestampleft(False):
2174
+ ... Timegrid.from_timepoints([1.0, 1.25, 1.5, 1.75], "1999-12-31", unit="d")
2175
+ Timegrid("1999-12-31 18:00:00",
2176
+ "2000-01-01 18:00:00",
2177
+ "6h")
2178
+ """
2179
+ refdate = Date(refdate)
2180
+ period = Period.from_cfunits(unit)
2181
+ delta = timepoints[1] - timepoints[0]
2182
+ shift = 0.0 if hydpy.pub.options.timestampleft else -delta
2183
+ firstdate = refdate + (timepoints[0] + shift) * period
2184
+ lastdate = refdate + (timepoints[-1] + delta + shift) * period
2185
+ stepsize = (lastdate - firstdate) / len(timepoints)
2186
+ return cls(firstdate, lastdate, stepsize)
2187
+
2188
+ def to_timepoints(
2189
+ self, unit: TypeUnit = "hours", offset: float | PeriodConstrArg = 0.0
2190
+ ) -> numpy.ndarray:
2191
+ """Return a |numpy.ndarray| representing the starting time points of the
2192
+ |Timegrid| object.
2193
+
2194
+ By default, method |Timegrid.to_timepoints| returns the time elapsed since the
2195
+ |Timegrid.firstdate| in hours:
2196
+
2197
+ >>> from hydpy import print_vector, Timegrid
2198
+ >>> timegrid = Timegrid("2000-01-01", "2000-01-02", "6h")
2199
+ >>> print_vector(timegrid.to_timepoints())
2200
+ 0.0, 6.0, 12.0, 18.0
2201
+
2202
+ You can define other time units (`days` or `min`) (only the first character
2203
+ counts):
2204
+
2205
+ >>> print_vector(timegrid.to_timepoints(unit="d"))
2206
+ 0.0, 0.25, 0.5, 0.75
2207
+
2208
+ Additionally, one can pass an `offset` that must be of type |int| or a valid
2209
+ |Period| initialisation argument:
2210
+
2211
+ >>> print_vector(timegrid.to_timepoints(offset=24))
2212
+ 24.0, 30.0, 36.0, 42.0
2213
+ >>> print_vector(timegrid.to_timepoints(offset="1d"))
2214
+ 24.0, 30.0, 36.0, 42.0
2215
+ >>> print_vector(timegrid.to_timepoints(unit="days", offset="1d"))
2216
+ 1.0, 1.25, 1.5, 1.75
2217
+
2218
+ When setting the |Options.timestampleft| option to |False|,
2219
+ |Timegrid.to_timepoints| assumes each time point to define the right side
2220
+ (the end) of a time interval. Repeating the above examples with this
2221
+ modification shifts the time points of the returned |numpy.ndarray| objects to
2222
+ the right:
2223
+
2224
+ >>> from hydpy import pub
2225
+ >>> with pub.options.timestampleft(False):
2226
+ ... print_vector(timegrid.to_timepoints())
2227
+ 6.0, 12.0, 18.0, 24.0
2228
+ >>> with pub.options.timestampleft(False):
2229
+ ... print_vector(timegrid.to_timepoints(unit="d"))
2230
+ 0.25, 0.5, 0.75, 1.0
2231
+ >>> with pub.options.timestampleft(False):
2232
+ ... print_vector(timegrid.to_timepoints(offset=24))
2233
+ 30.0, 36.0, 42.0, 48.0
2234
+ >>> with pub.options.timestampleft(False):
2235
+ ... print_vector(timegrid.to_timepoints(offset="1d"))
2236
+ 30.0, 36.0, 42.0, 48.0
2237
+ >>> with pub.options.timestampleft(False):
2238
+ ... print_vector(timegrid.to_timepoints(unit="days", offset="1d"))
2239
+ 1.25, 1.5, 1.75, 2.0
2240
+ """
2241
+ period = Period.from_cfunits(unit)
2242
+ if not isinstance(offset, (float, int)):
2243
+ offset = Period(offset) / period
2244
+ step = self.stepsize / period
2245
+ if not hydpy.pub.options.timestampleft:
2246
+ offset += step
2247
+ nmb = len(self)
2248
+ variable = numpy.linspace(offset, offset + step * (nmb - 1), nmb)
2249
+ return variable
2250
+
2251
+ def array2series(self, array: NDArrayFloat) -> NDArrayFloat:
2252
+ """Prefix the information of the actual |Timegrid| object to the given array
2253
+ and return it.
2254
+
2255
+ The |Timegrid| information is available in the first thirteen values of the
2256
+ first axis of the returned series (see the documentation on the method
2257
+ |Timegrid.from_array|).
2258
+
2259
+ To show how method |Timegrid.array2series| works, we first apply it on a simple
2260
+ list containing numbers:
2261
+
2262
+ >>> from hydpy import Timegrid
2263
+ >>> timegrid = Timegrid("2000-11-01 00:00", "2000-11-01 04:00", "1h")
2264
+ >>> series = timegrid.array2series([1, 2, 3.5, "5.0"])
2265
+
2266
+ The first six entries contain the first date of the timegrid (year, month, day,
2267
+ hour, minute, second):
2268
+
2269
+ >>> from hydpy import round_
2270
+ >>> round_(series[:6])
2271
+ 2000.0, 11.0, 1.0, 0.0, 0.0, 0.0
2272
+
2273
+ The six subsequent entries contain the last date:
2274
+
2275
+ >>> round_(series[6:12])
2276
+ 2000.0, 11.0, 1.0, 4.0, 0.0, 0.0
2277
+
2278
+ The thirteens value is the step size in seconds:
2279
+
2280
+ >>> round_(series[12])
2281
+ 3600.0
2282
+
2283
+ The last four values are the ones of the given vector:
2284
+
2285
+ >>> round_(series[-4:])
2286
+ 1.0, 2.0, 3.5, 5.0
2287
+
2288
+ The given array can have an arbitrary number of dimensions:
2289
+
2290
+ >>> import numpy
2291
+ >>> array = numpy.eye(4)
2292
+ >>> series = timegrid.array2series(array)
2293
+
2294
+ Now the timegrid information is stored in the first column:
2295
+
2296
+ >>> round_(series[:13, 0])
2297
+ 2000.0, 11.0, 1.0, 0.0, 0.0, 0.0, 2000.0, 11.0, 1.0, 4.0, 0.0, 0.0, \
2298
+ 3600.0
2299
+
2300
+ All other columns of the first thirteen rows contain |numpy.nan| values:
2301
+
2302
+ >>> round_(series[12, :])
2303
+ 3600.0, nan, nan, nan
2304
+
2305
+ The original values are available in the last four rows:
2306
+
2307
+ >>> round_(series[13, :])
2308
+ 1.0, 0.0, 0.0, 0.0
2309
+
2310
+ Inappropriate array objects result in error messages like the following:
2311
+
2312
+ >>> timegrid.array2series([[1, 2], [3]])
2313
+ Traceback (most recent call last):
2314
+ ...
2315
+ ValueError: While trying to prefix timegrid information to the given array, \
2316
+ the following error occurred: setting an array element with a sequence. The requested \
2317
+ array has an inhomogeneous shape after 1 dimensions. The detected shape was (2,) + \
2318
+ inhomogeneous part.
2319
+
2320
+ The following error occurs when the given array does not fit the defined time
2321
+ grid:
2322
+
2323
+ >>> timegrid.array2series([[1, 2], [3, 4]])
2324
+ Traceback (most recent call last):
2325
+ ...
2326
+ ValueError: When converting an array to a sequence, the lengths of the \
2327
+ timegrid and the given array must be equal, but the length of the timegrid object is \
2328
+ `4` and the length of the array object is `2`.
2329
+ """
2330
+ try:
2331
+ array = numpy.array(array, dtype=config.NP_FLOAT)
2332
+ except BaseException:
2333
+ objecttools.augment_excmessage(
2334
+ "While trying to prefix timegrid information to the given array"
2335
+ )
2336
+ if len(array) != len(self):
2337
+ raise ValueError(
2338
+ f"When converting an array to a sequence, the lengths of the timegrid "
2339
+ f"and the given array must be equal, but the length of the timegrid "
2340
+ f"object is `{len(self)}` and the length of the array object is "
2341
+ f"`{len(array)}`."
2342
+ )
2343
+ shape = list(array.shape)
2344
+ shape[0] += 13
2345
+ series = numpy.full(shape, numpy.nan)
2346
+ slices = [slice(0, 13)]
2347
+ subshape = [13]
2348
+ for dummy in range(1, series.ndim):
2349
+ slices.append(slice(0, 1))
2350
+ subshape.append(1)
2351
+ series[tuple(slices)] = self.to_array().reshape(subshape)
2352
+ series[13:] = array
2353
+ return series
2354
+
2355
+ def verify(self) -> None:
2356
+ """Raise a |ValueError| if the dates or the step size of the |Timegrid| object
2357
+ are currently inconsistent.
2358
+
2359
+ Method |Timegrid.verify| is called at the end of the initialisation of a new
2360
+ |Timegrid| object automatically:
2361
+
2362
+ >>> from hydpy import Timegrid
2363
+ >>> Timegrid("2001-01-01", "2000-01-01", "1d")
2364
+ Traceback (most recent call last):
2365
+ ...
2366
+ ValueError: While trying to prepare a Trimegrid object based on the arguments \
2367
+ `2001-01-01`, `2000-01-01`, and `1d`, the following error occurred: The temporal \
2368
+ sequence of the first date (`2001-01-01 00:00:00`) and the last date \
2369
+ (`2000-01-01 00:00:00`) is inconsistent.
2370
+
2371
+ However, the same does not hold when changing property |Timegrid.firstdate|,
2372
+ |Timegrid.lastdate|, or |Timegrid.stepsize|:
2373
+
2374
+ >>> timegrid = Timegrid("2000-01-01", "2001-01-01", "1d")
2375
+ >>> timegrid.stepsize = "4d"
2376
+
2377
+ When in doubt, call method |Timegrid.verify| manually:
2378
+
2379
+ >>> timegrid.verify()
2380
+ Traceback (most recent call last):
2381
+ ...
2382
+ ValueError: The interval between the first date (`2000-01-01 00:00:00`) and \
2383
+ the last date (`2001-01-01 00:00:00`) is `366d`, which is not an integral multiple of \
2384
+ the step size `4d`.
2385
+ """
2386
+ if self.firstdate >= self.lastdate:
2387
+ raise ValueError(
2388
+ f"The temporal sequence of the first date (`{self.firstdate}`) and "
2389
+ f"the last date (`{self.lastdate}`) is inconsistent."
2390
+ )
2391
+ if (self.lastdate - self.firstdate) % self.stepsize:
2392
+ raise ValueError(
2393
+ f"The interval between the first date (`{self.firstdate}`) and the "
2394
+ f"last date (`{self.lastdate}`) is `{self.lastdate-self.firstdate}`, "
2395
+ f"which is not an integral multiple of the step size `{self.stepsize}`."
2396
+ )
2397
+
2398
+ def modify(
2399
+ self,
2400
+ firstdate: DateConstrArg | None = None,
2401
+ lastdate: DateConstrArg | None = None,
2402
+ stepsize: PeriodConstrArg | None = None,
2403
+ ) -> None:
2404
+ """Modify one or more |Timegrid| attributes in one step.
2405
+
2406
+ If you want to change all attributes of an existing |Timegrid| object, it is
2407
+ often most convenient to do so in one step via method |Timegrid.modify|:
2408
+
2409
+ >>> from hydpy import Timegrid
2410
+ >>> timegrid = Timegrid("2000-01-01", "2001-01-01", "1d")
2411
+ >>> timegrid.modify("1999-01-01", "2002-01-01", "1h")
2412
+ >>> timegrid
2413
+ Timegrid("1999-01-01 00:00:00",
2414
+ "2002-01-01 00:00:00",
2415
+ "1h")
2416
+
2417
+ Another benefit of method |Timegrid.modify| is that all changes are optional.
2418
+ Ignore an argument or set it to |None| explicitly to leave the corresponding
2419
+ attribute unchanged:
2420
+
2421
+ >>> timegrid.modify(None, stepsize=None)
2422
+ >>> timegrid
2423
+ Timegrid("1999-01-01 00:00:00",
2424
+ "2002-01-01 00:00:00",
2425
+ "1h")
2426
+ """
2427
+ if firstdate is not None:
2428
+ self.firstdate = firstdate
2429
+ if lastdate is not None:
2430
+ self.lastdate = lastdate
2431
+ if stepsize is not None:
2432
+ self.stepsize = stepsize
2433
+
2434
+ @contextlib.contextmanager
2435
+ def __call__(
2436
+ self,
2437
+ firstdate: DateConstrArg | None = None,
2438
+ lastdate: DateConstrArg | None = None,
2439
+ stepsize: PeriodConstrArg | None = None,
2440
+ ) -> Iterator[Timegrid]:
2441
+ firstdate_copy = self.firstdate
2442
+ lastdate_copy = self.lastdate
2443
+ stepsize_copy = self.stepsize
2444
+ try:
2445
+ self.modify(firstdate=firstdate, lastdate=lastdate, stepsize=stepsize)
2446
+ yield self
2447
+ finally:
2448
+ self.firstdate = firstdate_copy
2449
+ self.lastdate = lastdate_copy
2450
+ self.stepsize = stepsize_copy
2451
+
2452
+ def __len__(self) -> int:
2453
+ return abs(int((self.lastdate - self.firstdate) / self.stepsize))
2454
+
2455
+ @overload
2456
+ def __getitem__(self, key: int) -> Date:
2457
+ """Get the date corresponding to the given index value."""
2458
+
2459
+ @overload
2460
+ def __getitem__(self, key: DateConstrArg) -> int:
2461
+ """Get the index value corresponding to the given date."""
2462
+
2463
+ def __getitem__(self, key: int | DateConstrArg) -> Date | int:
2464
+ if isinstance(key, (int, float)):
2465
+ return Date(self.firstdate + key * self.stepsize)
2466
+ key = Date(key)
2467
+ index = (key - self.firstdate) / self.stepsize
2468
+ if index % 1.0:
2469
+ raise ValueError(
2470
+ f"The given date `{key}` is not properly alligned on the indexed "
2471
+ f"timegrid `{self}`."
2472
+ )
2473
+ return int(index)
2474
+
2475
+ def __iter__(self) -> Iterator[Date]:
2476
+ dt = copy.deepcopy(self.firstdate).datetime
2477
+ last_dt = self.lastdate.datetime
2478
+ td = self.stepsize.timedelta
2479
+ if not hydpy.pub.options.timestampleft:
2480
+ dt += td
2481
+ last_dt += td
2482
+ from_datetime = Date.from_datetime
2483
+ while dt < last_dt:
2484
+ yield from_datetime(dt)
2485
+ dt = dt + td
2486
+
2487
+ def _containsdate(self, date: Date) -> bool:
2488
+ return (self.firstdate <= date <= self.lastdate) and not (
2489
+ (date - self.firstdate) % self.stepsize
2490
+ )
2491
+
2492
+ def _containstimegrid(self, timegrid: Timegrid) -> bool:
2493
+ return (
2494
+ self._containsdate(timegrid.firstdate)
2495
+ and self._containsdate(timegrid.lastdate)
2496
+ and (timegrid.stepsize == self.stepsize)
2497
+ )
2498
+
2499
+ def __contains__(self, other: DateConstrArg | Timegrid) -> bool:
2500
+ if isinstance(other, Timegrid):
2501
+ return self._containstimegrid(other)
2502
+ return self._containsdate(Date(other))
2503
+
2504
+ def __eq__(self, other: Any) -> bool:
2505
+ if isinstance(other, Timegrid):
2506
+ return (
2507
+ (self.firstdate == other.firstdate)
2508
+ and (self.lastdate == other.lastdate)
2509
+ and (self.stepsize == other.stepsize)
2510
+ )
2511
+ return False
2512
+
2513
+ def __repr__(self) -> str:
2514
+ return self.assignrepr("")
2515
+
2516
+ def __str__(self) -> str:
2517
+ return objecttools.flatten_repr(self)
2518
+
2519
+ def assignrepr(
2520
+ self, prefix: str, style: str | None = None, utcoffset: int | None = None
2521
+ ) -> str:
2522
+ """Return a |repr| string with a prefixed assignment.
2523
+
2524
+ >>> from hydpy import Timegrid
2525
+ >>> timegrid = Timegrid("1996-11-01 00:00:00",
2526
+ ... "1997-11-01 00:00:00",
2527
+ ... "1d")
2528
+ >>> print(timegrid.assignrepr(prefix="timegrid = "))
2529
+ timegrid = Timegrid("1996-11-01 00:00:00",
2530
+ "1997-11-01 00:00:00",
2531
+ "1d")
2532
+
2533
+ >>> print(timegrid.assignrepr(
2534
+ ... prefix="", style="iso1", utcoffset=120))
2535
+ Timegrid("1996-11-01T01:00:00+02:00",
2536
+ "1997-11-01T01:00:00+02:00",
2537
+ "1d")
2538
+ """
2539
+ skip = len(prefix) + 9
2540
+ blanks = " " * skip
2541
+ return (
2542
+ f'{prefix}Timegrid("'
2543
+ f'{self.firstdate.to_string(style, utcoffset)}",\n'
2544
+ f'{blanks}"{self.lastdate.to_string(style, utcoffset)}",\n'
2545
+ f'{blanks}"{str(self.stepsize)}")'
2546
+ )
2547
+
2548
+
2549
+ class Timegrids:
2550
+ """Handles the "initialisation", the "simulation", and the "evaluation |Timegrid|
2551
+ object of a *HydPy* project.
2552
+
2553
+ The HydPy framework distinguishes three "time frames", one associated with the
2554
+ initialisation period (|Timegrids.init|), one associated with the actual simulation
2555
+ period (|Timegrids.sim|), and one associated with the actual evaluation period
2556
+ (|Timegrids.eval_|). These time frames are represented by three different
2557
+ |Timegrid| objects, which are each handled by a single |Timegrid| object.
2558
+
2559
+ There is usually only one |Timegrids| object required within a *HydPy* project
2560
+ available as attribute `timegrids` of module |pub|. You have to create such an
2561
+ object at the beginning of your workflow.
2562
+
2563
+ In many cases, one either wants to perform simulations and evaluations covering the
2564
+ whole initialisation period or not perform any simulation or evaluation. In these
2565
+ situations, you can pass a single |Timegrid| instance to the constructor of class
2566
+ |Timegrids|:
2567
+
2568
+ >>> from hydpy import Timegrid, Timegrids
2569
+ >>> timegrids = Timegrids(Timegrid("2000-01-01", "2001-01-01", "1d"))
2570
+ >>> print(timegrids)
2571
+ Timegrids("2000-01-01 00:00:00", "2001-01-01 00:00:00", "1d")
2572
+
2573
+ An even shorter approach is to pass the arguments of the |Timegrid| constructor
2574
+ directly:
2575
+
2576
+ >>> timegrids == Timegrids("2000-01-01", "2001-01-01", "1d")
2577
+ True
2578
+
2579
+ To define a simulation time grid different from the initialisation time grid, pass
2580
+ both as two individual |Timegrid| objects:
2581
+
2582
+ >>> timegrids = Timegrids(Timegrid("2000-01-01", "2001-01-01", "1d"),
2583
+ ... Timegrid("2000-01-01", "2000-07-01", "1d"))
2584
+
2585
+ The evaluation time grid then corresponds to the simulation time grid:
2586
+
2587
+ >>> timegrids
2588
+ Timegrids(init=Timegrid("2000-01-01 00:00:00",
2589
+ "2001-01-01 00:00:00",
2590
+ "1d"),
2591
+ sim=Timegrid("2000-01-01 00:00:00",
2592
+ "2000-07-01 00:00:00",
2593
+ "1d"),
2594
+ eval_=Timegrid("2000-01-01 00:00:00",
2595
+ "2000-07-01 00:00:00",
2596
+ "1d"))
2597
+
2598
+ Of course, you can also define a separate evaluation period:
2599
+
2600
+ >>> timegrids = Timegrids(Timegrid("2000-01-01", "2001-01-01", "1d"),
2601
+ ... Timegrid("2000-01-01", "2000-07-01", "1d"),
2602
+ ... Timegrid("2000-06-01", "2000-07-01", "1d"))
2603
+ >>> timegrids.init
2604
+ Timegrid("2000-01-01 00:00:00",
2605
+ "2001-01-01 00:00:00",
2606
+ "1d")
2607
+ >>> timegrids.sim
2608
+ Timegrid("2000-01-01 00:00:00",
2609
+ "2000-07-01 00:00:00",
2610
+ "1d")
2611
+ >>> timegrids.eval_
2612
+ Timegrid("2000-06-01 00:00:00",
2613
+ "2000-07-01 00:00:00",
2614
+ "1d")
2615
+
2616
+ Class |Timegrids| supports keyword arguments:
2617
+
2618
+ >>> Timegrids(firstdate="2000-01-01 00:00:00",
2619
+ ... lastdate="2001-01-01 00:00:00",
2620
+ ... stepsize="1d")
2621
+ Timegrids("2000-01-01 00:00:00",
2622
+ "2001-01-01 00:00:00",
2623
+ "1d")
2624
+
2625
+ >>> Timegrids("2000-01-01 00:00:00",
2626
+ ... "2001-01-01 00:00:00",
2627
+ ... stepsize="1d")
2628
+ Timegrids("2000-01-01 00:00:00",
2629
+ "2001-01-01 00:00:00",
2630
+ "1d")
2631
+
2632
+ >>> Timegrids(init=Timegrid("2000-01-01 00:00:00",
2633
+ ... "2001-01-01 00:00:00",
2634
+ ... "1d"),
2635
+ ... sim=Timegrid("2000-01-01 00:00:00",
2636
+ ... "2000-07-01 00:00:00",
2637
+ ... "1d"))
2638
+ Timegrids(init=Timegrid("2000-01-01 00:00:00",
2639
+ "2001-01-01 00:00:00",
2640
+ "1d"),
2641
+ sim=Timegrid("2000-01-01 00:00:00",
2642
+ "2000-07-01 00:00:00",
2643
+ "1d"),
2644
+ eval_=Timegrid("2000-01-01 00:00:00",
2645
+ "2000-07-01 00:00:00",
2646
+ "1d"))
2647
+
2648
+ >>> Timegrids(init=Timegrid("2000-01-01 00:00:00",
2649
+ ... "2001-01-01 00:00:00",
2650
+ ... "1d"),
2651
+ ... eval_=Timegrid("2000-06-01 00:00:00",
2652
+ ... "2000-07-01 00:00:00",
2653
+ ... "1d"))
2654
+ Timegrids(init=Timegrid("2000-01-01 00:00:00",
2655
+ "2001-01-01 00:00:00",
2656
+ "1d"),
2657
+ sim=Timegrid("2000-01-01 00:00:00",
2658
+ "2001-01-01 00:00:00",
2659
+ "1d"),
2660
+ eval_=Timegrid("2000-06-01 00:00:00",
2661
+ "2000-07-01 00:00:00",
2662
+ "1d"))
2663
+
2664
+ Wrong arguments should result in understandable error messages:
2665
+
2666
+ >>> Timegrids(1, 2, 3, 4)
2667
+ Traceback (most recent call last):
2668
+ ...
2669
+ TypeError: While trying to define a new `Timegrids` object based on the arguments \
2670
+ `1, 2, 3, and 4`, the following error occurred: Initialising `Timegrids` objects \
2671
+ requires one, two, or three arguments but `4` are given.
2672
+
2673
+ >>> Timegrids("wrong")
2674
+ Traceback (most recent call last):
2675
+ ...
2676
+ ValueError: While trying to define a new `Timegrids` object based on the \
2677
+ arguments `wrong`, the following error occurred: Initialising a `Timegrids` object \
2678
+ either requires one, two, or three `Timegrid` objects or two dates objects (of type \
2679
+ `Date`, `datetime`, or `str`) and one period object (of type `Period`, `timedelta`, \
2680
+ or `str`), but objects of the types `str, None, and None` are given.
2681
+
2682
+ >>> Timegrids(wrong=Timegrid("2000-01-01", "2001-01-01", "1d"))
2683
+ Traceback (most recent call last):
2684
+ ...
2685
+ TypeError: While trying to define a new `Timegrids` object based on the arguments \
2686
+ `Timegrid("2000-01-01 00:00:00", "2001-01-01 00:00:00", "1d")`, the following error \
2687
+ occurred: Initialising class `Timegrids` does not support the following given \
2688
+ keywords: `wrong`.
2689
+
2690
+ >>> Timegrids(
2691
+ ... Timegrid("2000-01-01", "2001-01-01", "1d"),
2692
+ ... init=Timegrid("2000-01-01", "2001-01-01", "1d"))
2693
+ Traceback (most recent call last):
2694
+ ...
2695
+ TypeError: While trying to define a new `Timegrids` object based on the arguments \
2696
+ `Timegrid("2000-01-01 00:00:00", "2001-01-01 00:00:00", "1d") and \
2697
+ Timegrid("2000-01-01 00:00:00", "2001-01-01 00:00:00", "1d")`, the following error \
2698
+ occurred: There is a conflict between the given positional and keyword arguments.
2699
+
2700
+ Two |Timegrids| objects are equal if all handled |Timegrid| objects are equal:
2701
+
2702
+ >>> timegrids == Timegrids(
2703
+ ... Timegrid("2000-01-01", "2001-01-01", "1d"),
2704
+ ... Timegrid("2000-01-01", "2000-07-01", "1d"),
2705
+ ... Timegrid("2000-06-01", "2000-07-01", "1d"))
2706
+ True
2707
+ >>> timegrids == Timegrids(
2708
+ ... Timegrid("1999-01-01", "2001-01-01", "1d"),
2709
+ ... Timegrid("2000-01-01", "2000-07-01", "1d"),
2710
+ ... Timegrid("2000-06-01", "2000-07-01", "1d"))
2711
+ False
2712
+ >>> timegrids == Timegrids(
2713
+ ... Timegrid("2000-01-01", "2001-01-01", "1d"),
2714
+ ... Timegrid("2000-01-01", "2001-01-01", "1d"),
2715
+ ... Timegrid("2000-06-01", "2000-07-01", "1d"))
2716
+ False
2717
+ >>> timegrids == Timegrids(
2718
+ ... Timegrid("2000-01-01", "2001-01-01", "1d"),
2719
+ ... Timegrid("2000-01-01", "2000-07-01", "1d"),
2720
+ ... Timegrid("2000-01-01", "2000-07-01", "1d"))
2721
+ False
2722
+ >>> timegrids == Date("2000-01-01")
2723
+ False
2724
+
2725
+ .. role:: raw-html(raw)
2726
+ :format: html
2727
+
2728
+ |Timegrids| objects are iterable and yield their |Timegrid| objects in the order
2729
+ |Timegrids.init| :raw-html:`&rarr;` |Timegrids.sim| :raw-html:`&rarr;`
2730
+ |Timegrids.eval_|:
2731
+
2732
+ >>> for timegrid in timegrids:
2733
+ ... print(timegrid)
2734
+ Timegrid("2000-01-01 00:00:00", "2001-01-01 00:00:00", "1d")
2735
+ Timegrid("2000-01-01 00:00:00", "2000-07-01 00:00:00", "1d")
2736
+ Timegrid("2000-06-01 00:00:00", "2000-07-01 00:00:00", "1d")
2737
+ """
2738
+
2739
+ init: Timegrid
2740
+ """|Timegrid| object covering the whole initialisation period."""
2741
+ sim: Timegrid
2742
+ """|Timegrid| object covering the actual simulation period only."""
2743
+ eval_: Timegrid
2744
+ """|Timegrid| object covering the actual evaluation period only."""
2745
+
2746
+ @overload
2747
+ def __init__(
2748
+ self, init: Timegrid, sim: Timegrid | None = ..., eval_: Timegrid | None = ...
2749
+ ) -> None:
2750
+ """from timegrids"""
2751
+
2752
+ @overload
2753
+ def __init__(
2754
+ self,
2755
+ firstdate: DateConstrArg,
2756
+ lastdate: DateConstrArg,
2757
+ stepsize: PeriodConstrArg,
2758
+ ) -> None:
2759
+ """from timegrid constructor arguments"""
2760
+
2761
+ def __init__(
2762
+ self,
2763
+ *args: None | Timegrid | DateConstrArg | PeriodConstrArg,
2764
+ **kwargs: None | Timegrid | DateConstrArg | PeriodConstrArg,
2765
+ ) -> None:
2766
+ values = list(args) + list(kwargs.values())
2767
+ try:
2768
+ if not 1 <= len(values) <= 3:
2769
+ raise TypeError(
2770
+ f"Initialising `Timegrids` objects requires one, two, or three "
2771
+ f"arguments but `{len(values)}` are given."
2772
+ )
2773
+ arguments: list[None | Timegrid | DateConstrArg | PeriodConstrArg] = [
2774
+ None,
2775
+ None,
2776
+ None,
2777
+ ]
2778
+ for idx, arg in enumerate(args):
2779
+ arguments[idx] = arg
2780
+ for idx, name in (
2781
+ (0, "init"),
2782
+ (1, "sim"),
2783
+ (2, "eval_"),
2784
+ (0, "firstdate"),
2785
+ (1, "lastdate"),
2786
+ (2, "stepsize"),
2787
+ ):
2788
+ value = kwargs.pop(name, None)
2789
+ if value is not None:
2790
+ if arguments[idx] is None:
2791
+ arguments[idx] = value
2792
+ else:
2793
+ raise TypeError(
2794
+ "There is a conflict between the given positional and "
2795
+ "keyword arguments."
2796
+ )
2797
+ if kwargs:
2798
+ raise TypeError(
2799
+ f"Initialising class `Timegrids` does not support the following "
2800
+ f"given keywords: `{objecttools.enumeration(kwargs.keys())}`."
2801
+ )
2802
+ arg1, arg2, arg3 = arguments
2803
+ if (
2804
+ isinstance(arg1, Timegrid)
2805
+ and ((arg2 is None) or isinstance(arg2, Timegrid))
2806
+ and ((arg3 is None) or isinstance(arg3, Timegrid))
2807
+ ):
2808
+ self.init = copy.deepcopy(arg1)
2809
+ if arg2 is None:
2810
+ self.sim = copy.deepcopy(self.init)
2811
+ else:
2812
+ self.sim = copy.deepcopy(arg2)
2813
+ if arg3 is None:
2814
+ self.eval_ = copy.deepcopy(self.sim)
2815
+ else:
2816
+ self.eval_ = copy.deepcopy(arg3)
2817
+ elif (
2818
+ isinstance(arg1, (Date, datetime_.datetime, str))
2819
+ and isinstance(arg2, (Date, datetime_.datetime, str))
2820
+ and isinstance(arg3, (Period, datetime_.timedelta, str))
2821
+ ):
2822
+ self.init = Timegrid(arg1, arg2, arg3)
2823
+ self.sim = Timegrid(arg1, arg2, arg3)
2824
+ self.eval_ = Timegrid(arg1, arg2, arg3)
2825
+ else:
2826
+ types_ = objecttools.enumeration(
2827
+ "None" if arg is None else type(arg).__name__ for arg in arguments
2828
+ )
2829
+ raise ValueError(
2830
+ f"Initialising a `Timegrids` object either requires one, two, or "
2831
+ f"three `Timegrid` objects or two dates objects (of type `Date`, "
2832
+ f"`datetime`, or `str`) and one period object (of type `Period`, "
2833
+ f"`timedelta`, or `str`), but objects of the types `{types_}` are "
2834
+ f"given."
2835
+ )
2836
+ self.verify()
2837
+ except BaseException:
2838
+ objecttools.augment_excmessage(
2839
+ f"While trying to define a new `Timegrids` object based on the "
2840
+ f"arguments `{objecttools.enumeration(values)}`"
2841
+ )
2842
+
2843
+ def _get_stepsize(self) -> Period:
2844
+ """Stepsize of all handled |Timegrid| objects.
2845
+
2846
+ You can change the (the identical) |Timegrid.stepsize| of all handled
2847
+ |Timegrid| objects at once:
2848
+
2849
+ >>> from hydpy import Timegrids
2850
+ >>> timegrids = Timegrids("2000-01-01", "2001-01-01", "1d")
2851
+ >>> timegrids.sim.lastdate = "2000-02-01"
2852
+ >>> timegrids.eval_.lastdate = "2000-03-01"
2853
+ >>> timegrids
2854
+ Timegrids(init=Timegrid("2000-01-01 00:00:00",
2855
+ "2001-01-01 00:00:00",
2856
+ "1d"),
2857
+ sim=Timegrid("2000-01-01 00:00:00",
2858
+ "2000-02-01 00:00:00",
2859
+ "1d"),
2860
+ eval_=Timegrid("2000-01-01 00:00:00",
2861
+ "2000-03-01 00:00:00",
2862
+ "1d"))
2863
+
2864
+ >>> timegrids.stepsize
2865
+ Period("1d")
2866
+ >>> timegrids.stepsize = "1h"
2867
+ >>> timegrids
2868
+ Timegrids(init=Timegrid("2000-01-01 00:00:00",
2869
+ "2001-01-01 00:00:00",
2870
+ "1h"),
2871
+ sim=Timegrid("2000-01-01 00:00:00",
2872
+ "2000-02-01 00:00:00",
2873
+ "1h"),
2874
+ eval_=Timegrid("2000-01-01 00:00:00",
2875
+ "2000-03-01 00:00:00",
2876
+ "1h"))
2877
+ """
2878
+ return self.init.stepsize
2879
+
2880
+ def _set_stepsize(self, stepsize: PeriodConstrArg) -> None:
2881
+ self.init.stepsize = Period(stepsize)
2882
+ self.sim.stepsize = Period(stepsize)
2883
+ self.eval_.stepsize = Period(stepsize)
2884
+
2885
+ stepsize = propertytools.Property[PeriodConstrArg, Period](
2886
+ fget=_get_stepsize, fset=_set_stepsize
2887
+ )
2888
+
2889
+ def verify(self) -> None:
2890
+ """Raise a |ValueError| if the different |Timegrid| objects are inconsistent.
2891
+
2892
+ Method |Timegrids.verify| is called at the end of the initialisation of a new
2893
+ |Timegrids| object automatically:
2894
+
2895
+ >>> from hydpy import Timegrid, Timegrids
2896
+ >>> Timegrids(init=Timegrid("2001-01-01", "2002-01-01", "1d"),
2897
+ ... sim=Timegrid("2000-01-01", "2002-01-01", "1d"))
2898
+ Traceback (most recent call last):
2899
+ ...
2900
+ ValueError: While trying to define a new `Timegrids` object based on the \
2901
+ arguments `Timegrid("2001-01-01 00:00:00", "2002-01-01 00:00:00", "1d") and \
2902
+ Timegrid("2000-01-01 00:00:00", "2002-01-01 00:00:00", "1d")`, the following error \
2903
+ occurred: The first date of the initialisation period (`2001-01-01 00:00:00`) must \
2904
+ not be later than the first date of the simulation period (`2000-01-01 00:00:00`).
2905
+
2906
+ However, the same does not hold when one changes a single time grid later:
2907
+
2908
+ >>> timegrids = Timegrids(
2909
+ ... init=Timegrid("2001-01-01", "2002-01-01", "1d"),
2910
+ ... eval_=Timegrid("2001-01-01", "2002-01-01", "1d"))
2911
+ >>> timegrids.eval_.lastdate = "2003-01-01"
2912
+
2913
+ When in doubt, call method |Timegrids.verify| manually:
2914
+
2915
+ >>> timegrids.verify()
2916
+ Traceback (most recent call last):
2917
+ ...
2918
+ ValueError: The last date of the initialisation period (`2002-01-01 00:00:00`) \
2919
+ must not be earlier than the last date of the evaluation period (`2003-01-01 00:00:00`).
2920
+
2921
+ Besides both tests explained by the above error messages, method
2922
+ |Timegrids.verify| checks for an equal step size of all |Timegrid| objects and
2923
+ their proper alignment:
2924
+
2925
+ >>> timegrids.sim.lastdate = "2002-01-01"
2926
+ >>> timegrids.sim.stepsize = "5d"
2927
+ >>> timegrids.verify()
2928
+ Traceback (most recent call last):
2929
+ ...
2930
+ ValueError: The initialisation stepsize (`1d`) must be identical with the \
2931
+ simulation stepsize (`5d`).
2932
+
2933
+ >>> timegrids.sim = Timegrid(
2934
+ ... "2001-01-01 12:00", "2001-12-31 12:00", "1d")
2935
+ >>> timegrids.verify()
2936
+ Traceback (most recent call last):
2937
+ ...
2938
+ ValueError: The simulation time grid `Timegrid("2001-01-01 12:00:00", \
2939
+ "2001-12-31 12:00:00", "1d")` is not properly alligned on the initialisation time \
2940
+ grid `Timegrid("2001-01-01 00:00:00", "2002-01-01 00:00:00", "1d")`.
2941
+
2942
+ Additionally, the method |Timegrids.verify| calls the verification methods of
2943
+ all |Timegrid| objects:
2944
+
2945
+ >>> timegrids.sim.stepsize = "3d"
2946
+ >>> timegrids.verify()
2947
+ Traceback (most recent call last):
2948
+ ...
2949
+ ValueError: While trying to verify the simulation time grid \
2950
+ `Timegrid("2001-01-01 12:00:00", "2001-12-31 12:00:00", "3d")`, the following error \
2951
+ occurred: The interval between the first date (`2001-01-01 12:00:00`) and the last \
2952
+ date (`2001-12-31 12:00:00`) is `364d`, which is not an integral multiple of the step \
2953
+ size `3d`.
2954
+
2955
+ >>> timegrids.sim = timegrids.init
2956
+ >>> timegrids.eval_.stepsize = "3d"
2957
+ >>> timegrids.verify()
2958
+ Traceback (most recent call last):
2959
+ ...
2960
+ ValueError: While trying to verify the evaluation time grid \
2961
+ `Timegrid("2001-01-01 00:00:00", "2003-01-01 00:00:00", "3d")`, the following error \
2962
+ occurred: The interval between the first date (`2001-01-01 00:00:00`) and the last \
2963
+ date (`2003-01-01 00:00:00`) is `730d`, which is not an integral multiple of the step \
2964
+ size `3d`.
2965
+
2966
+ >>> timegrids.init.stepsize = "3d"
2967
+ >>> timegrids.verify()
2968
+ Traceback (most recent call last):
2969
+ ...
2970
+ ValueError: While trying to verify the initialisation time grid \
2971
+ `Timegrid("2001-01-01 00:00:00", "2002-01-01 00:00:00", "3d")`, the following error \
2972
+ occurred: The interval between the first date (`2001-01-01 00:00:00`) and the last \
2973
+ date (`2002-01-01 00:00:00`) is `365d`, which is not an integral multiple of the step \
2974
+ size `3d`.
2975
+ """
2976
+ try:
2977
+ self.init.verify()
2978
+ except BaseException:
2979
+ objecttools.augment_excmessage(
2980
+ f"While trying to verify the initialisation time grid `{self.init}`"
2981
+ )
2982
+ try:
2983
+ self.sim.verify()
2984
+ except BaseException:
2985
+ objecttools.augment_excmessage(
2986
+ f"While trying to verify the simulation time grid `{self.sim}`"
2987
+ )
2988
+ try:
2989
+ self.eval_.verify()
2990
+ except BaseException:
2991
+ objecttools.augment_excmessage(
2992
+ f"While trying to verify the evaluation time grid `{self.eval_}`"
2993
+ )
2994
+ for tg, descr in ((self.sim, "simulation"), (self.eval_, "evaluation")):
2995
+ if self.init.firstdate > tg.firstdate:
2996
+ raise ValueError(
2997
+ f"The first date of the initialisation period "
2998
+ f"(`{self.init.firstdate}`) must not be later than the first date "
2999
+ f"of the {descr} period (`{tg.firstdate}`)."
3000
+ )
3001
+ if self.init.lastdate < tg.lastdate:
3002
+ raise ValueError(
3003
+ f"The last date of the initialisation period "
3004
+ f"(`{self.init.lastdate}`) must not be earlier than the last date "
3005
+ f"of the {descr} period (`{tg.lastdate}`)."
3006
+ )
3007
+ if self.init.stepsize != tg.stepsize:
3008
+ raise ValueError(
3009
+ f"The initialisation stepsize (`{self.init.stepsize}`) must be "
3010
+ f"identical with the {descr} stepsize (`{tg.stepsize}`)."
3011
+ )
3012
+ try:
3013
+ self.init[tg.firstdate]
3014
+ except ValueError as exc:
3015
+ raise ValueError(
3016
+ f"The {descr} time grid `{tg}` is not properly alligned on the "
3017
+ f"initialisation time grid `{self.init}`."
3018
+ ) from exc
3019
+
3020
+ @property
3021
+ def initindices(self) -> tuple[int, int]:
3022
+ """A tuple containing the start and end index of the initialisation period.
3023
+
3024
+ >>> from hydpy import Timegrids
3025
+ >>> timegrids = Timegrids("2000-01-01", "2001-01-01", "1d")
3026
+ >>> timegrids.initindices
3027
+ (0, 366)
3028
+ """
3029
+ return 0, len(self.init)
3030
+
3031
+ @property
3032
+ def simindices(self) -> tuple[int, int]:
3033
+ """A tuple containing the start and end index of the simulation period
3034
+ regarding the initialisation period.
3035
+
3036
+ >>> from hydpy import Timegrids
3037
+ >>> timegrids = Timegrids("2000-01-01", "2001-01-01", "1d")
3038
+ >>> timegrids.simindices
3039
+ (0, 366)
3040
+ >>> timegrids.sim.firstdate = "2000-01-11"
3041
+ >>> timegrids.sim.lastdate = "2000-02-01"
3042
+ >>> timegrids.simindices
3043
+ (10, 31)
3044
+ """
3045
+ return self.init[self.sim.firstdate], self.init[self.sim.lastdate]
3046
+
3047
+ @property
3048
+ def evalindices(self) -> tuple[int, int]:
3049
+ """A tuple containing the start and end index of the evaluation period
3050
+ regarding the initialisation period.
3051
+
3052
+ >>> from hydpy import Timegrids
3053
+ >>> timegrids = Timegrids("2000-01-01", "2001-01-01", "1d")
3054
+ >>> timegrids.simindices
3055
+ (0, 366)
3056
+ >>> timegrids.eval_.firstdate = "2000-01-11"
3057
+ >>> timegrids.eval_.lastdate = "2000-02-01"
3058
+ >>> timegrids.evalindices
3059
+ (10, 31)
3060
+ """
3061
+ return self.init[self.eval_.firstdate], self.init[self.eval_.lastdate]
3062
+
3063
+ def qfactor(self, area: float) -> float:
3064
+ """Return the factor for converting `mm/stepsize` to `m³/s` for a reference
3065
+ area, given in `km²`.
3066
+
3067
+ >>> from hydpy import Timegrids, round_
3068
+ >>> timegrids = Timegrids("2000-01-01", "2001-01-01", "1s")
3069
+ >>> timegrids.qfactor(1.0)
3070
+ 1000.0
3071
+ >>> timegrids.stepsize = "2d"
3072
+ >>> round_(timegrids.qfactor(2.0))
3073
+ 0.011574
3074
+ """
3075
+ return area * 1000.0 / self.stepsize.seconds
3076
+
3077
+ def parfactor(self, stepsize: PeriodConstrArg) -> float:
3078
+ """Return the factor for adjusting time-dependent parameter values to the
3079
+ actual simulation step size (the given `stepsize` must be related to the
3080
+ original parameter values).
3081
+
3082
+ >>> from hydpy import Timegrids
3083
+ >>> timegrids = Timegrids("2000-01-01", "2001-01-01", "1d")
3084
+ >>> timegrids.parfactor("1d")
3085
+ 1.0
3086
+ >>> timegrids.parfactor("1h")
3087
+ 24.0
3088
+ """
3089
+ return self.stepsize / Period(stepsize)
3090
+
3091
+ def __iter__(self) -> Iterator[Timegrid]:
3092
+ yield self.init
3093
+ yield self.sim
3094
+ yield self.eval_
3095
+
3096
+ def __eq__(self, other: Any) -> bool:
3097
+ if isinstance(other, Timegrids):
3098
+ return (
3099
+ (self.init == other.init)
3100
+ and (self.sim == other.sim)
3101
+ and (self.eval_ == other.eval_)
3102
+ )
3103
+ return False
3104
+
3105
+ def __repr__(self) -> str:
3106
+ return self.assignrepr("")
3107
+
3108
+ def assignrepr(self, prefix: str) -> str:
3109
+ """Return a |repr| string with a prefixed assignment."""
3110
+ caller = "Timegrids("
3111
+ if self.init == self.sim == self.eval_:
3112
+ repr_tg = (
3113
+ self.init.assignrepr(prefix)
3114
+ .replace("Timegrid(", caller)
3115
+ .replace("\n", "\n ")
3116
+ )
3117
+ return f"{prefix}{repr_tg}"
3118
+ blanks = " " * (len(prefix) + len(caller))
3119
+ return (
3120
+ f"{self.init.assignrepr(f'{prefix}{caller}init=')},\n"
3121
+ f"{self.sim.assignrepr(f'{blanks}sim=')},\n"
3122
+ f"{self.eval_.assignrepr(f'{blanks}eval_=')})"
3123
+ )
3124
+
3125
+ def __str__(self) -> str:
3126
+ return objecttools.flatten_repr(self)
3127
+
3128
+
3129
+ class TOY:
3130
+ """Time of year handler.
3131
+
3132
+ |TOY| objects are used to define certain things that are true for a specific time
3133
+ point in each year. The smallest supported time unit is seconds.
3134
+
3135
+ For initialisation, one usually passes a string defining the month, the day, the
3136
+ hour, the minute and the second in the mentioned order and separated by single
3137
+ underscores:
3138
+
3139
+ >>> from hydpy.core.timetools import Date, TOY
3140
+ >>> t = TOY("3_13_23_33_43")
3141
+ >>> t.month
3142
+ 3
3143
+ >>> t.day
3144
+ 13
3145
+ >>> t.hour
3146
+ 23
3147
+ >>> t.minute
3148
+ 33
3149
+ >>> t.second
3150
+ 43
3151
+
3152
+ If a lower precision is required, one can shorten the string, which implicitly sets
3153
+ the omitted property to the lowest possible value:
3154
+
3155
+ >>> TOY("3_13_23_33")
3156
+ TOY("3_13_23_33_0")
3157
+
3158
+ The most extreme example is to pass no string at all:
3159
+
3160
+ >>> TOY()
3161
+ TOY("1_1_0_0_0")
3162
+
3163
+ One can prefix some information to the string, which is useful when the string is
3164
+ to be used as a valid variable name somewhere else:
3165
+
3166
+ >>> TOY("something_3_13_23_33_2")
3167
+ TOY("3_13_23_33_2")
3168
+
3169
+ As one can see, we lose the prefixed information in the printed string
3170
+ representation. Instead, applying "str" returns a string with a standard prefix:
3171
+
3172
+ >>> str(TOY('something_3_13_23_33_2'))
3173
+ 'toy_3_13_23_33_2'
3174
+
3175
+ Alternatively, one can use a |Date| object as an initialisation argument, omitting
3176
+ the year:
3177
+
3178
+ >>> TOY(Date("2001.02.03 04:05:06"))
3179
+ TOY("2_3_4_5_6")
3180
+
3181
+ Ill-defined constructor arguments result in error messages like the following:
3182
+
3183
+ >>> TOY("something")
3184
+ Traceback (most recent call last):
3185
+ ...
3186
+ ValueError: While trying to initialise a TOY object based on argument value \
3187
+ `something` of type `str`, the following error occurred: When passing a prefixed \
3188
+ string, you need to define at least the month.
3189
+
3190
+
3191
+ >>> TOY("2_30_4_5_6")
3192
+ Traceback (most recent call last):
3193
+ ...
3194
+ ValueError: While trying to initialise a TOY object based on argument value \
3195
+ `2_30_4_5_6` of type `str`, the following error occurred: While trying to retrieve \
3196
+ the day, the following error occurred: The value of property `day` of the actual TOY \
3197
+ (time of year) object must lie within the range `(1, 29)`, as the month has already \
3198
+ been set to `2`, but the given value is `30`.
3199
+
3200
+ It is only allowed to modify the mentioned properties, not to define new ones:
3201
+
3202
+ >>> t.microsecond = 53
3203
+ Traceback (most recent call last):
3204
+ ...
3205
+ AttributeError: TOY (time of year) objects only allow to set the properties \
3206
+ month, day, hour, minute, and second, but `microsecond` is given.
3207
+
3208
+ You can pass any objects that are convertible to integers:
3209
+
3210
+ >>> t.second = "53"
3211
+ >>> t.second
3212
+ 53
3213
+
3214
+ Unconvertible objects cause the following error:
3215
+
3216
+ >>> t.second = "fiftythree"
3217
+ Traceback (most recent call last):
3218
+ ...
3219
+ ValueError: For TOY (time of year) objects, all properties must be of type `int`, \
3220
+ but the value `fiftythree` of type `str` given for property `second` cannot be \
3221
+ converted to `int`.
3222
+
3223
+ Additionally, given values are checked to lie within a suitable range:
3224
+
3225
+ >>> t.second = 60
3226
+ Traceback (most recent call last):
3227
+ ...
3228
+ ValueError: The value of property `second` of TOY (time of year) objects must lie \
3229
+ within the range `(0, 59)`, but the given value is `60`.
3230
+
3231
+ Note that the allowed values for `month` and `day` depend on each other, which is
3232
+ why the order one defines them might be of importance. So, if January is
3233
+ predefined, one can set the day to the 31st:
3234
+
3235
+ >>> t.month = 1
3236
+ >>> t.day = 31
3237
+
3238
+ Afterwards, one cannot directly change the month to April:
3239
+
3240
+ >>> t.month = 4
3241
+ Traceback (most recent call last):
3242
+ ...
3243
+ ValueError: The value of property `month` of the actual TOY (time of year) object \
3244
+ must not be the given value `4`, as the day has already been set to `31`.
3245
+
3246
+ First, set `day` to a smaller value and change `month` afterwards:
3247
+
3248
+ >>> t.day = 30
3249
+ >>> t.month = 4
3250
+
3251
+ It is possible to compare two |TOY| instances:
3252
+
3253
+ >>> t1, t2 = TOY("1"), TOY("2")
3254
+ >>> t1 < t1, t1 < t2, t2 < t1
3255
+ (False, True, False)
3256
+ >>> t1 <= t1, t1 <= t2, t2 <= t1
3257
+ (True, True, False)
3258
+ >>> t1 == t1, t1 == t2, t1 == 1
3259
+ (True, False, False)
3260
+ >>> t1 != t1, t1 != t2, t1 != 1
3261
+ (False, True, True)
3262
+ >>> t1 >= t1, t1 >= t2, t2 >= t1
3263
+ (True, False, True)
3264
+ >>> t1 > t1, t1 > t2, t2 > t1
3265
+ (False, False, True)
3266
+
3267
+ Subtracting two |TOY| objects gives their time difference in seconds:
3268
+
3269
+ >>> TOY("1_1_0_3_0") - TOY("1_1_0_1_30")
3270
+ 90
3271
+
3272
+ Subtraction never results in negative values due to assuming the left operand is
3273
+ the posterior (eventually within the subsequent year):
3274
+
3275
+ >>> TOY("1_1_0_1_30") - TOY("12_31_23_58_30")
3276
+ 180
3277
+ """
3278
+
3279
+ _PROPERTIES = collections.OrderedDict(
3280
+ (
3281
+ ("month", (1, 12)),
3282
+ ("day", (1, 31)),
3283
+ ("hour", (0, 23)),
3284
+ ("minute", (0, 59)),
3285
+ ("second", (0, 59)),
3286
+ )
3287
+ )
3288
+ _STARTDATE = Date.from_datetime(datetime_.datetime(2000, 1, 1))
3289
+ _ENDDATE = Date.from_datetime(datetime_.datetime(2001, 1, 1))
3290
+
3291
+ month: int
3292
+ """The month of the current the actual time of the year."""
3293
+ day: int
3294
+ """The day of the current the actual time of the year."""
3295
+ hour: int
3296
+ """The hour of the current the actual time of the year."""
3297
+ minute: int
3298
+ """The minute of the current the actual time of the year."""
3299
+ second: int
3300
+ """The second of the current the actual time of the year."""
3301
+
3302
+ def __init__(self, value: str | Date = "") -> None:
3303
+ try:
3304
+ if isinstance(value, Date):
3305
+ datetime = value.datetime
3306
+ dict_ = vars(self)
3307
+ for name in self._PROPERTIES.keys():
3308
+ dict_[name] = getattr(datetime, name)
3309
+ else:
3310
+ values = value.split("_")
3311
+ if not values[0].isdigit():
3312
+ if values[0] and (len(values) == 1):
3313
+ raise ValueError(
3314
+ "When passing a prefixed string, you need to define at "
3315
+ "least the month."
3316
+ )
3317
+ del values[0]
3318
+ for prop in self._PROPERTIES:
3319
+ try:
3320
+ setattr(self, prop, values.pop(0))
3321
+ except IndexError:
3322
+ if prop in ("month", "day"):
3323
+ setattr(self, prop, 1)
3324
+ else:
3325
+ setattr(self, prop, 0)
3326
+ except ValueError:
3327
+ objecttools.augment_excmessage(
3328
+ f"While trying to retrieve the {prop}"
3329
+ )
3330
+ vars(self)["seconds_passed"] = None
3331
+ vars(self)["seconds_left"] = None
3332
+ except BaseException:
3333
+ objecttools.augment_excmessage(
3334
+ f"While trying to initialise a TOY object based on argument "
3335
+ f"{objecttools.value_of_type(value)}"
3336
+ )
3337
+
3338
+ def __setattr__(self, name: str, value: int) -> None:
3339
+ if name not in self._PROPERTIES:
3340
+ raise AttributeError(
3341
+ f"TOY (time of year) objects only allow to set the properties "
3342
+ f"{objecttools.enumeration(self._PROPERTIES.keys())}, but `{name}` is "
3343
+ f"given."
3344
+ )
3345
+ try:
3346
+ value = int(value)
3347
+ except ValueError:
3348
+ raise ValueError(
3349
+ f"For TOY (time of year) objects, all properties must be of type "
3350
+ f"`int`, but the {objecttools.value_of_type(value)} given for "
3351
+ f"property `{name}` cannot be converted to `int`."
3352
+ ) from None
3353
+ if (name == "day") and hasattr(self, "month"):
3354
+ bounds = (1, calendar.monthrange(2000, self.month)[1])
3355
+ if not bounds[0] <= value <= bounds[1]:
3356
+ raise ValueError(
3357
+ f"The value of property `day` of the actual TOY (time of year) "
3358
+ f"object must lie within the range `{bounds}`, as the month has "
3359
+ f"already been set to `{self.month}`, but the given value is "
3360
+ f"`{value}`."
3361
+ )
3362
+ elif (name == "month") and hasattr(self, "day"):
3363
+ bounds = (1, calendar.monthrange(2000, value)[1])
3364
+ if not bounds[0] <= self.day <= bounds[1]:
3365
+ raise ValueError(
3366
+ f"The value of property `month` of the actual TOY (time of year) "
3367
+ f"object must not be the given value `{value}`, as the day has "
3368
+ f"already been set to `{self.day}`."
3369
+ )
3370
+ else:
3371
+ bounds = self._PROPERTIES[name]
3372
+ if not bounds[0] <= value <= bounds[1]:
3373
+ raise ValueError(
3374
+ f"The value of property `{name}` of TOY (time of year) objects "
3375
+ f"must lie within the range `{bounds}`, but the given value is "
3376
+ f"`{value}`."
3377
+ )
3378
+ super().__setattr__(name, value)
3379
+ vars(self)["seconds_passed"] = None
3380
+ vars(self)["seconds_left"] = None
3381
+
3382
+ @property
3383
+ def seconds_passed(self) -> int:
3384
+ """The amount of time passed in seconds since the beginning of the year.
3385
+
3386
+ In the first example, the year is only one minute and thirty seconds old:
3387
+
3388
+ >>> from hydpy.core.timetools import TOY
3389
+ >>> toy = TOY("1_1_0_1_30")
3390
+ >>> toy.seconds_passed
3391
+ 90
3392
+
3393
+ Updating the |TOY| object triggers a recalculation of property
3394
+ |TOY.seconds_passed|:
3395
+
3396
+ >>> toy.day = 2
3397
+ >>> toy.seconds_passed
3398
+ 86490
3399
+
3400
+ The second example shows the general inclusion of the 29th of February:
3401
+
3402
+ >>> TOY("3").seconds_passed
3403
+ 5184000
3404
+ """
3405
+ seconds_passed = cast(int | None, vars(self)["seconds_passed"])
3406
+ if seconds_passed is None:
3407
+ seconds_passed = int(
3408
+ (self._datetime - self._STARTDATE.datetime).total_seconds()
3409
+ )
3410
+ vars(self)["seconds_passed"] = seconds_passed
3411
+ return seconds_passed
3412
+
3413
+ @property
3414
+ def seconds_left(self) -> int:
3415
+ """The remaining amount of time part of the year (in seconds).
3416
+
3417
+ In the first example, only one minute and thirty seconds of the year remain:
3418
+
3419
+ >>> from hydpy.core.timetools import TOY
3420
+ >>> toy = TOY("12_31_23_58_30")
3421
+ >>> toy.seconds_left
3422
+ 90
3423
+
3424
+ Updating the |TOY| object triggers a recalculation of property
3425
+ |TOY.seconds_passed|:
3426
+
3427
+ >>> toy.day = 30
3428
+ >>> toy.seconds_left
3429
+ 86490
3430
+
3431
+ The second example shows the general inclusion of the 29th of February:
3432
+
3433
+ >>> TOY("2").seconds_left
3434
+ 28944000
3435
+ """
3436
+ seconds_left = cast(int | None, vars(self)["seconds_left"])
3437
+ if seconds_left is None:
3438
+ seconds_left = int(
3439
+ (self._ENDDATE.datetime - self._datetime).total_seconds()
3440
+ )
3441
+ vars(self)["seconds_left"] = seconds_left
3442
+ return seconds_left
3443
+
3444
+ @property
3445
+ def _datetime(self) -> datetime_.datetime:
3446
+ return datetime_.datetime(
3447
+ 2000, self.month, self.day, self.hour, self.minute, self.second
3448
+ )
3449
+
3450
+ @classmethod
3451
+ def centred_timegrid(cls) -> tuple[Timegrid, NDArrayBool]:
3452
+ """Return a |Timegrid| object defining the central time points of the year
3453
+ 2000 and a boolean array describing its intersection with the current
3454
+ initialisation period, not taking the year information into account.
3455
+
3456
+ The returned |Timegrid| object does not depend on the defined initialisation
3457
+ period:
3458
+
3459
+ >>> from hydpy.core.timetools import TOY
3460
+ >>> from hydpy import pub
3461
+ >>> pub.timegrids = "2001-10-01", "2010-10-01", "1d"
3462
+ >>> TOY.centred_timegrid()[0]
3463
+ Timegrid("2000-01-01 12:00:00",
3464
+ "2001-01-01 12:00:00",
3465
+ "1d")
3466
+
3467
+ The same holds for the shape of the returned boolean array:
3468
+
3469
+ >>> len(TOY.centred_timegrid()[1])
3470
+ 366
3471
+
3472
+ However, the single boolean values depend on whether the respective centred
3473
+ date lies at least one time within the initialisation period when ignoring the
3474
+ year information. In our example, all centred dates are "relevant" due to the
3475
+ long initialisation period of ten years:
3476
+
3477
+ >>> from hydpy import print_vector, round_
3478
+ >>> round_(sum(TOY.centred_timegrid()[1]))
3479
+ 366
3480
+
3481
+ The boolean array contains only the value |True| for all initialisation periods
3482
+ covering at least a full year:
3483
+
3484
+ >>> pub.timegrids = "2000-02-01", "2001-02-01", "1d"
3485
+ >>> round_(sum(TOY.centred_timegrid()[1]))
3486
+ 366
3487
+ >>> pub.timegrids = "2001-10-01", "2002-10-01", "1d"
3488
+ >>> round_(sum(TOY.centred_timegrid()[1]))
3489
+ 366
3490
+
3491
+ In all other cases, only the values related to the intersection are |True|:
3492
+
3493
+ >>> pub.timegrids = "2001-01-03", "2001-01-05", "1d"
3494
+ >>> print_vector(TOY.centred_timegrid()[1][:5])
3495
+ False, False, True, True, False
3496
+
3497
+ >>> pub.timegrids = "2001-12-30", "2002-01-04", "1d"
3498
+ >>> print_vector(TOY.centred_timegrid()[1][:5])
3499
+ True, True, True, False, False
3500
+ >>> print_vector(TOY.centred_timegrid()[1][-5:])
3501
+ False, False, False, True, True
3502
+
3503
+ It makes no difference whether initialisation periods not spanning an entire
3504
+ year contain the 29th of February:
3505
+
3506
+ >>> pub.timegrids = "2001-02-27", "2001-03-01", "1d"
3507
+ >>> print_vector(TOY.centred_timegrid()[1][31+28-3-1:31+28+3-1])
3508
+ False, False, True, True, True, False
3509
+ >>> pub.timegrids = "2000-02-27", "2000-03-01", "1d"
3510
+ >>> print_vector(TOY.centred_timegrid()[1][31+28-3-1:31+28+3-1])
3511
+ False, False, True, True, True, False
3512
+ """
3513
+ init = hydpy.pub.timegrids.init
3514
+ shift = init.stepsize / 2.0
3515
+ centred = Timegrid(cls._STARTDATE + shift, cls._ENDDATE + shift, init.stepsize)
3516
+ if (init.lastdate - init.firstdate) >= "365d":
3517
+ return centred, numpy.ones(len(centred), dtype=config.NP_BOOL)
3518
+ date0 = copy.deepcopy(init.firstdate)
3519
+ date1 = copy.deepcopy(init.lastdate)
3520
+ date0.year = 2000
3521
+ date1.year = 2000
3522
+ relevant = numpy.zeros(len(centred), dtype=config.NP_BOOL)
3523
+ if date0 < date1:
3524
+ relevant[centred[date0 + shift] : centred[date1 + shift]] = True
3525
+ else:
3526
+ relevant[centred[date0 + shift] :] = True
3527
+ relevant[: centred[date1 + shift]] = True
3528
+ return centred, relevant
3529
+
3530
+ def __sub__(self, other: TOY) -> float:
3531
+ if self >= other:
3532
+ return self.seconds_passed - other.seconds_passed
3533
+ return self.seconds_passed + other.seconds_left
3534
+
3535
+ def __lt__(self, other: TOY) -> bool:
3536
+ return self.seconds_passed < other.seconds_passed
3537
+
3538
+ def __le__(self, other: TOY) -> bool:
3539
+ return self.seconds_passed <= other.seconds_passed
3540
+
3541
+ def __eq__(self, other: object) -> bool:
3542
+ if isinstance(other, TOY):
3543
+ return self.seconds_passed == other.seconds_passed
3544
+ return False
3545
+
3546
+ def __ne__(self, other: object) -> bool:
3547
+ if isinstance(other, TOY):
3548
+ return self.seconds_passed != other.seconds_passed
3549
+ return True
3550
+
3551
+ def __gt__(self, other: TOY) -> bool:
3552
+ return self.seconds_passed > other.seconds_passed
3553
+
3554
+ def __ge__(self, other: TOY) -> bool:
3555
+ return self.seconds_passed >= other.seconds_passed
3556
+
3557
+ def __str__(self) -> str:
3558
+ string = "_".join(str(getattr(self, prop)) for prop in self._PROPERTIES.keys())
3559
+ return f"toy_{string}"
3560
+
3561
+ def __repr__(self) -> str:
3562
+ content = "_".join(str(getattr(self, prop)) for prop in self._PROPERTIES.keys())
3563
+ return f'TOY("{content}")'
3564
+
3565
+
3566
+ TOY0 = TOY("1_1_0_0_0")
3567
+ """The very first time of the year."""