hydrotoolbox 2.2.0__tar.gz → 2.3.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/.gitmodules +2 -1
  2. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/.pre-commit-config.yaml +4 -4
  3. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/CHANGELOG.md +8 -0
  4. {hydrotoolbox-2.2.0/src/hydrotoolbox.egg-info → hydrotoolbox-2.3.1}/PKG-INFO +1 -1
  5. hydrotoolbox-2.3.1/VERSION +1 -0
  6. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/pyproject.toml +1 -1
  7. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/src/hydrotoolbox/baseflow/methods/lyne_hollick.py +7 -2
  8. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/src/hydrotoolbox/baseflow/separation.py +3 -2
  9. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/src/hydrotoolbox/baseflow_sep.py +6 -1
  10. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/src/hydrotoolbox/toolbox_utils/src/toolbox_utils/readers/hbn.py +32 -172
  11. hydrotoolbox-2.3.1/src/hydrotoolbox/toolbox_utils/src/toolbox_utils/readers/utils.py +179 -0
  12. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/src/hydrotoolbox/toolbox_utils/src/toolbox_utils/readers/wdm.py +4 -2
  13. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/src/hydrotoolbox/toolbox_utils/src/toolbox_utils/tsutils.py +93 -70
  14. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/src/hydrotoolbox/toolbox_utils/src/toolbox_utils/utils.py +52 -0
  15. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/src/hydrotoolbox/toolbox_utils/tests/test_asbestfreq.py +5 -3
  16. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/src/hydrotoolbox/toolbox_utils/tests/test_read.py +0 -1
  17. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/src/hydrotoolbox/toolbox_utils/tests/test_wdm.py +18 -17
  18. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1/src/hydrotoolbox.egg-info}/PKG-INFO +1 -1
  19. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/src/hydrotoolbox.egg-info/SOURCES.txt +1 -1
  20. hydrotoolbox-2.2.0/VERSION +0 -1
  21. hydrotoolbox-2.2.0/pytest.ini +0 -2
  22. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/.deepsource.toml +0 -0
  23. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/.github/dependabot.yml +0 -0
  24. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/.github/workflows/clean-workflow-runs.yml +0 -0
  25. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/.github/workflows/pypi-package.yml +0 -0
  26. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/.github/workflows/tests.yml +0 -0
  27. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/.gitignore +0 -0
  28. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/.sourcery.yaml +0 -0
  29. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/AUTHORS.rst +0 -0
  30. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/BADGES.rst +0 -0
  31. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/CONTRIBUTING.rst +0 -0
  32. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/LICENSE.txt +0 -0
  33. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/README.rst +0 -0
  34. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/docs/Makefile +0 -0
  35. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/docs/authors.rst +0 -0
  36. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/docs/command_line.rst +0 -0
  37. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/docs/conf.py +0 -0
  38. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/docs/contributing.rst +0 -0
  39. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/docs/function_summary.rst +0 -0
  40. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/docs/index.rst +0 -0
  41. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/docs/license.rst +0 -0
  42. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/docs/make.bat +0 -0
  43. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/docs/readme.rst +0 -0
  44. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/setup.cfg +0 -0
  45. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/src/hydrotoolbox/__init__.py +0 -0
  46. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/src/hydrotoolbox/baseflow/__init__.py +0 -0
  47. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/src/hydrotoolbox/baseflow/comparison.py +0 -0
  48. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/src/hydrotoolbox/baseflow/example.csv +0 -0
  49. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/src/hydrotoolbox/baseflow/methods/__init__.py +0 -0
  50. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/src/hydrotoolbox/baseflow/methods/boughton.py +0 -0
  51. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/src/hydrotoolbox/baseflow/methods/chapman.py +0 -0
  52. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/src/hydrotoolbox/baseflow/methods/chapman_maxwell.py +0 -0
  53. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/src/hydrotoolbox/baseflow/methods/eckhardt.py +0 -0
  54. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/src/hydrotoolbox/baseflow/methods/ewma.py +0 -0
  55. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/src/hydrotoolbox/baseflow/methods/five_day.py +0 -0
  56. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/src/hydrotoolbox/baseflow/methods/fixed.py +0 -0
  57. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/src/hydrotoolbox/baseflow/methods/furey.py +0 -0
  58. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/src/hydrotoolbox/baseflow/methods/general_form.py +0 -0
  59. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/src/hydrotoolbox/baseflow/methods/ihacres.py +0 -0
  60. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/src/hydrotoolbox/baseflow/methods/local.py +0 -0
  61. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/src/hydrotoolbox/baseflow/methods/slide.py +0 -0
  62. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/src/hydrotoolbox/baseflow/methods/strict.py +0 -0
  63. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/src/hydrotoolbox/baseflow/methods/ukih.py +0 -0
  64. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/src/hydrotoolbox/baseflow/methods/willems.py +0 -0
  65. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/src/hydrotoolbox/baseflow/param_estimate.py +0 -0
  66. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/src/hydrotoolbox/baseflow/utils.py +0 -0
  67. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/src/hydrotoolbox/hydrotoolbox.py +0 -0
  68. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/src/hydrotoolbox/indices/__init__.py +0 -0
  69. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/src/hydrotoolbox/indices/indices.py +0 -0
  70. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/src/hydrotoolbox/toolbox_utils/docs/conf.py +0 -0
  71. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/src/hydrotoolbox/toolbox_utils/src/toolbox_utils/__init__.py +0 -0
  72. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/src/hydrotoolbox/toolbox_utils/src/toolbox_utils/readers/__init__.py +0 -0
  73. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/src/hydrotoolbox/toolbox_utils/src/toolbox_utils/readers/plotgen.py +0 -0
  74. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/src/hydrotoolbox/toolbox_utils/tests/test_date_slice.py +0 -0
  75. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/src/hydrotoolbox/toolbox_utils/tests/test_dateparse.py +0 -0
  76. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/src/hydrotoolbox/toolbox_utils/tests/test_extract.py +0 -0
  77. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/src/hydrotoolbox/toolbox_utils/tests/test_hbn.py +0 -0
  78. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/src/hydrotoolbox/toolbox_utils/tests/test_make_list.py +0 -0
  79. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/src/hydrotoolbox/toolbox_utils/tests/test_period_timestamp.py +0 -0
  80. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/src/hydrotoolbox/toolbox_utils/tests/test_plotgen.py +0 -0
  81. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/src/hydrotoolbox/toolbox_utils/tests/test_range_to_numlist.py +0 -0
  82. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/src/hydrotoolbox/utils.py +0 -0
  83. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/src/hydrotoolbox.egg-info/dependency_links.txt +0 -0
  84. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/src/hydrotoolbox.egg-info/entry_points.txt +0 -0
  85. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/src/hydrotoolbox.egg-info/requires.txt +0 -0
  86. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/src/hydrotoolbox.egg-info/top_level.txt +0 -0
  87. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/tests/BEC_test.sh +0 -0
  88. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/tests/Q_BEC_BE_6500.csv +0 -0
  89. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/tests/Q_BEC_BE_6500.ssf +0 -0
  90. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/tests/black_earth_creek_05406500_flow.csv +0 -0
  91. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/tests/black_earth_creek_05406500_test.sh +0 -0
  92. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/tests/data.csv +0 -0
  93. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/tests/data_02239501.csv +0 -0
  94. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/tests/data_baseflow_boughton.csv +0 -0
  95. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/tests/data_baseflow_chapman.csv +0 -0
  96. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/tests/data_baseflow_chapman_maxwell.csv +0 -0
  97. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/tests/data_baseflow_eckhardt.csv +0 -0
  98. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/tests/data_baseflow_ewma.csv +0 -0
  99. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/tests/data_baseflow_five_day.csv +0 -0
  100. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/tests/data_baseflow_furey.csv +0 -0
  101. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/tests/data_baseflow_ihacres.csv +0 -0
  102. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/tests/data_baseflow_lyne_hollick.csv +0 -0
  103. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/tests/data_baseflow_strict.csv +0 -0
  104. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/tests/data_baseflow_ukih.csv +0 -0
  105. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/tests/data_baseflow_usgs_hysep_fixed.csv +0 -0
  106. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/tests/data_baseflow_usgs_hysep_local.csv +0 -0
  107. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/tests/data_baseflow_usgs_hysep_slide.csv +0 -0
  108. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/tests/data_baseflow_willems.csv +0 -0
  109. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/tests/data_percentile_test.csv +0 -0
  110. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/tests/data_short.csv +0 -0
  111. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/tests/data_test_storm_events.csv +0 -0
  112. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/tests/gtable.out +0 -0
  113. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/tests/gtable_1955.out +0 -0
  114. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/tests/hydrotoolbox_indices.txt +0 -0
  115. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/tests/run_test.sh +0 -0
  116. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/tests/test_baseflow_sep.py +0 -0
  117. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/tests/test_exceedance_time.py +0 -0
  118. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/tests/test_flow_duration.py +0 -0
  119. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/tests/test_indices.py +0 -0
  120. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/tests/test_recession.py +0 -0
  121. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/tests/test_storm_events.py +0 -0
  122. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/tests/tsproc_hydrologic_indices.inp +0 -0
  123. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/tests/tsproc_hydrologic_indices.rec +0 -0
  124. {hydrotoolbox-2.2.0 → hydrotoolbox-2.3.1}/tests/tsproc_hydrologic_indices_percentile_test.inp +0 -0
@@ -1,3 +1,4 @@
1
1
  [submodule "src/hydrotoolbox/toolbox_utils"]
2
2
  path = src/hydrotoolbox/toolbox_utils
3
- url = http://github.com/timcera/toolbox_utils
3
+ url = git@github.com:timcera/toolbox_utils.git
4
+ branch = main
@@ -29,7 +29,7 @@ repos:
29
29
 
30
30
  - repo: https://github.com/astral-sh/ruff-pre-commit
31
31
  # Ruff version.
32
- rev: v0.15.6
32
+ rev: v0.15.16
33
33
  hooks:
34
34
  # Run the linter.
35
35
  - id: ruff
@@ -40,7 +40,7 @@ repos:
40
40
  types_or: [python, pyi, jupyter]
41
41
 
42
42
  - repo: https://github.com/pycqa/isort
43
- rev: 8.0.1
43
+ rev: 9.0.0a3
44
44
  hooks:
45
45
  - id: isort
46
46
  name: isort (python)
@@ -52,7 +52,7 @@ repos:
52
52
  types: [pyi]
53
53
 
54
54
  - repo: https://github.com/pappasam/toml-sort
55
- rev: v0.24.3
55
+ rev: v0.24.4
56
56
  hooks:
57
57
  - id: toml-sort-fix
58
58
  args: [--in-place, --spaces-indent-inline-array, '4']
@@ -91,7 +91,7 @@ repos:
91
91
  - id: pyupgrade
92
92
 
93
93
  - repo: https://github.com/commitizen-tools/commitizen
94
- rev: v4.13.9
94
+ rev: v4.16.3
95
95
  hooks:
96
96
  - id: commitizen
97
97
  stages: [commit-msg]
@@ -1,3 +1,11 @@
1
+ ## v2.3.1 (2026-06-11)
2
+
3
+ ## v2.3.0 (2026-06-09)
4
+
5
+ ### Feat
6
+
7
+ - add "passes" to Lynne Hollick function
8
+
1
9
  ## v2.2.0 (2026-03-29)
2
10
 
3
11
  ### Feat
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hydrotoolbox
3
- Version: 2.2.0
3
+ Version: 2.3.1
4
4
  Summary: Command line script and Python library for analysis of flow time-series.
5
5
  Author-email: Tim Cera <tim@cerazone.net>
6
6
  License: BSD-3-Clause
@@ -0,0 +1 @@
1
+ 2.3.1
@@ -65,7 +65,7 @@ github = "https://github.com/timcera/hydrotoolbox"
65
65
  name = "cz_conventional_commits"
66
66
  tag_format = "v$version"
67
67
  update_changelog_on_bump = true
68
- version = "2.2.0"
68
+ version = "2.3.1"
69
69
  version_files = ["VERSION"]
70
70
 
71
71
  [tool.isort]
@@ -5,10 +5,15 @@ __all__ = [
5
5
  from hydrotoolbox.baseflow.methods.general_form import general_form_digital_filter
6
6
 
7
7
 
8
- def lyne_hollick(flow, k):
8
+ def lyne_hollick(flow, k, passes=1):
9
9
  """LH digital filter (Lyne & Hollick, 1979)"""
10
10
  alpha = k
11
11
  beta = (1 - k) / 2
12
12
  gamma = 1.0
13
13
 
14
- return general_form_digital_filter(flow, alpha, beta, gamma)
14
+ for pss in range(passes):
15
+ if pss > 0:
16
+ flow = flow[::-1]
17
+ flow, count = general_form_digital_filter(flow, alpha, beta, gamma)
18
+
19
+ return flow, count
@@ -37,6 +37,7 @@ def separation(
37
37
  C=None,
38
38
  a=None,
39
39
  bfi_max=None,
40
+ passes=1,
40
41
  ):
41
42
  if method == "all":
42
43
  method = [
@@ -76,7 +77,7 @@ def separation(
76
77
  "furey",
77
78
  "willems",
78
79
  ]:
79
- b_lh = lyne_hollick(Q, k=k)[0]
80
+ b_lh = lyne_hollick(Q, k=k, passes=passes)[0]
80
81
 
81
82
  if m == "ukih":
82
83
  b[m] = ukih(Q, b_lh)
@@ -91,7 +92,7 @@ def separation(
91
92
  b[m] = slide(Q, area=area, num_days=num_days)
92
93
 
93
94
  if m == "lyne_hollick":
94
- b[m] = lyne_hollick(Q, k=k)[0]
95
+ b[m] = lyne_hollick(Q, k=k, passes=passes)[0]
95
96
 
96
97
  if m == "chapman":
97
98
  b[m] = chapman(Q, k)[0]
@@ -104,6 +104,7 @@ def bfsep(
104
104
  C=None,
105
105
  a=None,
106
106
  bfi_max=None,
107
+ passes=1,
107
108
  ):
108
109
  complete_index = pd.date_range(start=flow.index[0], end=flow.index[-1], freq="D")
109
110
  flow = flow.reindex(complete_index, fill_value=np.nan)
@@ -154,6 +155,7 @@ def bfsep(
154
155
  C=C,
155
156
  a=a,
156
157
  bfi_max=bfi_max,
158
+ passes=passes,
157
159
  )[bfi],
158
160
  index=ncol.index,
159
161
  )
@@ -664,6 +666,7 @@ def furey(
664
666
  def lyne_hollick(
665
667
  input_ts="-",
666
668
  alpha=0.925,
669
+ passes=1,
667
670
  columns=None,
668
671
  source_units=None,
669
672
  start_date=None,
@@ -696,6 +699,8 @@ def lyne_hollick(
696
699
  ${columns}
697
700
  alpha
698
701
  Catchment constant (value between 0 and 1). Default is 0.925.
702
+ passes : int
703
+ The number of passes to use for the filter.
699
704
  ${source_units}
700
705
  ${start_date}
701
706
  ${end_date}
@@ -731,7 +736,7 @@ def lyne_hollick(
731
736
  source_units=source_units,
732
737
  target_units=target_units,
733
738
  )
734
- return bfsep(flow, "lyne_hollick", print_input, k=alpha)
739
+ return bfsep(flow, "lyne_hollick", print_input, k=alpha, passes=passes)
735
740
 
736
741
 
737
742
  @tsutils.doc(tsutils.docstrings)
@@ -1,23 +1,26 @@
1
- """hspfbintoolbox to read HSPF binary files."""
1
+ """function to read HSPF binary files."""
2
2
 
3
3
  import datetime
4
4
  import struct
5
5
  import sys
6
-
7
- try:
8
- from typing import Literal
9
- except ImportError:
10
- from typing import Literal
6
+ from typing import Literal
11
7
 
12
8
  import pandas as pd
13
9
 
14
10
  from .. import tsutils
11
+ from ..utils import pandas_period_by_version
12
+ from . import utils
15
13
 
16
14
  code2intervalmap = {5: "yearly", 4: "monthly", 3: "daily", 2: "bivl"}
17
15
 
18
16
  interval2codemap = {"yearly": 5, "monthly": 4, "daily": 3, "bivl": 2}
19
17
 
20
- code2freqmap = {5: "A", 4: "M", 3: "D", 2: None}
18
+ code2freqmap = {
19
+ 5: pandas_period_by_version("Y"),
20
+ 4: "M",
21
+ 3: "D",
22
+ 2: None,
23
+ }
21
24
 
22
25
 
23
26
  _LOCAL_DOCSTRINGS = {
@@ -27,168 +30,20 @@ _LOCAL_DOCSTRINGS = {
27
30
  }
28
31
 
29
32
 
30
- def tuple_match(findme, hay):
31
- """Part of partial ordered matching.
32
- See http://stackoverflow.com/a/4559604
33
- """
34
- return len(findme) == len(hay) and all(
35
- i is None or j is None or i == j for i, j in zip(findme, hay)
36
- )
37
-
38
-
39
- def tuple_combine(findme, hay):
40
- """Part of partial ordered matching.
41
- See http://stackoverflow.com/a/4559604
42
- """
43
- return tuple(i is None and j or i for i, j in zip(findme, hay))
44
-
45
-
46
- def tuple_search(findme, haystack):
47
- """Partial ordered matching with 'None' as wildcard
48
- See http://stackoverflow.com/a/4559604
49
- """
50
- return [
51
- (index, tuple_combine(findme, hay))
52
- for index, hay in enumerate(haystack)
53
- if tuple_match(findme, hay)
54
- ]
55
-
56
-
57
33
  def _get_data(binfilename, interval="daily", labels=None, catalog_only=True):
58
34
  """Underlying function to read from the binary file. Used by
59
35
  'extract', 'catalog'.
60
36
  """
61
- if labels is None:
62
- labels = [",,,"]
63
- testem = {
64
- "PERLND": [
65
- "ATEMP",
66
- "SNOW",
67
- "PWATER",
68
- "SEDMNT",
69
- "PSTEMP",
70
- "PWTGAS",
71
- "PQUAL",
72
- "MSTLAY",
73
- "PEST",
74
- "NITR",
75
- "PHOS",
76
- "TRACER",
77
- "",
78
- ],
79
- "IMPLND": ["ATEMP", "SNOW", "IWATER", "SOLIDS", "IWTGAS", "IQUAL", ""],
80
- "RCHRES": [
81
- "HYDR",
82
- "CONS",
83
- "HTRCH",
84
- "SEDTRN",
85
- "GQUAL",
86
- "OXRX",
87
- "NUTRX",
88
- "PLANK",
89
- "PHCARB",
90
- "INFLOW",
91
- "OFLOW",
92
- "ROFLOW",
93
- "",
94
- ],
95
- "BMPRAC": [""],
96
- "": [""],
97
- }
98
-
99
- collect_dict = {}
100
- lablist = []
101
-
102
37
  # Normalize interval code
103
38
  try:
104
39
  intervalcode = interval2codemap[interval.lower()]
105
40
  except AttributeError:
106
41
  intervalcode = None
107
42
 
108
- # convert label tuples to lists
109
- labels = list(labels)
110
-
111
- # turn into a list of lists
112
- nlabels = []
113
- for label in labels:
114
- if isinstance(label, str):
115
- nlabels.append(label.split(","))
116
- else:
117
- nlabels.append(label)
118
- labels = nlabels
119
-
120
- # Check the list members for valid values
121
- for label in labels:
122
- if len(label) != 4:
123
- raise ValueError(
124
- tsutils.error_wrapper(
125
- f"""The label '{label}' has the wrong number of entries.
126
- """
127
- )
128
- )
129
-
130
- # replace empty fields with None
131
- # operation,lue_number,group,variable
132
- words = [None if (i in ("", "None")) else i for i in label]
133
-
134
- # first word must be a valid operation type or None
135
- if words[0] is not None:
136
- # force uppercase before comparison
137
- words[0] = words[0].upper()
138
- if words[0] not in testem:
139
- raise ValueError(
140
- tsutils.error_wrapper(
141
- f"""Operation type must be one of 'PERLND', 'IMPLND',
142
- 'RCHRES', or 'BMPRAC', or missing (to get all) instead
143
- of {words[0]}.
144
- """
145
- )
146
- )
147
-
148
- # second word must be integer 1-999 or None or range to parse
149
- if words[1] is not None:
150
- try:
151
- words[1] = int(words[1])
152
- luelist = [words[1]]
153
- except ValueError:
154
- luelist = tsutils.range_to_numlist(words[1])
155
- for luenum in luelist:
156
- if luenum < 1 or luenum > 999:
157
- raise ValueError(
158
- tsutils.error_wrapper(
159
- f"""The land use element must be an integer from
160
- 1 to 999 inclusive, instead of {luenum}.
161
- """
162
- )
163
- )
164
- else:
165
- luelist = [None]
166
-
167
- # third word must be a valid group name or None
168
- if words[2] is not None:
169
- words[2] = words[2].upper()
170
- if words[2] not in testem[words[0]]:
171
- raise ValueError(
172
- tsutils.error_wrapper(
173
- f"""The {words[0]} operation type only allows the
174
- variable groups: {testem[words[0]][:-1]}, instead you
175
- gave {words[2]}.
176
- """
177
- )
178
- )
179
-
180
- # fourth word is currently not checked - assumed to be a variable name
181
- # if not, it will simply never be found in the file, so ok
182
- # but no warning for the user - add check?
183
-
184
- # add interval code as fifth word in list
185
- words.append(intervalcode)
186
-
187
- # add to new list of checked and expanded lists
188
- for luenum in luelist:
189
- words[1] = luenum
190
- lablist.append(list(words))
43
+ lablist = utils.normalize_labels(labels)
44
+ lablist = [i + [intervalcode] for i in lablist]
191
45
 
46
+ collect_dict = {}
192
47
  # Now read through the binary file and collect the data matching the labels
193
48
  with open(binfilename, "rb") as binfp:
194
49
  labeltest = set()
@@ -200,7 +55,8 @@ def _get_data(binfilename, interval="daily", labels=None, catalog_only=True):
200
55
  # not a valid HSPF binary file
201
56
  raise ValueError(
202
57
  tsutils.error_wrapper(
203
- f"""{binfilename} is not a valid HSPF binary output file
58
+ f"""
59
+ {binfilename} is not a valid HSPF binary output file
204
60
  (.hbn), The first byte must be FD hexadecimal, but it was
205
61
  {magicbyte}.
206
62
  """
@@ -251,9 +107,7 @@ def _get_data(binfilename, interval="daily", labels=None, catalog_only=True):
251
107
  # read the variable name
252
108
  variable_name = struct.unpack(f"{length}s", binfp.read(length))[0]
253
109
 
254
- # add variable name to the set for this operation
255
- # why a set instead of a list? There should never be
256
- # a duplicate anyway
110
+ # add variable name to the vnames list
257
111
  vnames.setdefault((lue, group), []).append(variable_name)
258
112
 
259
113
  # update how far along the record we are
@@ -275,11 +129,15 @@ def _get_data(binfilename, interval="daily", labels=None, catalog_only=True):
275
129
  vals = struct.unpack(f"{numvals}f", binfp.read(4 * numvals))
276
130
  recpos += 4 * numvals
277
131
 
132
+ # delta accounts for HSPF's use of hour 24 to represent
133
+ # the end of the last interval of the day.
278
134
  delta = datetime.timedelta(hours=0)
279
- if hour == 24:
280
- hour = 0
135
+ if level == interval2codemap["bivl"]:
136
+ delta = datetime.timedelta(hours=hour) + datetime.timedelta(
137
+ minutes=minute
138
+ )
281
139
 
282
- ndate = datetime.datetime(year, month, day, hour, minute) + delta
140
+ ndate = datetime.datetime(year, month, day) + delta
283
141
 
284
142
  # Go through labels to see if these values need to be
285
143
  # collected
@@ -291,9 +149,8 @@ def _get_data(binfilename, interval="daily", labels=None, catalog_only=True):
291
149
  vname.decode("ascii"),
292
150
  level,
293
151
  )
294
-
295
152
  for lbl in lablist:
296
- res = tuple_search(tmpkey, [lbl])
153
+ res = utils.tuple_search(tmpkey, [lbl])
297
154
  if not res:
298
155
  continue
299
156
  labeltest.add(tuple(lbl))
@@ -322,8 +179,9 @@ def _get_data(binfilename, interval="daily", labels=None, catalog_only=True):
322
179
  if not collect_dict:
323
180
  raise ValueError(
324
181
  tsutils.error_wrapper(
325
- f"""The label specifications below matched no records in the
326
- binary file.
182
+ f"""
183
+ The label specifications below matched no records in the binary
184
+ file.
327
185
 
328
186
  {lablist}
329
187
  """
@@ -337,8 +195,9 @@ def _get_data(binfilename, interval="daily", labels=None, catalog_only=True):
337
195
  if tuple(lbl) not in labeltest:
338
196
  sys.stderr.write(
339
197
  tsutils.error_wrapper(
340
- f"""Warning: The label '{lbl}' matched no records in
341
- the binary file.
198
+ f"""
199
+ Warning: The label '{lbl}' matched no records in the
200
+ binary file.
342
201
  """
343
202
  )
344
203
  )
@@ -365,7 +224,8 @@ def hbn_extract(
365
224
  if interval not in ("bivl", "daily", "monthly", "yearly"):
366
225
  raise ValueError(
367
226
  tsutils.error_wrapper(
368
- f"""The "interval" argument must be one of "bivl", "daily",
227
+ f"""
228
+ The "interval" argument must be one of "bivl", "daily",
369
229
  "monthly", or "yearly". You supplied "{interval}".
370
230
  """
371
231
  )
@@ -0,0 +1,179 @@
1
+ """A collection of functions used by toolbox_utils, wdmtoolbox, ...etc."""
2
+
3
+ from typing import List, Optional, Union
4
+
5
+ import pint_pandas # not used directly, but required to use pint in pandas
6
+
7
+ from .. import tsutils
8
+
9
+ # This is here so that linters don't remove the pint_pandas import which is
10
+ # needed to use pint in pandas
11
+ _ = pint_pandas.version("pint")
12
+
13
+
14
+ def normalize_labels(labels: Optional[Union[str, List[str]]]) -> List[str]:
15
+ """
16
+ Process labels for the hbn function.
17
+
18
+ Parameters
19
+ ----------
20
+ labels
21
+ The labels to be processed.
22
+
23
+ Returns
24
+ -------
25
+ process_labels
26
+ A list of processed labels.
27
+ """
28
+ if labels is None:
29
+ labels = [",,,"]
30
+
31
+ testem = {
32
+ "PERLND": [
33
+ "ATEMP",
34
+ "SNOW",
35
+ "PWATER",
36
+ "SEDMNT",
37
+ "PSTEMP",
38
+ "PWTGAS",
39
+ "PQUAL",
40
+ "MSTLAY",
41
+ "PEST",
42
+ "NITR",
43
+ "PHOS",
44
+ "TRACER",
45
+ "",
46
+ ],
47
+ "IMPLND": ["ATEMP", "SNOW", "IWATER", "SOLIDS", "IWTGAS", "IQUAL", ""],
48
+ "RCHRES": [
49
+ "HYDR",
50
+ "CONS",
51
+ "HTRCH",
52
+ "SEDTRN",
53
+ "GQUAL",
54
+ "OXRX",
55
+ "NUTRX",
56
+ "PLANK",
57
+ "PHCARB",
58
+ "INFLOW",
59
+ "OFLOW",
60
+ "ROFLOW",
61
+ "",
62
+ ],
63
+ "BMPRAC": [""],
64
+ "": [""],
65
+ }
66
+
67
+ lablist = []
68
+
69
+ # convert label tuples to lists
70
+ labels = list(labels)
71
+
72
+ # turn into a list of lists
73
+ nlabels = []
74
+ for label in labels:
75
+ if isinstance(label, str):
76
+ nlabels.append(label.split(","))
77
+ else:
78
+ nlabels.append(label)
79
+ labels = nlabels
80
+
81
+ # Check the list members for valid values
82
+ for label in labels:
83
+ if len(label) != 4:
84
+ raise ValueError(
85
+ tsutils.error_wrapper(
86
+ f"""
87
+ The label '{label}' has the wrong number of entries.
88
+ """
89
+ )
90
+ )
91
+
92
+ # replace empty fields with None
93
+ words = [None if i == "" else i for i in label]
94
+
95
+ # first word must be a valid operation type or None
96
+ if words[0] is not None:
97
+ # force uppercase before comparison
98
+ words[0] = words[0].upper()
99
+ if words[0] not in testem:
100
+ raise ValueError(
101
+ tsutils.error_wrapper(
102
+ f"""
103
+ Operation type must be one of 'PERLND', 'IMPLND',
104
+ 'RCHRES', or 'BMPRAC', or missing (to get all) instead
105
+ of {words[0]}.
106
+ """
107
+ )
108
+ )
109
+
110
+ # second word must be integer 1-999 or None or range to parse
111
+ if words[1] is not None:
112
+ try:
113
+ words[1] = int(words[1])
114
+ luelist = [words[1]]
115
+ except ValueError:
116
+ luelist = tsutils.range_to_numlist(words[1])
117
+ for luenum in luelist:
118
+ if luenum < 1 or luenum > 999:
119
+ raise ValueError(
120
+ tsutils.error_wrapper(
121
+ f"""
122
+ The land use element must be an integer from 1 to
123
+ 999 inclusive, instead of {luenum}.
124
+ """
125
+ )
126
+ )
127
+ else:
128
+ luelist = [None]
129
+
130
+ # third word must be a valid group name or None
131
+ if words[2] is not None:
132
+ words[2] = words[2].upper()
133
+ if (words[0] is not None) and (words[2] not in testem[words[0]]):
134
+ raise ValueError(
135
+ tsutils.error_wrapper(
136
+ f"""
137
+ The {words[0]} operation type only allows the variable
138
+ groups: {testem[words[0]][:-1]},
139
+ instead you gave {words[2]}.
140
+ """
141
+ )
142
+ )
143
+
144
+ # fourth word is currently not checked - assumed to be a variable name
145
+ # if not, it will simply never be found in the file, so ok
146
+ # but no warning for the user - add check?
147
+
148
+ # add to new list of checked and expanded lists
149
+ for luenum in luelist:
150
+ words[1] = luenum
151
+ lablist.append(list(words))
152
+ return lablist
153
+
154
+
155
+ def tuple_match(findme, hay):
156
+ """Part of partial ordered matching.
157
+ See http://stackoverflow.com/a/4559604
158
+ """
159
+ return len(findme) == len(hay) and all(
160
+ i is None or j is None or i == j for i, j in zip(findme, hay)
161
+ )
162
+
163
+
164
+ def tuple_combine(findme, hay):
165
+ """Part of partial ordered matching.
166
+ See http://stackoverflow.com/a/4559604
167
+ """
168
+ return tuple(i is None and j or i for i, j in zip(findme, hay))
169
+
170
+
171
+ def tuple_search(findme, haystack):
172
+ """Partial ordered matching with 'None' as wildcard
173
+ See http://stackoverflow.com/a/4559604
174
+ """
175
+ return [
176
+ (index, tuple_combine(findme, hay))
177
+ for index, hay in enumerate(haystack)
178
+ if tuple_match(findme, hay)
179
+ ]
@@ -143,10 +143,12 @@ def wdm_extract(wdmfile, *idsn):
143
143
  tgroup = dattr["TGROUP"]
144
144
  tstep = dattr["TSSTEP"]
145
145
  tcode = dattr["TCODE"]
146
- cindex = pd.date_range(start=start, periods=len(records) + 1, freq=freq[tgroup])
146
+ cindex = pd.date_range(
147
+ start=start, periods=len(records) + 1, freq=freq[tgroup]
148
+ ).astype("datetime64[ns]")
147
149
  tindex = pd.date_range(
148
150
  start=start, end=cindex[-1], freq=str(tstep) + freq[tcode]
149
- )
151
+ ).astype("datetime64[ns]")
150
152
  counts = np.diff(np.searchsorted(tindex, cindex))
151
153
 
152
154
  # Get timeseries data