• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * soup-date.c: Date/time handling
4  *
5  * Copyright (C) 2005, Novell, Inc.
6  * Copyright (C) 2007, Red Hat, Inc.
7  */
8 
9 #ifdef HAVE_CONFIG_H
10 #include <config.h>
11 #endif
12 
13 #include <stdlib.h>
14 #include <string.h>
15 
16 #include "soup-date.h"
17 #include "soup.h"
18 
19 /**
20  * SoupDate:
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
29 
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
36  * information.
37  **/
38 
39 /* Do not internationalize */
40 static const char *const months[] = {
41 	"Jan", "Feb", "Mar", "Apr", "May", "Jun",
42 	"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
43 };
44 
45 /* Do not internationalize */
46 static const char *const days[] = {
47 	"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
48 };
49 
50 static const int nonleap_days_in_month[] = {
51 	0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
52 };
53 
54 static const int nonleap_days_before[] = {
55 	0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365
56 };
57 
58 static inline gboolean
is_leap_year(int year)59 is_leap_year (int year)
60 {
61 	return (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0));
62 }
63 
64 /* Computes the number of days since proleptic Gregorian 0000-12-31.
65  * (That is, 0001-01-01 is "1", and 1970-01-01 is 719163.
66  */
67 static int
rata_die_day(SoupDate * date)68 rata_die_day (SoupDate *date)
69 {
70 	int day;
71 
72 	day = (date->year - 1) * 365 + ((date->year - 1) / 4) -
73 		((date->year - 1) / 100) + ((date->year - 1) / 400);
74 	day += nonleap_days_before[date->month] + date->day;
75 	if (is_leap_year (date->year) && date->month > 2)
76 		day++;
77 	return day;
78 }
79 
80 #define TIME_T_EPOCH_RATA_DIE_DAY 719163
81 
82 static inline int
days_in_month(int month,int year)83 days_in_month (int month, int year)
84 {
85 	if (month == 2 && is_leap_year (year))
86 		return 29;
87 	else
88 		return nonleap_days_in_month[month];
89 }
90 
G_DEFINE_BOXED_TYPE(SoupDate,soup_date,soup_date_copy,soup_date_free)91 G_DEFINE_BOXED_TYPE (SoupDate, soup_date, soup_date_copy, soup_date_free)
92 
93 static void
94 soup_date_fixup (SoupDate *date)
95 {
96 	/* We only correct date->second if it's negative or too high
97 	 * to be a leap second.
98 	 */
99 	if (date->second < 0 || date->second > 61) {
100 		date->minute += date->second / 60;
101 		date->second %= 60;
102 		if (date->second < 0)
103 			date->second += 60;
104 	}
105 
106 	if (date->minute < 0 || date->minute > 59) {
107 		date->hour += date->minute / 60;
108 		date->minute %= 60;
109 		if (date->minute < 0)
110 			date->minute += 60;
111 	}
112 
113 	if (date->hour < 0 || date->hour > 23) {
114 		date->day += date->hour / 24;
115 		date->hour %= 24;
116 		if (date->hour < 0)
117 			date->hour += 24;
118 	}
119 
120 	/* Have to make sure month is valid before we can look at the
121 	 * day.
122 	 */
123 	if (date->month < 1 || date->month > 12) {
124 		date->year += ((date->month - 1) / 12) + 1;
125 		date->month = ((date->month - 1) % 12) + 1;
126 		if (date->month < 1)
127 			date->month += 12;
128 	}
129 
130 	if (date->day < 0) {
131 		while (date->day < 0) {
132 			if (date->month == 1) {
133 				date->month = 12;
134 				date->year--;
135 			} else
136 				date->month--;
137 			date->day += days_in_month (date->month, date->year);
138 		}
139 	} else {
140 		while (date->day > days_in_month (date->month, date->year)) {
141 			date->day -= days_in_month (date->month, date->year);
142 			if (date->month == 12) {
143 				date->month = 1;
144 				date->year++;
145 			} else
146 				date->month++;
147 		}
148 	}
149 }
150 
151 /**
152  * soup_date_new:
153  * @year: the year (1-9999)
154  * @month: the month (1-12)
155  * @day: the day of the month (1-31, as appropriate for @month)
156  * @hour: the hour (0-23)
157  * @minute: the minute (0-59)
158  * @second: the second (0-59, or up to 61 for leap seconds)
159  *
160  * Creates a #SoupDate representing the indicated time, UTC.
161  *
162  * Return value: a new #SoupDate
163  **/
164 SoupDate *
soup_date_new(int year,int month,int day,int hour,int minute,int second)165 soup_date_new (int year, int month, int day,
166 	       int hour, int minute, int second)
167 {
168 	SoupDate *date = g_slice_new (SoupDate);
169 
170 	date->year   = year;
171 	date->month  = month;
172 	date->day    = day;
173 	date->hour   = hour;
174 	date->minute = minute;
175 	date->second = second;
176 	date->utc    = TRUE;
177 	date->offset = 0;
178 
179 	return date;
180 }
181 
182 /**
183  * soup_date_new_from_now:
184  * @offset_seconds: offset from current time
185  *
186  * Creates a #SoupDate representing a time @offset_seconds after the
187  * current time (or before it, if @offset_seconds is negative). If
188  * offset_seconds is 0, returns the current time.
189  *
190  * If @offset_seconds would indicate a time not expressible as a
191  * <type>time_t</type>, the return value will be clamped into range.
192  *
193  * Return value: a new #SoupDate
194  **/
195 SoupDate *
soup_date_new_from_now(int offset_seconds)196 soup_date_new_from_now (int offset_seconds)
197 {
198 	time_t now = time (NULL);
199 	time_t then = now + offset_seconds;
200 
201 	if (sizeof (time_t) == 4) {
202 		if (offset_seconds < 0 && then > now)
203 			return soup_date_new_from_time_t (-G_MAXINT);
204 		else if (offset_seconds > 0 && then < now)
205 			return soup_date_new_from_time_t (G_MAXINT);
206 	}
207 	return soup_date_new_from_time_t (then);
208 }
209 
210 static gboolean
parse_iso8601_date(SoupDate * date,const char * date_string)211 parse_iso8601_date (SoupDate *date, const char *date_string)
212 {
213 	gulong val;
214 
215 	if (strlen (date_string) < 15)
216 		return FALSE;
217 	if (date_string[4] == '-' &&
218 	    date_string[7] == '-' &&
219 	    date_string[10] == 'T') {
220 		/* YYYY-MM-DD */
221 		date->year  = atoi (date_string);
222 		date->month = atoi (date_string + 5);
223 		date->day   = atoi (date_string + 8);
224 		date_string += 11;
225 	} else if (date_string[8] == 'T') {
226 		/* YYYYMMDD */
227 		val = atoi (date_string);
228 		date->year = val / 10000;
229 		date->month = (val % 10000) / 100;
230 		date->day = val % 100;
231 		date_string += 9;
232 	} else
233 		return FALSE;
234 
235 	if (strlen (date_string) >= 8 &&
236 	    date_string[2] == ':' && date_string[5] == ':') {
237 		/* HH:MM:SS */
238 		date->hour   = atoi (date_string);
239 		date->minute = atoi (date_string + 3);
240 		date->second = atoi (date_string + 6);
241 		date_string += 8;
242 	} else if (strlen (date_string) >= 6) {
243 		/* HHMMSS */
244 		val = strtoul (date_string, (char **)&date_string, 10);
245 		date->hour   = val / 10000;
246 		date->minute = (val % 10000) / 100;
247 		date->second = val % 100;
248 	} else
249 		return FALSE;
250 
251 	if (*date_string == '.' || *date_string == ',')
252 		(void) strtoul (date_string + 1, (char **)&date_string, 10);
253 
254 	if (*date_string == 'Z') {
255 		date_string++;
256 		date->utc = TRUE;
257 		date->offset = 0;
258 	} else if (*date_string == '+' || *date_string == '-') {
259 		int sign = (*date_string == '+') ? -1 : 1;
260 		val = strtoul (date_string + 1, (char **)&date_string, 10);
261 		if (*date_string == ':')
262 			val = 60 * val + strtoul (date_string + 1, (char **)&date_string, 10);
263 		else
264 			val = 60 * (val / 100) + (val % 100);
265 		date->offset = sign * val;
266 		date->utc = !val;
267 	} else {
268 		date->offset = 0;
269 		date->utc = FALSE;
270 	}
271 
272 	return !*date_string;
273 }
274 
275 static inline gboolean
parse_day(SoupDate * date,const char ** date_string)276 parse_day (SoupDate *date, const char **date_string)
277 {
278 	char *end;
279 
280 	date->day = strtoul (*date_string, &end, 10);
281 	if (end == (char *)*date_string)
282 		return FALSE;
283 
284 	while (*end == ' ' || *end == '-')
285 		end++;
286 	*date_string = end;
287 	return TRUE;
288 }
289 
290 static inline gboolean
parse_month(SoupDate * date,const char ** date_string)291 parse_month (SoupDate *date, const char **date_string)
292 {
293 	int i;
294 
295 	for (i = 0; i < G_N_ELEMENTS (months); i++) {
296 		if (!g_ascii_strncasecmp (*date_string, months[i], 3)) {
297 			date->month = i + 1;
298 			*date_string += 3;
299 			while (**date_string == ' ' || **date_string == '-')
300 				(*date_string)++;
301 			return TRUE;
302 		}
303 	}
304 	return FALSE;
305 }
306 
307 static inline gboolean
parse_year(SoupDate * date,const char ** date_string)308 parse_year (SoupDate *date, const char **date_string)
309 {
310 	char *end;
311 
312 	date->year = strtoul (*date_string, &end, 10);
313 	if (end == (char *)*date_string)
314 		return FALSE;
315 
316 	if (end == (char *)*date_string + 2) {
317 		if (date->year < 70)
318 			date->year += 2000;
319 		else
320 			date->year += 1900;
321 	} else if (end == (char *)*date_string + 3)
322 		date->year += 1900;
323 
324 	while (*end == ' ' || *end == '-')
325 		end++;
326 	*date_string = end;
327 	return TRUE;
328 }
329 
330 static inline gboolean
parse_time(SoupDate * date,const char ** date_string)331 parse_time (SoupDate *date, const char **date_string)
332 {
333 	char *p, *end;
334 
335 	date->hour = strtoul (*date_string, &end, 10);
336 	if (end == (char *)*date_string || *end++ != ':')
337 		return FALSE;
338 	p = end;
339 	date->minute = strtoul (p, &end, 10);
340 	if (end == p || *end++ != ':')
341 		return FALSE;
342 	p = end;
343 	date->second = strtoul (p, &end, 10);
344 	if (end == p)
345 		return FALSE;
346 	p = end;
347 
348 	while (*p == ' ')
349 		p++;
350 	*date_string = p;
351 	return TRUE;
352 }
353 
354 static inline gboolean
parse_timezone(SoupDate * date,const char ** date_string)355 parse_timezone (SoupDate *date, const char **date_string)
356 {
357 	if (!**date_string) {
358 		date->utc = FALSE;
359 		date->offset = 0;
360 	} else if (**date_string == '+' || **date_string == '-') {
361 		gulong val;
362 		int sign = (**date_string == '+') ? -1 : 1;
363 		val = strtoul (*date_string + 1, (char **)date_string, 10);
364 		if (**date_string == ':')
365 			val = 60 * val + strtoul (*date_string + 1, (char **)date_string, 10);
366 		else
367 			val =  60 * (val / 100) + (val % 100);
368 		date->offset = sign * val;
369 		date->utc = (sign == -1) && !val;
370 	} else if (**date_string == 'Z') {
371 		date->offset = 0;
372 		date->utc = TRUE;
373 		(*date_string)++;
374 	} else if (!strcmp (*date_string, "GMT") ||
375 		   !strcmp (*date_string, "UTC")) {
376 		date->offset = 0;
377 		date->utc = TRUE;
378 		(*date_string) += 3;
379 	} else if (strchr ("ECMP", **date_string) &&
380 		   ((*date_string)[1] == 'D' || (*date_string)[1] == 'S') &&
381 		   (*date_string)[2] == 'T') {
382 		date->offset = -60 * (5 * strcspn ("ECMP", *date_string));
383 		if ((*date_string)[1] == 'D')
384 			date->offset += 60;
385 		date->utc = FALSE;
386 	} else
387 		return FALSE;
388 	return TRUE;
389 }
390 
391 static gboolean
parse_textual_date(SoupDate * date,const char * date_string)392 parse_textual_date (SoupDate *date, const char *date_string)
393 {
394 	/* If it starts with a word, it must be a weekday, which we skip */
395 	if (g_ascii_isalpha (*date_string)) {
396 		while (g_ascii_isalpha (*date_string))
397 			date_string++;
398 		if (*date_string == ',')
399 			date_string++;
400 		while (g_ascii_isspace (*date_string))
401 			date_string++;
402 	}
403 
404 	/* If there's now another word, this must be an asctime-date */
405 	if (g_ascii_isalpha (*date_string)) {
406 		/* (Sun) Nov  6 08:49:37 1994 */
407 		if (!parse_month (date, &date_string) ||
408 		    !parse_day (date, &date_string) ||
409 		    !parse_time (date, &date_string) ||
410 		    !parse_year (date, &date_string))
411 			return FALSE;
412 
413 		/* There shouldn't be a timezone, but check anyway */
414 		parse_timezone (date, &date_string);
415 	} else {
416 		/* Non-asctime date, so some variation of
417 		 * (Sun,) 06 Nov 1994 08:49:37 GMT
418 		 */
419 		if (!parse_day (date, &date_string) ||
420 		    !parse_month (date, &date_string) ||
421 		    !parse_year (date, &date_string) ||
422 		    !parse_time (date, &date_string))
423 			return FALSE;
424 
425 		/* This time there *should* be a timezone, but we
426 		 * survive if there isn't.
427 		 */
428 		parse_timezone (date, &date_string);
429 	}
430 	return TRUE;
431 }
432 
433 /**
434  * SoupDateFormat:
435  * @SOUP_DATE_HTTP: RFC 1123 format, used by the HTTP "Date" header. Eg
436  * "Sun, 06 Nov 1994 08:49:37 GMT"
437  * @SOUP_DATE_COOKIE: The format for the "Expires" timestamp in the
438  * Netscape cookie specification. Eg, "Sun, 06-Nov-1994 08:49:37 GMT".
439  * @SOUP_DATE_RFC2822: RFC 2822 format, eg "Sun, 6 Nov 1994 09:49:37 -0100"
440  * @SOUP_DATE_ISO8601_COMPACT: ISO 8601 date/time with no optional
441  * punctuation. Eg, "19941106T094937-0100".
442  * @SOUP_DATE_ISO8601_FULL: ISO 8601 date/time with all optional
443  * punctuation. Eg, "1994-11-06T09:49:37-01:00".
444  * @SOUP_DATE_ISO8601_XMLRPC: ISO 8601 date/time as used by XML-RPC.
445  * Eg, "19941106T09:49:37".
446  * @SOUP_DATE_ISO8601: An alias for @SOUP_DATE_ISO8601_FULL.
447  *
448  * Date formats that soup_date_to_string() can use.
449  *
450  * @SOUP_DATE_HTTP and @SOUP_DATE_COOKIE always coerce the time to
451  * UTC. @SOUP_DATE_ISO8601_XMLRPC uses the time as given, ignoring the
452  * offset completely. @SOUP_DATE_RFC2822 and the other ISO 8601
453  * variants use the local time, appending the offset information if
454  * available.
455  *
456  * This enum may be extended with more values in future releases.
457  **/
458 
459 /**
460  * soup_date_new_from_string:
461  * @date_string: the date in some plausible format
462  *
463  * Parses @date_string and tries to extract a date from it. This
464  * recognizes all of the "HTTP-date" formats from RFC 2616, all ISO
465  * 8601 formats containing both a time and a date, RFC 2822 dates,
466  * and reasonable approximations thereof. (Eg, it is lenient about
467  * whitespace, leading "0"s, etc.)
468  *
469  * Return value: (nullable): a new #SoupDate, or %NULL if @date_string
470  * could not be parsed.
471  **/
472 SoupDate *
soup_date_new_from_string(const char * date_string)473 soup_date_new_from_string (const char *date_string)
474 {
475 	SoupDate *date;
476 	gboolean success;
477 
478 	g_return_val_if_fail (date_string != NULL, NULL);
479 
480 	date = g_slice_new (SoupDate);
481 
482 	while (g_ascii_isspace (*date_string))
483 		date_string++;
484 
485 	/* If it starts with a digit, it's either an ISO 8601 date, or
486 	 * an RFC2822 date without the optional weekday; in the later
487 	 * case, there will be a month name later on, so look for one
488 	 * of the month-start letters.
489 	 */
490 	if (g_ascii_isdigit (*date_string) &&
491 	    !strpbrk (date_string, "JFMASOND"))
492 		success = parse_iso8601_date (date, date_string);
493 	else
494 		success = parse_textual_date (date, date_string);
495 
496 	if (!success) {
497 		g_slice_free (SoupDate, date);
498 		return NULL;
499 	}
500 
501 	if (date->year < 1 || date->year > 9999 ||
502 	    date->month < 1 || date->month > 12 ||
503 	    date->day < 1 ||
504 	    date->day > days_in_month (date->month, date->year) ||
505 	    date->hour < 0 || date->hour > 24 ||
506 	    date->minute < 0 || date->minute > 59 ||
507 	    date->second < 0 || date->second > 61) {
508 		soup_date_free (date);
509 		return NULL;
510 	}
511 	if (date->hour == 24) {
512 		/* ISO8601 allows this explicitly. We allow it for
513 		 * other types as well just for simplicity.
514 		 */
515 		if (date->minute == 0 && date->second == 0)
516 			soup_date_fixup (date);
517 		else {
518 			soup_date_free (date);
519 			return NULL;
520 		}
521 	}
522 
523 	return date;
524 }
525 
526 /**
527  * soup_date_new_from_time_t:
528  * @when: a <type>time_t</type>
529  *
530  * Creates a #SoupDate corresponding to @when
531  *
532  * Return value: a new #SoupDate
533  **/
534 SoupDate *
soup_date_new_from_time_t(time_t when)535 soup_date_new_from_time_t (time_t when)
536 {
537 	struct tm tm;
538 
539 #ifdef HAVE_GMTIME_R
540 	gmtime_r (&when, &tm);
541 #else
542 	tm = *gmtime (&when);
543 #endif
544 
545 	return soup_date_new (tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
546 			      tm.tm_hour, tm.tm_min, tm.tm_sec);
547 }
548 
549 static const char *
soup_date_weekday(SoupDate * date)550 soup_date_weekday (SoupDate *date)
551 {
552 	/* Proleptic Gregorian 0001-01-01 was a Monday, which
553 	 * corresponds to 1 in the days[] array.
554 	 */
555 	return days[rata_die_day (date) % 7];
556 }
557 
558 /**
559  * soup_date_to_string:
560  * @date: a #SoupDate
561  * @format: the format to generate the date in
562  *
563  * Converts @date to a string in the format described by @format.
564  *
565  * Return value: @date as a string
566  **/
567 char *
soup_date_to_string(SoupDate * date,SoupDateFormat format)568 soup_date_to_string (SoupDate *date, SoupDateFormat format)
569 {
570 	g_return_val_if_fail (date != NULL, NULL);
571 
572 	if (format == SOUP_DATE_HTTP || format == SOUP_DATE_COOKIE) {
573 		/* HTTP and COOKIE formats require UTC timestamp, so coerce
574 		 * @date if it's non-UTC.
575 		 */
576 		SoupDate utcdate;
577 
578 		if (date->offset != 0) {
579 			memcpy (&utcdate, date, sizeof (SoupDate));
580 			utcdate.minute += utcdate.offset;
581 			utcdate.offset = 0;
582 			utcdate.utc = TRUE;
583 			soup_date_fixup (&utcdate);
584 			date = &utcdate;
585 		}
586 
587 		switch (format) {
588 		case SOUP_DATE_HTTP:
589 			/* "Sun, 06 Nov 1994 08:49:37 GMT" */
590 			return g_strdup_printf (
591 				"%s, %02d %s %04d %02d:%02d:%02d GMT",
592 				soup_date_weekday (date), date->day,
593 				months[date->month - 1], date->year,
594 				date->hour, date->minute, date->second);
595 
596 		case SOUP_DATE_COOKIE:
597 			/* "Sun, 06-Nov-1994 08:49:37 GMT" */
598 			return g_strdup_printf (
599 				"%s, %02d-%s-%04d %02d:%02d:%02d GMT",
600 				soup_date_weekday (date), date->day,
601 				months[date->month - 1], date->year,
602 				date->hour, date->minute, date->second);
603 
604 		default:
605 			g_return_val_if_reached (NULL);
606 		}
607 	} else if (format == SOUP_DATE_ISO8601_XMLRPC) {
608 		/* Always "floating", ignore offset */
609 		return g_strdup_printf ("%04d%02d%02dT%02d:%02d:%02d",
610 					date->year, date->month, date->day,
611 					date->hour, date->minute, date->second);
612 	} else {
613 		int hour_offset, minute_offset;
614 		char zone[8], sign;
615 
616 		/* For other ISO8601 formats or RFC2822, use the
617 		 * offset given in @date. For ISO8601 formats, use "Z"
618 		 * for UTC, +-offset for non-UTC, and nothing for
619 		 * floating. For RFC2822, use +-offset for UTC or
620 		 * non-UTC, and -0000 for floating.
621 		 */
622 		hour_offset = abs (date->offset) / 60;
623 		minute_offset = abs (date->offset) - hour_offset * 60;
624 
625 		switch (format) {
626 		case SOUP_DATE_ISO8601_COMPACT:
627 			/* "19941106T084937[zone]" */
628 			if (date->utc)
629 				strcpy (zone, "Z");
630 			else if (date->offset) {
631 				g_snprintf (zone, sizeof (zone), "%c%02d%02d",
632 					    date->offset > 0 ? '-' : '+',
633 					    hour_offset, minute_offset);
634 			} else
635 				*zone = '\0';
636 
637 			return g_strdup_printf (
638 				"%04d%02d%02dT%02d%02d%02d%s",
639 				date->year, date->month, date->day,
640 				date->hour, date->minute, date->second,
641 				zone);
642 
643 		case SOUP_DATE_ISO8601_FULL:
644 			/* "1994-11-06T08:49:37[zone]" */
645 			if (date->utc)
646 				strcpy (zone, "Z");
647 			else if (date->offset) {
648 				g_snprintf (zone, sizeof (zone), "%c%02d:%02d",
649 					    date->offset > 0 ? '-' : '+',
650 					    hour_offset, minute_offset);
651 			} else
652 				*zone = '\0';
653 
654 			return g_strdup_printf (
655 				"%04d-%02d-%02dT%02d:%02d:%02d%s",
656 				date->year, date->month, date->day,
657 				date->hour, date->minute, date->second,
658 				zone);
659 
660 		case SOUP_DATE_RFC2822:
661 			/* "Sun, 6 Nov 1994 09:49:37 -0100" */
662 			if (date->offset)
663 				sign = (date->offset > 0) ? '-' : '+';
664 			else
665 				sign = date->utc ? '+' : '-';
666 			return g_strdup_printf (
667 				"%s, %d %s %04d %02d:%02d:%02d %c%02d%02d",
668 				soup_date_weekday (date), date->day,
669 				months[date->month - 1], date->year,
670 				date->hour, date->minute, date->second,
671 				sign, hour_offset, minute_offset);
672 
673 		default:
674 			return NULL;
675 		}
676 	}
677 }
678 
679 /**
680  * soup_date_to_time_t:
681  * @date: a #SoupDate
682  *
683  * Converts @date to a <type>time_t</type>, assumming it to be in
684  * UTC.
685  *
686  * If @date is not representable as a <type>time_t</type>, it will be
687  * clamped into range. (In particular, some HTTP cookies have
688  * expiration dates after "Y2.038k" (2038-01-19T03:14:07Z).)
689  *
690  * Return value: @date as a <type>time_t</type>
691  **/
692 time_t
soup_date_to_time_t(SoupDate * date)693 soup_date_to_time_t (SoupDate *date)
694 {
695 	GDateTime *datetime;
696 	gint64 seconds;
697 
698 	g_return_val_if_fail (date != NULL, 0);
699 
700 	if (date->year < 1970)
701 		return 0;
702 
703 	/* If the year is later than 2038, we're guaranteed to
704 	 * overflow a 32-bit time_t. (If it's exactly 2038, we'll
705 	 * *probably* overflow, but only by a little, and it's easiest
706 	 * to just clamp down the value if it's above G_MAXINT32.
707 	 */
708 	if (sizeof (time_t) == 4 && date->year > 2038)
709 		return (time_t)G_MAXINT32;
710 
711 	datetime = g_date_time_new_utc (date->year,
712 					date->month,
713 					date->day,
714 					date->hour,
715 					date->minute,
716 					date->second);
717 
718 	seconds = g_date_time_to_unix (datetime);
719 
720 	g_date_time_unref (datetime);
721 
722 	return (time_t) (sizeof (time_t) == 4 ? MIN(seconds, G_MAXINT32) : seconds);
723 }
724 
725 G_GNUC_BEGIN_IGNORE_DEPRECATIONS
726 /**
727  * soup_date_to_timeval:
728  * @date: a #SoupDate
729  * @time: (out): a #GTimeVal structure in which to store the converted time.
730  *
731  * Converts @date to a #GTimeVal.
732  *
733  * Deprecated: Do not use #GTimeVal, as it's not Y2038-safe.
734  *
735  * Since: 2.24
736  */
737 void
soup_date_to_timeval(SoupDate * date,GTimeVal * time)738 soup_date_to_timeval (SoupDate *date, GTimeVal *time)
739 {
740 	g_return_if_fail (date != NULL);
741 	g_return_if_fail (time != NULL);
742 
743 	/* FIXME: offset, etc */
744 
745 	time->tv_sec = rata_die_day (date) - TIME_T_EPOCH_RATA_DIE_DAY;
746 	time->tv_sec = ((((time->tv_sec * 24) + date->hour) * 60) + date->minute) * 60 + date->second;
747 	time->tv_usec = 0;
748 }
749 G_GNUC_END_IGNORE_DEPRECATIONS
750 
751 /**
752  * soup_date_is_past:
753  * @date: a #SoupDate
754  *
755  * Determines if @date is in the past.
756  *
757  * Return value: %TRUE if @date is in the past
758  *
759  * Since: 2.24
760  **/
761 gboolean
soup_date_is_past(SoupDate * date)762 soup_date_is_past (SoupDate *date)
763 {
764 	g_return_val_if_fail (date != NULL, TRUE);
765 
766 	/* optimization */
767 	if (date->year < 2020)
768 		return TRUE;
769 
770 	return soup_date_to_time_t (date) < time (NULL);
771 }
772 
773 /**
774  * soup_date_get_year:
775  * @date: a #SoupDate
776  *
777  * Gets @date's year.
778  *
779  * Return value: @date's year
780  *
781  * Since: 2.32
782  **/
783 int
soup_date_get_year(SoupDate * date)784 soup_date_get_year (SoupDate *date)
785 {
786 	return date->year;
787 }
788 
789 /**
790  * soup_date_get_month:
791  * @date: a #SoupDate
792  *
793  * Gets @date's month.
794  *
795  * Return value: @date's month
796  *
797  * Since: 2.32
798  **/
799 int
soup_date_get_month(SoupDate * date)800 soup_date_get_month (SoupDate *date)
801 {
802 	return date->month;
803 }
804 
805 /**
806  * soup_date_get_day:
807  * @date: a #SoupDate
808  *
809  * Gets @date's day.
810  *
811  * Return value: @date's day
812  *
813  * Since: 2.32
814  **/
815 int
soup_date_get_day(SoupDate * date)816 soup_date_get_day (SoupDate *date)
817 {
818 	return date->day;
819 }
820 
821 /**
822  * soup_date_get_hour:
823  * @date: a #SoupDate
824  *
825  * Gets @date's hour.
826  *
827  * Return value: @date's hour
828  *
829  * Since: 2.32
830  **/
831 int
soup_date_get_hour(SoupDate * date)832 soup_date_get_hour (SoupDate *date)
833 {
834 	return date->hour;
835 }
836 
837 /**
838  * soup_date_get_minute:
839  * @date: a #SoupDate
840  *
841  * Gets @date's minute.
842  *
843  * Return value: @date's minute
844  *
845  * Since: 2.32
846  **/
847 int
soup_date_get_minute(SoupDate * date)848 soup_date_get_minute (SoupDate *date)
849 {
850 	return date->minute;
851 }
852 
853 /**
854  * soup_date_get_second:
855  * @date: a #SoupDate
856  *
857  * Gets @date's second.
858  *
859  * Return value: @date's second
860  *
861  * Since: 2.32
862  **/
863 int
soup_date_get_second(SoupDate * date)864 soup_date_get_second (SoupDate *date)
865 {
866 	return date->second;
867 }
868 
869 /**
870  * soup_date_get_utc:
871  * @date: a #SoupDate
872  *
873  * Gets @date's UTC flag
874  *
875  * Return value: %TRUE if @date is UTC.
876  *
877  * Since: 2.32
878  **/
879 gboolean
soup_date_get_utc(SoupDate * date)880 soup_date_get_utc (SoupDate *date)
881 {
882 	return date->utc;
883 }
884 
885 /**
886  * soup_date_get_offset:
887  * @date: a #SoupDate
888  *
889  * Gets @date's offset from UTC.
890  *
891  * Return value: @date's offset from UTC. If soup_date_get_utc()
892  * returns %FALSE but soup_date_get_offset() returns 0, that means the
893  * date is a "floating" time with no associated offset information.
894  *
895  * Since: 2.32
896  **/
897 int
soup_date_get_offset(SoupDate * date)898 soup_date_get_offset (SoupDate *date)
899 {
900 	return date->offset;
901 }
902 
903 /**
904  * soup_date_copy:
905  * @date: a #SoupDate
906  *
907  * Copies @date.
908  *
909  * Since: 2.24
910  **/
911 SoupDate *
soup_date_copy(SoupDate * date)912 soup_date_copy (SoupDate *date)
913 {
914 	SoupDate *copy;
915 
916 	g_return_val_if_fail (date != NULL, NULL);
917 
918 	copy = g_slice_new (SoupDate);
919 	memcpy (copy, date, sizeof (SoupDate));
920 	return copy;
921 }
922 
923 /**
924  * soup_date_free:
925  * @date: a #SoupDate
926  *
927  * Frees @date.
928  *
929  * Since: 2.24
930  **/
931 void
soup_date_free(SoupDate * date)932 soup_date_free (SoupDate *date)
933 {
934 	g_return_if_fail (date != NULL);
935 
936 	g_slice_free (SoupDate, date);
937 }
938