anemoi-datasets 0.5.16__py3-none-any.whl → 0.5.17__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.
Files changed (155) hide show
  1. anemoi/datasets/__init__.py +4 -1
  2. anemoi/datasets/__main__.py +12 -2
  3. anemoi/datasets/_version.py +9 -4
  4. anemoi/datasets/commands/cleanup.py +17 -2
  5. anemoi/datasets/commands/compare.py +18 -2
  6. anemoi/datasets/commands/copy.py +196 -14
  7. anemoi/datasets/commands/create.py +50 -7
  8. anemoi/datasets/commands/finalise-additions.py +17 -2
  9. anemoi/datasets/commands/finalise.py +17 -2
  10. anemoi/datasets/commands/init-additions.py +17 -2
  11. anemoi/datasets/commands/init.py +16 -2
  12. anemoi/datasets/commands/inspect.py +283 -62
  13. anemoi/datasets/commands/load-additions.py +16 -2
  14. anemoi/datasets/commands/load.py +16 -2
  15. anemoi/datasets/commands/patch.py +17 -2
  16. anemoi/datasets/commands/publish.py +17 -2
  17. anemoi/datasets/commands/scan.py +31 -3
  18. anemoi/datasets/compute/recentre.py +47 -11
  19. anemoi/datasets/create/__init__.py +612 -85
  20. anemoi/datasets/create/check.py +142 -20
  21. anemoi/datasets/create/chunks.py +64 -4
  22. anemoi/datasets/create/config.py +185 -21
  23. anemoi/datasets/create/filter.py +50 -0
  24. anemoi/datasets/create/filters/__init__.py +33 -0
  25. anemoi/datasets/create/filters/empty.py +37 -0
  26. anemoi/datasets/create/filters/legacy.py +93 -0
  27. anemoi/datasets/create/filters/noop.py +37 -0
  28. anemoi/datasets/create/filters/orog_to_z.py +58 -0
  29. anemoi/datasets/create/{functions/filters → filters}/pressure_level_relative_humidity_to_specific_humidity.py +33 -10
  30. anemoi/datasets/create/{functions/filters → filters}/pressure_level_specific_humidity_to_relative_humidity.py +32 -8
  31. anemoi/datasets/create/filters/rename.py +205 -0
  32. anemoi/datasets/create/{functions/filters → filters}/rotate_winds.py +43 -28
  33. anemoi/datasets/create/{functions/filters → filters}/single_level_dewpoint_to_relative_humidity.py +32 -9
  34. anemoi/datasets/create/{functions/filters → filters}/single_level_relative_humidity_to_dewpoint.py +33 -9
  35. anemoi/datasets/create/{functions/filters → filters}/single_level_relative_humidity_to_specific_humidity.py +55 -7
  36. anemoi/datasets/create/{functions/filters → filters}/single_level_specific_humidity_to_relative_humidity.py +98 -37
  37. anemoi/datasets/create/filters/speeddir_to_uv.py +95 -0
  38. anemoi/datasets/create/{functions/filters → filters}/sum.py +24 -27
  39. anemoi/datasets/create/filters/transform.py +53 -0
  40. anemoi/datasets/create/{functions/filters → filters}/unrotate_winds.py +27 -18
  41. anemoi/datasets/create/filters/uv_to_speeddir.py +94 -0
  42. anemoi/datasets/create/{functions/filters → filters}/wz_to_w.py +51 -33
  43. anemoi/datasets/create/input/__init__.py +76 -5
  44. anemoi/datasets/create/input/action.py +149 -13
  45. anemoi/datasets/create/input/concat.py +81 -10
  46. anemoi/datasets/create/input/context.py +39 -4
  47. anemoi/datasets/create/input/data_sources.py +72 -6
  48. anemoi/datasets/create/input/empty.py +21 -3
  49. anemoi/datasets/create/input/filter.py +60 -12
  50. anemoi/datasets/create/input/function.py +154 -37
  51. anemoi/datasets/create/input/join.py +86 -14
  52. anemoi/datasets/create/input/misc.py +67 -17
  53. anemoi/datasets/create/input/pipe.py +33 -6
  54. anemoi/datasets/create/input/repeated_dates.py +189 -41
  55. anemoi/datasets/create/input/result.py +202 -87
  56. anemoi/datasets/create/input/step.py +119 -22
  57. anemoi/datasets/create/input/template.py +100 -13
  58. anemoi/datasets/create/input/trace.py +62 -7
  59. anemoi/datasets/create/patch.py +52 -4
  60. anemoi/datasets/create/persistent.py +134 -17
  61. anemoi/datasets/create/size.py +15 -1
  62. anemoi/datasets/create/source.py +51 -0
  63. anemoi/datasets/create/sources/__init__.py +36 -0
  64. anemoi/datasets/create/{functions/sources → sources}/accumulations.py +296 -30
  65. anemoi/datasets/create/{functions/sources → sources}/constants.py +27 -2
  66. anemoi/datasets/create/{functions/sources → sources}/eccc_fstd.py +7 -3
  67. anemoi/datasets/create/sources/empty.py +37 -0
  68. anemoi/datasets/create/{functions/sources → sources}/forcings.py +25 -1
  69. anemoi/datasets/create/sources/grib.py +297 -0
  70. anemoi/datasets/create/{functions/sources → sources}/hindcasts.py +38 -4
  71. anemoi/datasets/create/sources/legacy.py +93 -0
  72. anemoi/datasets/create/{functions/sources → sources}/mars.py +168 -20
  73. anemoi/datasets/create/sources/netcdf.py +42 -0
  74. anemoi/datasets/create/sources/opendap.py +43 -0
  75. anemoi/datasets/create/{functions/sources/__init__.py → sources/patterns.py} +35 -4
  76. anemoi/datasets/create/sources/recentre.py +150 -0
  77. anemoi/datasets/create/{functions/sources → sources}/source.py +27 -5
  78. anemoi/datasets/create/{functions/sources → sources}/tendencies.py +64 -7
  79. anemoi/datasets/create/sources/xarray.py +92 -0
  80. anemoi/datasets/create/sources/xarray_kerchunk.py +36 -0
  81. anemoi/datasets/create/sources/xarray_support/README.md +1 -0
  82. anemoi/datasets/create/{functions/sources/xarray → sources/xarray_support}/__init__.py +109 -8
  83. anemoi/datasets/create/sources/xarray_support/coordinates.py +442 -0
  84. anemoi/datasets/create/{functions/sources/xarray → sources/xarray_support}/field.py +94 -16
  85. anemoi/datasets/create/{functions/sources/xarray → sources/xarray_support}/fieldlist.py +90 -25
  86. anemoi/datasets/create/sources/xarray_support/flavour.py +1036 -0
  87. anemoi/datasets/create/{functions/sources/xarray → sources/xarray_support}/grid.py +92 -31
  88. anemoi/datasets/create/sources/xarray_support/metadata.py +395 -0
  89. anemoi/datasets/create/sources/xarray_support/patch.py +91 -0
  90. anemoi/datasets/create/sources/xarray_support/time.py +391 -0
  91. anemoi/datasets/create/sources/xarray_support/variable.py +331 -0
  92. anemoi/datasets/create/sources/xarray_zarr.py +41 -0
  93. anemoi/datasets/create/{functions/sources → sources}/zenodo.py +34 -5
  94. anemoi/datasets/create/statistics/__init__.py +233 -44
  95. anemoi/datasets/create/statistics/summary.py +52 -6
  96. anemoi/datasets/create/testing.py +76 -0
  97. anemoi/datasets/create/{functions/filters/noop.py → typing.py} +6 -3
  98. anemoi/datasets/create/utils.py +97 -6
  99. anemoi/datasets/create/writer.py +26 -4
  100. anemoi/datasets/create/zarr.py +170 -23
  101. anemoi/datasets/data/__init__.py +51 -4
  102. anemoi/datasets/data/complement.py +191 -40
  103. anemoi/datasets/data/concat.py +141 -16
  104. anemoi/datasets/data/dataset.py +552 -61
  105. anemoi/datasets/data/debug.py +197 -26
  106. anemoi/datasets/data/ensemble.py +93 -8
  107. anemoi/datasets/data/fill_missing.py +165 -18
  108. anemoi/datasets/data/forwards.py +428 -56
  109. anemoi/datasets/data/grids.py +323 -97
  110. anemoi/datasets/data/indexing.py +112 -19
  111. anemoi/datasets/data/interpolate.py +92 -12
  112. anemoi/datasets/data/join.py +158 -19
  113. anemoi/datasets/data/masked.py +129 -15
  114. anemoi/datasets/data/merge.py +137 -23
  115. anemoi/datasets/data/misc.py +172 -16
  116. anemoi/datasets/data/missing.py +233 -29
  117. anemoi/datasets/data/rescale.py +111 -10
  118. anemoi/datasets/data/select.py +168 -26
  119. anemoi/datasets/data/statistics.py +67 -6
  120. anemoi/datasets/data/stores.py +149 -64
  121. anemoi/datasets/data/subset.py +159 -25
  122. anemoi/datasets/data/unchecked.py +168 -57
  123. anemoi/datasets/data/xy.py +168 -25
  124. anemoi/datasets/dates/__init__.py +191 -16
  125. anemoi/datasets/dates/groups.py +189 -47
  126. anemoi/datasets/grids.py +270 -31
  127. anemoi/datasets/testing.py +28 -1
  128. {anemoi_datasets-0.5.16.dist-info → anemoi_datasets-0.5.17.dist-info}/METADATA +9 -6
  129. anemoi_datasets-0.5.17.dist-info/RECORD +137 -0
  130. {anemoi_datasets-0.5.16.dist-info → anemoi_datasets-0.5.17.dist-info}/WHEEL +1 -1
  131. anemoi/datasets/create/functions/__init__.py +0 -66
  132. anemoi/datasets/create/functions/filters/__init__.py +0 -9
  133. anemoi/datasets/create/functions/filters/empty.py +0 -17
  134. anemoi/datasets/create/functions/filters/orog_to_z.py +0 -58
  135. anemoi/datasets/create/functions/filters/rename.py +0 -79
  136. anemoi/datasets/create/functions/filters/speeddir_to_uv.py +0 -78
  137. anemoi/datasets/create/functions/filters/uv_to_speeddir.py +0 -56
  138. anemoi/datasets/create/functions/sources/empty.py +0 -15
  139. anemoi/datasets/create/functions/sources/grib.py +0 -150
  140. anemoi/datasets/create/functions/sources/netcdf.py +0 -15
  141. anemoi/datasets/create/functions/sources/opendap.py +0 -15
  142. anemoi/datasets/create/functions/sources/recentre.py +0 -60
  143. anemoi/datasets/create/functions/sources/xarray/coordinates.py +0 -255
  144. anemoi/datasets/create/functions/sources/xarray/flavour.py +0 -472
  145. anemoi/datasets/create/functions/sources/xarray/metadata.py +0 -148
  146. anemoi/datasets/create/functions/sources/xarray/patch.py +0 -44
  147. anemoi/datasets/create/functions/sources/xarray/time.py +0 -177
  148. anemoi/datasets/create/functions/sources/xarray/variable.py +0 -188
  149. anemoi/datasets/create/functions/sources/xarray_kerchunk.py +0 -42
  150. anemoi/datasets/create/functions/sources/xarray_zarr.py +0 -15
  151. anemoi/datasets/utils/fields.py +0 -47
  152. anemoi_datasets-0.5.16.dist-info/RECORD +0 -129
  153. {anemoi_datasets-0.5.16.dist-info → anemoi_datasets-0.5.17.dist-info}/entry_points.txt +0 -0
  154. {anemoi_datasets-0.5.16.dist-info → anemoi_datasets-0.5.17.dist-info/licenses}/LICENSE +0 -0
  155. {anemoi_datasets-0.5.16.dist-info → anemoi_datasets-0.5.17.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,391 @@
1
+ # (C) Copyright 2024 Anemoi contributors.
2
+ #
3
+ # This software is licensed under the terms of the Apache Licence Version 2.0
4
+ # which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
5
+ #
6
+ # In applying this licence, ECMWF does not waive the privileges and immunities
7
+ # granted to it by virtue of its status as an intergovernmental organisation
8
+ # nor does it submit to any jurisdiction.
9
+
10
+
11
+ import datetime
12
+ import logging
13
+ from abc import ABC
14
+ from abc import abstractmethod
15
+ from typing import Any
16
+ from typing import Dict
17
+ from typing import List
18
+ from typing import Optional
19
+
20
+ from anemoi.utils.dates import as_datetime
21
+
22
+ from .coordinates import Coordinate
23
+ from .variable import Variable
24
+
25
+ LOG = logging.getLogger(__name__)
26
+
27
+
28
+ class Time(ABC):
29
+ """Base class for different time representations."""
30
+
31
+ @classmethod
32
+ def from_coordinates(cls, coordinates: List[Coordinate]) -> "Time":
33
+ """Create a Time instance from a list of coordinates.
34
+
35
+ Returns
36
+ -------
37
+ Union[ForecastFromValidTimeAndStep, Analysis, Constant, ForecastFromValidTimeAndBaseTime, ForecastFromBaseTimeAndDate]
38
+ An instance of a subclass of Time.
39
+
40
+ Args
41
+ ----
42
+ coordinates : List[Coordinate]
43
+ List of coordinate objects.
44
+ """
45
+ time_coordinate = [c for c in coordinates if c.is_time]
46
+ step_coordinate = [c for c in coordinates if c.is_step]
47
+ date_coordinate = [c for c in coordinates if c.is_date]
48
+
49
+ if len(date_coordinate) == 0 and len(time_coordinate) == 1 and len(step_coordinate) == 1:
50
+ return ForecastFromValidTimeAndStep(time_coordinate[0], step_coordinate[0])
51
+
52
+ if len(date_coordinate) == 0 and len(time_coordinate) == 1 and len(step_coordinate) == 0:
53
+ return Analysis(time_coordinate[0])
54
+
55
+ if len(date_coordinate) == 0 and len(time_coordinate) == 0 and len(step_coordinate) == 0:
56
+ return Constant()
57
+
58
+ if len(date_coordinate) == 1 and len(time_coordinate) == 1 and len(step_coordinate) == 0:
59
+ return ForecastFromValidTimeAndBaseTime(date_coordinate[0], time_coordinate[0])
60
+
61
+ if len(date_coordinate) == 1 and len(time_coordinate) == 0 and len(step_coordinate) == 1:
62
+ return ForecastFromBaseTimeAndDate(date_coordinate[0], step_coordinate[0])
63
+
64
+ if len(date_coordinate) == 1 and len(time_coordinate) == 1 and len(step_coordinate) == 1:
65
+ return ForecastFromValidTimeAndStep(time_coordinate[0], step_coordinate[0], date_coordinate[0])
66
+
67
+ LOG.error("")
68
+ LOG.error(f"{len(date_coordinate)} date_coordinate")
69
+ for c in date_coordinate:
70
+ LOG.error(" %s %s %s %s", c, c.is_date, c.is_time, c.is_step)
71
+ # LOG.error(' %s', c.variable)
72
+
73
+ LOG.error("")
74
+ LOG.error(f"{len(time_coordinate)} time_coordinate")
75
+ for c in time_coordinate:
76
+ LOG.error(" %s %s %s %s", c, c.is_date, c.is_time, c.is_step)
77
+ # LOG.error(' %s', c.variable)
78
+
79
+ LOG.error("")
80
+ LOG.error(f"{len(step_coordinate)} step_coordinate")
81
+ for c in step_coordinate:
82
+ LOG.error(" %s %s %s %s", c, c.is_date, c.is_time, c.is_step)
83
+ # LOG.error(' %s', c.variable)
84
+
85
+ raise NotImplementedError(f"{len(date_coordinate)=} {len(time_coordinate)=} {len(step_coordinate)=}")
86
+
87
+ @abstractmethod
88
+ def select_valid_datetime(self, variable: Variable) -> Optional[str]:
89
+ """Select the valid datetime for a given variable.
90
+
91
+ Parameters
92
+ ----------
93
+ variable : Variable
94
+ The variable to select the datetime for.
95
+
96
+ Returns
97
+ -------
98
+ Optional[str]
99
+ The name of the time coordinate.
100
+ """
101
+ pass
102
+
103
+ @abstractmethod
104
+ def fill_time_metadata(self, coords_values: Dict[str, Any], metadata: Dict[str, Any]) -> None:
105
+ """Fill metadata with time information.
106
+
107
+ Args
108
+ ----
109
+ coords_values : Dict[str, Any]
110
+ Coordinate values.
111
+ metadata : Dict[str, Any]
112
+ Metadata dictionary.
113
+ """
114
+
115
+ pass
116
+
117
+
118
+ class Constant(Time):
119
+ """Represents a constant time."""
120
+
121
+ def fill_time_metadata(self, coords_values: Dict[str, Any], metadata: Dict[str, Any]) -> None:
122
+ """Fill metadata with time information.
123
+
124
+ Parameters
125
+ ----------
126
+ coords_values : Dict[str, Any]
127
+ Coordinate values.
128
+ metadata : Dict[str, Any]
129
+ Metadata dictionary.
130
+ """
131
+ return None
132
+
133
+ def select_valid_datetime(self, variable: Variable) -> None:
134
+ """Select the valid datetime for a given variable.
135
+
136
+ Parameters
137
+ ----------
138
+ variable : Variable
139
+ The variable to select the datetime for.
140
+ """
141
+ return None
142
+
143
+
144
+ class Analysis(Time):
145
+ """Represents an analysis time."""
146
+
147
+ def __init__(self, time_coordinate: Coordinate) -> None:
148
+ """Initialize Analysis with a time coordinate.
149
+
150
+ Parameters
151
+ ----------
152
+ time_coordinate : Coordinate
153
+ The time coordinate.
154
+ """
155
+ self.time_coordinate_name = time_coordinate.variable.name
156
+
157
+ def fill_time_metadata(self, coords_values: Dict[str, Any], metadata: Dict[str, Any]) -> Any:
158
+ """Fill metadata with time information.
159
+
160
+ Parameters
161
+ ----------
162
+ coords_values : Dict[str, Any]
163
+ Coordinate values.
164
+ metadata : Dict[str, Any]
165
+ Metadata dictionary.
166
+
167
+ Returns
168
+ -------
169
+ Any
170
+ The valid datetime.
171
+ """
172
+ valid_datetime = coords_values[self.time_coordinate_name]
173
+
174
+ metadata["date"] = as_datetime(valid_datetime).strftime("%Y%m%d")
175
+ metadata["time"] = as_datetime(valid_datetime).strftime("%H%M")
176
+ metadata["step"] = 0
177
+
178
+ return valid_datetime
179
+
180
+ def select_valid_datetime(self, variable: Variable) -> str:
181
+ """Select the valid datetime for a given variable.
182
+
183
+ Parameters
184
+ ----------
185
+ variable : Variable
186
+ The variable to select the datetime for.
187
+
188
+ Returns
189
+ -------
190
+ str
191
+ The name of the time coordinate.
192
+ """
193
+ return self.time_coordinate_name
194
+
195
+
196
+ class ForecastFromValidTimeAndStep(Time):
197
+ """Represents a forecast time derived from valid time and step."""
198
+
199
+ def __init__(
200
+ self, time_coordinate: Coordinate, step_coordinate: Coordinate, date_coordinate: Optional[Coordinate] = None
201
+ ) -> None:
202
+ """Initialize ForecastFromValidTimeAndStep with time, step, and optional date coordinates.
203
+
204
+ Args
205
+ ----
206
+ time_coordinate : Coordinate
207
+ The time coordinate.
208
+ step_coordinate : Coordinate
209
+ The step coordinate.
210
+ date_coordinate : Optional[Coordinate]
211
+ The date coordinate.
212
+ """
213
+ self.time_coordinate_name = time_coordinate.variable.name
214
+ self.step_coordinate_name = step_coordinate.variable.name
215
+ self.date_coordinate_name = date_coordinate.variable.name if date_coordinate else None
216
+
217
+ def fill_time_metadata(self, coords_values: Dict[str, Any], metadata: Dict[str, Any]) -> Any:
218
+ """Fill metadata with time information.
219
+
220
+ Returns
221
+ -------
222
+ Any
223
+ The valid datetime.
224
+
225
+ Args
226
+ ----
227
+ coords_values : Dict[str, Any]
228
+ Coordinate values.
229
+ metadata : Dict[str, Any]
230
+ Metadata dictionary.
231
+ """
232
+ valid_datetime = coords_values[self.time_coordinate_name]
233
+ step = coords_values[self.step_coordinate_name]
234
+
235
+ assert isinstance(step, datetime.timedelta)
236
+ base_datetime = valid_datetime - step
237
+
238
+ hours = step.total_seconds() / 3600
239
+ assert int(hours) == hours
240
+
241
+ metadata["date"] = as_datetime(base_datetime).strftime("%Y%m%d")
242
+ metadata["time"] = as_datetime(base_datetime).strftime("%H%M")
243
+ metadata["step"] = int(hours)
244
+
245
+ # When date is present, it should be compatible with time and step
246
+
247
+ if self.date_coordinate_name is not None:
248
+ # Not sure that this is the correct assumption
249
+ assert coords_values[self.date_coordinate_name] == base_datetime, (
250
+ coords_values[self.date_coordinate_name],
251
+ base_datetime,
252
+ )
253
+
254
+ return valid_datetime
255
+
256
+ def select_valid_datetime(self, variable: Variable) -> str:
257
+ """Select the valid datetime for a given variable.
258
+
259
+ Parameters
260
+ ----------
261
+ variable : Variable
262
+ The variable to select the datetime for.
263
+
264
+ Returns
265
+ -------
266
+ str
267
+ The name of the time coordinate.
268
+ """
269
+ return self.time_coordinate_name
270
+
271
+
272
+ class ForecastFromValidTimeAndBaseTime(Time):
273
+ """Represents a forecast time derived from valid time and base time."""
274
+
275
+ def __init__(self, date_coordinate: Coordinate, time_coordinate: Coordinate) -> None:
276
+ """Initialize ForecastFromValidTimeAndBaseTime with date and time coordinates.
277
+
278
+ Args
279
+ ----
280
+ date_coordinate : Coordinate
281
+ The date coordinate.
282
+ time_coordinate : Coordinate
283
+ The time coordinate.
284
+ """
285
+ self.date_coordinate_name = date_coordinate.name
286
+ self.time_coordinate_name = time_coordinate.name
287
+
288
+ def fill_time_metadata(self, coords_values: Dict[str, Any], metadata: Dict[str, Any]) -> Any:
289
+ """Fill metadata with time information.
290
+
291
+ Returns
292
+ -------
293
+ Any
294
+ The valid datetime.
295
+
296
+ Args
297
+ ----
298
+ coords_values : Dict[str, Any]
299
+ Coordinate values.
300
+ metadata : Dict[str, Any]
301
+ Metadata dictionary.
302
+ """
303
+ valid_datetime = coords_values[self.time_coordinate_name]
304
+ base_datetime = coords_values[self.date_coordinate_name]
305
+
306
+ step = valid_datetime - base_datetime
307
+
308
+ hours = step.total_seconds() / 3600
309
+ assert int(hours) == hours
310
+
311
+ metadata["date"] = as_datetime(base_datetime).strftime("%Y%m%d")
312
+ metadata["time"] = as_datetime(base_datetime).strftime("%H%M")
313
+ metadata["step"] = int(hours)
314
+
315
+ return valid_datetime
316
+
317
+ def select_valid_datetime(self, variable: Variable) -> str:
318
+ """Select the valid datetime for a given variable.
319
+
320
+ Parameters
321
+ ----------
322
+ variable : Variable
323
+ The variable to select the datetime for.
324
+
325
+ Returns
326
+ -------
327
+ str
328
+ The name of the time coordinate.
329
+ """
330
+ return self.time_coordinate_name
331
+
332
+
333
+ class ForecastFromBaseTimeAndDate(Time):
334
+ """Represents a forecast time derived from base time and date."""
335
+
336
+ def __init__(self, date_coordinate: Coordinate, step_coordinate: Coordinate) -> None:
337
+ """Initialize ForecastFromBaseTimeAndDate with date and step coordinates.
338
+
339
+ Args
340
+ ----
341
+ date_coordinate : Coordinate
342
+ The date coordinate.
343
+ step_coordinate : Coordinate
344
+ The step coordinate.
345
+ """
346
+ self.date_coordinate_name = date_coordinate.name
347
+ self.step_coordinate_name = step_coordinate.name
348
+
349
+ def fill_time_metadata(self, coords_values: Dict[str, Any], metadata: Dict[str, Any]) -> Any:
350
+ """Fill metadata with time information.
351
+
352
+ Returns
353
+ -------
354
+ Any
355
+ The valid datetime.
356
+
357
+ Args
358
+ ----
359
+ coords_values : Dict[str, Any]
360
+ Coordinate values.
361
+ metadata : Dict[str, Any]
362
+ Metadata dictionary.
363
+ """
364
+ date = coords_values[self.date_coordinate_name]
365
+ step = coords_values[self.step_coordinate_name]
366
+ assert isinstance(step, datetime.timedelta)
367
+
368
+ metadata["date"] = as_datetime(date).strftime("%Y%m%d")
369
+ metadata["time"] = as_datetime(date).strftime("%H%M")
370
+
371
+ hours = step.total_seconds() / 3600
372
+
373
+ assert int(hours) == hours
374
+ metadata["step"] = int(hours)
375
+
376
+ return date + step
377
+
378
+ def select_valid_datetime(self, variable: Variable) -> Optional[str]:
379
+ """Select the valid datetime for a given variable.
380
+
381
+ Parameters
382
+ ----------
383
+ variable : Variable
384
+ The variable to select the datetime for.
385
+
386
+ Returns
387
+ -------
388
+ Optional[str]
389
+ The name of the time coordinate.
390
+ """
391
+ raise NotImplementedError("ForecastFromBaseTimeAndDate.select_valid_datetime")