1 /*  Copyright (c) 2003-2014 Xfce Development Team
2  *
3  * This program is free software; you can redistribute it and/or modify
4  * it under the terms of the GNU General Public License as published by
5  * the Free Software Foundation; either version 2 of the License, or
6  * (at your option) any later version.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11  * General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License
14  * along with this program; if not, write to the Free Software
15  * Foundation, Inc., 51 Franklin Street, Fifth Floor,
16  * Boston, MA 02110-1301, USA.
17  */
18 
19 #ifdef HAVE_CONFIG_H
20 #include <config.h>
21 #endif
22 
23 #include <libxfce4util/libxfce4util.h>
24 #include <math.h>
25 
26 #include "weather-parsers.h"
27 #include "weather-data.h"
28 #include "weather.h"
29 #include "weather-debug.h"
30 
31 /* fallback values when astrodata is unavailable */
32 #define NIGHT_TIME_START 21
33 #define NIGHT_TIME_END 5
34 
35 /* interval used for searching raw data relevant to a day */
36 #define DAY_START 3
37 #define DAY_END 33
38 #define DAYTIME_LEN 6
39 
40 /* If some value is not present or cannot be computed, return this instead */
41 #define INVALID_VALUE -9999
42 
43 #define CHK_NULL(s) ((s) ? g_strdup(s) : g_strdup(""))
44 
45 #define ROUND_TO_INT(default_format) (round ? "%.0f" : default_format)
46 
47 /* Converts temperatures in Celcius to Fahrenheit while preventing
48  * negative values rounded to zero from being displayed as "-0 °F". */
49 #define CALC_FAHRENHEIT(round, temperature)              \
50 do {                                                     \
51     temperature = temperature * 9.0 / 5.0 + 32;          \
52     if (round && temperature > -0.5 && temperature < 0)  \
53         temperature = 0;                                 \
54 } while (0)
55 
56 #define LOCALE_DOUBLE(value, format)                        \
57     (value                                                  \
58      ? g_strdup_printf(format, g_ascii_strtod(value, NULL)) \
59      : g_strdup(""))
60 
61 #define INTERPOLATE_OR_COPY(var, radian)                    \
62     if (ipol)                                               \
63         comb->location->var =                               \
64             interpolate_gchar_value(start->location->var,   \
65                                     end->location->var,     \
66                                     comb->start, comb->end, \
67                                     comb->point, radian);   \
68     else                                                    \
69         comb->location->var = g_strdup(end->location->var);
70 
71 #define COMB_END_COPY(var)                              \
72     comb->location->var = g_strdup(end->location->var);
73 
74 
75 /* struct to store results from searches for point data */
76 typedef struct {
77     GArray *before;
78     time_t point;
79     GArray *after;
80 } point_data_results;
81 
82 
83 /* convert string to a double value, returning backup value on error */
84 gdouble
string_to_double(const gchar * str,const gdouble backup)85 string_to_double(const gchar *str,
86                  const gdouble backup)
87 {
88     gdouble d = backup;
89     if (str && strlen(str) > 0)
90         d = g_ascii_strtod(str, NULL);
91     return d;
92 }
93 
94 
95 /* convert double to string, using non-local format */
96 gchar *
double_to_string(const gdouble val,const gchar * format)97 double_to_string(const gdouble val,
98                  const gchar *format)
99 {
100     gchar buf[20];
101     return g_strdup(g_ascii_formatd(buf, 20,
102                                     format ? format : "%.1f",
103                                     val));
104 }
105 
106 
107 gchar *
format_date(time_t date_t,gchar * format,gboolean local)108 format_date(time_t date_t,
109             gchar *format,
110             gboolean local)
111 {
112     struct tm *tm;
113     gchar buf[40];
114     size_t size;
115 
116     if (format == NULL)
117         format = "%Y-%m-%d %H:%M:%S";
118 
119     if (G_LIKELY(local))
120         tm = localtime(&date_t);
121     else
122         tm = gmtime(&date_t);
123 
124     /* A year <= 1970 means date has not been set */
125     if (G_UNLIKELY(tm == NULL) || tm->tm_year <= 70)
126         return g_strdup("-");
127     size = strftime(buf, 40, format, tm);
128     return (size ? g_strdup(buf) : g_strdup("-"));
129 }
130 
131 
132 /* check whether timeslice is interval or point data */
133 gboolean
timeslice_is_interval(xml_time * timeslice)134 timeslice_is_interval(xml_time *timeslice)
135 {
136     return (timeslice->location->symbol != NULL ||
137             timeslice->location->precipitation_value != NULL);
138 }
139 
140 
141 /*
142  * Calculate dew point in Celsius, taking the Magnus formulae as a
143  * basis. Source: http://de.wikipedia.org/wiki/Taupunkt
144  */
145 static gdouble
calc_dewpoint(const xml_location * loc)146 calc_dewpoint(const xml_location *loc)
147 {
148     gdouble temp, humidity, val;
149 
150     if (G_UNLIKELY(loc->humidity_value == NULL))
151         return INVALID_VALUE;
152 
153     temp = string_to_double(loc->temperature_value, 0);
154     humidity = string_to_double(loc->humidity_value, 0);
155     val = log(humidity / 100);
156     return (241.2 * val + 4222.03716 * temp / (241.2 + temp))
157         / (17.5043 - val - 17.5043 * temp / (241.2 + temp));
158 }
159 
160 
161 /* Calculate felt air temperature, using the chosen model. */
162 static gdouble
calc_apparent_temperature(const xml_location * loc,const apparent_temp_models model,const gboolean night_time)163 calc_apparent_temperature(const xml_location *loc,
164                           const apparent_temp_models model,
165                           const gboolean night_time)
166 {
167     gdouble temp = string_to_double(loc->temperature_value, 0);
168     gdouble windspeed = string_to_double(loc->wind_speed_mps, 0);
169     gdouble humidity = string_to_double(loc->humidity_value, 0);
170     gdouble dp, e;
171 
172     switch (model) {
173     case WINDCHILL_HEATINDEX:
174         /* If temperature is lower than 10 °C, use wind chill index,
175            if above 26.7°C use the heat index / Summer Simmer Index. */
176 
177         /* Wind chill, source:
178            http://www.nws.noaa.gov/os/windchill/index.shtml */
179         if (temp <= 10.0) {
180             /* wind chill is only defined for wind speeds above 3.0 mph */
181             windspeed *= 3.6;
182             if (windspeed < 4.828032)
183                 return temp;
184 
185             return 13.12 + 0.6215 * temp - 11.37 * pow(windspeed, 0.16)
186                 + 0.3965 * temp * pow(windspeed, 0.16);
187         }
188 
189         if (temp >= 26.7 || (night_time && temp >= 22.0)) {
190             /* humidity needs to be higher than 40% for a valid result */
191             if (humidity < 40)
192                 return temp;
193 
194             temp = temp * 9.0 / 5.0 + 32.0;  /* both models use Fahrenheit */
195             if (!night_time)
196                 /* Heat index, source:
197                    Lans P. Rothfusz. "The Heat Index 'Equation' (or, More
198                    Than You Ever Wanted to Know About Heat Index)",
199                    Scientific Services Division (NWS Southern Region
200                    Headquarters), 1 July 1990.
201                    http://www.srh.noaa.gov/images/ffc/pdf/ta_htindx.PDF
202                 */
203                 return ((-42.379
204                          + 2.04901523 * temp
205                          + 10.14333127 * humidity
206                          - 0.22475541 * temp * humidity
207                          - 0.00683783 * temp * temp
208                          - 0.05481717 * humidity * humidity
209                          + 0.00122874 * temp * temp * humidity
210                          + 0.00085282 * temp * humidity * humidity
211                          - 0.00000199 * temp * temp * humidity * humidity)
212                         - 32.0) * 5.0 / 9.0;   /* convert back to Celsius */
213             else
214                 /* Summer Simmer Index, sources:
215                    http://www.summersimmer.com/home.htm
216                    http://www.gorhamschaffler.com/humidity_formulas.htm */
217                 return ((1.98 * (temp - (0.55 - 0.0055 * humidity)
218                                  * (temp - 58)) - 56.83)
219                         - 32.0) * 5.0 / 9.0;   /* convert back to Celsius */
220         }
221 
222         /* otherwise simply return the temperature */
223         return temp;
224 
225     case WINDCHILL_HUMIDEX:
226         /* If temperature is equal or lower than 0 °C, use wind chill index,
227            if above 20.0 °C use humidex. Source:
228            http://www.weatheroffice.gc.ca/mainmenu/faq_e.html */
229 
230         if (temp <= 0) {
231             /* wind chill is only defined for wind speeds above 2.0 km/h */
232             windspeed *= 3.6;
233             if (windspeed < 2.0)
234                 return temp;
235 
236             /* wind chill, source:
237                http://www.nws.noaa.gov/os/windchill/index.shtml */
238             return 13.12 + 0.6215 * temp - 11.37 * pow(windspeed, 0.16)
239                 + 0.3965 * temp * pow(windspeed, 0.16);
240         }
241 
242         if (temp >= 20.0) {
243             /* Canadian humidex, source:
244                http://www.weatheroffice.gc.ca/mainmenu/faq_e.html#weather6 */
245             dp = calc_dewpoint(loc);
246 
247             /* dew point needs to be above a certain limit for
248                valid results, see
249                http://www.weatheroffice.gc.ca/mainmenu/faq_e.html#weather5 */
250             if (dp < 0 || dp == INVALID_VALUE)
251                 return temp;
252 
253             /* dew point needs to be converted to Kelvin (easy job ;-) */
254             e = 6.11 * exp(5417.7530 * (1/273.16 - 1/(dp + 273.15)));
255             return temp + 0.5555 * (e - 10.0);
256         }
257         return temp;
258 
259     case STEADMAN:
260         /* Australians use a different formula. Source:
261            http://www.bom.gov.au/info/thermal_stress/#atapproximation */
262         e = humidity / 100 * 6.105 * exp(17.27 * temp / (237.7 + temp));
263         return temp + 0.33 * e - 0.7 * windspeed - 4.0;
264 
265     case QUAYLE_STEADMAN:
266         /* R. G. Quayle, R. G. Steadman: The Steadman wind chill: an
267            improvement over present scales. In: Weather and
268            Forecasting. 13, 1998, S. 1187–1193 */
269         return 1.41 - 1.162 * windspeed + 0.980 * temp
270             + 0.0124 * windspeed * windspeed + 0.0185 * windspeed * temp;
271 
272     default:
273         return temp;
274     }
275 }
276 
277 
278 /*
279  * Return wind direction name for wind degrees, which gives the
280  * direction the wind is coming _from_.
281  */
282 static gchar*
wind_dir_name_by_deg(gchar * degrees,gboolean long_name)283 wind_dir_name_by_deg(gchar *degrees, gboolean long_name)
284 {
285     gdouble deg;
286 
287     if (G_UNLIKELY(degrees == NULL))
288         return "";
289 
290     deg = string_to_double(degrees, 0);
291 
292     if (deg >= 360 - 22.5 || deg < 45 - 22.5)
293         return (long_name) ? _("North") : _("N");
294 
295     if (deg >= 45 - 22.5 && deg < 45 + 22.5)
296         return (long_name) ? _("North-East") : _("NE");
297 
298     if (deg >= 90 - 22.5 && deg < 90 + 22.5)
299         return (long_name) ? _("East") : _("E");
300 
301     if (deg >= 135 - 22.5 && deg < 135 + 22.5)
302         return (long_name) ? _("South-East") : _("SE");
303 
304     if (deg >= 180 - 22.5 && deg < 180 + 22.5)
305         return (long_name) ? _("South") : _("S");
306 
307     if (deg >= 225 - 22.5 && deg < 225 + 22.5)
308         return (long_name) ? _("South-West") : _("SW");
309 
310     if (deg >= 270 - 22.5 && deg < 270 + 22.5)
311         return (long_name) ? _("West") : _("W");
312 
313     if (deg >= 315 - 22.5 && deg < 315 + 22.5)
314         return (long_name) ? _("North-West") : _("NW");
315 
316     return "";
317 }
318 
319 
320 gchar *
get_data(const xml_time * timeslice,const units_config * units,const data_types type,const gboolean round,const gboolean night_time)321 get_data(const xml_time *timeslice,
322          const units_config *units,
323          const data_types type,
324          const gboolean round,
325          const gboolean night_time)
326 {
327     const xml_location *loc = NULL;
328     gdouble val, temp;
329 
330     if (timeslice == NULL || timeslice->location == NULL || units == NULL)
331         return g_strdup("");
332 
333     loc = timeslice->location;
334 
335     switch (type) {
336     case ALTITUDE:
337         switch (units->altitude) {
338         case METERS:
339             return LOCALE_DOUBLE(loc->altitude, "%.0f");
340 
341         case FEET:
342             val = string_to_double(loc->altitude, 0);
343             val /= 0.3048;
344             return g_strdup_printf(ROUND_TO_INT("%.2f"), val);
345         }
346         break;
347 
348     case LATITUDE:
349         return LOCALE_DOUBLE(loc->latitude, "%.4f");
350 
351     case LONGITUDE:
352         return LOCALE_DOUBLE(loc->longitude, "%.4f");
353 
354     case TEMPERATURE:      /* source is in °C */
355         val = string_to_double(loc->temperature_value, 0);
356         if (units->temperature == FAHRENHEIT)
357             CALC_FAHRENHEIT(round, val);
358         return g_strdup_printf(ROUND_TO_INT("%.1f"), val);
359 
360     case PRESSURE:         /* source is in hectopascals */
361         val = string_to_double(loc->pressure_value, 0);
362         switch (units->pressure) {
363         case INCH_MERCURY:
364             val *= 0.03;
365             break;
366         case PSI:
367             val *= 0.01450378911491;
368             break;
369         case TORR:
370             val /= 1.333224;
371             break;
372         }
373         return g_strdup_printf(ROUND_TO_INT("%.1f"), val);
374 
375     case WIND_SPEED:       /* source is in meters per hour */
376         val = string_to_double(loc->wind_speed_mps, 0);
377         switch (units->windspeed) {
378         case KMH:
379             val *= 3.6;
380             break;
381         case MPH:
382             val *= 2.2369362920544;
383             break;
384         case FTS:
385             val *= 3.2808399;
386             break;
387         case KNOTS:
388             val *= 1.9438445;
389             break;
390         }
391         return g_strdup_printf(ROUND_TO_INT("%.1f"), val);
392 
393     case WIND_BEAUFORT:
394         val = string_to_double(loc->wind_speed_beaufort, 0);
395         return g_strdup_printf("%.0f", val);
396 
397     case WIND_DIRECTION:
398         return g_strdup(wind_dir_name_by_deg(loc->wind_dir_deg, FALSE));
399 
400     case WIND_DIRECTION_DEG:
401         return LOCALE_DOUBLE(loc->wind_dir_deg, ROUND_TO_INT("%.1f"));
402 
403     case HUMIDITY:
404         return LOCALE_DOUBLE(loc->humidity_value, ROUND_TO_INT("%.1f"));
405 
406     case DEWPOINT:
407         val = calc_dewpoint(loc);
408         if (val == INVALID_VALUE)
409             return g_strdup("");
410         if (units->temperature == FAHRENHEIT)
411             CALC_FAHRENHEIT(round, val);
412         return g_strdup_printf(ROUND_TO_INT("%.1f"), val);
413 
414     case APPARENT_TEMPERATURE:
415         val = calc_apparent_temperature(loc, units->apparent_temperature,
416                                         night_time);
417         if (units->temperature == FAHRENHEIT)
418             CALC_FAHRENHEIT(round, val);
419         return g_strdup_printf(ROUND_TO_INT("%.1f"), val);
420 
421     case CLOUDS_LOW:
422         return LOCALE_DOUBLE(loc->clouds_percent[CLOUDS_PERC_LOW],
423                              ROUND_TO_INT("%.1f"));
424 
425     case CLOUDS_MID:
426         return LOCALE_DOUBLE(loc->clouds_percent[CLOUDS_PERC_MID],
427                              ROUND_TO_INT("%.1f"));
428 
429     case CLOUDS_HIGH:
430         return LOCALE_DOUBLE(loc->clouds_percent[CLOUDS_PERC_HIGH],
431                              ROUND_TO_INT("%.1f"));
432 
433     case CLOUDINESS:
434         return LOCALE_DOUBLE(loc->clouds_percent[CLOUDS_PERC_CLOUDINESS],
435                              ROUND_TO_INT("%.1f"));
436 
437     case FOG:
438         return LOCALE_DOUBLE(loc->fog_percent, ROUND_TO_INT("%.1f"));
439 
440     case PRECIPITATION:   /* source is in millimeters */
441         val = string_to_double(loc->precipitation_value, 0);
442 
443         /* For snow, adjust precipitation dependent on temperature. Source:
444            http://answers.yahoo.com/question/index?qid=20061230123635AAAdZAe */
445         if (loc->symbol_id == SYMBOL_SNOWSUN ||
446             loc->symbol_id == SYMBOL_SNOW ||
447             loc->symbol_id == SYMBOL_SNOWTHUNDER ||
448             loc->symbol_id == SYMBOL_SNOWSUNPOLAR ||
449             loc->symbol_id == SYMBOL_SNOWSUNTHUNDER) {
450             temp = string_to_double(loc->temperature_value, 0);
451             if (temp < -11.1111)      /* below 12 °F, low snow density */
452                 val *= 12;
453             else if (temp < -4.4444)  /* 12 to 24 °F, still low density */
454                 val *= 10;
455             else if (temp < -2.2222)  /* 24 to 28 °F, more density */
456                 val *= 7;
457             else if (temp < -0.5556)  /* 28 to 31 °F, wet, dense, melting */
458                 val *= 5;
459             else                      /* anything above 31 °F */
460                 val *= 3;
461         }
462 
463         if (units->precipitation == INCHES) {
464             val /= 25.4;
465             return g_strdup_printf("%.2f", val);
466         } else
467             return g_strdup_printf("%.1f", val);
468 
469     case SYMBOL:
470         return CHK_NULL(loc->symbol);
471     }
472 
473     return g_strdup("");
474 }
475 
476 
477 const gchar *
get_unit(const units_config * units,const data_types type)478 get_unit(const units_config *units,
479          const data_types type)
480 {
481     if (units == NULL)
482         return "";
483 
484     switch (type) {
485     case ALTITUDE:
486         return (units->altitude == FEET) ? _("ft") : _("m");
487     case TEMPERATURE:
488     case DEWPOINT:
489     case APPARENT_TEMPERATURE:
490         return (units->temperature == FAHRENHEIT) ? _("°F") : _("°C");
491     case PRESSURE:
492         switch (units->pressure) {
493         case HECTOPASCAL:
494             return _("hPa");
495         case INCH_MERCURY:
496             return _("inHg");
497         case PSI:
498             return _("psi");
499         case TORR:
500             return _("mmHg");
501         }
502         break;
503     case WIND_SPEED:
504         switch (units->windspeed) {
505         case KMH:
506             return _("km/h");
507         case MPH:
508             return _("mph");
509         case MPS:
510             return _("m/s");
511         case FTS:
512             return _("ft/s");
513         case KNOTS:
514             return _("kt");
515         }
516         break;
517     case WIND_DIRECTION_DEG:
518     case LATITUDE:
519     case LONGITUDE:
520         /* TRANSLATORS: The degree sign is used like a unit for
521            latitude, longitude, wind direction */
522         return _("°");
523     case HUMIDITY:
524     case CLOUDS_LOW:
525     case CLOUDS_MID:
526     case CLOUDS_HIGH:
527     case CLOUDINESS:
528     case FOG:
529         /* TRANSLATORS: Percentage sign is used like a unit for
530            clouds, fog, humidity */
531         return _("%");
532     case PRECIPITATION:
533         return (units->precipitation == INCHES) ? _("in") : _("mm");
534     case SYMBOL:
535     case WIND_BEAUFORT:
536     case WIND_DIRECTION:
537         return "";
538     }
539     return "";
540 }
541 
542 
543 /*
544  * Find out whether it's night or day.
545  *
546  * Either use the exact times for sunrise and sunset if
547  * available, or fallback to reasonable arbitrary values.
548  */
549 gboolean
is_night_time(const xml_astro * astro)550 is_night_time(const xml_astro *astro)
551 {
552     time_t now_t;
553     struct tm now_tm;
554 
555     time(&now_t);
556 
557     if (G_LIKELY(astro)) {
558         if (astro->sun_never_rises || astro->sun_never_sets){
559             /* Polar night */
560             if (astro->solarnoon_elevation <= 0)
561                 return TRUE;
562             /* Polar day */
563             if (astro->solarmidnight_elevation > 0)
564                 return FALSE;
565         }
566 
567         /* Sunrise and sunset are known */
568         if (difftime(astro->sunrise, now_t) > 0)
569             return TRUE;
570 
571         if (difftime(astro->sunset, now_t) <= 0)
572             return TRUE;
573 
574         return FALSE;
575     }
576 
577     /* no astrodata available, use fallback values */
578     now_tm = *localtime(&now_t);
579     return (now_tm.tm_hour >= NIGHT_TIME_START ||
580             now_tm.tm_hour < NIGHT_TIME_END);
581 }
582 
583 
584 static void
calculate_symbol(xml_time * timeslice,gboolean current_conditions)585 calculate_symbol(xml_time *timeslice,
586                  gboolean current_conditions)
587 {
588     xml_location *loc;
589     gdouble fog, cloudiness, precipitation;
590 
591     g_assert(timeslice != NULL && timeslice->location != NULL);
592     if (G_UNLIKELY(timeslice == NULL || timeslice->location == NULL))
593         return;
594 
595     loc = timeslice->location;
596 
597     precipitation = string_to_double(loc->precipitation_value, 0);
598     if (precipitation > 0)
599         return;
600 
601     /* do some modifications only if we're making a timeslice for
602        current conditions */
603     if (current_conditions) {
604         cloudiness =
605             string_to_double(loc->clouds_percent[CLOUDS_PERC_CLOUDINESS], 0);
606         if (cloudiness >= 90)
607             loc->symbol_id = SYMBOL_CLOUD;
608         else if (cloudiness >= 30)
609             loc->symbol_id = SYMBOL_PARTLYCLOUD;
610         else if (cloudiness >= 1.0 / 8.0)
611             loc->symbol_id = SYMBOL_LIGHTCLOUD;
612     }
613 
614     fog = string_to_double(loc->fog_percent, 0);
615     if (fog >= 80)
616         loc->symbol_id = SYMBOL_FOG;
617 
618     /* update symbol name */
619     g_free(loc->symbol);
620     loc->symbol = g_strdup(get_symbol_name(loc->symbol_id));
621 }
622 
623 
624 /*
625  * Interpolate data for a certain time in a given interval
626  */
627 static gdouble
interpolate_value(gdouble value_start,gdouble value_end,time_t start_t,time_t end_t,time_t between_t)628 interpolate_value(gdouble value_start,
629                   gdouble value_end,
630                   time_t start_t,
631                   time_t end_t,
632                   time_t between_t)
633 {
634     gdouble total, part, ratio, delta, result;
635 
636     /* calculate durations from start to end and start to between */
637     total = difftime(end_t, start_t);
638     part = difftime(between_t, start_t);
639 
640     /* calculate ratio of these durations */
641     ratio = part / total;
642 
643     /* now how big is that change? */
644     delta = (value_end - value_start) * ratio;
645 
646     /* apply change and return corresponding value for between_t */
647     result = value_start + delta;
648     return result;
649 }
650 
651 
652 /*
653  * convert gchar in a gdouble and interpolate the value
654  */
655 static gchar *
interpolate_gchar_value(gchar * value_start,gchar * value_end,time_t start_t,time_t end_t,time_t between_t,gboolean radian)656 interpolate_gchar_value(gchar *value_start,
657                         gchar *value_end,
658                         time_t start_t,
659                         time_t end_t,
660                         time_t between_t,
661                         gboolean radian)
662 {
663     gdouble val_start, val_end, val_result;
664 
665     if (G_UNLIKELY(value_end == NULL))
666         return NULL;
667 
668     if (value_start == NULL)
669         return g_strdup(value_end);
670 
671     val_start = string_to_double(value_start, 0);
672     val_end = string_to_double(value_end, 0);
673 
674     if (radian) {
675         if (val_end > val_start && val_end - val_start > 180)
676             val_start += 360;
677         else if (val_start > val_end && val_start - val_end > 180)
678             val_end += 360;
679     }
680 
681     val_result = interpolate_value(val_start, val_end,
682                                    start_t, end_t, between_t);
683     if (radian && val_result >= 360)
684         val_result -= 360;
685 
686     weather_debug("Interpolated data: start=%f, end=%f, result=%f",
687                   val_start, val_end, val_result);
688     return double_to_string(val_result, "%.1f");
689 }
690 
691 
692 /* Create a new combined timeslice, with optionally interpolated data */
693 static xml_time *
make_combined_timeslice(xml_weather * wd,const xml_time * interval,const time_t * between_t,gboolean current_conditions)694 make_combined_timeslice(xml_weather *wd,
695                         const xml_time *interval,
696                         const time_t *between_t,
697                         gboolean current_conditions)
698 {
699     xml_time *comb, *start, *end;
700     gboolean ipol = (between_t != NULL) ? TRUE : FALSE;
701     gint i;
702 
703     /* find point data at start of interval (may not be available) */
704     start = get_timeslice(wd, interval->start, interval->start, NULL);
705 
706     /* find point interval at end of interval */
707     end = get_timeslice(wd, interval->end, interval->end, NULL);
708 
709     if (start == NULL && end == NULL)
710         return NULL;
711 
712     /* create new timeslice to hold our copy */
713     comb = g_slice_new0(xml_time);
714     if (comb == NULL)
715         return NULL;
716 
717     comb->location = g_slice_new0(xml_location);
718     if (comb->location == NULL) {
719         g_slice_free(xml_time, comb);
720         return NULL;
721     }
722 
723     /* do not interpolate if no point data available at start of interval */
724     if (start == NULL) {
725         comb->point = end->start;
726         start = end;
727     } else if (ipol) {
728         /* deal with timeslices that are in the near future and use point
729            data available at the start of the interval */
730         if (difftime(*between_t, start->start) <= 0)
731             comb->point = start->start;
732         else
733             comb->point = *between_t;
734     }
735 
736     comb->start = interval->start;
737     comb->end = interval->end;
738 
739     COMB_END_COPY(altitude);
740     COMB_END_COPY(latitude);
741     COMB_END_COPY(longitude);
742 
743     INTERPOLATE_OR_COPY(temperature_value, FALSE);
744     COMB_END_COPY(temperature_unit);
745 
746     INTERPOLATE_OR_COPY(wind_dir_deg, TRUE);
747     comb->location->wind_dir_name =
748         g_strdup(wind_dir_name_by_deg(comb->location->wind_dir_deg, FALSE));
749 
750     INTERPOLATE_OR_COPY(wind_speed_mps, FALSE);
751     INTERPOLATE_OR_COPY(wind_speed_beaufort, FALSE);
752     INTERPOLATE_OR_COPY(humidity_value, FALSE);
753     COMB_END_COPY(humidity_unit);
754 
755     INTERPOLATE_OR_COPY(pressure_value, FALSE);
756     COMB_END_COPY(pressure_unit);
757 
758     for (i = 0; i < CLOUDS_PERC_NUM; i++)
759         INTERPOLATE_OR_COPY(clouds_percent[i], FALSE);
760 
761     INTERPOLATE_OR_COPY(fog_percent, FALSE);
762 
763     /* it makes no sense to interpolate the following (interval) values */
764     comb->location->precipitation_value =
765         g_strdup(interval->location->precipitation_value);
766     comb->location->precipitation_unit =
767         g_strdup(interval->location->precipitation_unit);
768 
769     comb->location->symbol_id = interval->location->symbol_id;
770     comb->location->symbol = g_strdup(interval->location->symbol);
771 
772     calculate_symbol(comb, current_conditions);
773     return comb;
774 }
775 
776 
777 void
merge_astro(GArray * astrodata,const xml_astro * astro)778 merge_astro(GArray *astrodata,
779             const xml_astro *astro)
780 {
781     xml_astro *old_astro, *new_astro;
782     guint index;
783 
784     g_assert(astrodata != NULL);
785     if (G_UNLIKELY(astrodata == NULL))
786         return;
787 
788     /* copy astro, as it may be deleted by the calling function */
789     new_astro = xml_astro_copy(astro);
790 
791     /* check for and replace existing astrodata of the same date */
792     if ((old_astro = get_astro(astrodata, astro->day, &index))) {
793         xml_astro_free(old_astro);
794         g_array_remove_index(astrodata, index);
795         g_array_insert_val(astrodata, index, new_astro);
796         weather_debug("Replaced existing astrodata at %d.", index);
797     } else {
798         g_array_append_val(astrodata, new_astro);
799         weather_debug("Appended new astrodata to the existing data.");
800     }
801 }
802 
803 
804 void
merge_timeslice(xml_weather * wd,const xml_time * timeslice)805 merge_timeslice(xml_weather *wd,
806                 const xml_time *timeslice)
807 {
808     xml_time *old_ts, *new_ts;
809     time_t now_t = time(NULL);
810     guint index;
811 
812     g_assert(wd != NULL);
813     if (G_UNLIKELY(wd == NULL))
814         return;
815 
816     /* first check if it isn't too old */
817     if (difftime(now_t, timeslice->end) > DATA_EXPIRY_TIME) {
818         weather_debug("Not merging timeslice because it has expired.");
819         return;
820     }
821 
822     /* Copy timeslice, as it will be deleted by the calling function */
823     new_ts = xml_time_copy(timeslice);
824 
825     /* check if there is a timeslice with the same interval and
826        replace it with the current data */
827     old_ts = get_timeslice(wd, timeslice->start, timeslice->end, &index);
828     if (old_ts) {
829         xml_time_free(old_ts);
830         g_array_remove_index(wd->timeslices, index);
831         g_array_insert_val(wd->timeslices, index, new_ts);
832         weather_debug("Replaced existing timeslice at %d.", index);
833     } else {
834         g_array_prepend_val(wd->timeslices, new_ts);
835         //weather_debug("Prepended timeslice to the existing timeslices.");
836     }
837 }
838 
839 
840 /* Return current weather conditions, or NULL if not available. */
841 xml_time *
get_current_conditions(const xml_weather * wd)842 get_current_conditions(const xml_weather *wd)
843 {
844     return wd ? wd->current_conditions : NULL;
845 }
846 
847 
848 time_t
time_calc(const struct tm time_tm,const gint year,const gint month,const gint day,const gint hour,const gint min,const gint sec)849 time_calc(const struct tm time_tm,
850           const gint year,
851           const gint month,
852           const gint day,
853           const gint hour,
854           const gint min,
855           const gint sec)
856 {
857     time_t result;
858     struct tm new_tm;
859 
860     new_tm = time_tm;
861     new_tm.tm_isdst = -1;
862     if (year)
863         new_tm.tm_year += year;
864     if (month)
865         new_tm.tm_mon += month;
866     if (day)
867         new_tm.tm_mday += day;
868     if (hour)
869         new_tm.tm_hour += hour;
870     if (min)
871         new_tm.tm_min += min;
872     if (sec)
873         new_tm.tm_sec += sec;
874     result = mktime(&new_tm);
875     return result;
876 }
877 
878 
879 time_t
time_calc_hour(const struct tm time_tm,const gint hours)880 time_calc_hour(const struct tm time_tm,
881                const gint hours)
882 {
883     return time_calc(time_tm, 0, 0, 0, hours, 0, 0);
884 }
885 
886 
887 time_t
time_calc_day(const struct tm time_tm,const gint days)888 time_calc_day(const struct tm time_tm,
889               const gint days)
890 {
891     return time_calc(time_tm, 0, 0, days, 0, 0, 0);
892 }
893 
894 
895 /*
896  * Compare two xml_astro structs using their date (days) field.
897  */
898 gint
xml_astro_compare(gconstpointer a,gconstpointer b)899 xml_astro_compare(gconstpointer a,
900                   gconstpointer b)
901 {
902     xml_astro *a1 = *(xml_astro **) a;
903     xml_astro *a2 = *(xml_astro **) b;
904 
905     if (G_UNLIKELY(a1 == NULL && a2 == NULL))
906         return 0;
907     if (G_UNLIKELY(a1 == NULL))
908         return 1;
909     if (G_UNLIKELY(a2 == NULL))
910         return -1;
911 
912     return (gint) difftime(a2->day, a1->day) * -1;
913 }
914 
915 
916 void
astrodata_clean(GArray * astrodata)917 astrodata_clean(GArray *astrodata)
918 {
919     xml_astro *astro;
920     time_t now_t = time(NULL);
921     guint i;
922 
923     if (G_UNLIKELY(astrodata == NULL))
924         return;
925 
926     for (i = 0; i < astrodata->len; i++) {
927         astro = g_array_index(astrodata, xml_astro *, i);
928         if (G_UNLIKELY(astro == NULL))
929             continue;
930         if (difftime(now_t, astro->day) >= 24 * 3600) {
931             weather_debug("Removing expired astrodata:");
932             weather_dump(weather_dump_astro, astro);
933             xml_astro_free(astro);
934             g_array_remove_index(astrodata, i--);
935             weather_debug("Remaining astrodata entries: %d", astrodata->len);
936         }
937     }
938 }
939 
940 
941 /*
942  * Compare two xml_time structs using their start and end times,
943  * returning the result as a qsort()-style comparison function (less
944  * than zero for first arg is less than second arg, zero for equal,
945  * greater zero if first arg is greater than second arg).
946  */
947 gint
xml_time_compare(gconstpointer a,gconstpointer b)948 xml_time_compare(gconstpointer a,
949                  gconstpointer b)
950 {
951     xml_time *ts1 = *(xml_time **) a;
952     xml_time *ts2 = *(xml_time **) b;
953     gdouble diff;
954 
955     if (G_UNLIKELY(ts1 == NULL && ts2 == NULL))
956         return 0;
957     if (G_UNLIKELY(ts1 == NULL))
958         return -1;
959     if (G_UNLIKELY(ts2 == NULL))
960         return 1;
961 
962     diff = difftime(ts2->start, ts1->start);
963     if (diff != 0)
964         return (gint) (diff * -1);
965 
966     /* start time is equal, now it's easy to check end time ;-) */
967     return (gint) (difftime(ts2->end, ts1->end) * -1);
968 }
969 
970 
971 static void
point_data_results_free(point_data_results * pdr)972 point_data_results_free(point_data_results *pdr)
973 {
974     g_assert(pdr != NULL);
975     if (G_UNLIKELY(pdr == NULL))
976         return;
977 
978     g_assert(pdr->before != NULL);
979     g_array_free(pdr->before, FALSE);
980     g_assert(pdr->after != NULL);
981     g_array_free(pdr->after, FALSE);
982     g_slice_free(point_data_results, pdr);
983     return;
984 }
985 
986 /*
987  * Given an array of point data, find two points for which
988  * corresponding interval data can be found so that the interval is as
989  * small as possible, returning NULL if such interval data doesn't
990  * exist.
991  */
992 static xml_time *
find_smallest_interval(xml_weather * wd,const point_data_results * pdr)993 find_smallest_interval(xml_weather *wd,
994                        const point_data_results *pdr)
995 {
996     GArray *before = pdr->before, *after = pdr->after;
997     xml_time *ts_before, *ts_after, *found;
998     guint i, j;
999 
1000     if (before->len == 0)
1001         return NULL;
1002 
1003     for (i = before->len - 1; i > 0; i--) {
1004         ts_before = g_array_index(before, xml_time *, i);
1005         for (j = 0; j < after->len; j++) {
1006             ts_after = g_array_index(after, xml_time *, j);
1007             found = get_timeslice(wd, ts_before->start, ts_after->end, NULL);
1008             if (found)
1009                 return found;
1010         }
1011     }
1012     return NULL;
1013 }
1014 
1015 
1016 static xml_time *
find_smallest_incomplete_interval(xml_weather * wd,time_t end_t)1017 find_smallest_incomplete_interval(xml_weather *wd,
1018                                   time_t end_t)
1019 {
1020     xml_time *timeslice, *found = NULL;
1021     guint i;
1022 
1023     weather_debug("Searching for the smallest incomplete interval.");
1024     /* search for all timeslices with interval data that have end time end_t */
1025     for (i = 0; i < wd->timeslices->len; i++) {
1026         timeslice = g_array_index(wd->timeslices, xml_time *, i);
1027         if (timeslice && difftime(timeslice->end, end_t) == 0
1028             && difftime(timeslice->end, timeslice->start) != 0) {
1029             if (found == NULL)
1030                 found = timeslice;
1031             else
1032                 if (difftime(timeslice->start, found->start) > 0)
1033                     found = timeslice;
1034             weather_dump(weather_dump_timeslice, found);
1035         }
1036     }
1037     weather_debug("Search result for smallest incomplete interval is:");
1038     weather_dump(weather_dump_timeslice, found);
1039     return found;
1040 }
1041 
1042 
1043 /* find point data within certain limits around a point in time */
1044 static point_data_results *
find_point_data(const xml_weather * wd,const time_t point_t,const gdouble min_diff,const gdouble max_diff)1045 find_point_data(const xml_weather *wd,
1046                 const time_t point_t,
1047                 const gdouble min_diff,
1048                 const gdouble max_diff)
1049 {
1050     point_data_results *found;
1051     xml_time *timeslice;
1052     gdouble diff;
1053     guint i;
1054 
1055     found = g_slice_new0(point_data_results);
1056     found->before = g_array_new(FALSE, TRUE, sizeof(xml_time *));
1057     found->after = g_array_new(FALSE, TRUE, sizeof(xml_time *));
1058 
1059     weather_debug("Checking %d timeslices for point data.",
1060                   wd->timeslices->len);
1061     for (i = 0; i < wd->timeslices->len; i++) {
1062         timeslice = g_array_index(wd->timeslices, xml_time *, i);
1063         /* look only for point data, not intervals */
1064         if (G_UNLIKELY(timeslice == NULL) || timeslice_is_interval(timeslice))
1065             continue;
1066 
1067         /* add point data if within limits */
1068         diff = difftime(timeslice->end, point_t);
1069         if (diff <= 0) {  /* before point_t */
1070             diff *= -1;
1071             if (diff < min_diff || diff > max_diff)
1072                 continue;
1073             g_array_append_val(found->before, timeslice);
1074             weather_dump(weather_dump_timeslice, timeslice);
1075         } else {          /* after point_t */
1076             if (diff < min_diff || diff > max_diff)
1077                 continue;
1078             g_array_append_val(found->after, timeslice);
1079             weather_dump(weather_dump_timeslice, timeslice);
1080         }
1081     }
1082     g_array_sort(found->before, (GCompareFunc) xml_time_compare);
1083     g_array_sort(found->after, (GCompareFunc) xml_time_compare);
1084     found->point = point_t;
1085     weather_debug("Found %d timeslices with point data, "
1086                   "%d before and %d after point_t.",
1087                   (found->before->len + found->after->len),
1088                   found->before->len, found->after->len);
1089     return found;
1090 }
1091 
1092 
1093 xml_time *
make_current_conditions(xml_weather * wd,time_t now_t)1094 make_current_conditions(xml_weather *wd,
1095                         time_t now_t)
1096 {
1097     point_data_results *found = NULL;
1098     xml_time *interval = NULL, *incomplete;
1099     struct tm point_tm = *localtime(&now_t);
1100     time_t point_t = now_t;
1101     gint i = 0;
1102 
1103     g_assert(wd != NULL);
1104     if (G_UNLIKELY(wd == NULL))
1105         return NULL;
1106 
1107     /* there may not be a timeslice available for the current
1108        interval, so look max three hours ahead */
1109     while (i < 3 && interval == NULL) {
1110         point_t = time_calc_hour(point_tm, i);
1111         found = find_point_data(wd, point_t, 1, 4 * 3600);
1112         interval = find_smallest_interval(wd, found);
1113         point_data_results_free(found);
1114 
1115         /* There may be interval data where point data is only
1116            available at the end of that interval. If such an interval
1117            exists, use it, it's still better than the next one where
1118            now_t is not in between. */
1119         if (interval && difftime(interval->start, now_t) > 0)
1120             if ((incomplete =
1121                  find_smallest_incomplete_interval(wd, interval->start)))
1122                 interval = incomplete;
1123         point_tm = *localtime(&point_t);
1124         i++;
1125     }
1126     weather_dump(weather_dump_timeslice, interval);
1127     if (interval == NULL)
1128         return NULL;
1129 
1130     return make_combined_timeslice(wd, interval, &now_t, TRUE);
1131 }
1132 
1133 
1134 /*
1135  * Add days to time_t and set the calculated day to midnight.
1136  */
1137 time_t
day_at_midnight(time_t day_t,const gint add_days)1138 day_at_midnight(time_t day_t,
1139                 const gint add_days)
1140 {
1141     struct tm day_tm;
1142 
1143     day_tm = *localtime(&day_t);
1144     day_tm.tm_mday += add_days;
1145     day_tm.tm_hour = day_tm.tm_min = day_tm.tm_sec = 0;
1146     day_tm.tm_isdst = -1;
1147     day_t = mktime(&day_tm);
1148     return day_t;
1149 }
1150 
1151 
1152 /*
1153  * Returns astro data for a given day.
1154  */
1155 xml_astro *
get_astro_data_for_day(const GArray * astrodata,const gint day)1156 get_astro_data_for_day(const GArray *astrodata,
1157                        const gint day)
1158 {
1159     xml_astro *astro;
1160     time_t day_t = time(NULL);
1161     guint i;
1162 
1163     if (G_UNLIKELY(astrodata == NULL))
1164         return NULL;
1165 
1166     day_t = day_at_midnight(day_t, day);
1167 
1168     for (i = 0; i < astrodata->len; i++) {
1169         astro = g_array_index(astrodata, xml_astro *, i);
1170         if (astro && (difftime(astro->day, day_t) == 0))
1171             return astro;
1172     }
1173 
1174     return NULL;
1175 }
1176 
1177 
1178 /*
1179  * Get all point data relevant for a given day.
1180  */
1181 GArray *
get_point_data_for_day(xml_weather * wd,gint day)1182 get_point_data_for_day(xml_weather *wd,
1183                        gint day)
1184 {
1185     GArray *found;
1186     xml_time *timeslice;
1187     time_t day_t = time(NULL);
1188     guint i;
1189 
1190     day_t = day_at_midnight(day_t, day);
1191 
1192     /* loop over weather data and pick relevant point data */
1193     found = g_array_new(FALSE, TRUE, sizeof(xml_time *));
1194     g_assert(found != NULL);
1195     if (G_UNLIKELY(found == NULL))
1196         return NULL;
1197 
1198     weather_debug("Checking %d timeslices for point data relevant to day %d.",
1199                   wd->timeslices->len, day);
1200     for (i = 0; i < wd->timeslices->len; i++) {
1201         timeslice = g_array_index(wd->timeslices, xml_time *, i);
1202 
1203         /* look only for point data, not intervals */
1204         if (G_UNLIKELY(timeslice == NULL) || timeslice_is_interval(timeslice))
1205             continue;
1206 
1207         if (difftime(timeslice->start, day_t) >= DAY_START * 3600 &&
1208             difftime(timeslice->end, day_t) <= DAY_END * 3600) {
1209             weather_dump(weather_dump_timeslice, timeslice);
1210             g_array_append_val(found, timeslice);
1211         }
1212     }
1213     g_array_sort(found, (GCompareFunc) xml_time_compare);
1214     weather_debug("Found %d timeslices for day %d.", found->len, day);
1215     return found;
1216 }
1217 
1218 
1219 /*
1220  * Return forecast data for a given daytime, using the data provided.
1221  */
1222 xml_time *
make_forecast_data(xml_weather * wd,GArray * daydata,gint day,daytime dt)1223 make_forecast_data(xml_weather *wd,
1224                    GArray *daydata,
1225                    gint day,
1226                    daytime dt)
1227 {
1228     xml_time *ts1, *ts2, *interval = NULL;
1229     struct tm point_tm, start_tm, end_tm, tm1, tm2;
1230     time_t point_t, start_t, end_t;
1231     gint min = 0, max = 0, point = 0;
1232     guint i, j;
1233 
1234     g_assert(wd != NULL);
1235     if (G_UNLIKELY(wd == NULL))
1236         return NULL;
1237 
1238     g_assert(daydata != NULL);
1239     if (G_UNLIKELY(daydata == NULL))
1240         return NULL;
1241 
1242     /* choose search interval and desired point in time depending on daytime */
1243     switch (dt) {
1244     case MORNING:
1245         min = 3;
1246         max = 15;
1247         point = 9;
1248         break;
1249     case AFTERNOON:
1250         min = 9;
1251         max = 21;
1252         point = 15;
1253         break;
1254     case EVENING:
1255         min = 15;
1256         max = 27;
1257         point = 21;
1258         break;
1259     case NIGHT:
1260         min = 21;
1261         max = 33;
1262         point = 27;
1263         break;
1264     }
1265 
1266     /* initialize times to the current day */
1267     time(&point_t);
1268     start_tm = end_tm = point_tm = *localtime(&point_t);
1269 
1270     /* calculate daytime limits for the requested day */
1271     point_tm.tm_mday += day;
1272     point_tm.tm_hour = point;
1273     point_tm.tm_min = point_tm.tm_sec = 0;
1274     point_tm.tm_isdst = -1;
1275     point_t = mktime(&point_tm);
1276 
1277     start_tm.tm_mday += day;
1278     start_tm.tm_hour = min;
1279     start_tm.tm_min = start_tm.tm_sec = 0;
1280     start_tm.tm_isdst = -1;
1281     start_t = mktime(&start_tm);
1282 
1283     end_tm.tm_mday += day;
1284     end_tm.tm_hour = max;
1285     end_tm.tm_min = end_tm.tm_sec = 0;
1286     end_tm.tm_isdst = -1;
1287     end_t = mktime(&end_tm);
1288 
1289     /* using search criteria, find an appropriate interval */
1290     for (i = 0; i < daydata->len; i++) {
1291         weather_debug("checking start ts %d", i);
1292 
1293         /* try start timeslice for interval */
1294         ts1 = g_array_index(daydata, xml_time *, i);
1295 
1296         if (G_UNLIKELY(ts1 == NULL))
1297             continue;
1298         weather_debug("start ts is not null");
1299 
1300         /* start timeslice needs to be within max daytime interval */
1301         if (difftime(ts1->start, start_t) < 0 ||
1302             difftime(end_t, ts1->start) < 0)
1303             continue;
1304         weather_debug("start ts is in max daytime interval");
1305 
1306         /* start timeslice needs to start at 0, 6, 12, or 18 hours UTC time */
1307         tm1 = *gmtime(&ts1->start);
1308         if (tm1.tm_hour != 0 && tm1.tm_hour % 6 != 0)
1309             continue;
1310         weather_debug("start ts does start at 0, 6, 12, 18 hour UTC time");
1311 
1312         for (j = 0; j < daydata->len; j++) {
1313             weather_debug("checking end ts %d", j);
1314 
1315             /* find end timeslice for interval */
1316             ts2 = g_array_index(daydata, xml_time *, j);
1317 
1318             if (G_UNLIKELY(ts2 == NULL))
1319                 continue;
1320             weather_debug("end ts is not null");
1321 
1322             /* end timeslice has to be different from the start timeslice */
1323             if (ts1 == ts2)
1324                 continue;
1325             weather_debug("start ts is different from end ts");
1326 
1327             /* end timeslice needs to be after start timeslice */
1328             if (difftime(ts2->start, ts1->start) <= 0)
1329                 continue;
1330             weather_debug("start ts is before end ts");
1331 
1332             /* end timeslice needs to be in max daytime interval */
1333             if (difftime(ts2->start, start_t) < 0 ||
1334                 difftime(end_t, ts2->start) < 0)
1335                 continue;
1336             weather_debug("end ts is in max daytime interval");
1337 
1338             /* end timeslice needs to start at 0, 6, 12, or 18 hours UTC time */
1339             tm2 = *gmtime(&ts2->start);
1340             if (tm2.tm_hour != 0 && tm2.tm_hour % 6 != 0)
1341                 continue;
1342             weather_debug("end ts does start at 0, 6, 12, 18 hour UTC time");
1343 
1344             /* start and end timeslice need to be a 6 hours interval... */
1345             if (difftime(ts2->start, ts1->start) != DAYTIME_LEN * 3600)
1346                 /* ...however we may need to take into account possible dst
1347                    difference so let's also try DAYTIME_LEN ±1 hour */
1348                 if ((difftime(ts2->start, ts1->start) < (DAYTIME_LEN - 1) * 3600 ||
1349                      difftime(ts2->start, ts1->start) > (DAYTIME_LEN + 1) * 3600) &&
1350                     get_timeslice(wd, ts1->start, ts2->end, NULL) == NULL)
1351                     continue;
1352             weather_debug("start and end ts are 6 hours apart");
1353 
1354             /* daytime point needs to be within the interval */
1355             if (difftime(point_t, ts1->start) < 0 ||
1356                 difftime(ts2->start, point_t) < 0)
1357                 continue;
1358             weather_debug("daytime point is within the found interval");
1359 
1360             /* check whether the desired interval exists */
1361             interval = get_timeslice(wd, ts1->start, ts2->end, NULL);
1362             if (interval == NULL)
1363                 continue;
1364 
1365             /* make and return a combined interval with interpolated data */
1366             weather_debug("returning valid interval");
1367             return make_combined_timeslice(wd, interval, &point_t, FALSE);
1368         }
1369     }
1370 
1371     /* Finding a 6 hours daytime interval failed; maybe current time
1372        is within this interval and therefore that 6 hours interval is
1373        not available anymore. In that case, simply trying the current
1374        conditions interval is better than nothing. */
1375     if (wd->current_conditions &&
1376         difftime(wd->current_conditions->start, start_t) >= 0 &&
1377         difftime(end_t, wd->current_conditions->end) >= 0) {
1378         interval = get_timeslice(wd, wd->current_conditions->start,
1379                                  wd->current_conditions->end, NULL);
1380         weather_debug("returning current conditions interval for daytime %d "
1381                       "of day %d", dt, day);
1382         return make_combined_timeslice(wd, interval,
1383                                        &wd->current_conditions->point, FALSE);
1384     }
1385 
1386     /* If we got as far as this, it's very unlikely we find a smaller
1387        interval, so just give up. */
1388     weather_debug("no forecast data for daytime %d of day %d", dt, day);
1389     return NULL;
1390 }
1391