1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * soup-date.c: Date/time handling
5 * Copyright (C) 2005, Novell, Inc.
6 * Copyright (C) 2007, Red Hat, Inc.
16 #include "soup-date.h"
20 #if ENABLE(TIZEN_TV_ADJUST_TIME)
25 * @year: the year, 1 to 9999
26 * @month: the month, 1 to 12
27 * @day: day of the month, 1 to 31
28 * @hour: hour of the day, 0 to 23
29 * @minute: minute, 0 to 59
30 * @second: second, 0 to 59 (or up to 61 in the case of leap seconds)
31 * @utc: %TRUE if the date is in UTC
32 * @offset: offset from UTC
34 * A date and time. The date is assumed to be in the (proleptic)
35 * Gregorian calendar. The time is in UTC if @utc is %TRUE. Otherwise,
36 * the time is a local time, and @offset gives the offset from UTC in
37 * minutes (such that adding @offset to the time would give the
38 * correct UTC time). If @utc is %FALSE and @offset is 0, then the
39 * %SoupDate represents a "floating" time with no associated timezone
43 /* Do not internationalize */
44 static const char *const months[] = {
45 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
46 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
49 /* Do not internationalize */
50 static const char *const days[] = {
51 "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
54 static const int nonleap_days_in_month[] = {
55 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
58 static const int nonleap_days_before[] = {
59 0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365
62 static inline gboolean
63 is_leap_year (int year)
65 return (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0));
68 /* Computes the number of days since proleptic Gregorian 0000-12-31.
69 * (That is, 0001-01-01 is "1", and 1970-01-01 is 719163.
72 rata_die_day (SoupDate *date)
76 day = (date->year - 1) * 365 + ((date->year - 1) / 4) -
77 ((date->year - 1) / 100) + ((date->year - 1) / 400);
78 day += nonleap_days_before[date->month] + date->day;
79 if (is_leap_year (date->year) && date->month > 2)
84 #define TIME_T_EPOCH_RATA_DIE_DAY 719163
87 days_in_month (int month, int year)
89 if (month == 2 && is_leap_year (year))
92 return nonleap_days_in_month[month];
95 G_DEFINE_BOXED_TYPE (SoupDate, soup_date, soup_date_copy, soup_date_free)
98 soup_date_fixup (SoupDate *date)
100 /* We only correct date->second if it's negative or too high
101 * to be a leap second.
103 if (date->second < 0 || date->second > 61) {
104 date->minute += date->second / 60;
106 if (date->second < 0)
110 if (date->minute < 0 || date->minute > 59) {
111 date->hour += date->minute / 60;
113 if (date->minute < 0)
117 if (date->hour < 0 || date->hour > 23) {
118 date->day += date->hour / 24;
124 /* Have to make sure month is valid before we can look at the
127 if (date->month < 1 || date->month > 12) {
128 date->year += ((date->month - 1) / 12) + 1;
129 date->month = ((date->month - 1) % 12) + 1;
135 while (date->day < 0) {
136 if (date->month == 1) {
141 date->day += days_in_month (date->month, date->year);
144 while (date->day > days_in_month (date->month, date->year)) {
145 date->day -= days_in_month (date->month, date->year);
146 if (date->month == 12) {
157 * @year: the year (1-9999)
158 * @month: the month (1-12)
159 * @day: the day of the month (1-31, as appropriate for @month)
160 * @hour: the hour (0-23)
161 * @minute: the minute (0-59)
162 * @second: the second (0-59, or up to 61 for leap seconds)
164 * Creates a #SoupDate representing the indicated time, UTC.
166 * Return value: a new #SoupDate
169 soup_date_new (int year, int month, int day,
170 int hour, int minute, int second)
172 SoupDate *date = g_slice_new (SoupDate);
178 date->minute = minute;
179 date->second = second;
187 * soup_date_new_from_now:
188 * @offset_seconds: offset from current time
190 * Creates a #SoupDate representing a time @offset_seconds after the
191 * current time (or before it, if @offset_seconds is negative). If
192 * offset_seconds is 0, returns the current time.
194 * If @offset_seconds would indicate a time not expressible as a
195 * <type>time_t</type>, the return value will be clamped into range.
197 * Return value: a new #SoupDate
200 soup_date_new_from_now (int offset_seconds)
202 time_t now = time (NULL);
203 time_t then = now + offset_seconds;
205 if (sizeof (time_t) == 4) {
206 if (offset_seconds < 0 && then > now)
207 return soup_date_new_from_time_t (-G_MAXINT);
208 else if (offset_seconds > 0 && then < now)
209 return soup_date_new_from_time_t (G_MAXINT);
211 return soup_date_new_from_time_t (then);
215 parse_iso8601_date (SoupDate *date, const char *date_string)
219 if (strlen (date_string) < 15)
221 if (date_string[4] == '-' &&
222 date_string[7] == '-' &&
223 date_string[10] == 'T') {
225 date->year = atoi (date_string);
226 date->month = atoi (date_string + 5);
227 date->day = atoi (date_string + 8);
229 } else if (date_string[8] == 'T') {
231 val = atoi (date_string);
232 date->year = val / 10000;
233 date->month = (val % 10000) / 100;
234 date->day = val % 100;
239 if (strlen (date_string) >= 8 &&
240 date_string[2] == ':' && date_string[5] == ':') {
242 date->hour = atoi (date_string);
243 date->minute = atoi (date_string + 3);
244 date->second = atoi (date_string + 6);
246 } else if (strlen (date_string) >= 6) {
248 val = strtoul (date_string, (char **)&date_string, 10);
249 date->hour = val / 10000;
250 date->minute = (val % 10000) / 100;
251 date->second = val % 100;
255 if (*date_string == '.' || *date_string == ',')
256 (void) strtoul (date_string + 1, (char **)&date_string, 10);
258 if (*date_string == 'Z') {
262 } else if (*date_string == '+' || *date_string == '-') {
263 int sign = (*date_string == '+') ? -1 : 1;
264 val = strtoul (date_string + 1, (char **)&date_string, 10);
265 if (*date_string == ':')
266 val = 60 * val + strtoul (date_string + 1, (char **)&date_string, 10);
268 val = 60 * (val / 100) + (val % 100);
269 date->offset = sign * val;
276 return !*date_string;
279 static inline gboolean
280 parse_day (SoupDate *date, const char **date_string)
284 date->day = strtoul (*date_string, &end, 10);
285 if (end == (char *)*date_string)
288 while (*end == ' ' || *end == '-')
294 static inline gboolean
295 parse_month (SoupDate *date, const char **date_string)
299 for (i = 0; i < G_N_ELEMENTS (months); i++) {
300 if (!g_ascii_strncasecmp (*date_string, months[i], 3)) {
303 while (**date_string == ' ' || **date_string == '-')
311 static inline gboolean
312 parse_year (SoupDate *date, const char **date_string)
316 date->year = strtoul (*date_string, &end, 10);
317 if (end == (char *)*date_string)
320 if (end == (char *)*date_string + 2) {
325 } else if (end == (char *)*date_string + 3)
328 while (*end == ' ' || *end == '-')
334 static inline gboolean
335 parse_time (SoupDate *date, const char **date_string)
339 date->hour = strtoul (*date_string, &end, 10);
340 if (end == (char *)*date_string || *end++ != ':')
343 date->minute = strtoul (p, &end, 10);
344 if (end == p || *end++ != ':')
347 date->second = strtoul (p, &end, 10);
358 static inline gboolean
359 parse_timezone (SoupDate *date, const char **date_string)
361 if (!**date_string) {
364 } else if (**date_string == '+' || **date_string == '-') {
366 int sign = (**date_string == '+') ? -1 : 1;
367 val = strtoul (*date_string + 1, (char **)date_string, 10);
368 if (**date_string == ':')
369 val = 60 * val + strtoul (*date_string + 1, (char **)date_string, 10);
371 val = 60 * (val / 100) + (val % 100);
372 date->offset = sign * val;
373 date->utc = (sign == -1) && !val;
374 } else if (**date_string == 'Z') {
378 } else if (!strcmp (*date_string, "GMT") ||
379 !strcmp (*date_string, "UTC")) {
383 } else if (strchr ("ECMP", **date_string) &&
384 ((*date_string)[1] == 'D' || (*date_string)[1] == 'S') &&
385 (*date_string)[2] == 'T') {
386 date->offset = -60 * (5 * strcspn ("ECMP", *date_string));
387 if ((*date_string)[1] == 'D')
396 parse_textual_date (SoupDate *date, const char *date_string)
398 /* If it starts with a word, it must be a weekday, which we skip */
399 if (g_ascii_isalpha (*date_string)) {
400 while (g_ascii_isalpha (*date_string))
402 if (*date_string == ',')
404 while (g_ascii_isspace (*date_string))
408 /* If there's now another word, this must be an asctime-date */
409 if (g_ascii_isalpha (*date_string)) {
410 /* (Sun) Nov 6 08:49:37 1994 */
411 if (!parse_month (date, &date_string) ||
412 !parse_day (date, &date_string) ||
413 !parse_time (date, &date_string) ||
414 !parse_year (date, &date_string))
417 /* There shouldn't be a timezone, but check anyway */
418 parse_timezone (date, &date_string);
420 /* Non-asctime date, so some variation of
421 * (Sun,) 06 Nov 1994 08:49:37 GMT
423 if (!parse_day (date, &date_string) ||
424 !parse_month (date, &date_string) ||
425 !parse_year (date, &date_string) ||
426 !parse_time (date, &date_string))
429 /* This time there *should* be a timezone, but we
430 * survive if there isn't.
432 parse_timezone (date, &date_string);
439 * @SOUP_DATE_HTTP: RFC 1123 format, used by the HTTP "Date" header. Eg
440 * "Sun, 06 Nov 1994 08:49:37 GMT"
441 * @SOUP_DATE_COOKIE: The format for the "Expires" timestamp in the
442 * Netscape cookie specification. Eg, "Sun, 06-Nov-1994 08:49:37 GMT".
443 * @SOUP_DATE_RFC2822: RFC 2822 format, eg "Sun, 6 Nov 1994 09:49:37 -0100"
444 * @SOUP_DATE_ISO8601_COMPACT: ISO 8601 date/time with no optional
445 * punctuation. Eg, "19941106T094937-0100".
446 * @SOUP_DATE_ISO8601_FULL: ISO 8601 date/time with all optional
447 * punctuation. Eg, "1994-11-06T09:49:37-01:00".
448 * @SOUP_DATE_ISO8601_XMLRPC: ISO 8601 date/time as used by XML-RPC.
449 * Eg, "19941106T09:49:37".
450 * @SOUP_DATE_ISO8601: An alias for @SOUP_DATE_ISO8601_FULL.
452 * Date formats that soup_date_to_string() can use.
454 * @SOUP_DATE_HTTP and @SOUP_DATE_COOKIE always coerce the time to
455 * UTC. @SOUP_DATE_ISO8601_XMLRPC uses the time as given, ignoring the
456 * offset completely. @SOUP_DATE_RFC2822 and the other ISO 8601
457 * variants use the local time, appending the offset information if
460 * This enum may be extended with more values in future releases.
464 * soup_date_new_from_string:
465 * @date_string: the date in some plausible format
467 * Parses @date_string and tries to extract a date from it. This
468 * recognizes all of the "HTTP-date" formats from RFC 2616, all ISO
469 * 8601 formats containing both a time and a date, RFC 2822 dates,
470 * and reasonable approximations thereof. (Eg, it is lenient about
471 * whitespace, leading "0"s, etc.)
473 * Return value: a new #SoupDate, or %NULL if @date_string could not
477 soup_date_new_from_string (const char *date_string)
482 g_return_val_if_fail (date_string != NULL, NULL);
484 date = g_slice_new (SoupDate);
486 while (g_ascii_isspace (*date_string))
489 /* If it starts with a digit, it's either an ISO 8601 date, or
490 * an RFC2822 date without the optional weekday; in the later
491 * case, there will be a month name later on, so look for one
492 * of the month-start letters.
494 if (g_ascii_isdigit (*date_string) &&
495 !strpbrk (date_string, "JFMASOND"))
496 success = parse_iso8601_date (date, date_string);
498 success = parse_textual_date (date, date_string);
501 g_slice_free (SoupDate, date);
505 if (date->year < 1 || date->year > 9999 ||
506 date->month < 1 || date->month > 12 ||
508 date->day > days_in_month (date->month, date->year) ||
509 date->hour < 0 || date->hour > 24 ||
510 date->minute < 0 || date->minute > 59 ||
511 date->second < 0 || date->second > 61) {
512 soup_date_free (date);
515 if (date->hour == 24) {
516 /* ISO8601 allows this explicitly. We allow it for
517 * other types as well just for simplicity.
519 if (date->minute == 0 && date->second == 0)
520 soup_date_fixup (date);
522 soup_date_free (date);
531 * soup_date_new_from_time_t:
532 * @when: a <type>time_t</type>
534 * Creates a #SoupDate corresponding to @when
536 * Return value: a new #SoupDate
539 soup_date_new_from_time_t (time_t when)
544 gmtime_r (&when, &tm);
546 tm = *gmtime (&when);
549 return soup_date_new (tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
550 tm.tm_hour, tm.tm_min, tm.tm_sec);
554 soup_date_weekday (SoupDate *date)
556 /* Proleptic Gregorian 0001-01-01 was a Monday, which
557 * corresponds to 1 in the days[] array.
559 return days[rata_die_day (date) % 7];
563 * soup_date_to_string:
565 * @format: the format to generate the date in
567 * Converts @date to a string in the format described by @format.
569 * Return value: @date as a string
572 soup_date_to_string (SoupDate *date, SoupDateFormat format)
574 g_return_val_if_fail (date != NULL, NULL);
576 if (format == SOUP_DATE_HTTP || format == SOUP_DATE_COOKIE) {
577 /* HTTP and COOKIE formats require UTC timestamp, so coerce
578 * @date if it's non-UTC.
582 if (date->offset != 0) {
583 memcpy (&utcdate, date, sizeof (SoupDate));
584 utcdate.minute += utcdate.offset;
587 soup_date_fixup (&utcdate);
593 /* "Sun, 06 Nov 1994 08:49:37 GMT" */
594 return g_strdup_printf (
595 "%s, %02d %s %04d %02d:%02d:%02d GMT",
596 soup_date_weekday (date), date->day,
597 months[date->month - 1], date->year,
598 date->hour, date->minute, date->second);
600 case SOUP_DATE_COOKIE:
601 /* "Sun, 06-Nov-1994 08:49:37 GMT" */
602 return g_strdup_printf (
603 "%s, %02d-%s-%04d %02d:%02d:%02d GMT",
604 soup_date_weekday (date), date->day,
605 months[date->month - 1], date->year,
606 date->hour, date->minute, date->second);
609 g_return_val_if_reached (NULL);
611 } else if (format == SOUP_DATE_ISO8601_XMLRPC) {
612 /* Always "floating", ignore offset */
613 return g_strdup_printf ("%04d%02d%02dT%02d:%02d:%02d",
614 date->year, date->month, date->day,
615 date->hour, date->minute, date->second);
617 int hour_offset, minute_offset;
620 /* For other ISO8601 formats or RFC2822, use the
621 * offset given in @date. For ISO8601 formats, use "Z"
622 * for UTC, +-offset for non-UTC, and nothing for
623 * floating. For RFC2822, use +-offset for UTC or
624 * non-UTC, and -0000 for floating.
626 hour_offset = abs (date->offset) / 60;
627 minute_offset = abs (date->offset) - hour_offset * 60;
630 case SOUP_DATE_ISO8601_COMPACT:
631 /* "19941106T084937[zone]" */
634 else if (date->offset) {
635 g_snprintf (zone, sizeof (zone), "%c%02d%02d",
636 date->offset > 0 ? '-' : '+',
637 hour_offset, minute_offset);
641 return g_strdup_printf (
642 "%04d%02d%02dT%02d%02d%02d%s",
643 date->year, date->month, date->day,
644 date->hour, date->minute, date->second,
647 case SOUP_DATE_ISO8601_FULL:
648 /* "1994-11-06T08:49:37[zone]" */
651 else if (date->offset) {
652 g_snprintf (zone, sizeof (zone), "%c%02d:%02d",
653 date->offset > 0 ? '-' : '+',
654 hour_offset, minute_offset);
658 return g_strdup_printf (
659 "%04d-%02d-%02dT%02d:%02d:%02d%s",
660 date->year, date->month, date->day,
661 date->hour, date->minute, date->second,
664 case SOUP_DATE_RFC2822:
665 /* "Sun, 6 Nov 1994 09:49:37 -0100" */
667 sign = (date->offset > 0) ? '-' : '+';
669 sign = date->utc ? '+' : '-';
670 return g_strdup_printf (
671 "%s, %d %s %04d %02d:%02d:%02d %c%02d%02d",
672 soup_date_weekday (date), date->day,
673 months[date->month - 1], date->year,
674 date->hour, date->minute, date->second,
675 sign, hour_offset, minute_offset);
684 * soup_date_to_time_t:
687 * Converts @date to a <type>time_t</type>.
689 * If @date is not representable as a <type>time_t</type>, it will be
690 * clamped into range. (In particular, some HTTP cookies have
691 * expiration dates after "Y2.038k" (2038-01-19T03:14:07Z).)
693 * Return value: @date as a <type>time_t</type>
696 soup_date_to_time_t (SoupDate *date)
701 g_return_val_if_fail (date != NULL, 0);
703 /* FIXME: offset, etc */
705 if (date->year < 1970)
708 /* If the year is later than 2038, we're guaranteed to
709 * overflow a 32-bit time_t. (If it's exactly 2038, we'll
710 * *probably* overflow, but only by a little, and it's easiest
711 * to test that at the end by seeing if the result has turned
714 if (sizeof (time_t) == 4 && date->year > 2038)
715 return (time_t)0x7fffffff;
717 soup_date_to_timeval (date, &val);
720 if (sizeof (time_t) == 4 && tt < 0)
721 return (time_t)0x7fffffff;
726 * soup_date_to_timeval:
728 * @time: (out): a #GTimeVal structure in which to store the converted time.
730 * Converts @date to a #GTimeVal.
735 soup_date_to_timeval (SoupDate *date, GTimeVal *time)
737 g_return_if_fail (date != NULL);
738 g_return_if_fail (time != NULL);
740 /* FIXME: offset, etc */
742 time->tv_sec = rata_die_day (date) - TIME_T_EPOCH_RATA_DIE_DAY;
743 time->tv_sec = ((((time->tv_sec * 24) + date->hour) * 60) + date->minute) * 60 + date->second;
747 #if ENABLE(TIZEN_TV_ADJUST_TIME)
749 * soup_date_set_timeOffset:
756 * Since: 2013.Aug, (porting from Orsay2014)
758 double soupTimeOffset = 0;
760 soup_date_set_timeOffset (double timeOffset)
765 TIZEN_LOGI("soup date set time offset is [%f], TV board time is [%ld]", timeOffset, timer);
766 TIZEN_LOGI("TV borad time is: %s",ctime(&timer));
767 timer = timer + timeOffset/1000;
768 TIZEN_LOGI("after off set time is: %s",ctime(&timer));
770 soupTimeOffset = timeOffset;
778 * Determines if @date is in the past.
780 * Return value: %TRUE if @date is in the past
785 soup_date_is_past (SoupDate *date)
787 g_return_val_if_fail (date != NULL, TRUE);
790 if (date->year < 2010)
792 #if ENABLE(TIZEN_TV_ADJUST_TIME)
793 return soup_date_to_time_t (date) < time (NULL) + (int)(soupTimeOffset / 1000);
795 return soup_date_to_time_t (date) < time (NULL);
800 * soup_date_get_year:
805 * Return value: @date's year
810 soup_date_get_year (SoupDate *date)
816 * soup_date_get_month:
819 * Gets @date's month.
821 * Return value: @date's month
826 soup_date_get_month (SoupDate *date)
837 * Return value: @date's day
842 soup_date_get_day (SoupDate *date)
848 * soup_date_get_hour:
853 * Return value: @date's hour
858 soup_date_get_hour (SoupDate *date)
864 * soup_date_get_minute:
867 * Gets @date's minute.
869 * Return value: @date's minute
874 soup_date_get_minute (SoupDate *date)
880 * soup_date_get_second:
883 * Gets @date's second.
885 * Return value: @date's second
890 soup_date_get_second (SoupDate *date)
899 * Gets @date's UTC flag
901 * Return value: %TRUE if @date is UTC.
906 soup_date_get_utc (SoupDate *date)
912 * soup_date_get_offset:
915 * Gets @date's offset from UTC.
917 * Return value: @date's offset from UTC. If soup_date_get_utc()
918 * returns %FALSE but soup_date_get_offset() returns 0, that means the
919 * date is a "floating" time with no associated offset information.
924 soup_date_get_offset (SoupDate *date)
938 soup_date_copy (SoupDate *date)
942 g_return_val_if_fail (date != NULL, NULL);
944 copy = g_slice_new (SoupDate);
945 memcpy (copy, date, sizeof (SoupDate));
958 soup_date_free (SoupDate *date)
960 g_return_if_fail (date != NULL);
962 g_slice_free (SoupDate, date);