Simplify gestalt checking code
[projects/chimara/chimara.git] / libchimara / datetime.c
1 #include <time.h>
2 #include <sys/time.h>
3 #include <strings.h> /* for bzero() */
4 #include <glib.h>
5 #include "glk.h"
6
7 /* FIXME: GDateTime was introduced in GLib 2.26, which is not standard on all
8  platforms yet. Therefore, we adapt Andrew Plotkin's implementation for now, and
9  will replace it with the (presumably) more portable GLib facilities later. */
10
11 /* Copy a POSIX tm structure to a glkdate. */
12 static void
13 gli_date_from_tm(glkdate_t *date, struct tm *tm)
14 {
15     date->year = 1900 + tm->tm_year;
16     date->month = 1 + tm->tm_mon;
17     date->day = tm->tm_mday;
18     date->weekday = tm->tm_wday;
19     date->hour = tm->tm_hour;
20     date->minute = tm->tm_min;
21     date->second = tm->tm_sec;
22 }
23
24 /* Copy a glkdate to a POSIX tm structure.
25  This is used in the "glk_date_to_..." functions, which are supposed
26  to normalize the glkdate. We're going to rely on the mktime() /
27  timegm() functions to do that -- except they don't handle microseconds.
28  So we'll have to do that normalization here, adjust the tm_sec value,
29  and return the normalized number of microseconds.
30  */
31 static glsi32
32 gli_date_to_tm(glkdate_t *date, struct tm *tm)
33 {
34     glsi32 microsec;
35
36     bzero(tm, sizeof(tm));
37     tm->tm_year = date->year - 1900;
38     tm->tm_mon = date->month - 1;
39     tm->tm_mday = date->day;
40     tm->tm_wday = date->weekday;
41     tm->tm_hour = date->hour;
42     tm->tm_min = date->minute;
43     tm->tm_sec = date->second;
44     microsec = date->microsec;
45
46     if (microsec >= G_USEC_PER_SEC) {
47         tm->tm_sec += (microsec / G_USEC_PER_SEC);
48         microsec = microsec % G_USEC_PER_SEC;
49     }
50     else if (microsec < 0) {
51         microsec = -1 - microsec;
52         tm->tm_sec -= (1 + microsec / G_USEC_PER_SEC);
53         microsec = (G_USEC_PER_SEC - 1) - (microsec % G_USEC_PER_SEC);
54     }
55
56     return microsec;
57 }
58
59 /* Convert a GTimeVal, along with a microseconds value, to a glktimeval. */
60 static void
61 gli_timestamp_to_time(long sec, long microsec, glktimeval_t *time)
62 {
63     if (sizeof(sec) <= 4) {
64         /* This platform has 32-bit time, but we can't do anything
65                  about that. Hope it's not 2038 yet. */
66         if (sec >= 0)
67             time->high_sec = 0;
68         else
69             time->high_sec = -1;
70         time->low_sec = sec;
71     }
72     else {
73         /* The cast to gint64 shouldn't be necessary, but it
74                  suppresses a pointless warning in the 32-bit case.
75                  (Remember that we won't be executing this line in the
76                  32-bit case.) */
77         time->high_sec = (((gint64)sec) >> 32) & 0xFFFFFFFF;
78         time->low_sec = sec & 0xFFFFFFFF;
79     }
80
81     time->microsec = microsec;
82 }
83
84 /* Divide a Unix timestamp by a (positive) value. */
85 static glsi32
86 gli_simplify_time(long timestamp, glui32 factor)
87 {
88     /* We want to round towards negative infinity, which takes a little
89          bit of fussing. */
90     if (timestamp >= 0) {
91         return timestamp / (time_t)factor;
92     }
93     else {
94         return -1 - (((long)-1 - timestamp) / (long)factor);
95     }
96 }
97
98 /**
99  * glk_current_time:
100  * @time: pointer to a #glktimeval_t structure.
101  *
102  * The current Unix time is stored in the structure @time. (The argument may not
103  * be %NULL.) This is the number of seconds since the beginning of 1970 (UTC).
104  *
105  * The first two values in the structure should be considered a single
106  * <emphasis>signed</emphasis> 64-bit number. This allows the #glktimeval_t to
107  * store a reasonable range of values in the future and past. The @high_sec
108  * value will remain zero until sometime in 2106. If your computer is running in
109  * 1969, perhaps due to an unexpected solar flare, then @high_sec will be
110  * negative.
111  * 
112  * The third value in the structure represents a fraction of a second, in
113  * microseconds (from 0 to 999999). The resolution of the glk_current_time()
114  * call is platform-dependent; the @microsec value may not be updated
115  * continuously.
116  */
117 void 
118 glk_current_time(glktimeval_t *time)
119 {
120         g_return_if_fail(time != NULL);
121
122         GTimeVal tv;
123         g_get_current_time(&tv);
124         gli_timestamp_to_time(tv.tv_sec, tv.tv_usec, time);
125 }
126
127 /**
128  * glk_current_simple_time:
129  * @factor: Factor by which to divide the time value.
130  *
131  * If dealing with 64-bit values is awkward, you can also get the current time
132  * as a lower-resolution 32-bit value. This is simply the Unix time divided by
133  * the @factor argument (which must not be zero). For example, if factor is 60,
134  * the result will be the number of minutes since 1970 (rounded towards negative
135  * infinity). If factor is 1, you will get the Unix time directly, but the value
136  * will be truncated starting some time in 2038.
137  *
138  * Returns: Unix time divided by @factor, truncated to 32 bits.
139  */
140 glsi32
141 glk_current_simple_time(glui32 factor)
142 {
143         g_return_val_if_fail(factor != 0, 0);
144         
145         GTimeVal tv;
146         g_get_current_time(&tv);
147     return gli_simplify_time(tv.tv_sec, factor);
148 }
149
150 /**
151  * glk_time_to_date_utc:
152  * @time: A #glktimeval_t structure as returned by glk_current_time().
153  * @date: An empty #glkdate_t structure to fill in.
154  *
155  * Convert the given timestamp (as returned by glk_current_time()) to a
156  * broken-out structure. This function returns a date and time in universal time
157  * (GMT).
158  *
159  * <note><para>
160  *   The seconds value may be 60 because of a leap second.
161  * </para></note>
162  */
163 void
164 glk_time_to_date_utc(glktimeval_t *time, glkdate_t *date)
165 {
166         g_return_if_fail(time != NULL);
167         g_return_if_fail(date != NULL);
168
169         time_t timestamp;
170     struct tm tm;
171
172     timestamp = time->low_sec;
173     if (sizeof(timestamp) > 4) {
174         timestamp += ((gint64)time->high_sec << 32);
175     }
176
177     gmtime_r(&timestamp, &tm);
178
179     gli_date_from_tm(date, &tm);
180     date->microsec = time->microsec;
181 }
182
183 /**
184  * glk_time_to_date_local:
185  * @time: A #glktimeval_t structure as returned by glk_current_time().
186  * @date: An empty #glkdate_t structure to fill in.
187  *
188  * Does the same thing as glk_time_to_date_utc(), but this function returns
189  * local time.
190  */
191 void
192 glk_time_to_date_local(glktimeval_t *time, glkdate_t *date)
193 {
194         g_return_if_fail(time != NULL);
195         g_return_if_fail(date != NULL);
196
197         time_t timestamp;
198     struct tm tm;
199
200     timestamp = time->low_sec;
201     if (sizeof(timestamp) > 4) {
202         timestamp += ((int64_t)time->high_sec << 32);
203     }
204
205     localtime_r(&timestamp, &tm);
206
207     gli_date_from_tm(date, &tm);
208     date->microsec = time->microsec;
209 }
210
211 /**
212  * glk_simple_time_to_date_utc:
213  * @time: Timestamp as returned by glk_current_simple_time().
214  * @factor: Factor by which to multiply @time in order to get seconds.
215  * @date: An empty #glkdate_t structure to fill in.
216  *
217  * Convert the given timestamp (as returned by glk_current_simple_time()) to a
218  * broken-out structure in universal time. The @time argument is multiplied by
219  * @factor to produce a Unix timestamp.
220  *
221  * Since the resolution of glk_simple_time_to_date_utc() and
222  * glk_simple_time_to_date_local() is no better than seconds, they will return
223  * zero for the microseconds value. 
224  */ 
225 void
226 glk_simple_time_to_date_utc(glsi32 time, glui32 factor, glkdate_t *date)
227 {
228         g_return_if_fail(factor != 0);
229         g_return_if_fail(date != NULL);
230
231         time_t timestamp = (time_t)time * factor;
232     struct tm tm;
233
234     gmtime_r(&timestamp, &tm);
235
236     gli_date_from_tm(date, &tm);
237     date->microsec = 0;
238 }
239
240 /**
241  * glk_simple_time_to_date_local:
242  * @time: Timestamp as returned by glk_current_simple_time().
243  * @factor: Factor by which to multiply @time in order to get seconds.
244  * @date: An empty #glkdate_t structure to fill in.
245  *
246  * Does the same thing as glk_simple_time_to_date_utc(), but fills in the @date
247  * structure in local time.
248  */
249 void
250 glk_simple_time_to_date_local(glsi32 time, glui32 factor, glkdate_t *date)
251 {
252         g_return_if_fail(factor != 0);
253         g_return_if_fail(date != NULL);
254
255         time_t timestamp = (time_t)time * factor;
256     struct tm tm;
257
258     localtime_r(&timestamp, &tm);
259
260     gli_date_from_tm(date, &tm);
261     date->microsec = 0;
262 }
263
264 /**
265  * glk_date_to_time_utc:
266  * @date: A date in the form of a #glkdate_t structure.
267  * @time: An empty #glktimeval_t structure to fill in.
268  *
269  * Convert the broken-out structure (interpreted as universal time) to a
270  * timestamp. The weekday value in @date is ignored. The other values need not
271  * be in their normal ranges; they will be normalized.
272  *
273  * If the time cannot be represented by the platform's time library, this may
274  * return -1 for the seconds value. (I.e., the @high_sec and @low_sec fields
275  * both $FFFFFFFF. The microseconds field is undefined in this case.)
276  */
277 void
278 glk_date_to_time_utc(glkdate_t *date, glktimeval_t *time)
279 {
280         g_return_if_fail(date != NULL);
281         g_return_if_fail(time != NULL);
282
283         time_t timestamp;
284     struct tm tm;
285     glsi32 microsec;
286
287     microsec = gli_date_to_tm(date, &tm);
288     /* The timegm function is not standard POSIX. If it's not available
289          on your platform, try setting the env var "TZ" to "", calling
290          mktime(), and then resetting "TZ". */
291     tm.tm_isdst = 0;
292     timestamp = timegm(&tm);
293
294     gli_timestamp_to_time(timestamp, microsec, time);
295 }
296
297 /**
298  * glk_date_to_time_local:
299  * @date: A date in the form of a #glkdate_t structure.
300  * @time: An empty #glktimeval_t structure to fill in.
301  *
302  * Does the same thing as glk_date_to_time_utc(), but interprets the broken-out
303  * structure as local time.
304  *
305  * The glk_date_to_time_local() function may not be smart about Daylight Saving
306  * Time conversions.
307  * <note><para>
308  *   If implemented with the mktime() libc function, it should use the negative
309  *   @tm_isdst flag to <quote>attempt to divine whether summer time is in
310  *   effect</quote>.
311  * </para></note>
312  */
313 void
314 glk_date_to_time_local(glkdate_t *date, glktimeval_t *time)
315 {
316         g_return_if_fail(date != NULL);
317         g_return_if_fail(time != NULL);
318
319         time_t timestamp;
320     struct tm tm;
321     glsi32 microsec;
322
323     microsec = gli_date_to_tm(date, &tm);
324     tm.tm_isdst = -1;
325     timestamp = mktime(&tm);
326
327     gli_timestamp_to_time(timestamp, microsec, time);
328 }
329
330 /**
331  * glk_date_to_simple_time_utc:
332  * @date: A date in the form of a #glkdate_t structure.
333  * @factor: Factor by which to divide the time value.
334  *
335  * Convert the broken-out structure (interpreted as universal time) to a
336  * timestamp divided by @factor. The weekday value in @date is ignored. The
337  * other values need not be in their normal ranges; they will be normalized.
338  *
339  * If the time cannot be represented by the platform's time library, this may
340  * return -1.
341  *
342  * Returns: a timestamp divided by @factor, and truncated to 32 bits, or -1 on
343  * error.
344  */
345 glsi32
346 glk_date_to_simple_time_utc(glkdate_t *date, glui32 factor)
347 {
348         g_return_val_if_fail(date != NULL, 0);
349         g_return_val_if_fail(factor != 0, 0);
350
351         time_t timestamp;
352     struct tm tm;
353
354     gli_date_to_tm(date, &tm);
355     /* The timegm function is not standard POSIX. If it's not available
356          on your platform, try setting the env var "TZ" to "", calling
357          mktime(), and then resetting "TZ". */
358     tm.tm_isdst = 0;
359     timestamp = timegm(&tm);
360
361     return gli_simplify_time(timestamp, factor);
362 }
363
364 /**
365  * glk_date_to_simple_time_local:
366  * @date: A date in the form of a #glkdate_t structure.
367  * @factor: Factor by which to divide the time value.
368  *
369  * Does the same thing as glk_date_to_simple_time_utc(), but interprets the
370  * broken-out structure as local time.
371  *
372  * Returns: a timestamp divided by @factor, and truncated to 32 bits, or -1 on
373  * error.
374  */
375 glsi32
376 glk_date_to_simple_time_local(glkdate_t *date, glui32 factor)
377 {
378         g_return_val_if_fail(date != NULL, 0);
379         g_return_val_if_fail(factor != 0, 0);
380
381         time_t timestamp;
382     struct tm tm;
383
384     gli_date_to_tm(date, &tm);
385     tm.tm_isdst = -1;
386     timestamp = mktime(&tm);
387
388     return gli_simplify_time(timestamp, factor);
389 }