1 /**
2 * @file
3 * Time and date handling routines
4 *
5 * @authors
6 * Copyright (C) 1996-2000 Michael R. Elkins <me@mutt.org>
7 *
8 * @copyright
9 * This program is free software: you can redistribute it and/or modify it under
10 * the terms of the GNU General Public License as published by the Free Software
11 * Foundation, either version 2 of the License, or (at your option) any later
12 * version.
13 *
14 * This program is distributed in the hope that it will be useful, but WITHOUT
15 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
17 * details.
18 *
19 * You should have received a copy of the GNU General Public License along with
20 * this program. If not, see <http://www.gnu.org/licenses/>.
21 */
22
23 /**
24 * @page mutt_date Time and date handling routines
25 *
26 * Some commonly used time and date functions.
27 */
28
29 #include "config.h"
30 #include <stdbool.h>
31 #include <stdint.h>
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <sys/time.h>
35 #include <time.h>
36 #include "date.h"
37 #include "buffer.h"
38 #include "logging.h"
39 #include "memory.h"
40 #include "prex.h"
41 #include "regex3.h"
42 #include "string2.h"
43
44 /**
45 * Weekdays - Day of the week (abbreviated)
46 */
47 static const char *const Weekdays[] = {
48 "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat",
49 };
50
51 /**
52 * Months - Months of the year (abbreviated)
53 */
54 static const char *const Months[] = {
55 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
56 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
57 };
58
59 /**
60 * TimeZones - Lookup table of Time Zones
61 *
62 * @note Keep in alphabetical order
63 */
64 static const struct Tz TimeZones[] = {
65 // clang-format off
66 { "aat", 1, 0, true }, /* Atlantic Africa Time */
67 { "adt", 4, 0, false }, /* Arabia DST */
68 { "ast", 3, 0, false }, /* Arabia */
69 //{ "ast", 4, 0, true }, /* Atlantic */
70 { "bst", 1, 0, false }, /* British DST */
71 { "cat", 1, 0, false }, /* Central Africa */
72 { "cdt", 5, 0, true },
73 { "cest", 2, 0, false }, /* Central Europe DST */
74 { "cet", 1, 0, false }, /* Central Europe */
75 { "cst", 6, 0, true },
76 //{ "cst", 8, 0, false }, /* China */
77 //{ "cst", 9, 30, false }, /* Australian Central Standard Time */
78 { "eat", 3, 0, false }, /* East Africa */
79 { "edt", 4, 0, true },
80 { "eest", 3, 0, false }, /* Eastern Europe DST */
81 { "eet", 2, 0, false }, /* Eastern Europe */
82 { "egst", 0, 0, false }, /* Eastern Greenland DST */
83 { "egt", 1, 0, true }, /* Eastern Greenland */
84 { "est", 5, 0, true },
85 { "gmt", 0, 0, false },
86 { "gst", 4, 0, false }, /* Presian Gulf */
87 { "hkt", 8, 0, false }, /* Hong Kong */
88 { "ict", 7, 0, false }, /* Indochina */
89 { "idt", 3, 0, false }, /* Israel DST */
90 { "ist", 2, 0, false }, /* Israel */
91 //{ "ist", 5, 30, false }, /* India */
92 { "jst", 9, 0, false }, /* Japan */
93 { "kst", 9, 0, false }, /* Korea */
94 { "mdt", 6, 0, true },
95 { "met", 1, 0, false }, /* This is now officially CET */
96 { "met dst", 2, 0, false }, /* MET in Daylight Saving Time */
97 { "msd", 4, 0, false }, /* Moscow DST */
98 { "msk", 3, 0, false }, /* Moscow */
99 { "mst", 7, 0, true },
100 { "nzdt", 13, 0, false }, /* New Zealand DST */
101 { "nzst", 12, 0, false }, /* New Zealand */
102 { "pdt", 7, 0, true },
103 { "pst", 8, 0, true },
104 { "sat", 2, 0, false }, /* South Africa */
105 { "smt", 4, 0, false }, /* Seychelles */
106 { "sst", 11, 0, true }, /* Samoa */
107 //{ "sst", 8, 0, false }, /* Singapore */
108 { "utc", 0, 0, false },
109 { "wat", 0, 0, false }, /* West Africa */
110 { "west", 1, 0, false }, /* Western Europe DST */
111 { "wet", 0, 0, false }, /* Western Europe */
112 { "wgst", 2, 0, true }, /* Western Greenland DST */
113 { "wgt", 3, 0, true }, /* Western Greenland */
114 { "wst", 8, 0, false }, /* Western Australia */
115 // clang-format on
116 };
117
118 /**
119 * compute_tz - Calculate the number of seconds east of UTC
120 * @param g Local time
121 * @param utc UTC time
122 * @retval num Seconds east of UTC
123 *
124 * returns the seconds east of UTC given 'g' and its corresponding gmtime()
125 * representation
126 */
compute_tz(time_t g,struct tm * utc)127 static time_t compute_tz(time_t g, struct tm *utc)
128 {
129 struct tm lt = mutt_date_localtime(g);
130
131 time_t t = (((lt.tm_hour - utc->tm_hour) * 60) + (lt.tm_min - utc->tm_min)) * 60;
132
133 int yday = (lt.tm_yday - utc->tm_yday);
134 if (yday != 0)
135 {
136 /* This code is optimized to negative timezones (West of Greenwich) */
137 if ((yday == -1) || /* UTC passed midnight before localtime */
138 (yday > 1)) /* UTC passed new year before localtime */
139 {
140 t -= (24 * 60 * 60);
141 }
142 else
143 {
144 t += (24 * 60 * 60);
145 }
146 }
147
148 return t;
149 }
150
151 /**
152 * add_tz_offset - Compute and add a timezone offset to an UTC time
153 * @param t UTC time
154 * @param w True if west of UTC, false if east
155 * @param h Number of hours in the timezone
156 * @param m Number of minutes in the timezone
157 * @retval Timezone offset in seconds
158 */
add_tz_offset(time_t t,bool w,time_t h,time_t m)159 static time_t add_tz_offset(time_t t, bool w, time_t h, time_t m)
160 {
161 if ((t != TIME_T_MAX) && (t != TIME_T_MIN))
162 return t + (w ? 1 : -1) * (((time_t) h * 3600) + ((time_t) m * 60));
163 else
164 return t;
165 }
166
167 /**
168 * find_tz - Look up a timezone
169 * @param s Timezone to lookup
170 * @param len Length of the s string
171 * @retval ptr Pointer to the Tz struct
172 * @retval NULL Not found
173 */
find_tz(const char * s,size_t len)174 static const struct Tz *find_tz(const char *s, size_t len)
175 {
176 for (size_t i = 0; i < mutt_array_size(TimeZones); i++)
177 {
178 if (mutt_istrn_equal(TimeZones[i].tzname, s, len))
179 return &TimeZones[i];
180 }
181 return NULL;
182 }
183
184 /**
185 * is_leap_year_feb - Is a given February in a leap year
186 * @param tm Date to be tested
187 * @retval true It's a leap year
188 */
is_leap_year_feb(struct tm * tm)189 static int is_leap_year_feb(struct tm *tm)
190 {
191 if (tm->tm_mon != 1)
192 return 0;
193
194 int y = tm->tm_year + 1900;
195 return ((y & 3) == 0) && (((y % 100) != 0) || ((y % 400) == 0));
196 }
197
198 /**
199 * mutt_date_local_tz - Calculate the local timezone in seconds east of UTC
200 * @param t Time to examine
201 * @retval num Seconds east of UTC
202 *
203 * Returns the local timezone in seconds east of UTC for the time t,
204 * or for the current time if t is zero.
205 */
mutt_date_local_tz(time_t t)206 time_t mutt_date_local_tz(time_t t)
207 {
208 /* Check we haven't overflowed the time (on 32-bit arches) */
209 if ((t == TIME_T_MAX) || (t == TIME_T_MIN))
210 return 0;
211
212 if (t == 0)
213 t = mutt_date_epoch();
214
215 struct tm tm = mutt_date_gmtime(t);
216 return compute_tz(t, &tm);
217 }
218
219 /**
220 * mutt_date_make_time - Convert `struct tm` to `time_t`
221 * @param t Time to convert
222 * @param local Should the local timezone be considered
223 * @retval num Time in Unix format
224 * @retval TIME_T_MIN Error
225 *
226 * Convert a struct tm to time_t, but don't take the local timezone into
227 * account unless "local" is nonzero
228 */
mutt_date_make_time(struct tm * t,bool local)229 time_t mutt_date_make_time(struct tm *t, bool local)
230 {
231 if (!t)
232 return TIME_T_MIN;
233
234 static const int AccumDaysPerMonth[mutt_array_size(Months)] = {
235 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334,
236 };
237
238 /* Prevent an integer overflow, with some arbitrary limits. */
239 if (t->tm_year > 10000)
240 return TIME_T_MAX;
241 if (t->tm_year < -10000)
242 return TIME_T_MIN;
243
244 if ((t->tm_mday < 1) || (t->tm_mday > 31))
245 return TIME_T_MIN;
246 if ((t->tm_hour < 0) || (t->tm_hour > 23) || (t->tm_min < 0) ||
247 (t->tm_min > 59) || (t->tm_sec < 0) || (t->tm_sec > 60))
248 {
249 return TIME_T_MIN;
250 }
251 if (t->tm_year > 9999)
252 return TIME_T_MAX;
253
254 /* Compute the number of days since January 1 in the same year */
255 time_t g = AccumDaysPerMonth[t->tm_mon % mutt_array_size(Months)];
256
257 /* The leap years are 1972 and every 4. year until 2096,
258 * but this algorithm will fail after year 2099 */
259 g += t->tm_mday;
260 if ((t->tm_year % 4) || (t->tm_mon < 2))
261 g--;
262 t->tm_yday = g;
263
264 /* Compute the number of days since January 1, 1970 */
265 g += (t->tm_year - 70) * (time_t) 365;
266 g += (t->tm_year - 69) / 4;
267
268 /* Compute the number of hours */
269 g *= 24;
270 g += t->tm_hour;
271
272 /* Compute the number of minutes */
273 g *= 60;
274 g += t->tm_min;
275
276 /* Compute the number of seconds */
277 g *= 60;
278 g += t->tm_sec;
279
280 if (local)
281 g -= compute_tz(g, t);
282
283 return g;
284 }
285
286 /**
287 * mutt_date_normalize_time - Fix the contents of a struct tm
288 * @param tm Time to correct
289 *
290 * If values have been added/subtracted from a struct tm, it can lead to
291 * invalid dates, e.g. Adding 10 days to the 25th of a month.
292 *
293 * This function will correct any over/under-flow.
294 */
mutt_date_normalize_time(struct tm * tm)295 void mutt_date_normalize_time(struct tm *tm)
296 {
297 if (!tm)
298 return;
299
300 static const char DaysPerMonth[mutt_array_size(Months)] = {
301 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31,
302 };
303 int leap;
304
305 while (tm->tm_sec < 0)
306 {
307 tm->tm_sec += 60;
308 tm->tm_min--;
309 }
310 while (tm->tm_sec >= 60)
311 {
312 tm->tm_sec -= 60;
313 tm->tm_min++;
314 }
315 while (tm->tm_min < 0)
316 {
317 tm->tm_min += 60;
318 tm->tm_hour--;
319 }
320 while (tm->tm_min >= 60)
321 {
322 tm->tm_min -= 60;
323 tm->tm_hour++;
324 }
325 while (tm->tm_hour < 0)
326 {
327 tm->tm_hour += 24;
328 tm->tm_mday--;
329 }
330 while (tm->tm_hour >= 24)
331 {
332 tm->tm_hour -= 24;
333 tm->tm_mday++;
334 }
335 /* use loops on NNNdwmy user input values? */
336 while (tm->tm_mon < 0)
337 {
338 tm->tm_mon += 12;
339 tm->tm_year--;
340 }
341 while (tm->tm_mon >= 12)
342 {
343 tm->tm_mon -= 12;
344 tm->tm_year++;
345 }
346 while (tm->tm_mday <= 0)
347 {
348 if (tm->tm_mon)
349 tm->tm_mon--;
350 else
351 {
352 tm->tm_mon = 11;
353 tm->tm_year--;
354 }
355 tm->tm_mday += DaysPerMonth[tm->tm_mon] + is_leap_year_feb(tm);
356 }
357 while (tm->tm_mday > (DaysPerMonth[tm->tm_mon] + (leap = is_leap_year_feb(tm))))
358 {
359 tm->tm_mday -= DaysPerMonth[tm->tm_mon] + leap;
360 if (tm->tm_mon < 11)
361 tm->tm_mon++;
362 else
363 {
364 tm->tm_mon = 0;
365 tm->tm_year++;
366 }
367 }
368 }
369
370 /**
371 * mutt_date_make_date - Write a date in RFC822 format to a buffer
372 * @param buf Buffer for result
373 * @param local If true, use the local timezone. Otherwise use UTC.
374 *
375 * Appends the date to the passed in buffer.
376 * The buffer is not cleared because some callers prepend quotes.
377 */
mutt_date_make_date(struct Buffer * buf,bool local)378 void mutt_date_make_date(struct Buffer *buf, bool local)
379 {
380 if (!buf)
381 return;
382
383 struct tm tm;
384 time_t tz = 0;
385
386 time_t t = mutt_date_epoch();
387 if (local)
388 {
389 tm = mutt_date_localtime(t);
390 tz = mutt_date_local_tz(t);
391 }
392 else
393 {
394 tm = mutt_date_gmtime(t);
395 }
396
397 tz /= 60;
398
399 mutt_buffer_add_printf(buf, "%s, %d %s %d %02d:%02d:%02d %+03d%02d",
400 Weekdays[tm.tm_wday], tm.tm_mday, Months[tm.tm_mon],
401 tm.tm_year + 1900, tm.tm_hour, tm.tm_min, tm.tm_sec,
402 (int) tz / 60, (int) abs((int) tz) % 60);
403 }
404
405 /**
406 * mutt_date_check_month - Is the string a valid month name
407 * @param s String to check
408 * @retval num Index into Months array (0-based)
409 * @retval -1 Error
410 *
411 * @note Only the first three characters are checked
412 * @note The comparison is case insensitive
413 */
mutt_date_check_month(const char * s)414 int mutt_date_check_month(const char *s)
415 {
416 for (int i = 0; i < mutt_array_size(Months); i++)
417 if (mutt_istr_startswith(s, Months[i]))
418 return i;
419
420 return -1; /* error */
421 }
422
423 /**
424 * mutt_date_epoch - Return the number of seconds since the Unix epoch
425 * @retval s The number of s since the Unix epoch, or 0 on failure
426 */
mutt_date_epoch(void)427 time_t mutt_date_epoch(void)
428 {
429 return mutt_date_epoch_ms() / 1000;
430 }
431
432 /**
433 * mutt_date_epoch_ms - Return the number of milliseconds since the Unix epoch
434 * @retval ms The number of ms since the Unix epoch, or 0 on failure
435 */
mutt_date_epoch_ms(void)436 uint64_t mutt_date_epoch_ms(void)
437 {
438 struct timeval tv = { 0, 0 };
439 gettimeofday(&tv, NULL);
440 /* We assume that gettimeofday doesn't modify its first argument on failure.
441 * We also kind of assume that gettimeofday does not fail. */
442 return (uint64_t) tv.tv_sec * 1000 + tv.tv_usec / 1000;
443 }
444
445 /**
446 * mutt_date_parse_date - Parse a date string in RFC822 format
447 * @param[in] s String to parse
448 * @param[out] tz_out Pointer to timezone (optional)
449 * @retval num Unix time in seconds
450 *
451 * Parse a date of the form:
452 * `[ weekday , ] day-of-month month year hour:minute:second [ timezone ]`
453 *
454 * The 'timezone' field is optional; it defaults to +0000 if missing.
455 */
mutt_date_parse_date(const char * s,struct Tz * tz_out)456 time_t mutt_date_parse_date(const char *s, struct Tz *tz_out)
457 {
458 if (!s)
459 return -1;
460
461 bool lax = false;
462
463 const regmatch_t *match = mutt_prex_capture(PREX_RFC5322_DATE, s);
464 if (!match)
465 {
466 match = mutt_prex_capture(PREX_RFC5322_DATE_LAX, s);
467 if (!match)
468 {
469 mutt_debug(LL_DEBUG1, "Could not parse date: <%s>\n", s);
470 return -1;
471 }
472 lax = true;
473 mutt_debug(LL_DEBUG2, "Fallback regex for date: <%s>\n", s);
474 }
475
476 struct tm tm = { 0 };
477
478 // clang-format off
479 const regmatch_t *mday = &match[lax ? PREX_RFC5322_DATE_LAX_MATCH_DAY : PREX_RFC5322_DATE_MATCH_DAY];
480 const regmatch_t *mmonth = &match[lax ? PREX_RFC5322_DATE_LAX_MATCH_MONTH : PREX_RFC5322_DATE_MATCH_MONTH];
481 const regmatch_t *myear = &match[lax ? PREX_RFC5322_DATE_LAX_MATCH_YEAR : PREX_RFC5322_DATE_MATCH_YEAR];
482 const regmatch_t *mhour = &match[lax ? PREX_RFC5322_DATE_LAX_MATCH_HOUR : PREX_RFC5322_DATE_MATCH_HOUR];
483 const regmatch_t *mminute = &match[lax ? PREX_RFC5322_DATE_LAX_MATCH_MINUTE : PREX_RFC5322_DATE_MATCH_MINUTE];
484 const regmatch_t *msecond = &match[lax ? PREX_RFC5322_DATE_LAX_MATCH_SECOND : PREX_RFC5322_DATE_MATCH_SECOND];
485 const regmatch_t *mtz = &match[lax ? PREX_RFC5322_DATE_LAX_MATCH_TZ : PREX_RFC5322_DATE_MATCH_TZ];
486 const regmatch_t *mtzobs = &match[lax ? PREX_RFC5322_DATE_LAX_MATCH_TZ_OBS : PREX_RFC5322_DATE_MATCH_TZ_OBS];
487 // clang-format on
488
489 /* Day */
490 sscanf(s + mutt_regmatch_start(mday), "%d", &tm.tm_mday);
491 if (tm.tm_mday > 31)
492 return -1;
493
494 /* Month */
495 tm.tm_mon = mutt_date_check_month(s + mutt_regmatch_start(mmonth));
496
497 /* Year */
498 sscanf(s + mutt_regmatch_start(myear), "%d", &tm.tm_year);
499 if (tm.tm_year < 50)
500 tm.tm_year += 100;
501 else if (tm.tm_year >= 1900)
502 tm.tm_year -= 1900;
503
504 /* Time */
505 int hour, min, sec = 0;
506 sscanf(s + mutt_regmatch_start(mhour), "%d", &hour);
507 sscanf(s + mutt_regmatch_start(mminute), "%d", &min);
508 if (mutt_regmatch_start(msecond) != -1)
509 sscanf(s + mutt_regmatch_start(msecond), "%d", &sec);
510 if ((hour > 23) || (min > 59) || (sec > 60))
511 return -1;
512 tm.tm_hour = hour;
513 tm.tm_min = min;
514 tm.tm_sec = sec;
515
516 /* Time zone */
517 int zhours = 0;
518 int zminutes = 0;
519 bool zoccident = false;
520 if (mutt_regmatch_start(mtz) != -1)
521 {
522 char direction;
523 sscanf(s + mutt_regmatch_start(mtz), "%c%02d%02d", &direction, &zhours, &zminutes);
524 zoccident = (direction == '-');
525 }
526 else if (mutt_regmatch_start(mtzobs) != -1)
527 {
528 const struct Tz *tz =
529 find_tz(s + mutt_regmatch_start(mtzobs), mutt_regmatch_len(mtzobs));
530 if (tz)
531 {
532 zhours = tz->zhours;
533 zminutes = tz->zminutes;
534 zoccident = tz->zoccident;
535 }
536 }
537
538 if (tz_out)
539 {
540 tz_out->zhours = zhours;
541 tz_out->zminutes = zminutes;
542 tz_out->zoccident = zoccident;
543 }
544
545 return add_tz_offset(mutt_date_make_time(&tm, false), zoccident, zhours, zminutes);
546 }
547
548 /**
549 * mutt_date_make_imap - Format date in IMAP style: DD-MMM-YYYY HH:MM:SS +ZZzz
550 * @param buf Buffer to store the results
551 * @param buflen Length of buffer
552 * @param timestamp Time to format
553 * @retval num Characters written to buf
554 *
555 * Caller should provide a buffer of at least 27 bytes.
556 */
mutt_date_make_imap(char * buf,size_t buflen,time_t timestamp)557 int mutt_date_make_imap(char *buf, size_t buflen, time_t timestamp)
558 {
559 if (!buf)
560 return -1;
561
562 struct tm tm = mutt_date_localtime(timestamp);
563 time_t tz = mutt_date_local_tz(timestamp);
564
565 tz /= 60;
566
567 return snprintf(buf, buflen, "%02d-%s-%d %02d:%02d:%02d %+03d%02d",
568 tm.tm_mday, Months[tm.tm_mon], tm.tm_year + 1900, tm.tm_hour,
569 tm.tm_min, tm.tm_sec, (int) tz / 60, (int) abs((int) tz) % 60);
570 }
571
572 /**
573 * mutt_date_make_tls - Format date in TLS certificate verification style
574 * @param buf Buffer to store the results
575 * @param buflen Length of buffer
576 * @param timestamp Time to format
577 * @retval num Characters written to buf
578 *
579 * e.g., Mar 17 16:40:46 2016 UTC. The time is always in UTC.
580 *
581 * Caller should provide a buffer of at least 27 bytes.
582 */
mutt_date_make_tls(char * buf,size_t buflen,time_t timestamp)583 int mutt_date_make_tls(char *buf, size_t buflen, time_t timestamp)
584 {
585 if (!buf)
586 return -1;
587
588 struct tm tm = mutt_date_gmtime(timestamp);
589 return snprintf(buf, buflen, "%s, %d %s %d %02d:%02d:%02d UTC",
590 Weekdays[tm.tm_wday], tm.tm_mday, Months[tm.tm_mon],
591 tm.tm_year + 1900, tm.tm_hour, tm.tm_min, tm.tm_sec);
592 }
593
594 /**
595 * mutt_date_parse_imap - Parse date of the form: DD-MMM-YYYY HH:MM:SS +ZZzz
596 * @param s Date in string form
597 * @retval num Unix time
598 * @retval 0 Error
599 */
mutt_date_parse_imap(const char * s)600 time_t mutt_date_parse_imap(const char *s)
601 {
602 const regmatch_t *match = mutt_prex_capture(PREX_IMAP_DATE, s);
603 if (!match)
604 return 0;
605
606 const regmatch_t *mday = &match[PREX_IMAP_DATE_MATCH_DAY];
607 const regmatch_t *mmonth = &match[PREX_IMAP_DATE_MATCH_MONTH];
608 const regmatch_t *myear = &match[PREX_IMAP_DATE_MATCH_YEAR];
609 const regmatch_t *mtime = &match[PREX_IMAP_DATE_MATCH_TIME];
610 const regmatch_t *mtz = &match[PREX_IMAP_DATE_MATCH_TZ];
611
612 struct tm tm;
613
614 sscanf(s + mutt_regmatch_start(mday), " %d", &tm.tm_mday);
615 tm.tm_mon = mutt_date_check_month(s + mutt_regmatch_start(mmonth));
616 sscanf(s + mutt_regmatch_start(myear), "%d", &tm.tm_year);
617 tm.tm_year -= 1900;
618 sscanf(s + mutt_regmatch_start(mtime), "%d:%d:%d", &tm.tm_hour, &tm.tm_min, &tm.tm_sec);
619
620 char direction;
621 int zhours, zminutes;
622 sscanf(s + mutt_regmatch_start(mtz), "%c%02d%02d", &direction, &zhours, &zminutes);
623 bool zoccident = (direction == '-');
624
625 return add_tz_offset(mutt_date_make_time(&tm, false), zoccident, zhours, zminutes);
626 }
627
628 /**
629 * mutt_date_add_timeout - Safely add a timeout to a given time_t value
630 * @param now Time now
631 * @param timeout Timeout in seconds
632 * @retval num Unix time to timeout
633 *
634 * This will truncate instead of overflowing.
635 */
mutt_date_add_timeout(time_t now,time_t timeout)636 time_t mutt_date_add_timeout(time_t now, time_t timeout)
637 {
638 if (timeout < 0)
639 return now;
640
641 if ((TIME_T_MAX - now) < timeout)
642 return TIME_T_MAX;
643
644 return now + timeout;
645 }
646
647 /**
648 * mutt_date_localtime - Converts calendar time to a broken-down time structure expressed in user timezone
649 * @param t Time
650 * @retval obj Broken-down time representation
651 *
652 * Uses current time if t is #MUTT_DATE_NOW
653 */
mutt_date_localtime(time_t t)654 struct tm mutt_date_localtime(time_t t)
655 {
656 struct tm tm = { 0 };
657
658 if (t == MUTT_DATE_NOW)
659 t = mutt_date_epoch();
660
661 localtime_r(&t, &tm);
662 return tm;
663 }
664
665 /**
666 * mutt_date_gmtime - Converts calendar time to a broken-down time structure expressed in UTC timezone
667 * @param t Time
668 * @retval obj Broken-down time representation
669 *
670 * Uses current time if t is #MUTT_DATE_NOW
671 */
mutt_date_gmtime(time_t t)672 struct tm mutt_date_gmtime(time_t t)
673 {
674 struct tm tm = { 0 };
675
676 if (t == MUTT_DATE_NOW)
677 t = mutt_date_epoch();
678
679 gmtime_r(&t, &tm);
680 return tm;
681 }
682
683 /**
684 * mutt_date_localtime_format - Format localtime
685 * @param buf Buffer to store formatted time
686 * @param buflen Buffer size
687 * @param format Format to apply
688 * @param t Time to format
689 * @retval num Number of Bytes added to buffer, excluding null byte.
690 */
mutt_date_localtime_format(char * buf,size_t buflen,const char * format,time_t t)691 size_t mutt_date_localtime_format(char *buf, size_t buflen, const char *format, time_t t)
692 {
693 if (!buf || !format)
694 return 0;
695
696 struct tm tm = mutt_date_localtime(t);
697 return strftime(buf, buflen, format, &tm);
698 }
699
700 /**
701 * mutt_date_sleep_ms - Sleep for milliseconds
702 * @param ms Number of milliseconds to sleep
703 */
mutt_date_sleep_ms(size_t ms)704 void mutt_date_sleep_ms(size_t ms)
705 {
706 const struct timespec sleep = {
707 .tv_sec = ms / 1000,
708 .tv_nsec = (ms % 1000) * 1000000UL,
709 };
710 nanosleep(&sleep, NULL);
711 }
712