@@ -108,7 +108,8 @@ class Forecast:
108
108
109
109
def __init__ (self , frequency , api_data , convert_weather_code ):
110
110
"""
111
- :param frequency: Frequency of forecast: 'hourly', 'three-hourly' or 'daily'
111
+ :param frequency: Frequency of forecast: 'hourly', 'three-hourly',
112
+ 'twice-daily', 'daily'
112
113
:param api_data: Data returned from API call
113
114
:param: convert_weather_code: Convert numeric weather codes to string description
114
115
:type frequency: string
@@ -149,14 +150,14 @@ def __init__(self, frequency, api_data, convert_weather_code):
149
150
150
151
forecasts = api_data ["features" ][0 ]["properties" ]["timeSeries" ]
151
152
parameters = api_data ["parameters" ][0 ]
152
- if frequency == "daily" :
153
- self .timesteps = self ._build_timesteps_from_daily (forecasts , parameters )
153
+ if frequency == "twice- daily" :
154
+ self .timesteps = self ._build_twice_daily_timesteps (forecasts , parameters )
154
155
else :
155
156
self .timesteps = []
156
157
for forecast in forecasts :
157
158
self .timesteps .append (self ._build_timestep (forecast , parameters ))
158
159
159
- def _build_timesteps_from_daily (self , forecasts , parameters ):
160
+ def _build_twice_daily_timesteps (self , forecasts , parameters ):
160
161
"""Build individual timesteps from forecasts and metadata
161
162
162
163
Take the forecast data from DataHub and combine with unit information
@@ -188,38 +189,25 @@ def _build_timesteps_from_daily(self, forecasts, parameters):
188
189
189
190
for element , value in forecast .items ():
190
191
if element .startswith ("midday" ):
191
- trimmed_element = element .replace ("midday" , "" )
192
- case_corrected_element = (
193
- trimmed_element [0 ].lower () + trimmed_element [1 :]
194
- )
195
- day_step [case_corrected_element ] = {
192
+ day_step [element ] = {
196
193
"value" : value ,
197
194
"description" : parameters [element ]["description" ],
198
195
"unit_name" : parameters [element ]["unit" ]["label" ],
199
196
"unit_symbol" : parameters [element ]["unit" ]["symbol" ]["type" ],
200
197
}
201
198
elif element .startswith ("midnight" ):
202
- trimmed_element = element .replace ("midnight" , "" )
203
- case_corrected_element = (
204
- trimmed_element [0 ].lower () + trimmed_element [1 :]
205
- )
206
- night_step [case_corrected_element ] = {
199
+ night_step [element ] = {
207
200
"value" : value ,
208
201
"description" : parameters [element ]["description" ],
209
202
"unit_name" : parameters [element ]["unit" ]["label" ],
210
203
"unit_symbol" : parameters [element ]["unit" ]["symbol" ]["type" ],
211
204
}
212
205
elif element .startswith ("day" ):
213
- trimmed_element = element .replace ("day" , "" )
214
- case_corrected_element = (
215
- trimmed_element [0 ].lower () + trimmed_element [1 :]
216
- )
217
-
218
206
if (
219
- case_corrected_element == "significantWeatherCode "
207
+ element == "daySignificantWeatherCode "
220
208
and self .convert_weather_code
221
209
):
222
- day_step [case_corrected_element ] = {
210
+ day_step [element ] = {
223
211
"value" : WEATHER_CODES [str (value )],
224
212
"description" : parameters [element ]["description" ],
225
213
"unit_name" : parameters [element ]["unit" ]["label" ],
@@ -229,7 +217,7 @@ def _build_timesteps_from_daily(self, forecasts, parameters):
229
217
}
230
218
231
219
else :
232
- day_step [case_corrected_element ] = {
220
+ day_step [element ] = {
233
221
"value" : value ,
234
222
"description" : parameters [element ]["description" ],
235
223
"unit_name" : parameters [element ]["unit" ]["label" ],
@@ -238,16 +226,11 @@ def _build_timesteps_from_daily(self, forecasts, parameters):
238
226
],
239
227
}
240
228
elif element .startswith ("night" ):
241
- trimmed_element = element .replace ("night" , "" )
242
- case_corrected_element = (
243
- trimmed_element [0 ].lower () + trimmed_element [1 :]
244
- )
245
-
246
229
if (
247
- case_corrected_element == "significantWeatherCode "
230
+ element == "nightSignificantWeatherCode "
248
231
and self .convert_weather_code
249
232
):
250
- night_step [case_corrected_element ] = {
233
+ night_step [element ] = {
251
234
"value" : WEATHER_CODES [str (value )],
252
235
"description" : parameters [element ]["description" ],
253
236
"unit_name" : parameters [element ]["unit" ]["label" ],
@@ -257,7 +240,7 @@ def _build_timesteps_from_daily(self, forecasts, parameters):
257
240
}
258
241
259
242
else :
260
- night_step [case_corrected_element ] = {
243
+ night_step [element ] = {
261
244
"value" : value ,
262
245
"description" : parameters [element ]["description" ],
263
246
"unit_name" : parameters [element ]["unit" ]["label" ],
@@ -305,7 +288,14 @@ def _build_timestep(self, forecast, parameters):
305
288
forecast ["time" ], "%Y-%m-%dT%H:%M%z"
306
289
)
307
290
308
- elif element == "significantWeatherCode" and self .convert_weather_code :
291
+ elif (
292
+ element
293
+ in (
294
+ "significantWeatherCode" ,
295
+ "daySignificantWeatherCode" ,
296
+ "nightSignificantWeatherCode" ,
297
+ )
298
+ ) and self .convert_weather_code :
309
299
timestep [element ] = {
310
300
"value" : WEATHER_CODES [str (value )],
311
301
"description" : parameters [element ]["description" ],
@@ -366,6 +356,19 @@ def _check_requested_time(self, target):
366
356
367
357
raise APIException (err_str )
368
358
359
+ # If we have a twice-daily forecast, check that the requested time is
360
+ # at most 6 hours before the first datetime we have a forecast for.
361
+ if self .frequency == "twice-daily" and target < self .timesteps [0 ][
362
+ "time"
363
+ ] - datetime .timedelta (hours = 6 ):
364
+ err_str = (
365
+ "There is no forecast available for the requested time. "
366
+ "The requested time is more than 6 hours before the first "
367
+ "available forecast."
368
+ )
369
+
370
+ raise APIException (err_str )
371
+
369
372
# If we have an hourly forecast, check that the requested time is at
370
373
# most 30 minutes after the final datetime we have a forecast for
371
374
if self .frequency == "hourly" and target > (
@@ -405,6 +408,19 @@ def _check_requested_time(self, target):
405
408
406
409
raise APIException (err_str )
407
410
411
+ # If we have a twice-daily forecast, then the target must be within 6 hours
412
+ # of the last timestep
413
+ if self .frequency == "twice-daily" and target > (
414
+ self .timesteps [- 1 ]["time" ] + datetime .timedelta (hours = 6 )
415
+ ):
416
+ err_str = (
417
+ "There is no forecast available for the requested time. The "
418
+ "requested time is more than 6 hours after the first available "
419
+ "forecast."
420
+ )
421
+
422
+ raise APIException (err_str )
423
+
408
424
def at_datetime (self , target ):
409
425
"""Return the timestep closest to the target datetime
410
426
0 commit comments