gstdatetime: Adds GstDateTime
[platform/upstream/gstreamer.git] / gst / gstdatetime.c
1 /* GStreamer
2  * Copyright (C) 2010 Thiago Santos <thiago.sousa.santos@collabora.co.uk>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 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  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif
23
24 #include "gst_private.h"
25 #include "gstdatetime.h"
26
27 /**
28  * SECTION:gst-date-time
29  * @title: GstDateTime
30  * @short_description: A date, time and timezone structure
31  *
32  * Struct to store date, time and timezone information altogether.
33  * #GstDateTime is refcounted and immutable.
34  *
35  * Date information is handled using the proleptic Gregorian calendar.
36  *
37  * Provides basic creation functions and accessor functions to its fields.
38  *
39  * Since: 0.10.31
40  */
41
42 #define GST_DATE_TIME_SEC_PER_DAY          (G_GINT64_CONSTANT (86400))
43 #define GST_DATE_TIME_USEC_PER_DAY         (G_GINT64_CONSTANT (86400000000))
44 #define GST_DATE_TIME_USEC_PER_HOUR        (G_GINT64_CONSTANT (3600000000))
45 #define GST_DATE_TIME_USEC_PER_MINUTE      (G_GINT64_CONSTANT (60000000))
46 #define GST_DATE_TIME_USEC_PER_SECOND      (G_GINT64_CONSTANT (1000000))
47 #define GST_DATE_TIME_USEC_PER_MILLISECOND (G_GINT64_CONSTANT (1000))
48
49 #define MAX_SUPPORTED_YEAR 9999
50 #define GREGORIAN_LEAP(y)  (((y%4)==0)&&(!(((y%100)==0)&&((y%400)!=0))))
51
52 static const guint16 days_in_months[2][13] = {
53   {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
54   {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
55 };
56
57 struct _GstDateTime
58 {
59   /*
60    * As we don't have a math API, we can have fields split here.
61    * (There is still some math done internally, but nothing really relevant).
62    *
63    * If we ever add one, we should go for a days since some epoch counter.
64    * (Proleptic Gregorian with 0001-01-01 as day 1)
65    */
66   gint16 year;
67   gint8 month;
68   gint8 day;
69   guint64 usec;                 /* Microsecond timekeeping within Day */
70
71   gint tzoffset;
72
73   volatile gint ref_count;
74 };
75
76 /*
77  * Returns the utc offset in seconds for this time structure
78  */
79 static gint
80 gmt_offset (struct tm *tm, time_t t)
81 {
82 #if defined (HAVE_TM_GMTOFF)
83   return tm->tm_gmtoff;
84 #else
85   struct tm g;
86   time_t t2;
87 #ifdef HAVE_GMTIME_R
88   gmtime_r (&t, &g);
89 #else
90   g = *gmtime (&t);
91 #endif
92   t2 = mktime (&g);
93   return (int) difftime (t, t2);
94 #endif
95 }
96
97 static void
98 gst_date_time_set_local_timezone (GstDateTime * dt)
99 {
100   struct tm tt;
101   time_t t;
102
103   g_return_if_fail (dt != NULL);
104
105   memset (&tt, 0, sizeof (tt));
106
107   tt.tm_mday = gst_date_time_get_day (dt);
108   tt.tm_mon = gst_date_time_get_month (dt) - 1;
109   tt.tm_year = gst_date_time_get_year (dt) - 1900;
110   tt.tm_hour = gst_date_time_get_hour (dt);
111   tt.tm_min = gst_date_time_get_minute (dt);
112   tt.tm_sec = gst_date_time_get_second (dt);
113
114   t = mktime (&tt);
115
116   dt->tzoffset = gmt_offset (&tt, t) / 60;
117 }
118
119 static GstDateTime *
120 gst_date_time_alloc (void)
121 {
122   GstDateTime *datetime;
123
124   datetime = g_slice_new0 (GstDateTime);
125   datetime->ref_count = 1;
126
127   return datetime;
128 }
129
130 static void
131 gst_date_time_free (GstDateTime * datetime)
132 {
133   g_slice_free (GstDateTime, datetime);
134 }
135
136 static GstDateTime *
137 gst_date_time_new_from_date (gint year, gint month, gint day)
138 {
139   GstDateTime *dt;
140
141   g_return_val_if_fail (year > 0 && year <= 9999, NULL);
142   g_return_val_if_fail ((month > 0 && month <= 12), NULL);
143   g_return_val_if_fail ((day > 0 && day <= 31), NULL);
144
145   dt = gst_date_time_alloc ();
146
147   dt->year = year;
148   dt->month = month;
149   dt->day = day;
150   gst_date_time_set_local_timezone (dt);
151
152   return dt;
153 }
154
155 /**
156  * gst_date_time_get_year:
157  * @datetime: a #GstDateTime
158  *
159  * Returns the year of this #GstDateTime
160  *
161  * Return value: The year of this #GstDateTime
162  * Since: 0.10.31
163  */
164 gint
165 gst_date_time_get_year (const GstDateTime * datetime)
166 {
167   g_return_val_if_fail (datetime != NULL, 0);
168
169   return datetime->year;
170 }
171
172 /**
173  * gst_date_time_get_month:
174  * @datetime: a #GstDateTime
175  *
176  * Returns the month of this #GstDateTime. January is 1, February is 2, etc..
177  *
178  * Return value: The month of this #GstDateTime
179  * Since: 0.10.31
180  */
181 gint
182 gst_date_time_get_month (const GstDateTime * datetime)
183 {
184   g_return_val_if_fail (datetime != NULL, 0);
185
186   return datetime->month;
187 }
188
189 /**
190  * gst_date_time_get_day:
191  * @datetime: a #GstDateTime
192  *
193  * Returns the day of this #GstDateTime.
194  *
195  * Return value: The day of this #GstDateTime
196  * Since: 0.10.31
197  */
198 gint
199 gst_date_time_get_day (const GstDateTime * datetime)
200 {
201   g_return_val_if_fail (datetime != NULL, 0);
202
203   return datetime->day;
204 }
205
206 /**
207  * gst_date_time_get_hour:
208  * @datetime: a #GstDateTime
209  *
210  * Retrieves the hour of the day represented by @datetime in the gregorian
211  * calendar. The return is in the range of 0 to 23.
212  *
213  * Return value: the hour of the day
214  *
215  * Since: 0.10.31
216  */
217 gint
218 gst_date_time_get_hour (const GstDateTime * datetime)
219 {
220   g_return_val_if_fail (datetime != NULL, 0);
221   return (datetime->usec / GST_DATE_TIME_USEC_PER_HOUR);
222 }
223
224 /**
225  * gst_date_time_get_microsecond:
226  * @datetime: a #GstDateTime
227  *
228  * Retrieves the fractional part of the seconds in microseconds represented by
229  * @datetime in the gregorian calendar.
230  *
231  * Return value: the microsecond of the second
232  *
233  * Since: 0.10.31
234  */
235 gint
236 gst_date_time_get_microsecond (const GstDateTime * datetime)
237 {
238   g_return_val_if_fail (datetime != NULL, 0);
239   return (datetime->usec % GST_DATE_TIME_USEC_PER_SECOND);
240 }
241
242 /**
243  * gst_date_time_get_minute:
244  * @datetime: a #GstDateTime
245  *
246  * Retrieves the minute of the hour represented by @datetime in the gregorian
247  * calendar.
248  *
249  * Return value: the minute of the hour
250  *
251  * Since: 0.10.31
252  */
253 gint
254 gst_date_time_get_minute (const GstDateTime * datetime)
255 {
256   g_return_val_if_fail (datetime != NULL, 0);
257   return (datetime->usec % GST_DATE_TIME_USEC_PER_HOUR) /
258       GST_DATE_TIME_USEC_PER_MINUTE;
259 }
260
261 /**
262  * gst_date_time_get_second:
263  * @datetime: a #GstDateTime
264  *
265  * Retrieves the second of the minute represented by @datetime in the gregorian
266  * calendar.
267  *
268  * Return value: the second represented by @datetime
269  *
270  * Since: 0.10.31
271  */
272 gint
273 gst_date_time_get_second (const GstDateTime * datetime)
274 {
275   g_return_val_if_fail (datetime != NULL, 0);
276   return (datetime->usec % GST_DATE_TIME_USEC_PER_MINUTE) /
277       GST_DATE_TIME_USEC_PER_SECOND;
278 }
279
280 /**
281  * gst_date_time_get_time_zone_offset:
282  * @datetime: a #GstDateTime
283  *
284  * Retrieves the offset from UTC in hours that the timezone specified
285  * by @datetime represents. Timezones ahead (to the east) of UTC have positive
286  * values, timezones before (to the west) of UTC have negative values.
287  * If @datetime represents UTC time, then the offset is zero.
288  *
289  * Return value: the offset from UTC in hours
290  * Since: 0.10.31
291  */
292 gfloat
293 gst_date_time_get_time_zone_offset (const GstDateTime * datetime)
294 {
295   g_return_val_if_fail (datetime != NULL, 0);
296
297   return datetime->tzoffset / 60.0f;
298 }
299
300 /**
301  * gst_date_time_new_from_unix_epoch:
302  * @t: seconds from the Unix epoch
303  *
304  * Creates a new #GstDateTime using the time since Jan 1, 1970 specified by @t.
305  * The #GstDateTime is in the local timezone.
306  *
307  * Return value: the newly created #GstDateTime
308  *
309  * Since: 0.10.31
310  */
311 GstDateTime *
312 gst_date_time_new_from_unix_epoch (gint64 t)
313 {
314   GstDateTime *dt;
315   struct tm tm;
316   time_t tt;
317
318   memset (&tm, 0, sizeof (tm));
319   tt = (time_t) t;
320 #ifdef HAVE_LOCALTIME_R
321   localtime_r (&tt, &tm);
322 #else
323   localtime (&tt, &tm);
324 #endif
325
326   dt = gst_date_time_new (tm.tm_year + 1900,
327       tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, 0, 0);
328   gst_date_time_set_local_timezone (dt);
329   return dt;
330 }
331
332 /**
333  * gst_date_time_new_local_time:
334  * @year: the gregorian year
335  * @month: the gregorian month
336  * @day: the day of the gregorian month
337  * @hour: the hour of the day
338  * @minute: the minute of the hour
339  * @second: the second of the minute
340  * @microsecond: the microsecond of the second
341  *
342  * Creates a new #GstDateTime using the date and times in the gregorian calendar
343  * in the local timezone.
344  *
345  * @year should be from 1 to 9999, @month should be from 1 to 12, @day from
346  * 1 to 31, @hour from 0 to 23, @minutes and @seconds from 0 to 59 and
347  * @microsecond from 0 to 999999.
348  *
349  * Return value: the newly created #GstDateTime
350  *
351  * Since: 0.10.31
352  */
353 GstDateTime *
354 gst_date_time_new_local_time (gint year, gint month, gint day, gint hour,
355     gint minute, gint second, gint microsecond)
356 {
357   GstDateTime *dt;
358
359   dt = gst_date_time_new (year, month, day, hour, minute, second, microsecond,
360       0);
361
362   gst_date_time_set_local_timezone (dt);
363
364   return dt;
365 }
366
367 /**
368  * gst_date_time_new:
369  * @year: the gregorian year
370  * @month: the gregorian month
371  * @day: the day of the gregorian month
372  * @hour: the hour of the day
373  * @minute: the minute of the hour
374  * @second: the second of the minute
375  * @microsecond: the microsecond of the second
376  * @tzoffset: Offset from UTC in hours.
377  *
378  * Creates a new #GstDateTime using the date and times in the gregorian calendar
379  * in the supplied timezone.
380  *
381  * @year should be from 1 to 9999, @month should be from 1 to 12, @day from
382  * 1 to 31, @hour from 0 to 23, @minutes and @seconds from 0 to 59 and
383  * @microsecond from 0 to 999999.
384  *
385  * Note that @tzoffset is a float and was chosen so for being able to handle
386  * some fractional timezones, while it still keeps the readability of
387  * represeting it in hours for most timezones.
388  *
389  * Return value: the newly created #GstDateTime
390  *
391  * Since: 0.10.31
392  */
393 GstDateTime *
394 gst_date_time_new (gint year, gint month, gint day, gint hour,
395     gint minute, gint second, gint microsecond, gfloat tzoffset)
396 {
397   GstDateTime *dt;
398
399   g_return_val_if_fail (hour >= 0 && hour < 24, NULL);
400   g_return_val_if_fail (minute >= 0 && minute < 60, NULL);
401   g_return_val_if_fail (second >= 0 && second < 60, NULL);
402   g_return_val_if_fail (microsecond >= 0 && microsecond < 1000000, NULL);
403
404   if (!(dt = gst_date_time_new_from_date (year, month, day)))
405     return NULL;
406
407   dt->usec = (hour * GST_DATE_TIME_USEC_PER_HOUR)
408       + (minute * GST_DATE_TIME_USEC_PER_MINUTE)
409       + (second * GST_DATE_TIME_USEC_PER_SECOND)
410       + microsecond;
411   dt->tzoffset = (gint) (60 * tzoffset);
412
413   return dt;
414 }
415
416 /**
417  * gst_date_time_new_now_local_time:
418  *
419  * Creates a new #GstDateTime representing the current date and time.
420  *
421  * Return value: the newly created #GstDateTime which should be freed with
422  *   gst_date_time_unref().
423  *
424  * Since: 0.10.31
425  */
426 GstDateTime *
427 gst_date_time_new_now_local_time (void)
428 {
429   GstDateTime *datetime;
430   GTimeVal tv;
431   g_get_current_time (&tv);
432
433   datetime = gst_date_time_new_from_unix_epoch (tv.tv_sec);
434   datetime->usec += tv.tv_usec;
435   gst_date_time_set_local_timezone (datetime);
436   return datetime;
437 }
438
439 /**
440  * gst_date_time_ref:
441  * @datetime: a #GstDateTime
442  *
443  * Atomically increments the reference count of @datetime by one.
444  *
445  * Return value: the reference @datetime
446  *
447  * Since: 0.10.31
448  */
449 GstDateTime *
450 gst_date_time_ref (GstDateTime * datetime)
451 {
452   g_return_val_if_fail (datetime != NULL, NULL);
453   g_return_val_if_fail (datetime->ref_count > 0, NULL);
454   g_atomic_int_inc (&datetime->ref_count);
455   return datetime;
456 }
457
458 /**
459  * gst_date_time_unref:
460  * @datetime: a #GstDateTime
461  *
462  * Atomically decrements the reference count of @datetime by one.  When the
463  * reference count reaches zero, the structure is freed.
464  *
465  * Since: 0.10.31
466  */
467 void
468 gst_date_time_unref (GstDateTime * datetime)
469 {
470   g_return_if_fail (datetime != NULL);
471   g_return_if_fail (datetime->ref_count > 0);
472
473   if (g_atomic_int_dec_and_test (&datetime->ref_count))
474     gst_date_time_free (datetime);
475 }
476
477 static GstDateTime *
478 gst_date_time_copy (const GstDateTime * dt)
479 {
480   GstDateTime *copy = gst_date_time_alloc ();
481
482   memcpy (copy, dt, sizeof (GstDateTime));
483   copy->ref_count = 1;
484
485   return copy;
486 }
487
488 static GstDateTime *
489 gst_date_time_to_utc (const GstDateTime * dt)
490 {
491   GstDateTime *utc;
492   gint64 usec;
493   gint days;
494   gint leap;
495
496   g_return_val_if_fail (dt != NULL, NULL);
497
498   utc = gst_date_time_copy (dt);
499
500   usec = dt->usec - dt->tzoffset * GST_DATE_TIME_USEC_PER_MINUTE;
501   days = usec / GST_DATE_TIME_USEC_PER_DAY;
502   if (usec < 0)
503     days--;
504   utc->day += days;
505
506   leap = GREGORIAN_LEAP (utc->year) ? 1 : 0;
507
508   /* check if we should update month/year */
509   if (utc->day < 1) {
510     if (utc->month == 1) {
511       utc->year--;
512       utc->month = 12;
513     } else {
514       utc->month--;
515     }
516     if (GREGORIAN_LEAP (utc->year))
517       utc->day = days_in_months[1][utc->month];
518     else
519       utc->day = days_in_months[0][utc->month];
520   } else if (utc->day > days_in_months[leap][utc->month]) {
521     if (utc->month == 12) {
522       utc->year++;
523       utc->month = 1;
524     } else {
525       utc->month++;
526     }
527     utc->day = 1;
528   }
529
530   if (usec < 0)
531     utc->usec =
532         GST_DATE_TIME_USEC_PER_DAY + (usec % GST_DATE_TIME_USEC_PER_DAY);
533   else
534     utc->usec = usec % GST_DATE_TIME_USEC_PER_DAY;
535
536   return utc;
537 }
538
539 /**
540  * gst_date_time_new_now_utc:
541  *
542  * Creates a new #GstDateTime that represents the current instant at Universal
543  * coordinated time.
544  *
545  * Return value: the newly created #GstDateTime which should be freed with
546  *   gst_date_time_unref().
547  *
548  * Since: 0.10.31
549  */
550 GstDateTime *
551 gst_date_time_new_now_utc (void)
552 {
553   GstDateTime *now, *utc;
554
555   now = gst_date_time_new_now_local_time ();
556   utc = gst_date_time_to_utc (now);
557   gst_date_time_unref (now);
558   return utc;
559 }