SFI: compile sfitime as C++ source
[stwbeast.git] / sfi / sfitime.cc
1 /* SFI - Synthesis Fusion Kit Interface
2  * Copyright (C) 2002 Tim Janik
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * A copy of the GNU Lesser General Public License should ship along
15  * with this library; if not, see http://www.gnu.org/copyleft/.
16  */
17 #include "topconfig.h"
18 #include "sfitime.h"
19 #include "sfiring.h"
20 #include "sfiprimitives.h"
21 #include <sys/time.h>
22 #include <string.h>
23 #include <time.h>
24 #include <errno.h>
25 #include <stdio.h>
26
27
28 #define SFI_ERROR_DOMAIN        g_quark_from_static_string ("sfi-error-domain")
29 enum {
30   ERROR_DATE_INVALID    = 1,
31   ERROR_DATE_CLUTTERED,
32   ERROR_DATE_YEAR_BOUNDS,
33   ERROR_DATE_MONTH_BOUNDS,
34   ERROR_DATE_DAY_BOUNDS,
35   ERROR_DATE_HOUR_BOUNDS,
36   ERROR_DATE_MINUTE_BOUNDS,
37   ERROR_DATE_SECOND_BOUNDS,
38 };
39
40
41 /* --- variables --- */
42 static SfiTime   gmt_diff = 0;
43
44
45 /* --- functions --- */
46 void
47 _sfi_init_time (void)
48 {
49   static gboolean initialized = FALSE;
50   struct timeval tv = { 0, };
51   time_t t;
52   gint error;
53
54   g_assert (initialized++ == FALSE);
55
56   tzset ();
57   error = gettimeofday (&tv, NULL);
58   if (error)
59     g_error ("gettimeofday() failed: %s", g_strerror (errno));
60   t = tv.tv_sec + tv.tv_usec / 1000000;
61
62   /* we need to find out the timezone offset relative to GMT here */
63 #if 0
64   { /* aparently FreeBSD/BSD4.3 doesn't have an extern long timezone; set by
65      * localtime(). if present, timezone contains # of seconds west of GMT.
66      */
67     localtime (&t);
68     gmt_diff = timezone;
69   }
70 #else
71   { /* however, struct tm { ... long tm_gmtoff; }; is hopefully available on
72      * all recent glibc versions and BSDs. tm_gmtoff contains # of seconds east of UTC.
73      */
74     struct tm tmdata;
75     localtime_r (&t, &tmdata);
76     gmt_diff = -tmdata.tm_gmtoff;
77   }
78 #endif
79
80   gmt_diff *= SFI_USEC_FACTOR;
81 }
82
83 /**
84  * @return              Current system time in micro seconds
85  *
86  * Get the current system time in micro seconds.
87  * Subsequent calls to this function do not necessarily
88  * return greater values. In fact, a second call may return
89  * a value smaller than the first call under certain system
90  * conditions. The time returned is UTC, refer to
91  * sfi_time_from_utc() in order to retrieve the local
92  * standard time.
93  * This function is MT-safe and may be called from any thread.
94  */
95 SfiTime
96 sfi_time_system (void)
97 {
98   struct timeval tv;
99   SfiTime ustime;
100
101   gettimeofday (&tv, NULL);
102   ustime = tv.tv_sec;
103   ustime = ustime * SFI_USEC_FACTOR + tv.tv_usec;
104
105   return ustime;
106 }
107
108 /**
109  * @param ustime        local standard time in micro seconds
110  * @return              UTC relative time in micro seconds
111  *
112  * Convert the local standard time @a ustime into
113  * Coordinated Universal Time (UTC).
114  * This function is MT-safe and may be called from any thread.
115  */
116 SfiTime
117 sfi_time_to_utc (SfiTime ustime)
118 {
119   return ustime + gmt_diff;
120 }
121
122 /**
123  * @param ustime        UTC relative time in micro seconds
124  * @return              local standard time in micro seconds
125  *
126  * Convert the Coordinated Universal Time (UTC)
127  * @a ustime into local standard time.
128  * This function is MT-safe and may be called from any thread.
129  */
130 SfiTime
131 sfi_time_from_utc (SfiTime ustime)
132 {
133   return ustime - gmt_diff;
134 }
135
136 /**
137  * @param ustime        time in micro seconds
138  * @return              newly allocated string
139  *
140  * Retrieve the time @a ustime in human readable form.
141  * The returned time string describes UTC time and
142  * thus contains no time zone or UTC offset information.
143  */
144 gchar*
145 sfi_time_to_string (SfiTime ustime)
146 {
147   time_t t = CLAMP (ustime, SFI_MIN_TIME, SFI_MAX_TIME) / SFI_USEC_FACTOR;
148   struct tm bt;
149   
150   bt = *gmtime (&t);    /* FIXME: not thread safe */
151   
152   return g_strdup_printf ("%04d-%02d-%02d %02d:%02d:%02d",
153                           bt.tm_year + 1900,
154                           bt.tm_mon + 1,
155                           bt.tm_mday,
156                           bt.tm_hour,
157                           bt.tm_min,
158                           bt.tm_sec);
159 }
160
161 /**
162  * @param ustime        time in micro seconds
163  * @param elements      string identifying time elements
164  * @return              newly allocated string
165  * Retrieve the time @a ustime in human readable form.
166  * Within the rnage of date and time formats parsable by
167  * sfi_time_from_string(), the nicest display is selected
168  * according to the current locale and other user settings.
169  * By means of the @a elements argument, various elemtns of
170  * a full date string can be selected:
171  * @li @c H - display hours
172  * @li @c M - display minutes
173  * @li @c S - display seconds
174  * @li @c d - display day
175  * @li @c m - display month
176  * @li @c y - display year
177  *
178  * The returned time string describes UTC time and
179  * thus contains no time zone or UTC offset information.
180  */
181 gchar*
182 sfi_time_to_nice_string (SfiTime      ustime,
183                          const gchar *elements)
184 {
185   time_t t = CLAMP (ustime, SFI_MIN_TIME, SFI_MAX_TIME) / SFI_USEC_FACTOR;
186   struct tm bt;
187   if (!elements)
188     elements = "";
189
190   bt = *gmtime (&t);    /* FIXME: not thread safe */
191
192   const bool wtime = strchr (elements, 'H') || strchr (elements, 'M') || strchr (elements, 'S');
193   const bool wdate = strchr (elements, 'd') || strchr (elements, 'm') || strchr (elements, 'y');
194
195   if (wdate && !wtime)
196     return g_strdup_printf ("%04d-%02d-%02d",
197                             bt.tm_year + 1900,
198                             bt.tm_mon + 1,
199                             bt.tm_mday);
200   if (!wdate && wtime)
201     return g_strdup_printf ("%02d:%02d:%02d",
202                             bt.tm_hour,
203                             bt.tm_min,
204                             bt.tm_sec);
205   else
206     return g_strdup_printf ("%02d:%02d:%02d %04d-%02d-%02d",
207                             bt.tm_hour,
208                             bt.tm_min,
209                             bt.tm_sec,
210                             bt.tm_year + 1900,
211                             bt.tm_mon + 1,
212                             bt.tm_mday);
213 }
214
215 /**
216  * @param time_string   string containing human readable date and time
217  * @return              parsed time in micro seconds or 0 on error
218  *
219  * Simple variant of sfi_time_from_string_err().
220  */
221 SfiTime
222 sfi_time_from_string (const gchar *time_string)
223 {
224   return sfi_time_from_string_err (time_string, NULL);
225 }
226
227 /**
228  * @param time_string   string containing human readable date and time
229  * @param error_p       location for newly allocated string containing conversion errors
230  * @return              parsed time in micro seconds, may be 0 on error
231  *
232  * Parse date and time from a string of characters and indicate possible errors.
233  * Several attempts are made to reconstruct a valid date and time despite possible
234  * errors. However, if all attempts fail, the returned time is 0. The time returned
235  * is UTC, refer to sfi_time_from_utc() in order to retrieve the local standard time.
236  */
237 SfiTime
238 sfi_time_from_string_err (const gchar *time_string,
239                           gchar      **error_p)
240 {
241   const guint n_formats = 15;
242   guint year[n_formats];
243   guint month[n_formats];
244   guint day[n_formats];
245   guint hour[n_formats];
246   guint minute[n_formats];
247   guint second[n_formats];
248   gboolean success[n_formats];
249   gboolean garbage[n_formats];
250   gboolean finished;
251   gchar *string;
252   SfiTime ustime;
253   SfiRing *ring, *warnings = NULL;
254   guint i;
255   
256   g_return_val_if_fail (time_string != NULL, 0);
257   
258   /* here, we support several date formats by making several attempts
259    * to match a string and pick the best one. if we acquire a full match
260    * before all match possibilities have been tryed, we skip outstanding
261    * match attempts. we do not use strptime(3), since it carries the locale(7)
262    * junk that doesn't do anything useful for the purpose of generic file
263    * parsing and it doesn't give the smallest clue whether the source
264    * string was (not) valid in any meaningfull sense.
265    * rules:
266    * - years need to be specified by 4 digits
267    * - date _and_ time need to be specified
268    * - seconds are optional
269    *
270    * the following formats are currently implemented:
271    * "yyyy-mm-dd hh:mm:ss"
272    * "yyyy-mm-dd hh:mm"
273    * "yyyy-mm-dd"
274    * "mm/dd/yyyy hh:mm:ss"
275    * "mm/dd/yyyy hh:mm"
276    * "mm/dd/yyyy"
277    * "dd.mm.yyyy hh:mm:ss"
278    * "dd.mm.yyyy hh:mm"
279    * "dd.mm.yyyy"
280    * "hh:mm:ss yyyy-mm-dd"
281    * "hh:mm yyyy-mm-dd"
282    * "hh:mm:ss mm/dd/yyyy"
283    * "hh:mm mm/dd/yyyy"
284    * "hh:mm:ss dd.mm.yyyy"
285    * "hh:mm dd.mm.yyyy"
286    *
287    * more on time formats (ISO 8601) can be found at:
288    *   http://www.cl.cam.ac.uk/~mgk25/iso-time.html
289    */
290   
291   string = g_strdup (time_string);
292   
293   for (i = 0; i < n_formats; i++)
294     {
295       year[i] = month[i] = day[i] = 0;
296       hour[i] = minute[i] = second[i] = 0;
297       success[i] = garbage[i] = FALSE;
298     }
299   
300   finished = FALSE;
301   i = 0;
302   
303 #define DATE_CHECK(index)       (year[(index)] >= 1990 &&       \
304                                  month[(index)] >= 1 &&         \
305                                  month[(index)] <= 12 &&        \
306                                  day[(index)] >= 1 &&           \
307                                  day[(index)] <= 31 &&          \
308                                  hour[(index)] >= 0 &&          \
309                                  hour[(index)] <= 23 &&         \
310                                  minute[(index)] >= 0 &&        \
311                                  minute[(index)] <= 59 &&       \
312                                  second[(index)] >= 0 &&        \
313                                  second[(index)] <= 61)
314   /* g_print ("DEBUG: \"%s\" -> y%u m%u d%u h%u m%u s%u\n", string,
315    *          year[i], month[i], day[i], hour[i], minute[i], second[i]);
316    */
317   if (!finished) /* parse "yyyy-mm-dd hh:mm:ss" e.g. "1998-04-16 23:59:59" */
318     {
319       gint n_values;
320       gchar end_char = 0;
321       
322       n_values = sscanf (string,
323                          "%u-%u-%u %u:%u:%u%c",
324                          &year[i], &month[i], &day[i],
325                          &hour[i], &minute[i], &second[i],
326                          &end_char);
327       success[i] = n_values >= 6;
328       garbage[i] = n_values > 6;
329       finished = success[i] && !garbage[i] && DATE_CHECK (i);
330       i++;
331     }
332   if (!finished) /* parse "yyyy-mm-dd hh:mm" e.g. "1998-04-16 23:59" */
333     {
334       gint n_values;
335       gchar end_char = 0;
336       
337       second[i] = 0;
338       n_values = sscanf (string,
339                          "%u-%u-%u %u:%u%c",
340                          &year[i], &month[i], &day[i],
341                          &hour[i], &minute[i],
342                          &end_char);
343       success[i] = n_values >= 5;
344       garbage[i] = n_values > 5;
345       finished = success[i] && !garbage[i] && DATE_CHECK (i);
346       i++;
347     }
348   if (!finished) /* parse "yyyy-mm-dd" e.g. "1998-04-16" */
349     {
350       gint n_values;
351       gchar end_char = 0;
352       
353       second[i] = 0;
354       n_values = sscanf (string,
355                          "%u-%u-%u%c",
356                          &year[i], &month[i], &day[i],
357                          &end_char);
358       success[i] = n_values >= 3;
359       garbage[i] = n_values > 3;
360       finished = success[i] && !garbage[i] && DATE_CHECK (i);
361       i++;
362     }
363   if (!finished) /* parse "mm/dd/yyyy hh:mm:ss" e.g. "04/16/1998 23:59:59" */
364     
365     {
366       gint n_values;
367       gchar end_char = 0;
368       
369       n_values = sscanf (string,
370                          "%u/%u/%u %u:%u:%u%c",
371                          &month[i], &day[i], &year[i],
372                          &hour[i], &minute[i], &second[i],
373                          &end_char);
374       success[i] = n_values >= 6;
375       garbage[i] = n_values > 6;
376       finished = success[i] && !garbage[i] && DATE_CHECK (i);
377       i++;
378     }
379   if (!finished) /* parse "mm/dd/yyyy hh:mm" e.g. "04/16/1998 23:59" */
380     {
381       gint n_values;
382       gchar end_char = 0;
383       
384       second[i] = 0;
385       n_values = sscanf (string,
386                          "%u/%u/%u %u:%u%c",
387                          &month[i], &day[i], &year[i],
388                          &hour[i], &minute[i],
389                          &end_char);
390       success[i] = n_values >= 5;
391       garbage[i] = n_values > 5;
392       finished = success[i] && !garbage[i] && DATE_CHECK (i);
393       i++;
394     }
395   if (!finished) /* parse "mm/dd/yyyy" e.g. "04/16/1998" */
396     {
397       gint n_values;
398       gchar end_char = 0;
399       
400       second[i] = 0;
401       n_values = sscanf (string,
402                          "%u/%u/%u%c",
403                          &month[i], &day[i], &year[i],
404                          &end_char);
405       success[i] = n_values >= 3;
406       garbage[i] = n_values > 3;
407       finished = success[i] && !garbage[i] && DATE_CHECK (i);
408       i++;
409     }
410   if (!finished) /* parse "dd.mm.yyyy hh:mm:ss" e.g. "16.4.1998 23:59:59" */
411     {
412       gint n_values;
413       gchar end_char = 0;
414       
415       n_values = sscanf (string,
416                          "%u.%u.%u %u:%u:%u%c",
417                          &day[i], &month[i], &year[i],
418                          &hour[i], &minute[i], &second[i],
419                          &end_char);
420       success[i] = n_values >= 6;
421       garbage[i] = n_values > 6;
422       finished = success[i] && !garbage[i] && DATE_CHECK (i);
423       i++;
424     }
425   if (!finished) /* parse "dd.mm.yyyy hh:mm" e.g. "16.4.1998 23:59" */
426     {
427       gint n_values;
428       gchar end_char = 0;
429       
430       second[i] = 0;
431       n_values = sscanf (string,
432                          "%u.%u.%u %u:%u%c",
433                          &day[i], &month[i], &year[i],
434                          &hour[i], &minute[i],
435                          &end_char);
436       success[i] = n_values >= 5;
437       garbage[i] = n_values > 5;
438       finished = success[i] && !garbage[i] && DATE_CHECK (i);
439       i++;
440     }
441   if (!finished) /* parse "dd.mm.yyyy" e.g. "16.4.1998" */
442     {
443       gint n_values;
444       gchar end_char = 0;
445       
446       second[i] = 0;
447       n_values = sscanf (string,
448                          "%u.%u.%u%c",
449                          &day[i], &month[i], &year[i],
450                          &end_char);
451       success[i] = n_values >= 3;
452       garbage[i] = n_values > 3;
453       finished = success[i] && !garbage[i] && DATE_CHECK (i);
454       i++;
455     }
456   if (!finished) /* parse "hh:mm:ss yyyy-mm-dd" e.g. "23:59:59 1998-04-16" */
457     {
458       gint n_values;
459       gchar end_char = 0;
460       
461       n_values = sscanf (string,
462                          "%u:%u:%u %u-%u-%u%c",
463                          &hour[i], &minute[i], &second[i],
464                          &year[i], &month[i], &day[i],
465                          &end_char);
466       success[i] = n_values >= 6;
467       garbage[i] = n_values > 6;
468       finished = success[i] && !garbage[i] && DATE_CHECK (i);
469       i++;
470     }
471   if (!finished) /* parse "hh:mm yyyy-mm-dd" e.g. "23:59 1998-04-16" */
472     {
473       gint n_values;
474       gchar end_char = 0;
475       
476       second[i] = 0;
477       n_values = sscanf (string,
478                          "%u:%u %u-%u-%u%c",
479                          &hour[i], &minute[i],
480                          &year[i], &month[i], &day[i],
481                          &end_char);
482       success[i] = n_values >= 5;
483       garbage[i] = n_values > 5;
484       finished = success[i] && !garbage[i] && DATE_CHECK (i);
485       i++;
486     }
487   if (!finished) /* parse "hh:mm:ss mm/dd/yyyy" e.g. "23:59:59 04/16/1998" */
488     {
489       gint n_values;
490       gchar end_char = 0;
491       
492       n_values = sscanf (string,
493                          "%u:%u:%u %u/%u/%u%c",
494                          &hour[i], &minute[i], &second[i],
495                          &month[i], &day[i], &year[i],
496                          &end_char);
497       success[i] = n_values >= 6;
498       garbage[i] = n_values > 6;
499       finished = success[i] && !garbage[i] && DATE_CHECK (i);
500       i++;
501     }
502   if (!finished) /* parse "hh:mm mm/dd/yyyy" e.g. "23:59 04/16/1998" */
503     {
504       gint n_values;
505       gchar end_char = 0;
506       
507       second[i] = 0;
508       n_values = sscanf (string,
509                          "%u:%u %u/%u/%u%c",
510                          &hour[i], &minute[i],
511                          &month[i], &day[i], &year[i],
512                          &end_char);
513       success[i] = n_values >= 5;
514       garbage[i] = n_values > 5;
515       finished = success[i] && !garbage[i] && DATE_CHECK (i);
516       i++;
517     }
518   if (!finished) /* parse "hh:mm:ss dd.mm.yyyy" e.g. "23:59:59 16.4.1998" */
519     {
520       gint n_values;
521       gchar end_char = 0;
522       
523       n_values = sscanf (string,
524                          "%u:%u:%u %u.%u.%u%c",
525                          &hour[i], &minute[i], &second[i],
526                          &day[i], &month[i], &year[i],
527                          &end_char);
528       success[i] = n_values >= 6;
529       garbage[i] = n_values > 6;
530       finished = success[i] && !garbage[i] && DATE_CHECK (i);
531       i++;
532     }
533   if (!finished) /* parse "hh:mm dd.mm.yyyy" e.g. "23:59:59 16.4.1998" */
534     {
535       gint n_values;
536       gchar end_char = 0;
537       
538       second[i] = 0;
539       n_values = sscanf (string,
540                          "%u:%u %u.%u.%u%c",
541                          &hour[i], &minute[i],
542                          &day[i], &month[i], &year[i],
543                          &end_char);
544       success[i] = n_values >= 5;
545       garbage[i] = n_values > 5;
546       finished = success[i] && !garbage[i] && DATE_CHECK (i);
547       i++;
548     }
549 #undef  DATE_CHECK
550   
551   /* try to find out the best/first match if any */
552   if (finished)
553     i--;
554   else
555     {
556       for (i = 0; i < n_formats - 1; i++)
557         if (success[i])
558           break;
559     }
560   
561   if (!success[i])
562     {
563       warnings = sfi_ring_append (warnings, g_strdup ("invalid date specification"));
564       ustime = 0;
565     }
566   else
567     {
568       struct tm tm_data = { 0 };
569       time_t ttime;
570
571       if (garbage[i])
572         warnings = sfi_ring_append (warnings, g_strdup ("junk characters at end of date"));
573       if (year[i] < 1990)
574         {
575           warnings = sfi_ring_append (warnings, g_strdup_printf ("%s out of bounds", "year"));
576           year[i] = 1990;
577         }
578       if (month[i] < 1 || month[i] > 12)
579         {
580           warnings = sfi_ring_append (warnings, g_strdup_printf ("%s out of bounds", "month"));
581           month[i] = CLAMP (month[i], 1, 12);
582         }
583       if (day[i] < 1 || day[i] > 31)
584         {
585           warnings = sfi_ring_append (warnings, g_strdup_printf ("%s out of bounds", "day"));
586           month[i] = CLAMP (day[i], 1, 31);
587         }
588       if (hour[i] < 0 || hour[i] > 23)
589         {
590           warnings = sfi_ring_append (warnings, g_strdup_printf ("%s out of bounds", "hour"));
591           hour[i] = CLAMP (hour[i], 0, 23);
592         }
593       if (minute[i] < 0 || minute[i] > 59)
594         {
595           warnings = sfi_ring_append (warnings, g_strdup_printf ("%s out of bounds", "minute"));
596           minute[i] = CLAMP (minute[i], 0, 59);
597         }
598       if (second[i] < 0 || second[i] > 61)
599         {
600           warnings = sfi_ring_append (warnings, g_strdup_printf ("%s out of bounds", "second"));
601           second[i] = CLAMP (second[i], 0, 61);
602         }
603       
604       tm_data.tm_sec = second[i];
605       tm_data.tm_min = minute[i];
606       tm_data.tm_hour = hour[i];
607       tm_data.tm_mday = day[i];
608       tm_data.tm_mon = month[i] - 1;
609       tm_data.tm_year = year[i] - 1900;
610       tm_data.tm_wday = 0;
611       tm_data.tm_yday = 0;
612       tm_data.tm_isdst = 1;
613       
614 #if HAVE_TIMEGM
615       ttime = timegm (&tm_data);                        /* returns -1 on error */
616 #else
617       {
618         char *tz = g_strdup (g_getenv ("TZ"));
619         g_setenv ("TZ", "", 1);
620         tzset();
621         ttime = mktime (&tm_data);
622         if (tz)
623           g_setenv ("TZ", tz, 1);
624         else
625           g_unsetenv ("TZ");
626         tzset();
627         g_free (tz);
628       }
629 #endif
630       
631       ustime = ttime;
632       ustime *= SFI_USEC_FACTOR;
633       ustime = MAX (ustime, 0);
634       
635       /* g_print ("mktime(): year(%u) month(%u) day(%u) hour(%u) minute(%u) second(%u)\n",
636        *           year[i], month[i], day[i], hour[i], minute[i], second[i]);
637        */
638       
639       if (ustime < SFI_MIN_TIME)        /* limit ustime to 1.1.1990 */
640         {
641           warnings = sfi_ring_append (warnings, g_strdup_printf ("invalid date specification (%lld < %lld, gmt-diff=%lld)",
642                                                                  ustime, SFI_MIN_TIME, gmt_diff));
643           ustime = SFI_MIN_TIME;
644         }
645     }
646
647   /* general cleanup and error return */
648   g_free (string);
649   if (error_p && warnings)
650     {
651       GString *gstring = g_string_new (NULL);
652       for (ring = warnings; ring; ring = sfi_ring_walk (ring, warnings))
653         {
654           if (gstring->len)
655             g_string_append (gstring, ", ");
656           g_string_append (gstring, (char*) ring->data);
657         }
658       g_string_aprintf (gstring, " in date: \"%s\"", time_string);
659       *error_p = g_string_free (gstring, FALSE);
660     }
661   else if (error_p)
662     *error_p = NULL;
663   for (ring = warnings; ring; ring = sfi_ring_walk (ring, warnings))
664     g_free (ring->data);
665   sfi_ring_free (warnings);
666   return ustime;
667 }