1 /* Miscellaneous time-related utilities
2  *
3  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
4  *
5  * This library is free software: you can redistribute it and/or modify it
6  * under the terms of the GNU Lesser General Public License as published by
7  * the Free Software Foundation.
8  *
9  * This library is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
12  * for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public License
15  * along with this library. If not, see <http://www.gnu.org/licenses/>.
16  *
17  * Authors: Federico Mena <federico@ximian.com>
18  *          Miguel de Icaza <miguel@ximian.com>
19  *          Damon Chaplin <damon@ximian.com>
20  */
21 
22 #include <string.h>
23 #include <ctype.h>
24 #include "e-cal-time-util.h"
25 
26 #ifdef G_OS_WIN32
27 #ifdef gmtime_r
28 #undef gmtime_r
29 #endif
30 
31 /* The gmtime() in Microsoft's C library is MT-safe */
32 #define gmtime_r(tp,tmp) (gmtime(tp)?(*(tmp)=*gmtime(tp),(tmp)):0)
33 #endif
34 
35 #define REFORMATION_DAY 639787	/* First day of the reformation, counted from 1 Jan 1 */
36 #define MISSING_DAYS 11		/* They corrected out 11 days */
37 #define THURSDAY 4		/* First day of reformation */
38 #define SATURDAY 6		/* Offset value; 1 Jan 1 was a Saturday */
39 #define ISODATE_LENGTH 17 /* 4+2+2+1+2+2+2+1 + 1 */
40 
41 /* Number of days in a month, using 0 (Jan) to 11 (Dec). For leap years,
42  * add 1 to February (month 1). */
43 static const gint days_in_month[12] = {
44 	31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
45 };
46 
47 /**************************************************************************
48  * time_t manipulation functions.
49  *
50  * NOTE: these use the Unix timezone functions like mktime() and localtime()
51  * and so should not be used in Evolution. New Evolution code should use
52  * ICalTime values rather than time_t values wherever possible.
53  **************************************************************************/
54 
55 /**
56  * time_add_day:
57  * @time: A time_t value.
58  * @days: Number of days to add.
59  *
60  * Adds a day onto the time, using local time.
61  * Note that if clocks go forward due to daylight savings time, there are
62  * some non-existent local times, so the hour may be changed to make it a
63  * valid time. This also means that it may not be wise to keep calling
64  * time_add_day() to step through a certain period - if the hour gets changed
65  * to make it valid time, any further calls to time_add_day() will also return
66  * this hour, which may not be what you want.
67  *
68  * Returns: a time_t value containing @time plus the days added.
69  */
70 time_t
time_add_day(time_t time,gint days)71 time_add_day (time_t time,
72               gint days)
73 {
74 	struct tm *tm;
75 
76 	tm = localtime (&time);
77 	tm->tm_mday += days;
78 	tm->tm_isdst = -1;
79 
80 	return mktime (tm);
81 }
82 
83 /**
84  * time_add_week:
85  * @time: A time_t value.
86  * @weeks: Number of weeks to add.
87  *
88  * Adds the given number of weeks to a time value.
89  *
90  * Returns: a time_t value containing @time plus the weeks added.
91  */
92 time_t
time_add_week(time_t time,gint weeks)93 time_add_week (time_t time,
94                gint weeks)
95 {
96 	return time_add_day (time, weeks * 7);
97 }
98 
99 /**
100  * time_day_begin:
101  * @t: A time_t value.
102  *
103  * Returns the start of the day, according to the local time.
104  *
105  * Returns: the time corresponding to the beginning of the day.
106  */
107 time_t
time_day_begin(time_t t)108 time_day_begin (time_t t)
109 {
110 	struct tm tm;
111 
112 	tm = *localtime (&t);
113 	tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
114 	tm.tm_isdst = -1;
115 
116 	return mktime (&tm);
117 }
118 
119 /**
120  * time_day_end:
121  * @t: A time_t value.
122  *
123  * Returns the end of the day, according to the local time.
124  *
125  * Returns: the time corresponding to the end of the day.
126  */
127 time_t
time_day_end(time_t t)128 time_day_end (time_t t)
129 {
130 	struct tm tm;
131 
132 	tm = *localtime (&t);
133 	tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
134 	tm.tm_mday++;
135 	tm.tm_isdst = -1;
136 
137 	return mktime (&tm);
138 }
139 
140 /**************************************************************************
141  * time_t manipulation functions, using timezones in libical.
142  *
143  * NOTE: these are only here to make the transition to the timezone
144  * functions easier. New code should use ICalTime values rather than
145  * time_t values wherever possible.
146  **************************************************************************/
147 
148 /**
149  * time_add_day_with_zone:
150  * @time: A time_t value.
151  * @days: Number of days to add.
152  * @zone: Timezone to use.
153  *
154  * Adds or subtracts a number of days to/from the given time_t value, using
155  * the given timezone.
156  * NOTE: this function is only here to make the transition to the timezone
157  * functions easier. New code should use ICalTime values and
158  * i_cal_time_adjust() to add or subtract days, hours, minutes & seconds.
159  *
160  * Returns: a time_t value containing @time plus the days added.
161  */
162 time_t
time_add_day_with_zone(time_t time,gint days,const ICalTimezone * zone)163 time_add_day_with_zone (time_t time,
164 			gint days,
165 			const ICalTimezone *zone)
166 {
167 	ICalTime *tt;
168 	time_t res;
169 
170 	/* Convert to an ICalTime. */
171 	tt = i_cal_time_new_from_timet_with_zone (time, FALSE, (ICalTimezone *) zone);
172 
173 	/* Add/subtract the number of days. */
174 	i_cal_time_adjust (tt, days, 0, 0, 0);
175 
176 	/* Convert back to a time_t. */
177 	res = i_cal_time_as_timet_with_zone (tt, (ICalTimezone *) zone);
178 
179 	g_object_unref (tt);
180 
181 	return res;
182 }
183 
184 /**
185  * time_add_week_with_zone:
186  * @time: A time_t value.
187  * @weeks: Number of weeks to add.
188  * @zone: Timezone to use.
189  *
190  * Adds or subtracts a number of weeks to/from the given time_t value, using
191  * the given timezone.
192  * NOTE: this function is only here to make the transition to the timezone
193  * functions easier. New code should use ICalTime values and
194  * i_cal_time_adjust() to add or subtract days, hours, minutes & seconds.
195  *
196  * Returns: a time_t value containing @time plus the weeks added.
197  */
198 time_t
time_add_week_with_zone(time_t time,gint weeks,const ICalTimezone * zone)199 time_add_week_with_zone (time_t time,
200 			 gint weeks,
201 			 const ICalTimezone *zone)
202 {
203 	return time_add_day_with_zone (time, weeks * 7, zone);
204 }
205 
206 /**
207  * time_add_month_with_zone:
208  * @time: A time_t value.
209  * @months: Number of months to add.
210  * @zone: Timezone to use.
211  *
212  * Adds or subtracts a number of months to/from the given time_t value, using
213  * the given timezone.
214  *
215  * If the day would be off the end of the month (e.g. adding 1 month to
216  * 30th January, would lead to an invalid day, 30th February), it moves it
217  * down to the last day in the month, e.g. 28th Feb (or 29th in a leap year.)
218  *
219  * NOTE: this function is only here to make the transition to the timezone
220  * functions easier. New code should use ICalTime values and
221  * i_cal_time_adjust() to add or subtract days, hours, minutes & seconds.
222  *
223  * Returns: a time_t value containing @time plus the months added.
224  */
225 time_t
time_add_month_with_zone(time_t time,gint months,const ICalTimezone * zone)226 time_add_month_with_zone (time_t time,
227 			  gint months,
228 			  const ICalTimezone *zone)
229 {
230 	ICalTime *tt;
231 	gint day, days_in_month;
232 	time_t res;
233 
234 	/* Convert to an ICalTime. */
235 	tt = i_cal_time_new_from_timet_with_zone (time, FALSE, (ICalTimezone *) zone);
236 
237 	/* Add on the number of months. */
238 	i_cal_time_set_month (tt, i_cal_time_get_month (tt) + months);
239 
240 	/* Save the day, and set it to 1, so we don't overflow into the next
241 	 * month. */
242 	day = i_cal_time_get_day (tt);
243 	i_cal_time_set_day (tt, 1);
244 
245 	/* Normalize it, fixing any month overflow. */
246 	i_cal_time_normalize_inplace (tt);
247 
248 	/* If we go past the end of a month, set it to the last day. */
249 	days_in_month = time_days_in_month (i_cal_time_get_year (tt), i_cal_time_get_month (tt) - 1);
250 	if (day > days_in_month)
251 		day = days_in_month;
252 
253 	i_cal_time_set_day (tt, day);
254 
255 	/* Convert back to a time_t. */
256 	res = i_cal_time_as_timet_with_zone (tt, (ICalTimezone *) zone);
257 
258 	g_object_unref (tt);
259 
260 	return res;
261 }
262 
263 /**
264  * time_year_begin_with_zone:
265  * @time: A time_t value.
266  * @zone: Timezone to use.
267  *
268  * Returns the start of the year containing the given time_t, using the given
269  * timezone.
270  * NOTE: this function is only here to make the transition to the timezone
271  * functions easier. New code should use ICalTime values and
272  * i_cal_time_adjust() to add or subtract days, hours, minutes & seconds.
273  *
274  * Returns: the beginning of the year.
275  */
276 time_t
time_year_begin_with_zone(time_t time,const ICalTimezone * zone)277 time_year_begin_with_zone (time_t time,
278 			   const ICalTimezone *zone)
279 {
280 	ICalTime *tt;
281 	time_t res;
282 
283 	/* Convert to an ICalTime. */
284 	tt = i_cal_time_new_from_timet_with_zone (time, FALSE, (ICalTimezone *) zone);
285 
286 	/* Set it to the start of the year. */
287 	i_cal_time_set_month (tt, 1);
288 	i_cal_time_set_day (tt, 1);
289 	i_cal_time_set_hour (tt, 0);
290 	i_cal_time_set_minute (tt, 0);
291 	i_cal_time_set_second (tt, 0);
292 
293 	/* Convert back to a time_t. */
294 	res = i_cal_time_as_timet_with_zone (tt, (ICalTimezone *) zone);
295 
296 	g_object_unref (tt);
297 
298 	return res;
299 }
300 
301 /**
302  * time_month_begin_with_zone:
303  * @time: A time_t value.
304  * @zone: Timezone to use.
305  *
306  * Returns the start of the month containing the given time_t, using the given
307  * timezone.
308  * NOTE: this function is only here to make the transition to the timezone
309  * functions easier. New code should use ICalTime values and
310  * i_cal_time_adjust() to add or subtract days, hours, minutes & seconds.
311  *
312  * Returns: the beginning of the month.
313  */
314 time_t
time_month_begin_with_zone(time_t time,const ICalTimezone * zone)315 time_month_begin_with_zone (time_t time,
316 			    const ICalTimezone *zone)
317 {
318 	ICalTime *tt;
319 	time_t res;
320 
321 	/* Convert to an ICalTime. */
322 	tt = i_cal_time_new_from_timet_with_zone (time, FALSE, (ICalTimezone *) zone);
323 
324 	/* Set it to the start of the month. */
325 	i_cal_time_set_day (tt, 1);
326 	i_cal_time_set_hour (tt, 0);
327 	i_cal_time_set_minute (tt, 0);
328 	i_cal_time_set_second (tt, 0);
329 
330 	/* Convert back to a time_t. */
331 	res = i_cal_time_as_timet_with_zone (tt, (ICalTimezone *) zone);
332 
333 	g_object_unref (tt);
334 
335 	return res;
336 }
337 
338 /**
339  * time_week_begin_with_zone:
340  * @time: A time_t value.
341  * @week_start_day: Day to use as the starting of the week.
342  * @zone: Timezone to use.
343  *
344  * Returns the start of the week containing the given time_t, using the given
345  * timezone. week_start_day should use the same values as mktime(),
346  * i.e. 0 (Sun) to 6 (Sat).
347  * NOTE: this function is only here to make the transition to the timezone
348  * functions easier. New code should use ICalTime values and
349  * i_cal_time_adjust() to add or subtract days, hours, minutes & seconds.
350  *
351  * Returns: the beginning of the week.
352  */
353 time_t
time_week_begin_with_zone(time_t time,gint week_start_day,const ICalTimezone * zone)354 time_week_begin_with_zone (time_t time,
355 			   gint week_start_day,
356 			   const ICalTimezone *zone)
357 {
358 	ICalTime *tt;
359 	gint weekday, offset;
360 	time_t res;
361 
362 	/* Convert to an ICalTime. */
363 	tt = i_cal_time_new_from_timet_with_zone (time, FALSE, (ICalTimezone *) zone);
364 
365 	/* Get the weekday. */
366 	weekday = time_day_of_week (i_cal_time_get_day (tt), i_cal_time_get_month (tt) - 1, i_cal_time_get_year (tt));
367 
368 	/* Calculate the current offset from the week start day. */
369 	offset = (weekday + 7 - week_start_day) % 7;
370 
371 	/* Set it to the start of the month. */
372 	i_cal_time_set_day (tt, i_cal_time_get_day (tt) - offset);
373 	i_cal_time_set_hour (tt, 0);
374 	i_cal_time_set_minute (tt, 0);
375 	i_cal_time_set_second (tt, 0);
376 
377 	/* Normalize it, to fix any overflow. */
378 	i_cal_time_normalize_inplace (tt);
379 
380 	/* Convert back to a time_t. */
381 	res = i_cal_time_as_timet_with_zone (tt, (ICalTimezone *) zone);
382 
383 	g_object_unref (tt);
384 
385 	return res;
386 }
387 
388 /**
389  * time_day_begin_with_zone:
390  * @time: A time_t value.
391  * @zone: Timezone to use.
392  *
393  * Returns the start of the day containing the given time_t, using the given
394  * timezone.
395  * NOTE: this function is only here to make the transition to the timezone
396  * functions easier. New code should use ICalTime values and
397  * i_cal_time_adjust() to add or subtract days, hours, minutes & seconds.
398  *
399  * Returns: the beginning of the day.
400  */
401 time_t
time_day_begin_with_zone(time_t time,const ICalTimezone * zone)402 time_day_begin_with_zone (time_t time,
403 			  const ICalTimezone *zone)
404 {
405 	ICalTime *tt;
406 	time_t new_time;
407 
408 	/* Convert to an ICalTime. */
409 	tt = i_cal_time_new_from_timet_with_zone (time, FALSE, (ICalTimezone *) zone);
410 
411 	/* Set it to the start of the day. */
412 	i_cal_time_set_hour (tt, 0);
413 	i_cal_time_set_minute (tt, 0);
414 	i_cal_time_set_second (tt, 0);
415 
416 	/* Convert back to a time_t and make sure the time is in the past. */
417 	while (new_time = i_cal_time_as_timet_with_zone (tt, (ICalTimezone *) zone), new_time > time) {
418 		i_cal_time_adjust (tt, 0, -1, 0, 0);
419 	}
420 
421 	g_object_unref (tt);
422 
423 	return new_time;
424 }
425 
426 /**
427  * time_day_end_with_zone:
428  * @time: A time_t value.
429  * @zone: Timezone to use.
430  *
431  * Returns the end of the day containing the given time_t, using the given
432  * timezone. (The end of the day is the start of the next day.)
433  * NOTE: this function is only here to make the transition to the timezone
434  * functions easier. New code should use ICalTime values and
435  * i_cal_time_adjust() to add or subtract days, hours, minutes & seconds.
436  *
437  * Returns: the end of the day.
438  */
439 time_t
time_day_end_with_zone(time_t time,const ICalTimezone * zone)440 time_day_end_with_zone (time_t time,
441 			const ICalTimezone *zone)
442 {
443 	ICalTime *tt;
444 	time_t new_time;
445 
446 	/* Convert to an ICalTime. */
447 	tt = i_cal_time_new_from_timet_with_zone (time, FALSE, (ICalTimezone *) zone);
448 
449 	/* Set it to the start of the next day. */
450 	i_cal_time_set_hour (tt, 0);
451 	i_cal_time_set_minute (tt, 0);
452 	i_cal_time_set_second (tt, 0);
453 
454 	i_cal_time_adjust (tt, 1, 0, 0, 0);
455 
456 	/* Convert back to a time_t and make sure the time is in the future. */
457 	while (new_time = i_cal_time_as_timet_with_zone (tt, (ICalTimezone *) zone), new_time <= time) {
458 		i_cal_time_adjust (tt, 0, 1, 0, 0);
459 	}
460 
461 	g_object_unref (tt);
462 
463 	return new_time;
464 }
465 
466 /**
467  * time_to_gdate_with_zone:
468  * @date: Destination #GDate value.
469  * @time: A time value.
470  * @zone: (nullable): Desired timezone for destination @date, or %NULL if
471  *    the UTC timezone is desired.
472  *
473  * Converts a time_t value to a #GDate structure using the specified timezone.
474  * This is analogous to g_date_set_time() but takes the timezone into account.
475  **/
476 void
time_to_gdate_with_zone(GDate * date,time_t time,const ICalTimezone * zone)477 time_to_gdate_with_zone (GDate *date,
478 			 time_t time,
479 			 const ICalTimezone *zone)
480 {
481 	ICalTime *tt;
482 
483 	g_return_if_fail (date != NULL);
484 	g_return_if_fail (time != -1);
485 
486 	tt = i_cal_time_new_from_timet_with_zone (
487 		time, FALSE,
488 		zone ? (ICalTimezone *) zone : i_cal_timezone_get_utc_timezone ());
489 
490 	g_date_set_dmy (date, i_cal_time_get_day (tt), i_cal_time_get_month (tt), i_cal_time_get_year (tt));
491 
492 	g_object_unref (tt);
493 }
494 
495 /**************************************************************************
496  * General time functions.
497  **************************************************************************/
498 
499 /**
500  * time_days_in_month:
501  * @year: The year.
502  * @month: The month.
503  *
504  * Returns the number of days in the month. Year is the normal year, e.g. 2001.
505  * Month is 0 (Jan) to 11 (Dec).
506  *
507  * Returns: number of days in the given month/year.
508  */
509 gint
time_days_in_month(gint year,gint month)510 time_days_in_month (gint year,
511                     gint month)
512 {
513 	gint days;
514 
515 	g_return_val_if_fail (year >= 1900, 0);
516 	g_return_val_if_fail ((month >= 0) && (month < 12), 0);
517 
518 	days = days_in_month[month];
519 	if (month == 1 && time_is_leap_year (year))
520 		days++;
521 
522 	return days;
523 }
524 
525 /**
526  * time_day_of_year:
527  * @day: The day.
528  * @month: The month.
529  * @year: The year.
530  *
531  * Returns the 1-based day number within the year of the specified date.
532  * Year is the normal year, e.g. 2001. Month is 0 to 11.
533  *
534  * Returns: the day of the year.
535  */
536 gint
time_day_of_year(gint day,gint month,gint year)537 time_day_of_year (gint day,
538                   gint month,
539                   gint year)
540 {
541 	gint i;
542 
543 	for (i = 0; i < month; i++) {
544 		day += days_in_month[i];
545 
546 		if (i == 1 && time_is_leap_year (year))
547 			day++;
548 	}
549 
550 	return day;
551 }
552 
553 /**
554  * time_day_of_week:
555  * @day: The day.
556  * @month: The month.
557  * @year: The year.
558  *
559  * Returns the day of the week for the specified date, 0 (Sun) to 6 (Sat).
560  * For the days that were removed on the Gregorian reformation, it returns
561  * Thursday. Year is the normal year, e.g. 2001. Month is 0 to 11.
562  *
563  * Returns: the day of the week for the given date.
564  */
565 gint
time_day_of_week(gint day,gint month,gint year)566 time_day_of_week (gint day,
567                   gint month,
568                   gint year)
569 {
570 	gint n;
571 
572 	n = (year - 1) * 365 + time_leap_years_up_to (year - 1)
573 	  + time_day_of_year (day, month, year);
574 
575 	if (n < REFORMATION_DAY)
576 		return (n - 1 + SATURDAY) % 7;
577 
578 	if (n >= (REFORMATION_DAY + MISSING_DAYS))
579 		return (n - 1 + SATURDAY - MISSING_DAYS) % 7;
580 
581 	return THURSDAY;
582 }
583 
584 /**
585  * time_is_leap_year:
586  * @year: The year.
587  *
588  * Returns whether the specified year is a leap year. Year is the normal year,
589  * e.g. 2001.
590  *
591  * Returns: TRUE if the year is leap, FALSE if not.
592  */
593 gboolean
time_is_leap_year(gint year)594 time_is_leap_year (gint year)
595 {
596 	if (year <= 1752)
597 		return !(year % 4);
598 	else
599 		return (!(year % 4) && (year % 100)) || !(year % 400);
600 }
601 
602 /**
603  * time_leap_years_up_to:
604  * @year: The year.
605  *
606  * Returns the number of leap years since year 1 up to (but not including) the
607  * specified year. Year is the normal year, e.g. 2001.
608  *
609  * Returns: number of leap years.
610  */
611 gint
time_leap_years_up_to(gint year)612 time_leap_years_up_to (gint year)
613 {
614 	/* There is normally a leap year every 4 years, except at the turn of
615 	 * centuries since 1700. But there is a leap year on centuries since 1700
616 	 * which are divisible by 400. */
617 	return (year / 4
618 		- ((year > 1700) ? (year / 100 - 17) : 0)
619 		+ ((year > 1600) ? ((year - 1600) / 400) : 0));
620 }
621 
622 /**
623  * isodate_from_time_t:
624  * @t: A time value.
625  *
626  * Creates an ISO 8601 UTC representation from a time value.
627  *
628  * Returns: String with the ISO 8601 representation of the UTC time.
629  **/
630 gchar *
isodate_from_time_t(time_t t)631 isodate_from_time_t (time_t t)
632 {
633 	gchar *ret;
634 	struct tm stm;
635 	const gchar fmt[] = "%04d%02d%02dT%02d%02d%02dZ";
636 
637 	gmtime_r (&t, &stm);
638 	ret = g_malloc (ISODATE_LENGTH);
639 	g_snprintf (
640 		ret, ISODATE_LENGTH, fmt,
641 		(stm.tm_year + 1900),
642 		(stm.tm_mon + 1),
643 		stm.tm_mday,
644 		stm.tm_hour,
645 		stm.tm_min,
646 		stm.tm_sec);
647 
648 	return ret;
649 }
650 
651 /**
652  * time_from_isodate:
653  * @str: Date/time value in ISO 8601 format.
654  *
655  * Converts an ISO 8601 UTC time string into a time_t value.
656  *
657  * Returns: Time_t corresponding to the specified ISO string.
658  * Note that we only allow UTC times at present.
659  **/
660 time_t
time_from_isodate(const gchar * str)661 time_from_isodate (const gchar *str)
662 {
663 	ICalTime *tt;
664 	ICalTimezone *utc_zone;
665 	gint len, i;
666 	time_t res;
667 
668 	g_return_val_if_fail (str != NULL, -1);
669 
670 	/* yyyymmdd[Thhmmss[Z]] */
671 
672 	len = strlen (str);
673 
674 	if (!(len == 8 || len == 15 || len == 16))
675 		return -1;
676 
677 	for (i = 0; i < len; i++)
678 		if (!((i != 8 && i != 15 && isdigit (str[i]))
679 		      || (i == 8 && str[i] == 'T')
680 		      || (i == 15 && str[i] == 'Z')))
681 			return -1;
682 
683 #define digit_at(x,y) (x[y] - '0')
684 
685 	tt = i_cal_time_new_null_time ();
686 
687 	i_cal_time_set_year (tt, digit_at (str, 0) * 1000 +
688 				     digit_at (str, 1) * 100 +
689 				     digit_at (str, 2) * 10 +
690 				     digit_at (str, 3));
691 
692 	i_cal_time_set_month (tt, digit_at (str, 4) * 10 +
693 				      digit_at (str, 5));
694 
695 	i_cal_time_set_day (tt, digit_at (str, 6) * 10 +
696 				    digit_at (str, 7));
697 
698 	if (len > 8) {
699 		i_cal_time_set_hour (tt, digit_at (str, 9) * 10 +
700 					     digit_at (str, 10));
701 		i_cal_time_set_minute (tt, digit_at (str, 11) * 10 +
702 					       digit_at (str, 12));
703 		i_cal_time_set_second (tt, digit_at (str, 13) * 10 +
704 					       digit_at (str, 14));
705 	}
706 
707 	utc_zone = i_cal_timezone_get_utc_timezone ();
708 
709 	res = i_cal_time_as_timet_with_zone (tt, utc_zone);
710 
711 	g_object_unref (tt);
712 
713 	return res;
714 }
715 
716 /**
717  * e_cal_util_icaltime_to_tm:
718  * @itt: An #ICalTime
719  *
720  * Converts an #ICalTime into a GLibc's struct tm.
721  *
722  * Returns: The converted time as a struct tm. All fields will be
723  *    set properly except for tm.tm_yday.
724  *
725  * Since: 2.22
726  */
727 struct tm
e_cal_util_icaltime_to_tm(const ICalTime * itt)728 e_cal_util_icaltime_to_tm (const ICalTime *itt)
729 {
730 	struct tm tm;
731 	ICalTime *tt = (ICalTime *) itt;
732 
733 	memset (&tm, 0, sizeof (struct tm));
734 
735 	g_return_val_if_fail (itt != NULL, tm);
736 
737 	if (!i_cal_time_is_date (tt)) {
738 		tm.tm_sec = i_cal_time_get_second (tt);
739 		tm.tm_min = i_cal_time_get_minute (tt);
740 		tm.tm_hour = i_cal_time_get_hour (tt);
741 	}
742 
743 	tm.tm_mday = i_cal_time_get_day (tt);
744 	tm.tm_mon = i_cal_time_get_month (tt) - 1;
745 	tm.tm_year = i_cal_time_get_year (tt) - 1900;
746 	tm.tm_wday = time_day_of_week (i_cal_time_get_day (tt), i_cal_time_get_month (tt) - 1, i_cal_time_get_year (tt));
747 	tm.tm_isdst = -1;
748 
749 	return tm;
750 }
751 
752 /**
753  * e_cal_util_icaltime_to_tm_with_zone:
754  * @itt: A time value.
755  * @from_zone: Source timezone.
756  * @to_zone: Destination timezone.
757  *
758  * Converts a time value from one timezone to another, and returns a struct tm
759  * representation of the time.
760  *
761  * Returns: The converted time as a struct tm. All fields will be
762  *    set properly except for tm.tm_yday.
763  *
764  * Since: 2.22
765  **/
766 struct tm
e_cal_util_icaltime_to_tm_with_zone(const ICalTime * itt,const ICalTimezone * from_zone,const ICalTimezone * to_zone)767 e_cal_util_icaltime_to_tm_with_zone (const ICalTime *itt,
768 				     const ICalTimezone *from_zone,
769 				     const ICalTimezone *to_zone)
770 {
771 	struct tm tm;
772 	ICalTime *itt_copy;
773 
774 	memset (&tm, 0, sizeof (tm));
775 	tm.tm_isdst = -1;
776 
777 	g_return_val_if_fail (itt != NULL, tm);
778 
779 	itt_copy = i_cal_time_clone (itt);
780 
781 	i_cal_time_convert_timezone (itt_copy, (ICalTimezone *) from_zone, (ICalTimezone *) to_zone);
782 	tm = e_cal_util_icaltime_to_tm (itt_copy);
783 	g_object_unref (itt_copy);
784 
785 	return tm;
786 }
787 
788 /**
789  * e_cal_util_tm_to_icaltime:
790  * @tm: A struct tm.
791  * @is_date: Whether the given time is a date only or not.
792  *
793  * Converts a struct tm into an #ICalTime. Free the returned object
794  * with g_object_unref(), when no longer needed.
795  *
796  * Returns: (transfer full): The converted time as an #ICalTime.
797  *
798  * Since: 2.22
799  */
800 ICalTime *
e_cal_util_tm_to_icaltime(struct tm * tm,gboolean is_date)801 e_cal_util_tm_to_icaltime (struct tm *tm,
802 			   gboolean is_date)
803 {
804 	ICalTime *itt;
805 
806 	g_return_val_if_fail (tm != NULL, NULL);
807 
808 	itt = i_cal_time_new_null_time ();
809 
810 	if (!is_date)
811 		i_cal_time_set_time (itt, tm->tm_hour, tm->tm_min, tm->tm_sec);
812 
813 	i_cal_time_set_date (itt, tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
814 	i_cal_time_set_is_date (itt, is_date);
815 
816 	return itt;
817 }
818