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