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.
17 #include "soup-date.h"
21 * @year: the year, 1 to 9999
22 * @month: the month, 1 to 12
23 * @day: day of the month, 1 to 31
24 * @hour: hour of the day, 0 to 23
25 * @minute: minute, 0 to 59
26 * @second: second, 0 to 59 (or up to 61 in the case of leap seconds)
27 * @utc: %TRUE if the date is in UTC
28 * @offset: offset from UTC
30 * A date and time. The date is assumed to be in the (proleptic)
31 * Gregorian calendar. The time is in UTC if @utc is %TRUE. Otherwise,
32 * the time is a local time, and @offset gives the offset from UTC in
33 * minutes (such that adding @offset to the time would give the
34 * correct UTC time). If @utc is %FALSE and @offset is 0, then the
35 * %SoupDate represents a "floating" time with no associated timezone
39 /* Do not internationalize */
40 static const char *months[] = {
41 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
42 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
45 /* Do not internationalize */
46 static const char *days[] = {
47 "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
50 static const int days_before[] = {
51 0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365
55 soup_date_get_type (void)
57 static volatile gsize type_volatile = 0;
59 if (g_once_init_enter (&type_volatile)) {
60 GType type = g_boxed_type_register_static (
61 g_intern_static_string ("SoupDate"),
62 (GBoxedCopyFunc) soup_date_copy,
63 (GBoxedFreeFunc) soup_date_free);
64 g_once_init_leave (&type_volatile, type);
71 * @year: the year (1-9999)
72 * @month: the month (1-12)
73 * @day: the day of the month (1-31, as appropriate for @month)
74 * @hour: the hour (0-23)
75 * @minute: the minute (0-59)
76 * @second: the second (0-59)
78 * Creates a #SoupDate representing the indicated time, UTC.
80 * Return value: a new #SoupDate
83 soup_date_new (int year, int month, int day,
84 int hour, int minute, int second)
86 SoupDate *date = g_slice_new (SoupDate);
92 date->minute = minute;
93 date->second = second;
101 * soup_date_new_from_now:
102 * @offset_seconds: offset from current time
104 * Creates a #SoupDate representing a time @offset_seconds after the
105 * current time (or before it, if @offset_seconds is negative). If
106 * offset_seconds is 0, returns the current time.
108 * Return value: a new #SoupDate
111 soup_date_new_from_now (int offset_seconds)
113 return soup_date_new_from_time_t (time (NULL) + offset_seconds);
117 parse_iso8601_date (SoupDate *date, const char *date_string)
121 if (strlen (date_string) < 15)
123 if (date_string[4] == '-' &&
124 date_string[7] == '-' &&
125 date_string[10] == 'T') {
127 date->year = atoi (date_string);
128 date->month = atoi (date_string + 5);
129 date->day = atoi (date_string + 8);
131 } else if (date_string[8] == 'T') {
133 val = atoi (date_string);
134 date->year = val / 10000;
135 date->month = (val % 10000) / 100;
136 date->day = val % 100;
141 if (strlen (date_string) >= 8 &&
142 date_string[2] == ':' && date_string[5] == ':') {
144 date->hour = atoi (date_string);
145 date->minute = atoi (date_string + 3);
146 date->second = atoi (date_string + 6);
148 } else if (strlen (date_string) >= 6) {
150 val = strtoul (date_string, (char **)&date_string, 10);
151 date->hour = val / 10000;
152 date->minute = (val % 10000) / 100;
153 date->second = val % 100;
157 if (*date_string == '.')
158 strtoul (date_string + 1, (char **)&date_string, 10);
160 if (*date_string == 'Z') {
164 } else if (*date_string == '+' || *date_string == '-') {
165 int sign = (*date_string == '+') ? -1 : 1;
166 val = strtoul (date_string + 1, (char **)&date_string, 10);
167 if (*date_string == ':')
168 val = 60 * val + strtoul (date_string + 1, (char **)&date_string, 10);
170 val = 60 * (val / 100) + (val % 100);
171 date->offset = sign * val;
172 date->utc = sign && !val;
175 return !*date_string;
178 static inline gboolean
179 parse_day (SoupDate *date, const char **date_string)
183 date->day = strtoul (*date_string, &end, 10);
184 if (end == (char *)date_string)
187 while (*end == ' ' || *end == '-')
193 static inline gboolean
194 parse_month (SoupDate *date, const char **date_string)
198 for (i = 0; i < G_N_ELEMENTS (months); i++) {
199 if (!strncmp (*date_string, months[i], 3)) {
202 while (**date_string == ' ' || **date_string == '-')
210 static inline gboolean
211 parse_year (SoupDate *date, const char **date_string)
215 date->year = strtoul (*date_string, &end, 10);
216 if (end == (char *)date_string)
219 if (end == (char *)*date_string + 2) {
224 } else if (end == (char *)*date_string + 3)
227 while (*end == ' ' || *end == '-')
233 static inline gboolean
234 parse_time (SoupDate *date, const char **date_string)
238 date->hour = strtoul (*date_string, &p, 10);
241 date->minute = strtoul (p, &p, 10);
244 date->second = strtoul (p, &p, 10);
252 static inline gboolean
253 parse_timezone (SoupDate *date, const char **date_string)
255 if (**date_string == '+' || **date_string == '-') {
257 int sign = (**date_string == '+') ? -1 : 1;
258 val = strtoul (*date_string + 1, (char **)date_string, 10);
259 if (**date_string != ':')
261 val = 60 * val + strtoul (*date_string + 1, (char **)date_string, 10);
262 date->offset = sign * val;
263 date->utc = sign && !val;
264 } else if (**date_string == 'Z') {
268 } else if (!strcmp (*date_string, "GMT") ||
269 !strcmp (*date_string, "UTC")) {
273 } else if (strchr ("ECMP", **date_string) &&
274 ((*date_string)[1] == 'D' || (*date_string)[1] == 'S') &&
275 (*date_string)[2] == 'T') {
276 date->offset = -60 * (5 * strcspn ("ECMP", *date_string));
277 if ((*date_string)[1] == 'D')
280 } else if (!**date_string) {
289 parse_textual_date (SoupDate *date, const char *date_string)
291 /* If it starts with a word, it must be a weekday, which we skip */
292 while (g_ascii_isalpha (*date_string))
294 if (*date_string == ',')
296 while (g_ascii_isspace (*date_string))
299 /* If there's now another word, this must be an asctime-date */
300 if (g_ascii_isalpha (*date_string)) {
301 /* (Sun) Nov 6 08:49:37 1994 */
302 if (!parse_month (date, &date_string) ||
303 !parse_day (date, &date_string) ||
304 !parse_time (date, &date_string) ||
305 !parse_year (date, &date_string))
308 /* There shouldn't be a timezone, but check anyway */
309 parse_timezone (date, &date_string);
311 /* Non-asctime date, so some variation of
312 * (Sun,) 06 Nov 1994 08:49:37 GMT
314 if (!parse_day (date, &date_string) ||
315 !parse_month (date, &date_string) ||
316 !parse_year (date, &date_string) ||
317 !parse_time (date, &date_string))
320 /* This time there *should* be a timezone, but we
321 * survive if there isn't.
323 parse_timezone (date, &date_string);
329 days_in_month (int month, int year)
331 return days_before[month + 1] - days_before[month] +
332 (((year % 4 == 0) && month == 2) ? 1 : 0);
337 * @SOUP_DATE_HTTP: RFC 1123 format, used by the HTTP "Date" header. Eg
338 * "Sun, 06 Nov 1994 08:49:37 GMT"
339 * @SOUP_DATE_COOKIE: The format for the "Expires" timestamp in the
340 * Netscape cookie specification. Eg, "Sun, 06-Nov-1994 08:49:37 GMT".
341 * @SOUP_DATE_RFC2822: RFC 2822 format, eg "Sun, 6 Nov 1994 09:49:37 -0100"
342 * @SOUP_DATE_ISO8601_COMPACT: ISO 8601 date/time with no optional
343 * punctuation. Eg, "19941106T094937-0100".
344 * @SOUP_DATE_ISO8601_FULL: ISO 8601 date/time with all optional
345 * punctuation. Eg, "1994-11-06T09:49:37-01:00".
346 * @SOUP_DATE_ISO8601_XMLRPC: ISO 8601 date/time as used by XML-RPC.
347 * Eg, "19941106T09:49:37".
348 * @SOUP_DATE_ISO8601: An alias for @SOUP_DATE_ISO8601_FULL.
350 * Date formats that soup_date_to_string() can use.
352 * @SOUP_DATE_HTTP and @SOUP_DATE_COOKIE always coerce the time to
353 * UTC. @SOUP_DATE_ISO8601_XMLRPC uses the time as given, ignoring the
354 * offset completely. @SOUP_DATE_RFC2822 and the other ISO 8601
355 * variants use the local time, appending the offset information if
358 * This enum may be extended with more values in future releases.
362 * soup_date_new_from_string:
363 * @date_string: the date in some plausible format
365 * Parses @date_string and tries to extract a date from it. This
366 * recognizes all of the "HTTP-date" formats from RFC 2616, all ISO
367 * 8601 formats containing both a time and a date, RFC 2822 dates,
368 * and reasonable approximations thereof. (Eg, it is lenient about
369 * whitespace, leading "0"s, etc.)
371 * Return value: a new #SoupDate
374 soup_date_new_from_string (const char *date_string)
376 SoupDate *date = g_slice_new (SoupDate);
379 while (g_ascii_isspace (*date_string))
382 /* If it starts with a digit, it's either an ISO 8601 date, or
383 * an RFC2822 date without the optional weekday; in the later
384 * case, there will be a month name later on, so look for one
385 * of the month-start letters.
387 if (g_ascii_isdigit (*date_string) &&
388 !strpbrk (date_string, "JFMASOND"))
389 success = parse_iso8601_date (date, date_string);
391 success = parse_textual_date (date, date_string);
394 g_slice_free (SoupDate, date);
398 if (date->year < 1 || date->year > 9999 ||
399 date->month < 1 || date->month > 12 ||
401 date->day > days_in_month (date->month, date->year) ||
402 date->hour < 0 || date->hour > 23 ||
403 date->minute < 0 || date->minute > 59 ||
404 date->second < 0 || date->second > 59) {
405 g_slice_free (SoupDate, date);
412 * soup_date_new_from_time_t:
415 * Creates a #SoupDate corresponding to @when
417 * Return value: a new #SoupDate
420 soup_date_new_from_time_t (time_t when)
425 gmtime_r (&when, &tm);
427 tm = *gmtime (&when);
430 return soup_date_new (tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
431 tm.tm_hour, tm.tm_min, tm.tm_sec);
435 soup_date_weekday (SoupDate *date)
439 /* Proleptic Gregorian 0001-01-01 was a Monday, which
440 * corresponds to 1 in the days[] array. So we take the
441 * number of days since 0000-12-31, modulo 7.
443 day = (date->year - 1) * 365 + ((date->year - 1) / 4);
444 day += days_before[date->month] + date->day;
445 if (date->year % 4 == 0 && date->month > 2)
448 return days[day % 7];
452 * soup_date_to_string:
454 * @format: the format to generate the date in
456 * Converts @date to a string in the format described by @format.
458 * Return value: @date as a string
461 soup_date_to_string (SoupDate *date, SoupDateFormat format)
463 /* FIXME: offset, 8601 zones, etc */
467 /* "Sun, 06 Nov 1994 08:49:37 GMT" */
468 return g_strdup_printf ("%s, %02d %s %04d %02d:%02d:%02d GMT",
469 soup_date_weekday (date), date->day,
470 months[date->month - 1],
471 date->year, date->hour, date->minute,
474 case SOUP_DATE_COOKIE:
475 /* "Sun, 06-Nov-1994 08:49:37 GMT" */
476 return g_strdup_printf ("%s, %02d-%s-%04d %02d:%02d:%02d GMT",
477 soup_date_weekday (date), date->day,
478 months[date->month - 1],
479 date->year, date->hour, date->minute,
482 case SOUP_DATE_ISO8601_COMPACT:
483 return g_strdup_printf ("%04d%02d%02dT%02d%02d%02d",
484 date->year, date->month, date->day,
485 date->hour, date->minute, date->second);
486 case SOUP_DATE_ISO8601_FULL:
487 return g_strdup_printf ("%04d-%02d-%02dT%02d:%02d:%02d",
488 date->year, date->month, date->day,
489 date->hour, date->minute, date->second);
490 case SOUP_DATE_ISO8601_XMLRPC:
491 return g_strdup_printf ("%04d%02d%02dT%02d:%02d:%02d",
492 date->year, date->month, date->day,
493 date->hour, date->minute, date->second);
501 * soup_date_to_time_t:
504 * Converts @date to a %time_t
506 * Return value: @date as a %time_t
509 soup_date_to_time_t (SoupDate *date)
513 static const int days_before[] = {
514 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
519 /* FIXME: offset, etc */
521 tm.tm_year = date->year - 1900;
522 tm.tm_mon = date->month - 1;
523 tm.tm_mday = date->day;
524 tm.tm_hour = date->hour;
525 tm.tm_min = date->minute;
526 tm.tm_sec = date->second;
531 /* We check the month because (a) if we don't, the
532 * days_before[] part below may access random memory, and (b)
533 * soup_date_parse() doesn't check the return value of
534 * parse_month(). The caller is responsible for ensuring the
535 * sanity of everything else.
537 if (tm.tm_mon < 0 || tm.tm_mon > 11)
540 tt = (tm.tm_year - 70) * 365;
541 tt += (tm.tm_year - 68) / 4;
542 tt += days_before[tm.tm_mon] + tm.tm_mday - 1;
543 if (tm.tm_year % 4 == 0 && tm.tm_mon < 2)
545 tt = ((((tt * 24) + tm.tm_hour) * 60) + tm.tm_min) * 60 + tm.tm_sec;
558 soup_date_copy (SoupDate *date)
560 SoupDate *copy = g_slice_new (SoupDate);
562 memcpy (copy, date, sizeof (SoupDate));
573 soup_date_free (SoupDate *date)
575 g_slice_free (SoupDate, date);