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.
- hydpy/__init__.py +275 -0
- hydpy/aliases.py +2554 -0
- hydpy/auxs/__init__.py +0 -0
- hydpy/auxs/anntools.py +1305 -0
- hydpy/auxs/armatools.py +883 -0
- hydpy/auxs/calibtools.py +3337 -0
- hydpy/auxs/interptools.py +1094 -0
- hydpy/auxs/iuhtools.py +543 -0
- hydpy/auxs/networktools.py +597 -0
- hydpy/auxs/ppolytools.py +809 -0
- hydpy/auxs/quadtools.py +61 -0
- hydpy/auxs/roottools.py +228 -0
- hydpy/auxs/smoothtools.py +273 -0
- hydpy/auxs/statstools.py +2125 -0
- hydpy/auxs/validtools.py +81 -0
- hydpy/conf/HydPyConfigBase.xsd +68637 -0
- hydpy/conf/HydPyConfigBase.xsdt +358 -0
- hydpy/conf/HydPyConfigMultipleRuns.xsd +25 -0
- hydpy/conf/HydPyConfigSingleRun.xsd +24 -0
- hydpy/conf/__init__.py +0 -0
- hydpy/conf/a_coefficients_explicit_lobatto_sequence.npy +0 -0
- hydpy/conf/support_points_for_smoothpar_logistic2.npy +0 -0
- hydpy/config.py +42 -0
- hydpy/core/__init__.py +0 -0
- hydpy/core/aliastools.py +214 -0
- hydpy/core/autodoctools.py +1947 -0
- hydpy/core/auxfiletools.py +1169 -0
- hydpy/core/devicetools.py +3810 -0
- hydpy/core/exceptiontools.py +269 -0
- hydpy/core/filetools.py +1985 -0
- hydpy/core/hydpytools.py +3089 -0
- hydpy/core/importtools.py +1398 -0
- hydpy/core/indextools.py +345 -0
- hydpy/core/itemtools.py +1849 -0
- hydpy/core/masktools.py +460 -0
- hydpy/core/modeltools.py +4868 -0
- hydpy/core/netcdftools.py +2683 -0
- hydpy/core/objecttools.py +2023 -0
- hydpy/core/optiontools.py +1045 -0
- hydpy/core/parametertools.py +4674 -0
- hydpy/core/printtools.py +222 -0
- hydpy/core/propertytools.py +643 -0
- hydpy/core/pubtools.py +254 -0
- hydpy/core/selectiontools.py +1571 -0
- hydpy/core/sequencetools.py +4476 -0
- hydpy/core/seriestools.py +339 -0
- hydpy/core/testtools.py +2483 -0
- hydpy/core/timetools.py +3567 -0
- hydpy/core/typingtools.py +333 -0
- hydpy/core/variabletools.py +2615 -0
- hydpy/cythons/__init__.py +24 -0
- hydpy/cythons/annutils.pxd +33 -0
- hydpy/cythons/annutils.pyi +25 -0
- hydpy/cythons/annutils.pyx +120 -0
- hydpy/cythons/autogen/__init__.py +0 -0
- hydpy/cythons/autogen/annutils.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/annutils.pxd +42 -0
- hydpy/cythons/autogen/annutils.pyx +129 -0
- hydpy/cythons/autogen/c_arma.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_arma.pxd +179 -0
- hydpy/cythons/autogen/c_arma.pyx +356 -0
- hydpy/cythons/autogen/c_arma_rimorido.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_arma_rimorido.pxd +179 -0
- hydpy/cythons/autogen/c_arma_rimorido.pyx +356 -0
- hydpy/cythons/autogen/c_conv.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_conv.pxd +198 -0
- hydpy/cythons/autogen/c_conv.pyx +491 -0
- hydpy/cythons/autogen/c_conv_idw.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_conv_idw.pxd +124 -0
- hydpy/cythons/autogen/c_conv_idw.pyx +264 -0
- hydpy/cythons/autogen/c_conv_idw_ed.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_conv_idw_ed.pxd +197 -0
- hydpy/cythons/autogen/c_conv_idw_ed.pyx +481 -0
- hydpy/cythons/autogen/c_conv_nn.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_conv_nn.pxd +120 -0
- hydpy/cythons/autogen/c_conv_nn.pyx +224 -0
- hydpy/cythons/autogen/c_dam.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_dam.pxd +805 -0
- hydpy/cythons/autogen/c_dam.pyx +1477 -0
- hydpy/cythons/autogen/c_dam_llake.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_dam_llake.pxd +364 -0
- hydpy/cythons/autogen/c_dam_llake.pyx +705 -0
- hydpy/cythons/autogen/c_dam_lreservoir.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_dam_lreservoir.pxd +365 -0
- hydpy/cythons/autogen/c_dam_lreservoir.pyx +708 -0
- hydpy/cythons/autogen/c_dam_lretention.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_dam_lretention.pxd +340 -0
- hydpy/cythons/autogen/c_dam_lretention.pyx +625 -0
- hydpy/cythons/autogen/c_dam_pump.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_dam_pump.pxd +402 -0
- hydpy/cythons/autogen/c_dam_pump.pyx +724 -0
- hydpy/cythons/autogen/c_dam_pump_sluice.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_dam_pump_sluice.pxd +452 -0
- hydpy/cythons/autogen/c_dam_pump_sluice.pyx +829 -0
- hydpy/cythons/autogen/c_dam_sluice.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_dam_sluice.pxd +404 -0
- hydpy/cythons/autogen/c_dam_sluice.pyx +726 -0
- hydpy/cythons/autogen/c_dam_v001.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_dam_v001.pxd +452 -0
- hydpy/cythons/autogen/c_dam_v001.pyx +816 -0
- hydpy/cythons/autogen/c_dam_v002.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_dam_v002.pxd +394 -0
- hydpy/cythons/autogen/c_dam_v002.pyx +703 -0
- hydpy/cythons/autogen/c_dam_v003.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_dam_v003.pxd +417 -0
- hydpy/cythons/autogen/c_dam_v003.pyx +744 -0
- hydpy/cythons/autogen/c_dam_v004.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_dam_v004.pxd +486 -0
- hydpy/cythons/autogen/c_dam_v004.pyx +891 -0
- hydpy/cythons/autogen/c_dam_v005.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_dam_v005.pxd +524 -0
- hydpy/cythons/autogen/c_dam_v005.pyx +928 -0
- hydpy/cythons/autogen/c_dummy.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_dummy.pxd +151 -0
- hydpy/cythons/autogen/c_dummy.pyx +263 -0
- hydpy/cythons/autogen/c_dummy_interceptedwater.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_dummy_interceptedwater.pxd +69 -0
- hydpy/cythons/autogen/c_dummy_interceptedwater.pyx +104 -0
- hydpy/cythons/autogen/c_dummy_node2node.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_dummy_node2node.pxd +89 -0
- hydpy/cythons/autogen/c_dummy_node2node.pyx +148 -0
- hydpy/cythons/autogen/c_dummy_snowalbedo.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_dummy_snowalbedo.pxd +69 -0
- hydpy/cythons/autogen/c_dummy_snowalbedo.pyx +104 -0
- hydpy/cythons/autogen/c_dummy_snowcover.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_dummy_snowcover.pxd +69 -0
- hydpy/cythons/autogen/c_dummy_snowcover.pyx +104 -0
- hydpy/cythons/autogen/c_dummy_snowycanopy.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_dummy_snowycanopy.pxd +69 -0
- hydpy/cythons/autogen/c_dummy_snowycanopy.pyx +104 -0
- hydpy/cythons/autogen/c_dummy_soilwater.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_dummy_soilwater.pxd +69 -0
- hydpy/cythons/autogen/c_dummy_soilwater.pyx +104 -0
- hydpy/cythons/autogen/c_evap.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_evap.pxd +1029 -0
- hydpy/cythons/autogen/c_evap.pyx +2601 -0
- hydpy/cythons/autogen/c_evap_aet_hbv96.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_evap_aet_hbv96.pxd +227 -0
- hydpy/cythons/autogen/c_evap_aet_hbv96.pyx +584 -0
- hydpy/cythons/autogen/c_evap_aet_minhas.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_evap_aet_minhas.pxd +193 -0
- hydpy/cythons/autogen/c_evap_aet_minhas.pyx +478 -0
- hydpy/cythons/autogen/c_evap_aet_morsim.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_evap_aet_morsim.pxd +681 -0
- hydpy/cythons/autogen/c_evap_aet_morsim.pyx +1642 -0
- hydpy/cythons/autogen/c_evap_pet_ambav1.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_evap_pet_ambav1.pxd +532 -0
- hydpy/cythons/autogen/c_evap_pet_ambav1.pyx +1296 -0
- hydpy/cythons/autogen/c_evap_pet_hbv96.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_evap_pet_hbv96.pxd +179 -0
- hydpy/cythons/autogen/c_evap_pet_hbv96.pyx +328 -0
- hydpy/cythons/autogen/c_evap_pet_m.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_evap_pet_m.pxd +124 -0
- hydpy/cythons/autogen/c_evap_pet_m.pyx +214 -0
- hydpy/cythons/autogen/c_evap_pet_mlc.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_evap_pet_mlc.pxd +126 -0
- hydpy/cythons/autogen/c_evap_pet_mlc.pyx +214 -0
- hydpy/cythons/autogen/c_evap_ret_fao56.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_evap_ret_fao56.pxd +305 -0
- hydpy/cythons/autogen/c_evap_ret_fao56.pyx +624 -0
- hydpy/cythons/autogen/c_evap_ret_io.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_evap_ret_io.pxd +112 -0
- hydpy/cythons/autogen/c_evap_ret_io.pyx +176 -0
- hydpy/cythons/autogen/c_evap_ret_tw2002.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_evap_ret_tw2002.pxd +139 -0
- hydpy/cythons/autogen/c_evap_ret_tw2002.pyx +273 -0
- hydpy/cythons/autogen/c_exch.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_exch.pxd +230 -0
- hydpy/cythons/autogen/c_exch.pyx +462 -0
- hydpy/cythons/autogen/c_exch_branch_hbv96.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_exch_branch_hbv96.pxd +134 -0
- hydpy/cythons/autogen/c_exch_branch_hbv96.pyx +255 -0
- hydpy/cythons/autogen/c_exch_waterlevel.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_exch_waterlevel.pxd +54 -0
- hydpy/cythons/autogen/c_exch_waterlevel.pyx +78 -0
- hydpy/cythons/autogen/c_exch_weir_hbv96.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_exch_weir_hbv96.pxd +156 -0
- hydpy/cythons/autogen/c_exch_weir_hbv96.pyx +282 -0
- hydpy/cythons/autogen/c_ga.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_ga.pxd +353 -0
- hydpy/cythons/autogen/c_ga.pyx +1204 -0
- hydpy/cythons/autogen/c_ga_garto.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_ga_garto.pxd +330 -0
- hydpy/cythons/autogen/c_ga_garto.pyx +1105 -0
- hydpy/cythons/autogen/c_ga_garto_submodel1.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_ga_garto_submodel1.pxd +236 -0
- hydpy/cythons/autogen/c_ga_garto_submodel1.pyx +944 -0
- hydpy/cythons/autogen/c_gland.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_gland.pxd +437 -0
- hydpy/cythons/autogen/c_gland.pyx +726 -0
- hydpy/cythons/autogen/c_gland_gr4.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_gland_gr4.pxd +382 -0
- hydpy/cythons/autogen/c_gland_gr4.pyx +605 -0
- hydpy/cythons/autogen/c_gland_gr5.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_gland_gr5.pxd +368 -0
- hydpy/cythons/autogen/c_gland_gr5.pyx +568 -0
- hydpy/cythons/autogen/c_gland_gr6.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_gland_gr6.pxd +420 -0
- hydpy/cythons/autogen/c_gland_gr6.pyx +673 -0
- hydpy/cythons/autogen/c_hland.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_hland.pxd +855 -0
- hydpy/cythons/autogen/c_hland.pyx +2486 -0
- hydpy/cythons/autogen/c_hland_96.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_hland_96.pxd +631 -0
- hydpy/cythons/autogen/c_hland_96.pyx +1724 -0
- hydpy/cythons/autogen/c_hland_96c.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_hland_96c.pxd +621 -0
- hydpy/cythons/autogen/c_hland_96c.pyx +1822 -0
- hydpy/cythons/autogen/c_hland_96p.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_hland_96p.pxd +683 -0
- hydpy/cythons/autogen/c_hland_96p.pyx +1911 -0
- hydpy/cythons/autogen/c_kinw.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_kinw.pxd +509 -0
- hydpy/cythons/autogen/c_kinw.pyx +965 -0
- hydpy/cythons/autogen/c_kinw_williams.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_kinw_williams.pxd +409 -0
- hydpy/cythons/autogen/c_kinw_williams.pyx +763 -0
- hydpy/cythons/autogen/c_kinw_williams_ext.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_kinw_williams_ext.pxd +220 -0
- hydpy/cythons/autogen/c_kinw_williams_ext.pyx +440 -0
- hydpy/cythons/autogen/c_lland.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_lland.pxd +1386 -0
- hydpy/cythons/autogen/c_lland.pyx +3679 -0
- hydpy/cythons/autogen/c_lland_dd.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_lland_dd.pxd +679 -0
- hydpy/cythons/autogen/c_lland_dd.pyx +1719 -0
- hydpy/cythons/autogen/c_lland_knauf.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_lland_knauf.pxd +1096 -0
- hydpy/cythons/autogen/c_lland_knauf.pyx +2784 -0
- hydpy/cythons/autogen/c_lland_knauf_ic.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_lland_knauf_ic.pxd +1369 -0
- hydpy/cythons/autogen/c_lland_knauf_ic.pyx +3625 -0
- hydpy/cythons/autogen/c_meteo.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_meteo.pxd +469 -0
- hydpy/cythons/autogen/c_meteo.pyx +879 -0
- hydpy/cythons/autogen/c_meteo_clear_glob_io.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_meteo_clear_glob_io.pxd +75 -0
- hydpy/cythons/autogen/c_meteo_clear_glob_io.pyx +107 -0
- hydpy/cythons/autogen/c_meteo_glob_fao56.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_meteo_glob_fao56.pxd +209 -0
- hydpy/cythons/autogen/c_meteo_glob_fao56.pyx +339 -0
- hydpy/cythons/autogen/c_meteo_glob_io.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_meteo_glob_io.pxd +63 -0
- hydpy/cythons/autogen/c_meteo_glob_io.pyx +91 -0
- hydpy/cythons/autogen/c_meteo_glob_morsim.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_meteo_glob_morsim.pxd +289 -0
- hydpy/cythons/autogen/c_meteo_glob_morsim.pyx +527 -0
- hydpy/cythons/autogen/c_meteo_precip_io.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_meteo_precip_io.pxd +112 -0
- hydpy/cythons/autogen/c_meteo_precip_io.pyx +176 -0
- hydpy/cythons/autogen/c_meteo_psun_sun_glob_io.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_meteo_psun_sun_glob_io.pxd +87 -0
- hydpy/cythons/autogen/c_meteo_psun_sun_glob_io.pyx +123 -0
- hydpy/cythons/autogen/c_meteo_sun_fao56.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_meteo_sun_fao56.pxd +209 -0
- hydpy/cythons/autogen/c_meteo_sun_fao56.pyx +343 -0
- hydpy/cythons/autogen/c_meteo_sun_morsim.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_meteo_sun_morsim.pxd +286 -0
- hydpy/cythons/autogen/c_meteo_sun_morsim.pyx +519 -0
- hydpy/cythons/autogen/c_meteo_temp_io.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_meteo_temp_io.pxd +112 -0
- hydpy/cythons/autogen/c_meteo_temp_io.pyx +176 -0
- hydpy/cythons/autogen/c_musk.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_musk.pxd +282 -0
- hydpy/cythons/autogen/c_musk.pyx +605 -0
- hydpy/cythons/autogen/c_musk_classic.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_musk_classic.pxd +138 -0
- hydpy/cythons/autogen/c_musk_classic.pyx +226 -0
- hydpy/cythons/autogen/c_musk_mct.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_musk_mct.pxd +282 -0
- hydpy/cythons/autogen/c_musk_mct.pyx +609 -0
- hydpy/cythons/autogen/c_rconc.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_rconc.pxd +119 -0
- hydpy/cythons/autogen/c_rconc.pyx +174 -0
- hydpy/cythons/autogen/c_rconc_nash.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_rconc_nash.pxd +111 -0
- hydpy/cythons/autogen/c_rconc_nash.pyx +185 -0
- hydpy/cythons/autogen/c_rconc_uh.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_rconc_uh.pxd +92 -0
- hydpy/cythons/autogen/c_rconc_uh.pyx +125 -0
- hydpy/cythons/autogen/c_sw1d.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_sw1d.pxd +511 -0
- hydpy/cythons/autogen/c_sw1d.pyx +1263 -0
- hydpy/cythons/autogen/c_sw1d_channel.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_sw1d_channel.pxd +119 -0
- hydpy/cythons/autogen/c_sw1d_channel.pyx +300 -0
- hydpy/cythons/autogen/c_sw1d_gate_out.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_sw1d_gate_out.pxd +240 -0
- hydpy/cythons/autogen/c_sw1d_gate_out.pyx +476 -0
- hydpy/cythons/autogen/c_sw1d_lias.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_sw1d_lias.pxd +320 -0
- hydpy/cythons/autogen/c_sw1d_lias.pyx +619 -0
- hydpy/cythons/autogen/c_sw1d_lias_sluice.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_sw1d_lias_sluice.pxd +325 -0
- hydpy/cythons/autogen/c_sw1d_lias_sluice.pyx +644 -0
- hydpy/cythons/autogen/c_sw1d_network.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_sw1d_network.pxd +90 -0
- hydpy/cythons/autogen/c_sw1d_network.pyx +246 -0
- hydpy/cythons/autogen/c_sw1d_pump.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_sw1d_pump.pxd +256 -0
- hydpy/cythons/autogen/c_sw1d_pump.pyx +502 -0
- hydpy/cythons/autogen/c_sw1d_q_in.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_sw1d_q_in.pxd +224 -0
- hydpy/cythons/autogen/c_sw1d_q_in.pyx +383 -0
- hydpy/cythons/autogen/c_sw1d_q_out.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_sw1d_q_out.pxd +224 -0
- hydpy/cythons/autogen/c_sw1d_q_out.pyx +383 -0
- hydpy/cythons/autogen/c_sw1d_storage.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_sw1d_storage.pxd +193 -0
- hydpy/cythons/autogen/c_sw1d_storage.pyx +349 -0
- hydpy/cythons/autogen/c_sw1d_weir_out.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_sw1d_weir_out.pxd +212 -0
- hydpy/cythons/autogen/c_sw1d_weir_out.pyx +404 -0
- hydpy/cythons/autogen/c_test.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_test.pxd +175 -0
- hydpy/cythons/autogen/c_test.pyx +348 -0
- hydpy/cythons/autogen/c_test_discontinous.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_test_discontinous.pxd +146 -0
- hydpy/cythons/autogen/c_test_discontinous.pyx +256 -0
- hydpy/cythons/autogen/c_test_stiff0d.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_test_stiff0d.pxd +146 -0
- hydpy/cythons/autogen/c_test_stiff0d.pyx +250 -0
- hydpy/cythons/autogen/c_test_stiff1d.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_test_stiff1d.pxd +145 -0
- hydpy/cythons/autogen/c_test_stiff1d.pyx +294 -0
- hydpy/cythons/autogen/c_whmod.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_whmod.pxd +482 -0
- hydpy/cythons/autogen/c_whmod.pyx +1156 -0
- hydpy/cythons/autogen/c_whmod_rural.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_whmod_rural.pxd +411 -0
- hydpy/cythons/autogen/c_whmod_rural.pyx +982 -0
- hydpy/cythons/autogen/c_whmod_urban.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_whmod_urban.pxd +482 -0
- hydpy/cythons/autogen/c_whmod_urban.pyx +1155 -0
- hydpy/cythons/autogen/c_wland.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_wland.pxd +842 -0
- hydpy/cythons/autogen/c_wland.pyx +1890 -0
- hydpy/cythons/autogen/c_wland_gd.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_wland_gd.pxd +829 -0
- hydpy/cythons/autogen/c_wland_gd.pyx +1847 -0
- hydpy/cythons/autogen/c_wland_wag.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_wland_wag.pxd +810 -0
- hydpy/cythons/autogen/c_wland_wag.pyx +1780 -0
- hydpy/cythons/autogen/c_wq.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_wq.pxd +275 -0
- hydpy/cythons/autogen/c_wq.pyx +652 -0
- hydpy/cythons/autogen/c_wq_trapeze.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_wq_trapeze.pxd +170 -0
- hydpy/cythons/autogen/c_wq_trapeze.pyx +400 -0
- hydpy/cythons/autogen/c_wq_trapeze_strickler.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_wq_trapeze_strickler.pxd +243 -0
- hydpy/cythons/autogen/c_wq_trapeze_strickler.pyx +578 -0
- hydpy/cythons/autogen/c_wq_walrus.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/c_wq_walrus.pxd +61 -0
- hydpy/cythons/autogen/c_wq_walrus.pyx +82 -0
- hydpy/cythons/autogen/configutils.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/configutils.pxd +17 -0
- hydpy/cythons/autogen/configutils.pyx +119 -0
- hydpy/cythons/autogen/interfaceutils.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/interfaceutils.pxd +31 -0
- hydpy/cythons/autogen/interfaceutils.pyx +82 -0
- hydpy/cythons/autogen/interputils.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/interputils.pxd +42 -0
- hydpy/cythons/autogen/interputils.pyx +118 -0
- hydpy/cythons/autogen/masterinterface.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/masterinterface.pxd +153 -0
- hydpy/cythons/autogen/masterinterface.pyx +222 -0
- hydpy/cythons/autogen/pointerutils.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/pointerutils.pxd +31 -0
- hydpy/cythons/autogen/pointerutils.pyx +650 -0
- hydpy/cythons/autogen/ppolyutils.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/ppolyutils.pxd +35 -0
- hydpy/cythons/autogen/ppolyutils.pyx +59 -0
- hydpy/cythons/autogen/quadutils.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/quadutils.pxd +26 -0
- hydpy/cythons/autogen/quadutils.pyx +973 -0
- hydpy/cythons/autogen/rootutils.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/rootutils.pxd +28 -0
- hydpy/cythons/autogen/rootutils.pyx +109 -0
- hydpy/cythons/autogen/sequenceutils.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/sequenceutils.pxd +45 -0
- hydpy/cythons/autogen/sequenceutils.pyx +101 -0
- hydpy/cythons/autogen/smoothutils.cp313-win_amd64.pyd +0 -0
- hydpy/cythons/autogen/smoothutils.pxd +29 -0
- hydpy/cythons/autogen/smoothutils.pyx +833 -0
- hydpy/cythons/configutils.pxd +8 -0
- hydpy/cythons/configutils.pyi +5 -0
- hydpy/cythons/configutils.pyx +110 -0
- hydpy/cythons/interfaceutils.pxd +22 -0
- hydpy/cythons/interfaceutils.pyi +15 -0
- hydpy/cythons/interfaceutils.pyx +73 -0
- hydpy/cythons/interputils.pxd +33 -0
- hydpy/cythons/interputils.pyi +32 -0
- hydpy/cythons/interputils.pyx +109 -0
- hydpy/cythons/modelutils.py +2990 -0
- hydpy/cythons/pointerutils.pxd +22 -0
- hydpy/cythons/pointerutils.pyi +89 -0
- hydpy/cythons/pointerutils.pyx +641 -0
- hydpy/cythons/ppolyutils.pxd +26 -0
- hydpy/cythons/ppolyutils.pyi +21 -0
- hydpy/cythons/ppolyutils.pyx +50 -0
- hydpy/cythons/quadutils.pxd +17 -0
- hydpy/cythons/quadutils.pyi +13 -0
- hydpy/cythons/quadutils.pyx +964 -0
- hydpy/cythons/rootutils.pxd +19 -0
- hydpy/cythons/rootutils.pyi +21 -0
- hydpy/cythons/rootutils.pyx +100 -0
- hydpy/cythons/sequenceutils.pxd +36 -0
- hydpy/cythons/sequenceutils.pyi +7 -0
- hydpy/cythons/sequenceutils.pyx +92 -0
- hydpy/cythons/smoothutils.pxd +20 -0
- hydpy/cythons/smoothutils.pyi +15 -0
- hydpy/cythons/smoothutils.pyx +824 -0
- hydpy/data/HydPy-H-Lahn/conditions/init_1996_01_01_00_00_00/land_dill_assl.py +13 -0
- hydpy/data/HydPy-H-Lahn/conditions/init_1996_01_01_00_00_00/land_lahn_kalk.py +13 -0
- hydpy/data/HydPy-H-Lahn/conditions/init_1996_01_01_00_00_00/land_lahn_leun.py +14 -0
- hydpy/data/HydPy-H-Lahn/conditions/init_1996_01_01_00_00_00/land_lahn_marb.py +13 -0
- hydpy/data/HydPy-H-Lahn/conditions/init_1996_01_01_00_00_00/stream_dill_assl_lahn_leun.py +5 -0
- hydpy/data/HydPy-H-Lahn/conditions/init_1996_01_01_00_00_00/stream_lahn_leun_lahn_kalk.py +5 -0
- hydpy/data/HydPy-H-Lahn/conditions/init_1996_01_01_00_00_00/stream_lahn_marb_lahn_leun.py +5 -0
- hydpy/data/HydPy-H-Lahn/control/default/land.py +9 -0
- hydpy/data/HydPy-H-Lahn/control/default/land_dill_assl.py +57 -0
- hydpy/data/HydPy-H-Lahn/control/default/land_lahn_kalk.py +57 -0
- hydpy/data/HydPy-H-Lahn/control/default/land_lahn_leun.py +56 -0
- hydpy/data/HydPy-H-Lahn/control/default/land_lahn_marb.py +57 -0
- hydpy/data/HydPy-H-Lahn/control/default/stream_dill_assl_lahn_leun.py +7 -0
- hydpy/data/HydPy-H-Lahn/control/default/stream_lahn_leun_lahn_kalk.py +7 -0
- hydpy/data/HydPy-H-Lahn/control/default/stream_lahn_marb_lahn_leun.py +7 -0
- hydpy/data/HydPy-H-Lahn/multiple_runs.xml +309 -0
- hydpy/data/HydPy-H-Lahn/multiple_runs_alpha.xml +71 -0
- hydpy/data/HydPy-H-Lahn/network/default/headwaters.py +11 -0
- hydpy/data/HydPy-H-Lahn/network/default/nonheadwaters.py +11 -0
- hydpy/data/HydPy-H-Lahn/network/default/streams.py +8 -0
- hydpy/data/HydPy-H-Lahn/series/default/dill_assl_obs_q.asc +11387 -0
- hydpy/data/HydPy-H-Lahn/series/default/evap_pet_hbv96_input_normalairtemperature.nc +0 -0
- hydpy/data/HydPy-H-Lahn/series/default/evap_pet_hbv96_input_normalevapotranspiration.nc +0 -0
- hydpy/data/HydPy-H-Lahn/series/default/hland_96_input_p.nc +0 -0
- hydpy/data/HydPy-H-Lahn/series/default/hland_96_input_t.nc +0 -0
- hydpy/data/HydPy-H-Lahn/series/default/lahn_kalk_obs_q.asc +11387 -0
- hydpy/data/HydPy-H-Lahn/series/default/lahn_leun_obs_q.asc +11387 -0
- hydpy/data/HydPy-H-Lahn/series/default/lahn_marb_obs_q.asc +11387 -0
- hydpy/data/HydPy-H-Lahn/series/default/land_dill_assl_evap_pet_hbv96_input_normalairtemperature.asc +11387 -0
- hydpy/data/HydPy-H-Lahn/series/default/land_dill_assl_evap_pet_hbv96_input_normalevapotranspiration.asc +11387 -0
- hydpy/data/HydPy-H-Lahn/series/default/land_dill_assl_hland_96_input_p.asc +11387 -0
- hydpy/data/HydPy-H-Lahn/series/default/land_dill_assl_hland_96_input_t.asc +11387 -0
- hydpy/data/HydPy-H-Lahn/series/default/land_lahn_kalk_evap_pet_hbv96_input_normalairtemperature.asc +11387 -0
- hydpy/data/HydPy-H-Lahn/series/default/land_lahn_kalk_evap_pet_hbv96_input_normalevapotranspiration.asc +11387 -0
- hydpy/data/HydPy-H-Lahn/series/default/land_lahn_kalk_hland_96_input_p.asc +11387 -0
- hydpy/data/HydPy-H-Lahn/series/default/land_lahn_kalk_hland_96_input_t.asc +11387 -0
- hydpy/data/HydPy-H-Lahn/series/default/land_lahn_leun_evap_pet_hbv96_input_normalairtemperature.asc +11387 -0
- hydpy/data/HydPy-H-Lahn/series/default/land_lahn_leun_evap_pet_hbv96_input_normalevapotranspiration.asc +11387 -0
- hydpy/data/HydPy-H-Lahn/series/default/land_lahn_leun_hland_96_input_p.asc +11387 -0
- hydpy/data/HydPy-H-Lahn/series/default/land_lahn_leun_hland_96_input_t.asc +11387 -0
- hydpy/data/HydPy-H-Lahn/series/default/land_lahn_marb_evap_pet_hbv96_input_normalairtemperature.asc +11387 -0
- hydpy/data/HydPy-H-Lahn/series/default/land_lahn_marb_evap_pet_hbv96_input_normalevapotranspiration.asc +11387 -0
- hydpy/data/HydPy-H-Lahn/series/default/land_lahn_marb_hland_96_input_p.asc +11387 -0
- hydpy/data/HydPy-H-Lahn/series/default/land_lahn_marb_hland_96_input_t.asc +11387 -0
- hydpy/data/HydPy-H-Lahn/series/default/obs_q.nc +0 -0
- hydpy/data/HydPy-H-Lahn/single_run.xml +152 -0
- hydpy/data/HydPy-H-Lahn/single_run.xmlt +143 -0
- hydpy/data/__init__.py +17 -0
- hydpy/docs/__init__.py +0 -0
- hydpy/docs/autofigs/__init__.py +0 -0
- hydpy/docs/bib/__init__.py +0 -0
- hydpy/docs/bib/refs.bib +566 -0
- hydpy/docs/combine_docversions.py +133 -0
- hydpy/docs/draw_model_sketches.py +1301 -0
- hydpy/docs/enable_autodoc.py +7 -0
- hydpy/docs/figs/HydPy-G-GR4.png +0 -0
- hydpy/docs/figs/HydPy-G-GR5.png +0 -0
- hydpy/docs/figs/HydPy-G-GR6.png +0 -0
- hydpy/docs/figs/HydPy-H-HBV96-COSERO.png +0 -0
- hydpy/docs/figs/HydPy-H-HBV96-PREVAH.png +0 -0
- hydpy/docs/figs/HydPy-H-HBV96.png +0 -0
- hydpy/docs/figs/HydPy-H-Lahn.png +0 -0
- hydpy/docs/figs/HydPy-KinW-Williams.png +0 -0
- hydpy/docs/figs/HydPy-L-DD.png +0 -0
- hydpy/docs/figs/HydPy-W-Wag.png +0 -0
- hydpy/docs/figs/HydPy_Logo.png +0 -0
- hydpy/docs/figs/HydPy_Logo_Text.png +0 -0
- hydpy/docs/figs/IDLE-editor.png +0 -0
- hydpy/docs/figs/IDLE-shell.png +0 -0
- hydpy/docs/figs/LAWA_river-basin-bumbers.png +0 -0
- hydpy/docs/figs/__init__.py +0 -0
- hydpy/docs/html_/__init__.py +0 -0
- hydpy/docs/polish_html.py +57 -0
- hydpy/docs/prepare.py +224 -0
- hydpy/docs/publish_docs.py +53 -0
- hydpy/docs/rst/HydPy-ARMA.rst +27 -0
- hydpy/docs/rst/HydPy-Conv.rst +22 -0
- hydpy/docs/rst/HydPy-Dam.rst +79 -0
- hydpy/docs/rst/HydPy-Dummy.rst +21 -0
- hydpy/docs/rst/HydPy-Evap.rst +26 -0
- hydpy/docs/rst/HydPy-Exch.rst +36 -0
- hydpy/docs/rst/HydPy-G.rst +40 -0
- hydpy/docs/rst/HydPy-GA.rst +34 -0
- hydpy/docs/rst/HydPy-H.rst +24 -0
- hydpy/docs/rst/HydPy-KinW.rst +32 -0
- hydpy/docs/rst/HydPy-L.rst +42 -0
- hydpy/docs/rst/HydPy-Meteo.rst +28 -0
- hydpy/docs/rst/HydPy-Musk.rst +21 -0
- hydpy/docs/rst/HydPy-Rconc.rst +17 -0
- hydpy/docs/rst/HydPy-SW1D.rst +49 -0
- hydpy/docs/rst/HydPy-Test.rst +19 -0
- hydpy/docs/rst/HydPy-W.rst +20 -0
- hydpy/docs/rst/HydPy-WHMod.rst +19 -0
- hydpy/docs/rst/HydPy-WQ.rst +20 -0
- hydpy/docs/rst/__init__.py +0 -0
- hydpy/docs/rst/additional_repositories.rst +40 -0
- hydpy/docs/rst/auxiliaries.rst +31 -0
- hydpy/docs/rst/continuous_integration.rst +75 -0
- hydpy/docs/rst/core.rst +75 -0
- hydpy/docs/rst/cythons.rst +47 -0
- hydpy/docs/rst/definitions.rst +506 -0
- hydpy/docs/rst/developer_guide.rst +54 -0
- hydpy/docs/rst/example_projects.rst +40 -0
- hydpy/docs/rst/execution.rst +22 -0
- hydpy/docs/rst/framework_tools.rst +56 -0
- hydpy/docs/rst/how_to_read_the_reference_manual.rst +156 -0
- hydpy/docs/rst/hydpydependencies.rst +55 -0
- hydpy/docs/rst/index.rst +125 -0
- hydpy/docs/rst/installation.rst +155 -0
- hydpy/docs/rst/model_families.rst +79 -0
- hydpy/docs/rst/model_overview.rst +291 -0
- hydpy/docs/rst/modelimports.rst +10 -0
- hydpy/docs/rst/options.rst +119 -0
- hydpy/docs/rst/programming_style.rst +572 -0
- hydpy/docs/rst/project_structure.rst +520 -0
- hydpy/docs/rst/quickstart.rst +304 -0
- hydpy/docs/rst/reference_manual.rst +29 -0
- hydpy/docs/rst/required_tools.rst +50 -0
- hydpy/docs/rst/simulation.rst +514 -0
- hydpy/docs/rst/submodel_interfaces.rst +32 -0
- hydpy/docs/rst/tests_and_documentation.rst +85 -0
- hydpy/docs/rst/user_guide.rst +38 -0
- hydpy/docs/rst/version_control.rst +116 -0
- hydpy/docs/rst/zbibliography.rst +8 -0
- hydpy/docs/sphinx/__init__.py +0 -0
- hydpy/docs/sphinx/_themes/basic_hydpy/changes/frameset.html +11 -0
- hydpy/docs/sphinx/_themes/basic_hydpy/changes/rstsource.html +15 -0
- hydpy/docs/sphinx/_themes/basic_hydpy/changes/versionchanges.html +33 -0
- hydpy/docs/sphinx/_themes/basic_hydpy/defindex.html +35 -0
- hydpy/docs/sphinx/_themes/basic_hydpy/domainindex.html +56 -0
- hydpy/docs/sphinx/_themes/basic_hydpy/genindex-single.html +63 -0
- hydpy/docs/sphinx/_themes/basic_hydpy/genindex-split.html +41 -0
- hydpy/docs/sphinx/_themes/basic_hydpy/genindex.html +76 -0
- hydpy/docs/sphinx/_themes/basic_hydpy/globaltoc.html +11 -0
- hydpy/docs/sphinx/_themes/basic_hydpy/layout.html +221 -0
- hydpy/docs/sphinx/_themes/basic_hydpy/localtoc.html +15 -0
- hydpy/docs/sphinx/_themes/basic_hydpy/opensearch.xml +13 -0
- hydpy/docs/sphinx/_themes/basic_hydpy/page.html +13 -0
- hydpy/docs/sphinx/_themes/basic_hydpy/relations.html +23 -0
- hydpy/docs/sphinx/_themes/basic_hydpy/search.html +65 -0
- hydpy/docs/sphinx/_themes/basic_hydpy/searchbox.html +21 -0
- hydpy/docs/sphinx/_themes/basic_hydpy/searchfield.html +23 -0
- hydpy/docs/sphinx/_themes/basic_hydpy/sourcelink.html +18 -0
- hydpy/docs/sphinx/_themes/basic_hydpy/static/basic.css_t +925 -0
- hydpy/docs/sphinx/_themes/basic_hydpy/static/doctools.js +156 -0
- hydpy/docs/sphinx/_themes/basic_hydpy/static/documentation_options.js_t +13 -0
- hydpy/docs/sphinx/_themes/basic_hydpy/static/file.png +0 -0
- hydpy/docs/sphinx/_themes/basic_hydpy/static/language_data.js_t +26 -0
- hydpy/docs/sphinx/_themes/basic_hydpy/static/minus.png +0 -0
- hydpy/docs/sphinx/_themes/basic_hydpy/static/plus.png +0 -0
- hydpy/docs/sphinx/_themes/basic_hydpy/static/searchtools.js +574 -0
- hydpy/docs/sphinx/_themes/basic_hydpy/static/sphinx_highlight.js +154 -0
- hydpy/docs/sphinx/_themes/basic_hydpy/theme.conf +16 -0
- hydpy/docs/sphinx/_themes/classic_hydpy/layout.html +23 -0
- hydpy/docs/sphinx/_themes/classic_hydpy/static/classic.css_t +358 -0
- hydpy/docs/sphinx/_themes/classic_hydpy/static/sidebar.js_t +72 -0
- hydpy/docs/sphinx/_themes/classic_hydpy/theme.conf +32 -0
- hydpy/docs/sphinx/conf.py +398 -0
- hydpy/docs/sphinx/defaultlinks_extension.py +36 -0
- hydpy/docs/sphinx/integrationtest_extension.py +104 -0
- hydpy/docs/sphinx/projectstructure_extension.py +58 -0
- hydpy/docs/sphinx/submodelgraph_extension.py +53 -0
- hydpy/exe/__init__.py +0 -0
- hydpy/exe/commandtools.py +651 -0
- hydpy/exe/hyd.py +277 -0
- hydpy/exe/modelimports.py +41 -0
- hydpy/exe/replacetools.py +216 -0
- hydpy/exe/servertools.py +2348 -0
- hydpy/exe/xmltools.py +3280 -0
- hydpy/interfaces/__init__.py +0 -0
- hydpy/interfaces/aetinterfaces.py +94 -0
- hydpy/interfaces/dischargeinterfaces.py +45 -0
- hydpy/interfaces/petinterfaces.py +117 -0
- hydpy/interfaces/precipinterfaces.py +42 -0
- hydpy/interfaces/radiationinterfaces.py +79 -0
- hydpy/interfaces/rconcinterfaces.py +30 -0
- hydpy/interfaces/routinginterfaces.py +324 -0
- hydpy/interfaces/soilinterfaces.py +96 -0
- hydpy/interfaces/stateinterfaces.py +98 -0
- hydpy/interfaces/tempinterfaces.py +46 -0
- hydpy/models/__init__.py +0 -0
- hydpy/models/arma/__init__.py +14 -0
- hydpy/models/arma/arma_control.py +383 -0
- hydpy/models/arma/arma_derived.py +204 -0
- hydpy/models/arma/arma_fluxes.py +41 -0
- hydpy/models/arma/arma_inlets.py +11 -0
- hydpy/models/arma/arma_logs.py +19 -0
- hydpy/models/arma/arma_model.py +461 -0
- hydpy/models/arma/arma_outlets.py +11 -0
- hydpy/models/arma_rimorido.py +381 -0
- hydpy/models/conv/__init__.py +12 -0
- hydpy/models/conv/conv_control.py +303 -0
- hydpy/models/conv/conv_derived.py +271 -0
- hydpy/models/conv/conv_fluxes.py +54 -0
- hydpy/models/conv/conv_inlets.py +11 -0
- hydpy/models/conv/conv_model.py +687 -0
- hydpy/models/conv/conv_outlets.py +11 -0
- hydpy/models/conv_idw.py +120 -0
- hydpy/models/conv_idw_ed.py +184 -0
- hydpy/models/conv_nn.py +112 -0
- hydpy/models/dam/__init__.py +16 -0
- hydpy/models/dam/dam_aides.py +17 -0
- hydpy/models/dam/dam_control.py +346 -0
- hydpy/models/dam/dam_derived.py +559 -0
- hydpy/models/dam/dam_factors.py +46 -0
- hydpy/models/dam/dam_fluxes.py +179 -0
- hydpy/models/dam/dam_inlets.py +29 -0
- hydpy/models/dam/dam_logs.py +52 -0
- hydpy/models/dam/dam_model.py +5011 -0
- hydpy/models/dam/dam_outlets.py +23 -0
- hydpy/models/dam/dam_receivers.py +41 -0
- hydpy/models/dam/dam_senders.py +23 -0
- hydpy/models/dam/dam_solver.py +75 -0
- hydpy/models/dam/dam_states.py +11 -0
- hydpy/models/dam_llake.py +499 -0
- hydpy/models/dam_lreservoir.py +548 -0
- hydpy/models/dam_lretention.py +343 -0
- hydpy/models/dam_pump.py +278 -0
- hydpy/models/dam_pump_sluice.py +339 -0
- hydpy/models/dam_sluice.py +319 -0
- hydpy/models/dam_v001.py +1127 -0
- hydpy/models/dam_v002.py +381 -0
- hydpy/models/dam_v003.py +422 -0
- hydpy/models/dam_v004.py +665 -0
- hydpy/models/dam_v005.py +479 -0
- hydpy/models/dummy/__init__.py +15 -0
- hydpy/models/dummy/dummy_control.py +22 -0
- hydpy/models/dummy/dummy_fluxes.py +11 -0
- hydpy/models/dummy/dummy_inlets.py +11 -0
- hydpy/models/dummy/dummy_inputs.py +41 -0
- hydpy/models/dummy/dummy_model.py +196 -0
- hydpy/models/dummy/dummy_outlets.py +11 -0
- hydpy/models/dummy_interceptedwater.py +85 -0
- hydpy/models/dummy_node2node.py +83 -0
- hydpy/models/dummy_snowalbedo.py +84 -0
- hydpy/models/dummy_snowcover.py +84 -0
- hydpy/models/dummy_snowycanopy.py +86 -0
- hydpy/models/dummy_soilwater.py +85 -0
- hydpy/models/evap/__init__.py +13 -0
- hydpy/models/evap/evap_control.py +354 -0
- hydpy/models/evap/evap_derived.py +236 -0
- hydpy/models/evap/evap_factors.py +188 -0
- hydpy/models/evap/evap_fixed.py +68 -0
- hydpy/models/evap/evap_fluxes.py +150 -0
- hydpy/models/evap/evap_inputs.py +54 -0
- hydpy/models/evap/evap_logs.py +91 -0
- hydpy/models/evap/evap_masks.py +48 -0
- hydpy/models/evap/evap_model.py +9170 -0
- hydpy/models/evap/evap_parameters.py +149 -0
- hydpy/models/evap/evap_sequences.py +32 -0
- hydpy/models/evap/evap_states.py +18 -0
- hydpy/models/evap_aet_hbv96.py +372 -0
- hydpy/models/evap_aet_minhas.py +331 -0
- hydpy/models/evap_aet_morsim.py +627 -0
- hydpy/models/evap_pet_ambav1.py +483 -0
- hydpy/models/evap_pet_hbv96.py +147 -0
- hydpy/models/evap_pet_m.py +94 -0
- hydpy/models/evap_pet_mlc.py +107 -0
- hydpy/models/evap_ret_fao56.py +265 -0
- hydpy/models/evap_ret_io.py +74 -0
- hydpy/models/evap_ret_tw2002.py +165 -0
- hydpy/models/exch/__init__.py +14 -0
- hydpy/models/exch/exch_control.py +262 -0
- hydpy/models/exch/exch_derived.py +36 -0
- hydpy/models/exch/exch_factors.py +26 -0
- hydpy/models/exch/exch_fluxes.py +48 -0
- hydpy/models/exch/exch_inlets.py +11 -0
- hydpy/models/exch/exch_logs.py +12 -0
- hydpy/models/exch/exch_model.py +451 -0
- hydpy/models/exch/exch_outlets.py +17 -0
- hydpy/models/exch/exch_receivers.py +17 -0
- hydpy/models/exch_branch_hbv96.py +186 -0
- hydpy/models/exch_waterlevel.py +73 -0
- hydpy/models/exch_weir_hbv96.py +609 -0
- hydpy/models/ga/__init__.py +14 -0
- hydpy/models/ga/ga_aides.py +17 -0
- hydpy/models/ga/ga_control.py +208 -0
- hydpy/models/ga/ga_derived.py +77 -0
- hydpy/models/ga/ga_fluxes.py +83 -0
- hydpy/models/ga/ga_inputs.py +26 -0
- hydpy/models/ga/ga_logs.py +17 -0
- hydpy/models/ga/ga_model.py +2952 -0
- hydpy/models/ga/ga_states.py +87 -0
- hydpy/models/ga_garto.py +1001 -0
- hydpy/models/ga_garto_submodel1.py +79 -0
- hydpy/models/gland/__init__.py +14 -0
- hydpy/models/gland/gland_control.py +90 -0
- hydpy/models/gland/gland_derived.py +113 -0
- hydpy/models/gland/gland_fluxes.py +137 -0
- hydpy/models/gland/gland_inputs.py +12 -0
- hydpy/models/gland/gland_model.py +1439 -0
- hydpy/models/gland/gland_outlets.py +11 -0
- hydpy/models/gland/gland_states.py +90 -0
- hydpy/models/gland_gr4.py +501 -0
- hydpy/models/gland_gr5.py +463 -0
- hydpy/models/gland_gr6.py +487 -0
- hydpy/models/hland/__init__.py +20 -0
- hydpy/models/hland/hland_aides.py +19 -0
- hydpy/models/hland/hland_constants.py +37 -0
- hydpy/models/hland/hland_control.py +1530 -0
- hydpy/models/hland/hland_derived.py +683 -0
- hydpy/models/hland/hland_factors.py +57 -0
- hydpy/models/hland/hland_fixed.py +42 -0
- hydpy/models/hland/hland_fluxes.py +279 -0
- hydpy/models/hland/hland_inputs.py +19 -0
- hydpy/models/hland/hland_masks.py +107 -0
- hydpy/models/hland/hland_model.py +4664 -0
- hydpy/models/hland/hland_outlets.py +11 -0
- hydpy/models/hland/hland_parameters.py +227 -0
- hydpy/models/hland/hland_sequences.py +382 -0
- hydpy/models/hland/hland_states.py +236 -0
- hydpy/models/hland_96.py +1812 -0
- hydpy/models/hland_96c.py +1196 -0
- hydpy/models/hland_96p.py +1204 -0
- hydpy/models/kinw/__init__.py +18 -0
- hydpy/models/kinw/kinw_aides.py +306 -0
- hydpy/models/kinw/kinw_control.py +270 -0
- hydpy/models/kinw/kinw_derived.py +197 -0
- hydpy/models/kinw/kinw_fixed.py +33 -0
- hydpy/models/kinw/kinw_fluxes.py +37 -0
- hydpy/models/kinw/kinw_inlets.py +11 -0
- hydpy/models/kinw/kinw_model.py +3026 -0
- hydpy/models/kinw/kinw_outlets.py +11 -0
- hydpy/models/kinw/kinw_solver.py +45 -0
- hydpy/models/kinw/kinw_states.py +17 -0
- hydpy/models/kinw_williams.py +1299 -0
- hydpy/models/kinw_williams_ext.py +768 -0
- hydpy/models/lland/__init__.py +42 -0
- hydpy/models/lland/lland_aides.py +38 -0
- hydpy/models/lland/lland_constants.py +88 -0
- hydpy/models/lland/lland_control.py +1329 -0
- hydpy/models/lland/lland_derived.py +380 -0
- hydpy/models/lland/lland_factors.py +18 -0
- hydpy/models/lland/lland_fixed.py +128 -0
- hydpy/models/lland/lland_fluxes.py +626 -0
- hydpy/models/lland/lland_inlets.py +12 -0
- hydpy/models/lland/lland_inputs.py +33 -0
- hydpy/models/lland/lland_logs.py +17 -0
- hydpy/models/lland/lland_masks.py +212 -0
- hydpy/models/lland/lland_model.py +7690 -0
- hydpy/models/lland/lland_outlets.py +12 -0
- hydpy/models/lland/lland_parameters.py +195 -0
- hydpy/models/lland/lland_sequences.py +67 -0
- hydpy/models/lland/lland_states.py +280 -0
- hydpy/models/lland_dd.py +2270 -0
- hydpy/models/lland_knauf.py +2156 -0
- hydpy/models/lland_knauf_ic.py +1920 -0
- hydpy/models/meteo/__init__.py +12 -0
- hydpy/models/meteo/meteo_control.py +154 -0
- hydpy/models/meteo/meteo_derived.py +159 -0
- hydpy/models/meteo/meteo_factors.py +88 -0
- hydpy/models/meteo/meteo_fixed.py +19 -0
- hydpy/models/meteo/meteo_fluxes.py +46 -0
- hydpy/models/meteo/meteo_inputs.py +47 -0
- hydpy/models/meteo/meteo_logs.py +30 -0
- hydpy/models/meteo/meteo_model.py +2904 -0
- hydpy/models/meteo/meteo_parameters.py +14 -0
- hydpy/models/meteo/meteo_sequences.py +22 -0
- hydpy/models/meteo_clear_glob_io.py +77 -0
- hydpy/models/meteo_glob_fao56.py +217 -0
- hydpy/models/meteo_glob_io.py +68 -0
- hydpy/models/meteo_glob_morsim.py +444 -0
- hydpy/models/meteo_precip_io.py +76 -0
- hydpy/models/meteo_psun_sun_glob_io.py +83 -0
- hydpy/models/meteo_sun_fao56.py +188 -0
- hydpy/models/meteo_sun_morsim.py +466 -0
- hydpy/models/meteo_temp_io.py +76 -0
- hydpy/models/musk/__init__.py +15 -0
- hydpy/models/musk/musk_control.py +328 -0
- hydpy/models/musk/musk_derived.py +32 -0
- hydpy/models/musk/musk_factors.py +53 -0
- hydpy/models/musk/musk_fluxes.py +24 -0
- hydpy/models/musk/musk_inlets.py +11 -0
- hydpy/models/musk/musk_masks.py +15 -0
- hydpy/models/musk/musk_model.py +838 -0
- hydpy/models/musk/musk_outlets.py +11 -0
- hydpy/models/musk/musk_sequences.py +88 -0
- hydpy/models/musk/musk_solver.py +68 -0
- hydpy/models/musk/musk_states.py +64 -0
- hydpy/models/musk_classic.py +228 -0
- hydpy/models/musk_mct.py +1247 -0
- hydpy/models/rconc/__init__.py +12 -0
- hydpy/models/rconc/rconc_control.py +473 -0
- hydpy/models/rconc/rconc_derived.py +76 -0
- hydpy/models/rconc/rconc_fluxes.py +19 -0
- hydpy/models/rconc/rconc_logs.py +74 -0
- hydpy/models/rconc/rconc_model.py +260 -0
- hydpy/models/rconc/rconc_states.py +11 -0
- hydpy/models/rconc_nash.py +48 -0
- hydpy/models/rconc_uh.py +53 -0
- hydpy/models/sw1d/__init__.py +17 -0
- hydpy/models/sw1d/sw1d_control.py +356 -0
- hydpy/models/sw1d/sw1d_derived.py +85 -0
- hydpy/models/sw1d/sw1d_factors.py +78 -0
- hydpy/models/sw1d/sw1d_fixed.py +12 -0
- hydpy/models/sw1d/sw1d_fluxes.py +55 -0
- hydpy/models/sw1d/sw1d_inlets.py +17 -0
- hydpy/models/sw1d/sw1d_model.py +3385 -0
- hydpy/models/sw1d/sw1d_outlets.py +11 -0
- hydpy/models/sw1d/sw1d_receivers.py +11 -0
- hydpy/models/sw1d/sw1d_senders.py +11 -0
- hydpy/models/sw1d/sw1d_states.py +23 -0
- hydpy/models/sw1d_channel.py +2051 -0
- hydpy/models/sw1d_gate_out.py +599 -0
- hydpy/models/sw1d_lias.py +105 -0
- hydpy/models/sw1d_lias_sluice.py +531 -0
- hydpy/models/sw1d_network.py +1219 -0
- hydpy/models/sw1d_pump.py +448 -0
- hydpy/models/sw1d_q_in.py +79 -0
- hydpy/models/sw1d_q_out.py +81 -0
- hydpy/models/sw1d_storage.py +78 -0
- hydpy/models/sw1d_weir_out.py +75 -0
- hydpy/models/test/__init__.py +14 -0
- hydpy/models/test/test_control.py +28 -0
- hydpy/models/test/test_fluxes.py +17 -0
- hydpy/models/test/test_model.py +201 -0
- hydpy/models/test/test_solver.py +48 -0
- hydpy/models/test/test_states.py +17 -0
- hydpy/models/test_discontinous.py +46 -0
- hydpy/models/test_stiff0d.py +47 -0
- hydpy/models/test_stiff1d.py +42 -0
- hydpy/models/whmod/__init__.py +21 -0
- hydpy/models/whmod/whmod_constants.py +77 -0
- hydpy/models/whmod/whmod_control.py +333 -0
- hydpy/models/whmod/whmod_derived.py +210 -0
- hydpy/models/whmod/whmod_factors.py +9 -0
- hydpy/models/whmod/whmod_fluxes.py +105 -0
- hydpy/models/whmod/whmod_inputs.py +15 -0
- hydpy/models/whmod/whmod_masks.py +178 -0
- hydpy/models/whmod/whmod_model.py +2091 -0
- hydpy/models/whmod/whmod_parameters.py +155 -0
- hydpy/models/whmod/whmod_sequences.py +193 -0
- hydpy/models/whmod/whmod_states.py +73 -0
- hydpy/models/whmod_rural.py +794 -0
- hydpy/models/whmod_urban.py +1011 -0
- hydpy/models/wland/__init__.py +43 -0
- hydpy/models/wland/wland_aides.py +55 -0
- hydpy/models/wland/wland_constants.py +103 -0
- hydpy/models/wland/wland_control.py +508 -0
- hydpy/models/wland/wland_derived.py +330 -0
- hydpy/models/wland/wland_factors.py +11 -0
- hydpy/models/wland/wland_fixed.py +12 -0
- hydpy/models/wland/wland_fluxes.py +166 -0
- hydpy/models/wland/wland_inputs.py +33 -0
- hydpy/models/wland/wland_masks.py +54 -0
- hydpy/models/wland/wland_model.py +3755 -0
- hydpy/models/wland/wland_outlets.py +11 -0
- hydpy/models/wland/wland_parameters.py +214 -0
- hydpy/models/wland/wland_sequences.py +108 -0
- hydpy/models/wland/wland_solver.py +45 -0
- hydpy/models/wland/wland_states.py +56 -0
- hydpy/models/wland_gd.py +888 -0
- hydpy/models/wland_wag.py +1244 -0
- hydpy/models/wq/__init__.py +14 -0
- hydpy/models/wq/wq_control.py +117 -0
- hydpy/models/wq/wq_derived.py +182 -0
- hydpy/models/wq/wq_factors.py +79 -0
- hydpy/models/wq/wq_fluxes.py +17 -0
- hydpy/models/wq/wq_model.py +1889 -0
- hydpy/models/wq_trapeze.py +168 -0
- hydpy/models/wq_trapeze_strickler.py +101 -0
- hydpy/models/wq_walrus.py +57 -0
- hydpy/py.typed +0 -0
- hydpy/tests/.coveragerc +22 -0
- hydpy/tests/__init__.py +0 -0
- hydpy/tests/check_consistency.py +32 -0
- hydpy/tests/hydpydoctestcustomize.pth +1 -0
- hydpy/tests/hydpydoctestcustomize.py +15 -0
- hydpy/tests/iotesting/__init__.py +0 -0
- hydpy/tests/run_doctests.py +233 -0
- hydpy-6.2.dev1.data/scripts/hyd.py +277 -0
- hydpy-6.2.dev1.dist-info/LICENSE +165 -0
- hydpy-6.2.dev1.dist-info/METADATA +163 -0
- hydpy-6.2.dev1.dist-info/RECORD +890 -0
- hydpy-6.2.dev1.dist-info/WHEEL +5 -0
- hydpy-6.2.dev1.dist-info/licenses_hydpy_installer.txt +42 -0
- hydpy-6.2.dev1.dist-info/top_level.txt +1 -0
hydpy/core/timetools.py
ADDED
|
@@ -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:`→` |Timegrids.sim| :raw-html:`→`
|
|
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."""
|