tonik 0.1.19__py3-none-any.whl → 0.1.20__py3-none-any.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.
- tonik/utils.py +140 -2
- {tonik-0.1.19.dist-info → tonik-0.1.20.dist-info}/METADATA +1 -1
- {tonik-0.1.19.dist-info → tonik-0.1.20.dist-info}/RECORD +6 -6
- {tonik-0.1.19.dist-info → tonik-0.1.20.dist-info}/WHEEL +0 -0
- {tonik-0.1.19.dist-info → tonik-0.1.20.dist-info}/entry_points.txt +0 -0
- {tonik-0.1.19.dist-info → tonik-0.1.20.dist-info}/licenses/LICENSE +0 -0
tonik/utils.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import List
|
|
1
|
+
from typing import List, Union
|
|
2
2
|
from datetime import datetime, timezone, timedelta
|
|
3
3
|
|
|
4
4
|
import numpy as np
|
|
@@ -13,12 +13,40 @@ def generate_test_data(dim=1, ndays=30, nfreqs=10,
|
|
|
13
13
|
freq_names=None, add_nans=True):
|
|
14
14
|
"""
|
|
15
15
|
Generate a 1D or 2D feature for testing.
|
|
16
|
+
|
|
17
|
+
Parameters
|
|
18
|
+
----------
|
|
19
|
+
dim : int
|
|
20
|
+
Dimension of the data (1 or 2).
|
|
21
|
+
ndays : int
|
|
22
|
+
Number of days to generate data for.
|
|
23
|
+
nfreqs : int
|
|
24
|
+
Number of frequencies (only for dim=2).
|
|
25
|
+
tstart : datetime
|
|
26
|
+
Start time of the data.
|
|
27
|
+
freq : str
|
|
28
|
+
Frequency of the data (e.g., '10min').
|
|
29
|
+
intervals : int
|
|
30
|
+
Number of intervals to generate. If None, calculated from ndays and freq.
|
|
31
|
+
feature_names : list
|
|
32
|
+
Names of the features to generate.
|
|
33
|
+
seed : int
|
|
34
|
+
Random seed for reproducibility.
|
|
35
|
+
freq_names : list
|
|
36
|
+
Names of the frequency dimensions (only for dim=2).
|
|
37
|
+
add_nans : bool
|
|
38
|
+
Whether to add NaN values to the data.
|
|
39
|
+
|
|
40
|
+
Returns
|
|
41
|
+
-------
|
|
42
|
+
xr.Dataset
|
|
43
|
+
Generated test dataset.
|
|
16
44
|
"""
|
|
17
45
|
assert dim < 3
|
|
18
46
|
assert dim > 0
|
|
19
47
|
|
|
20
48
|
if intervals is None:
|
|
21
|
-
nints = ndays *
|
|
49
|
+
nints = ndays * int(pd.Timedelta('1h')/pd.Timedelta(freq)) * 24
|
|
22
50
|
else:
|
|
23
51
|
nints = intervals
|
|
24
52
|
dates = pd.date_range(tstart, freq=freq, periods=nints)
|
|
@@ -59,6 +87,116 @@ def generate_test_data(dim=1, ndays=30, nfreqs=10,
|
|
|
59
87
|
return xds
|
|
60
88
|
|
|
61
89
|
|
|
90
|
+
def round_datetime(dt: datetime, interval: Union[int, float, timedelta]) -> datetime:
|
|
91
|
+
"""
|
|
92
|
+
Find closest multiple of interval to given time.
|
|
93
|
+
|
|
94
|
+
Parameters:
|
|
95
|
+
-----------
|
|
96
|
+
dt : datetime
|
|
97
|
+
The datetime to round.
|
|
98
|
+
interval : Union[int, float, timedelta]
|
|
99
|
+
The interval to which to round the datetime.
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
--------
|
|
103
|
+
datetime
|
|
104
|
+
The rounded datetime.
|
|
105
|
+
"""
|
|
106
|
+
# Normalize interval to whole seconds (supports float/timedelta inputs)
|
|
107
|
+
if isinstance(interval, timedelta):
|
|
108
|
+
interval_sec = int(interval.total_seconds())
|
|
109
|
+
else:
|
|
110
|
+
interval_sec = int(interval)
|
|
111
|
+
|
|
112
|
+
if interval_sec <= 0:
|
|
113
|
+
raise ValueError("interval must be positive (seconds)")
|
|
114
|
+
|
|
115
|
+
# Accept ObsPy UTCDateTime transparently (preserve type on return)
|
|
116
|
+
_is_obspy = False
|
|
117
|
+
try:
|
|
118
|
+
from obspy import UTCDateTime as _UTCDateTime # type: ignore
|
|
119
|
+
if isinstance(dt, _UTCDateTime):
|
|
120
|
+
_is_obspy = True
|
|
121
|
+
dt_py = dt.datetime # Python datetime in UTC
|
|
122
|
+
else:
|
|
123
|
+
dt_py = dt
|
|
124
|
+
except Exception:
|
|
125
|
+
dt_py = dt
|
|
126
|
+
|
|
127
|
+
epoch = (
|
|
128
|
+
datetime(1970, 1, 1)
|
|
129
|
+
if dt_py.tzinfo is None
|
|
130
|
+
else datetime(1970, 1, 1, tzinfo=dt_py.tzinfo)
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
# Compute integer seconds since epoch to avoid float precision issues
|
|
134
|
+
seconds = int((dt_py - epoch).total_seconds())
|
|
135
|
+
floored = (seconds + 0.5 * interval_sec) % interval_sec
|
|
136
|
+
rounded = epoch + timedelta(seconds=seconds + 0.5 * interval_sec - floored)
|
|
137
|
+
|
|
138
|
+
if _is_obspy:
|
|
139
|
+
from obspy import UTCDateTime as _UTCDateTime # type: ignore
|
|
140
|
+
return _UTCDateTime(rounded)
|
|
141
|
+
|
|
142
|
+
return rounded
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def floor_datetime(dt: datetime, interval: Union[int, float, timedelta]) -> datetime:
|
|
146
|
+
"""
|
|
147
|
+
Floor a datetime to the latest multiple of a given interval.
|
|
148
|
+
|
|
149
|
+
Assumes ``dt`` represents a UTC time (naive or tz-aware is fine) and
|
|
150
|
+
aligns against the Unix epoch 1970-01-01T00:00:00Z. The interval is in
|
|
151
|
+
seconds (int/float) or a timedelta. Returns a datetime with the same
|
|
152
|
+
"naive vs aware" form as ``dt``.
|
|
153
|
+
|
|
154
|
+
Examples
|
|
155
|
+
--------
|
|
156
|
+
>>> from datetime import datetime
|
|
157
|
+
>>> floor_datetime(datetime.fromisoformat('2025-11-27T10:12:43'), 600)
|
|
158
|
+
datetime.datetime(2025, 11, 27, 10, 10)
|
|
159
|
+
"""
|
|
160
|
+
|
|
161
|
+
# Normalize interval to whole seconds (supports float/timedelta inputs)
|
|
162
|
+
if isinstance(interval, timedelta):
|
|
163
|
+
interval_sec = int(interval.total_seconds())
|
|
164
|
+
else:
|
|
165
|
+
interval_sec = int(interval)
|
|
166
|
+
|
|
167
|
+
if interval_sec <= 0:
|
|
168
|
+
raise ValueError("interval must be positive (seconds)")
|
|
169
|
+
|
|
170
|
+
# Accept ObsPy UTCDateTime transparently (preserve type on return)
|
|
171
|
+
_is_obspy = False
|
|
172
|
+
try:
|
|
173
|
+
from obspy import UTCDateTime as _UTCDateTime # type: ignore
|
|
174
|
+
if isinstance(dt, _UTCDateTime):
|
|
175
|
+
_is_obspy = True
|
|
176
|
+
dt_py = dt.datetime # Python datetime in UTC
|
|
177
|
+
else:
|
|
178
|
+
dt_py = dt
|
|
179
|
+
except Exception:
|
|
180
|
+
dt_py = dt
|
|
181
|
+
|
|
182
|
+
epoch = (
|
|
183
|
+
datetime(1970, 1, 1)
|
|
184
|
+
if dt_py.tzinfo is None
|
|
185
|
+
else datetime(1970, 1, 1, tzinfo=dt_py.tzinfo)
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
# Compute integer seconds since epoch to avoid float precision issues
|
|
189
|
+
seconds = int((dt_py - epoch).total_seconds())
|
|
190
|
+
floored = seconds - (seconds % interval_sec)
|
|
191
|
+
rounded = epoch + timedelta(seconds=floored)
|
|
192
|
+
|
|
193
|
+
if _is_obspy:
|
|
194
|
+
from obspy import UTCDateTime as _UTCDateTime # type: ignore
|
|
195
|
+
return _UTCDateTime(rounded)
|
|
196
|
+
|
|
197
|
+
return rounded
|
|
198
|
+
|
|
199
|
+
|
|
62
200
|
def get_dt(times):
|
|
63
201
|
"""
|
|
64
202
|
Infer the sampling of the time dimension.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tonik
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.20
|
|
4
4
|
Summary: Store time series data as HDF5 files and access them through an API.
|
|
5
5
|
Project-URL: Homepage, https://tsc-tools.github.io/tonik
|
|
6
6
|
Project-URL: Issues, https://github.com/tsc-tools/tonik/issues
|
|
@@ -2,13 +2,13 @@ tonik/__init__.py,sha256=dov-nMeGFBzLspmj4rWKjC4r736vmaPDgMEkHSUfP98,523
|
|
|
2
2
|
tonik/api.py,sha256=vW0ykOo5iGAV0_WuOepdrnUyFp83F7KyJTd43ksLmUk,7985
|
|
3
3
|
tonik/grafana_annotations.py,sha256=ZU9Cy-HT4vvMfYIQzD9WboaDVOCBDv__NmXbk1qKWJo,5838
|
|
4
4
|
tonik/storage.py,sha256=jcCVx2N8J1ZBKM73k-OaxB0uxukn4VAM_-CCaCeAKwk,10589
|
|
5
|
-
tonik/utils.py,sha256=
|
|
5
|
+
tonik/utils.py,sha256=GwAXfGFQWhlsLThQvSux1SooRkW-iIkJP99JMH72t5Y,11791
|
|
6
6
|
tonik/xarray2netcdf.py,sha256=nq6RHk5ciaAg1bxNDiyHPRdAts1C7fj7jtDbaLaSTWM,6497
|
|
7
7
|
tonik/xarray2zarr.py,sha256=Dg9_b6Zwj_UQMhOez6wrPeHn0rUffUHqbv-e4pP_t3w,11233
|
|
8
8
|
tonik/package_data/index.html,sha256=ZCZ-BtGRERsL-6c_dfY43qd2WAaggH7xereennGL6ww,4372
|
|
9
9
|
tonik/package_data/whakaari_labels.json,sha256=96UZSq41yXgAJxuKivLBKlRTw-33jkjh7AGKTsDQ9Yg,3993
|
|
10
|
-
tonik-0.1.
|
|
11
|
-
tonik-0.1.
|
|
12
|
-
tonik-0.1.
|
|
13
|
-
tonik-0.1.
|
|
14
|
-
tonik-0.1.
|
|
10
|
+
tonik-0.1.20.dist-info/METADATA,sha256=CU6bp4tr1Z6dAPxPGLXkuP38N10SonJKzMwLakr38Xc,2207
|
|
11
|
+
tonik-0.1.20.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
12
|
+
tonik-0.1.20.dist-info/entry_points.txt,sha256=y82XyTeQddM87gCTzgSQaTlKF3VFicO4hhClHUv6j1A,127
|
|
13
|
+
tonik-0.1.20.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
14
|
+
tonik-0.1.20.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|