NeoMutt  2019-11-11
Teaching an old dog new tricks
DOXYGEN
date.c
Go to the documentation of this file.
1 
29 #include "config.h"
30 #include <ctype.h>
31 #include <stdbool.h>
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <sys/time.h>
36 #include <time.h>
37 #include "date.h"
38 #include "logging.h"
39 #include "memory.h"
40 #include "string2.h"
41 
42 // clang-format off
46 static const char *const Weekdays[] = {
47  "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat",
48 };
49 
53 static const char *const Months[] = {
54  "Jan", "Feb", "Mar", "Apr", "May", "Jun",
55  "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
56 };
57 
63 static const struct Tz TimeZones[] = {
64  { "aat", 1, 0, true }, /* Atlantic Africa Time */
65  { "adt", 4, 0, false }, /* Arabia DST */
66  { "ast", 3, 0, false }, /* Arabia */
67 //{ "ast", 4, 0, true }, /* Atlantic */
68  { "bst", 1, 0, false }, /* British DST */
69  { "cat", 1, 0, false }, /* Central Africa */
70  { "cdt", 5, 0, true },
71  { "cest", 2, 0, false }, /* Central Europe DST */
72  { "cet", 1, 0, false }, /* Central Europe */
73  { "cst", 6, 0, true },
74 //{ "cst", 8, 0, false }, /* China */
75 //{ "cst", 9, 30, false }, /* Australian Central Standard Time */
76  { "eat", 3, 0, false }, /* East Africa */
77  { "edt", 4, 0, true },
78  { "eest", 3, 0, false }, /* Eastern Europe DST */
79  { "eet", 2, 0, false }, /* Eastern Europe */
80  { "egst", 0, 0, false }, /* Eastern Greenland DST */
81  { "egt", 1, 0, true }, /* Eastern Greenland */
82  { "est", 5, 0, true },
83  { "gmt", 0, 0, false },
84  { "gst", 4, 0, false }, /* Presian Gulf */
85  { "hkt", 8, 0, false }, /* Hong Kong */
86  { "ict", 7, 0, false }, /* Indochina */
87  { "idt", 3, 0, false }, /* Israel DST */
88  { "ist", 2, 0, false }, /* Israel */
89 //{ "ist", 5, 30, false }, /* India */
90  { "jst", 9, 0, false }, /* Japan */
91  { "kst", 9, 0, false }, /* Korea */
92  { "mdt", 6, 0, true },
93  { "met", 1, 0, false }, /* This is now officially CET */
94  { "msd", 4, 0, false }, /* Moscow DST */
95  { "msk", 3, 0, false }, /* Moscow */
96  { "mst", 7, 0, true },
97  { "nzdt", 13, 0, false }, /* New Zealand DST */
98  { "nzst", 12, 0, false }, /* New Zealand */
99  { "pdt", 7, 0, true },
100  { "pst", 8, 0, true },
101  { "sat", 2, 0, false }, /* South Africa */
102  { "smt", 4, 0, false }, /* Seychelles */
103  { "sst", 11, 0, true }, /* Samoa */
104 //{ "sst", 8, 0, false }, /* Singapore */
105  { "utc", 0, 0, false },
106  { "wat", 0, 0, false }, /* West Africa */
107  { "west", 1, 0, false }, /* Western Europe DST */
108  { "wet", 0, 0, false }, /* Western Europe */
109  { "wgst", 2, 0, true }, /* Western Greenland DST */
110  { "wgt", 3, 0, true }, /* Western Greenland */
111  { "wst", 8, 0, false }, /* Western Australia */
112 };
113 // clang-format on
114 
124 static time_t compute_tz(time_t g, struct tm *utc)
125 {
126  struct tm lt = mutt_date_localtime(g);
127 
128  time_t t = (((lt.tm_hour - utc->tm_hour) * 60) + (lt.tm_min - utc->tm_min)) * 60;
129 
130  int yday = (lt.tm_yday - utc->tm_yday);
131  if (yday != 0)
132  {
133  /* This code is optimized to negative timezones (West of Greenwich) */
134  if ((yday == -1) || /* UTC passed midnight before localtime */
135  (yday > 1)) /* UTC passed new year before localtime */
136  {
137  t -= (24 * 60 * 60);
138  }
139  else
140  {
141  t += (24 * 60 * 60);
142  }
143  }
144 
145  return t;
146 }
147 
153 static int is_leap_year_feb(struct tm *tm)
154 {
155  if (tm->tm_mon != 1)
156  return 0;
157 
158  int y = tm->tm_year + 1900;
159  return ((y & 3) == 0) && (((y % 100) != 0) || ((y % 400) == 0));
160 }
161 
174 static const char *uncomment_timezone(char *buf, size_t buflen, const char *tz)
175 {
176  char *p = NULL;
177  size_t len;
178 
179  if (*tz != '(')
180  return tz; /* no need to do anything */
181  tz = mutt_str_skip_email_wsp(tz + 1);
182  p = strpbrk(tz, " )");
183  if (!p)
184  return tz;
185  len = p - tz;
186  if (len > (buflen - 1))
187  len = buflen - 1; /* LCOV_EXCL_LINE */
188  memcpy(buf, tz, len);
189  buf[len] = '\0';
190  return buf;
191 }
192 
201 time_t mutt_date_local_tz(time_t t)
202 {
203  /* Check we haven't overflowed the time (on 32-bit arches) */
204  if ((t == TIME_T_MAX) || (t == TIME_T_MIN))
205  return 0;
206 
207  if (t == 0)
208  t = mutt_date_epoch();
209 
210  struct tm tm = mutt_date_gmtime(t);
211  return compute_tz(t, &tm);
212 }
213 
224 time_t mutt_date_make_time(struct tm *t, bool local)
225 {
226  if (!t)
227  return TIME_T_MIN;
228 
229  static const int AccumDaysPerMonth[mutt_array_size(Months)] = {
230  0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334,
231  };
232 
233  /* Prevent an integer overflow, with some arbitrary limits. */
234  if (t->tm_year > 10000)
235  return TIME_T_MAX;
236  if (t->tm_year < -10000)
237  return TIME_T_MIN;
238 
239  if ((t->tm_mday < 1) || (t->tm_mday > 31))
240  return TIME_T_MIN;
241  if ((t->tm_hour < 0) || (t->tm_hour > 23) || (t->tm_min < 0) ||
242  (t->tm_min > 59) || (t->tm_sec < 0) || (t->tm_sec > 60))
243  {
244  return TIME_T_MIN;
245  }
246  if (t->tm_year > 9999)
247  return TIME_T_MAX;
248 
249  /* Compute the number of days since January 1 in the same year */
250  time_t g = AccumDaysPerMonth[t->tm_mon % mutt_array_size(Months)];
251 
252  /* The leap years are 1972 and every 4. year until 2096,
253  * but this algorithm will fail after year 2099 */
254  g += t->tm_mday;
255  if ((t->tm_year % 4) || (t->tm_mon < 2))
256  g--;
257  t->tm_yday = g;
258 
259  /* Compute the number of days since January 1, 1970 */
260  g += (t->tm_year - 70) * (time_t) 365;
261  g += (t->tm_year - 69) / 4;
262 
263  /* Compute the number of hours */
264  g *= 24;
265  g += t->tm_hour;
266 
267  /* Compute the number of minutes */
268  g *= 60;
269  g += t->tm_min;
270 
271  /* Compute the number of seconds */
272  g *= 60;
273  g += t->tm_sec;
274 
275  if (local)
276  g -= compute_tz(g, t);
277 
278  return g;
279 }
280 
290 void mutt_date_normalize_time(struct tm *tm)
291 {
292  if (!tm)
293  return;
294 
295  static const char DaysPerMonth[mutt_array_size(Months)] = {
296  31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31,
297  };
298  int leap;
299 
300  while (tm->tm_sec < 0)
301  {
302  tm->tm_sec += 60;
303  tm->tm_min--;
304  }
305  while (tm->tm_sec >= 60)
306  {
307  tm->tm_sec -= 60;
308  tm->tm_min++;
309  }
310  while (tm->tm_min < 0)
311  {
312  tm->tm_min += 60;
313  tm->tm_hour--;
314  }
315  while (tm->tm_min >= 60)
316  {
317  tm->tm_min -= 60;
318  tm->tm_hour++;
319  }
320  while (tm->tm_hour < 0)
321  {
322  tm->tm_hour += 24;
323  tm->tm_mday--;
324  }
325  while (tm->tm_hour >= 24)
326  {
327  tm->tm_hour -= 24;
328  tm->tm_mday++;
329  }
330  /* use loops on NNNdwmy user input values? */
331  while (tm->tm_mon < 0)
332  {
333  tm->tm_mon += 12;
334  tm->tm_year--;
335  }
336  while (tm->tm_mon >= 12)
337  {
338  tm->tm_mon -= 12;
339  tm->tm_year++;
340  }
341  while (tm->tm_mday <= 0)
342  {
343  if (tm->tm_mon)
344  tm->tm_mon--;
345  else
346  {
347  tm->tm_mon = 11;
348  tm->tm_year--;
349  }
350  tm->tm_mday += DaysPerMonth[tm->tm_mon] + is_leap_year_feb(tm);
351  }
352  while (tm->tm_mday > (DaysPerMonth[tm->tm_mon] + (leap = is_leap_year_feb(tm))))
353  {
354  tm->tm_mday -= DaysPerMonth[tm->tm_mon] + leap;
355  if (tm->tm_mon < 11)
356  tm->tm_mon++;
357  else
358  {
359  tm->tm_mon = 0;
360  tm->tm_year++;
361  }
362  }
363 }
364 
371 char *mutt_date_make_date(char *buf, size_t buflen)
372 {
373  if (!buf)
374  return NULL;
375 
376  time_t t = mutt_date_epoch();
377  struct tm tm = mutt_date_localtime(t);
378  time_t tz = mutt_date_local_tz(t);
379 
380  tz /= 60;
381 
382  snprintf(buf, buflen, "Date: %s, %d %s %d %02d:%02d:%02d %+03d%02d\n",
383  Weekdays[tm.tm_wday], tm.tm_mday, Months[tm.tm_mon], tm.tm_year + 1900,
384  tm.tm_hour, tm.tm_min, tm.tm_sec, (int) tz / 60, (int) abs((int) tz) % 60);
385  return buf;
386 }
387 
397 int mutt_date_check_month(const char *s)
398 {
399  for (int i = 0; i < mutt_array_size(Months); i++)
401  return i;
402 
403  return -1; /* error */
404 }
405 
410 time_t mutt_date_epoch(void)
411 {
412  return mutt_date_epoch_ms() / 1000;
413 }
414 
419 size_t mutt_date_epoch_ms(void)
420 {
421  struct timeval tv = { 0, 0 };
422  gettimeofday(&tv, NULL);
423  /* We assume that gettimeofday doesn't modify its first argument on failure.
424  * We also kind of assume that gettimeofday does not fail. */
425  return tv.tv_sec * 1000 + tv.tv_usec / 1000;
426 }
427 
436 bool mutt_date_is_day_name(const char *s)
437 {
438  if (!s || (strlen(s) < 3) || (s[3] == '\0') || !IS_SPACE(s[3]))
439  return false;
440 
441  for (int i = 0; i < mutt_array_size(Weekdays); i++)
443  return true;
444 
445  return false;
446 }
447 
459 time_t mutt_date_parse_date(const char *s, struct Tz *tz_out)
460 {
461  if (!s)
462  return -1;
463 
464  int count = 0;
465  int hour, min, sec;
466  struct tm tm = { 0 };
467  int i;
468  int tz_offset = 0;
469  int zhours = 0;
470  int zminutes = 0;
471  bool zoccident = false;
472  const char *ptz = NULL;
473  char tzstr[128];
474  char scratch[128];
475 
476  /* Don't modify our argument. Fixed-size buffer is ok here since
477  * the date format imposes a natural limit. */
478 
479  mutt_str_strfcpy(scratch, s, sizeof(scratch));
480 
481  /* kill the day of the week, if it exists. */
482  char *t = strchr(scratch, ',');
483  if (t)
484  t++;
485  else
486  t = scratch;
488 
489  while ((t = strtok(t, " \t")))
490  {
491  switch (count)
492  {
493  case 0: /* day of the month */
494  if ((mutt_str_atoi(t, &tm.tm_mday) < 0) || (tm.tm_mday < 0))
495  return -1;
496  if (tm.tm_mday > 31)
497  return -1;
498  break;
499 
500  case 1: /* month of the year */
501  i = mutt_date_check_month(t);
502  if ((i < 0) || (i > 11))
503  return -1;
504  tm.tm_mon = i;
505  break;
506 
507  case 2: /* year */
508  if ((mutt_str_atoi(t, &tm.tm_year) < 0) || (tm.tm_year < 0))
509  return -1;
510  if ((tm.tm_year < 0) || (tm.tm_year > 9999))
511  return -1;
512  if (tm.tm_year < 50)
513  tm.tm_year += 100;
514  else if (tm.tm_year >= 1900)
515  tm.tm_year -= 1900;
516  break;
517 
518  case 3: /* time of day */
519  if (sscanf(t, "%d:%d:%d", &hour, &min, &sec) == 3)
520  ;
521  else if (sscanf(t, "%d:%d", &hour, &min) == 2)
522  sec = 0;
523  else
524  {
525  mutt_debug(LL_DEBUG1, "could not process time format: %s\n", t);
526  return -1;
527  }
528  if ((hour < 0) || (hour > 23) || (min < 0) || (min > 59) || (sec < 0) || (sec > 60))
529  return -1;
530  tm.tm_hour = hour;
531  tm.tm_min = min;
532  tm.tm_sec = sec;
533  break;
534 
535  case 4: /* timezone */
536  /* sometimes we see things like (MST) or (-0700) so attempt to
537  * compensate by uncommenting the string if non-RFC822 compliant */
538  ptz = uncomment_timezone(tzstr, sizeof(tzstr), t);
539 
540  if ((*ptz == '+') || (*ptz == '-'))
541  {
542  if ((ptz[1] != '\0') && (ptz[2] != '\0') && (ptz[3] != '\0') && (ptz[4] != '\0') &&
543  isdigit((unsigned char) ptz[1]) && isdigit((unsigned char) ptz[2]) &&
544  isdigit((unsigned char) ptz[3]) && isdigit((unsigned char) ptz[4]))
545  {
546  zhours = (ptz[1] - '0') * 10 + (ptz[2] - '0');
547  zminutes = (ptz[3] - '0') * 10 + (ptz[4] - '0');
548 
549  if (ptz[0] == '-')
550  zoccident = true;
551  }
552  }
553  else
554  {
555  /* This is safe to do: A pointer to a struct equals a pointer to its first element */
556  struct Tz *tz =
557  bsearch(ptz, TimeZones, mutt_array_size(TimeZones), sizeof(struct Tz),
558  (int (*)(const void *, const void *)) mutt_str_strcasecmp);
559 
560  if (tz)
561  {
562  zhours = tz->zhours;
563  zminutes = tz->zminutes;
564  zoccident = tz->zoccident;
565  }
566 
567  /* ad hoc support for the European MET (now officially CET) TZ */
568  if (mutt_str_strcasecmp(t, "MET") == 0)
569  {
570  t = strtok(NULL, " \t");
571  if (t)
572  {
573  if (mutt_str_strcasecmp(t, "DST") == 0)
574  zhours++;
575  }
576  }
577  }
578  tz_offset = (zhours * 3600) + (zminutes * 60);
579  if (!zoccident)
580  tz_offset = -tz_offset;
581  break;
582  }
583  count++;
584  t = 0;
585  }
586 
587  if (count < 4) /* don't check for missing timezone */
588  {
589  mutt_debug(LL_DEBUG1, "error parsing date format, using received time\n");
590  return -1;
591  }
592 
593  if (tz_out)
594  {
595  tz_out->zhours = zhours;
596  tz_out->zminutes = zminutes;
597  tz_out->zoccident = zoccident;
598  }
599 
600  time_t time = mutt_date_make_time(&tm, false);
601  /* Check we haven't overflowed the time (on 32-bit arches) */
602  if ((time != TIME_T_MAX) && (time != TIME_T_MIN))
603  time += tz_offset;
604 
605  return time;
606 }
607 
617 int mutt_date_make_imap(char *buf, size_t buflen, time_t timestamp)
618 {
619  if (!buf)
620  return -1;
621 
622  struct tm tm = mutt_date_localtime(timestamp);
623  time_t tz = mutt_date_local_tz(timestamp);
624 
625  tz /= 60;
626 
627  return snprintf(buf, buflen, "%02d-%s-%d %02d:%02d:%02d %+03d%02d",
628  tm.tm_mday, Months[tm.tm_mon], tm.tm_year + 1900, tm.tm_hour,
629  tm.tm_min, tm.tm_sec, (int) tz / 60, (int) abs((int) tz) % 60);
630 }
631 
643 int mutt_date_make_tls(char *buf, size_t buflen, time_t timestamp)
644 {
645  if (!buf)
646  return -1;
647 
648  struct tm tm = mutt_date_gmtime(timestamp);
649  return snprintf(buf, buflen, "%s, %d %s %d %02d:%02d:%02d UTC",
650  Weekdays[tm.tm_wday], tm.tm_mday, Months[tm.tm_mon],
651  tm.tm_year + 1900, tm.tm_hour, tm.tm_min, tm.tm_sec);
652 }
653 
660 time_t mutt_date_parse_imap(const char *s)
661 {
662  if (!s)
663  return 0;
664 
665  struct tm t;
666  time_t tz;
667 
668  t.tm_mday = ((s[0] == ' ') ? s[1] - '0' : (s[0] - '0') * 10 + (s[1] - '0'));
669  s += 2;
670  if (*s != '-')
671  return 0;
672  s++;
673  t.tm_mon = mutt_date_check_month(s);
674  s += 3;
675  if (*s != '-')
676  return 0;
677  s++;
678  t.tm_year = (s[0] - '0') * 1000 + (s[1] - '0') * 100 + (s[2] - '0') * 10 +
679  (s[3] - '0') - 1900;
680  s += 4;
681  if (*s != ' ')
682  return 0;
683  s++;
684 
685  /* time */
686  t.tm_hour = (s[0] - '0') * 10 + (s[1] - '0');
687  s += 2;
688  if (*s != ':')
689  return 0;
690  s++;
691  t.tm_min = (s[0] - '0') * 10 + (s[1] - '0');
692  s += 2;
693  if (*s != ':')
694  return 0;
695  s++;
696  t.tm_sec = (s[0] - '0') * 10 + (s[1] - '0');
697  s += 2;
698  if (*s != ' ')
699  return 0;
700  s++;
701 
702  /* timezone */
703  tz = ((s[1] - '0') * 10 + (s[2] - '0')) * 3600 + ((s[3] - '0') * 10 + (s[4] - '0')) * 60;
704  if (s[0] == '+')
705  tz = -tz;
706 
707  return mutt_date_make_time(&t, false) + tz;
708 }
709 
718 time_t mutt_date_add_timeout(time_t now, long timeout)
719 {
720  if (timeout < 0)
721  return now;
722 
723  if ((TIME_T_MAX - now) < timeout)
724  return TIME_T_MAX;
725 
726  return now + timeout;
727 }
728 
736 struct tm mutt_date_localtime(time_t t)
737 {
738  struct tm tm = { 0 };
739 
740  if (t == MUTT_DATE_NOW)
741  t = mutt_date_epoch();
742 
743  localtime_r(&t, &tm);
744  return tm;
745 }
746 
754 struct tm mutt_date_gmtime(time_t t)
755 {
756  struct tm tm = { 0 };
757 
758  if (t == MUTT_DATE_NOW)
759  t = mutt_date_epoch();
760 
761  gmtime_r(&t, &tm);
762  return tm;
763 }
764 
773 size_t mutt_date_localtime_format(char *buf, size_t buflen, const char *format, time_t t)
774 {
775  if (!buf || !format)
776  return 0;
777 
778  struct tm tm = mutt_date_localtime(t);
779  return strftime(buf, buflen, format, &tm);
780 }
781 
786 void mutt_date_sleep_ms(size_t ms)
787 {
788  const struct timespec sleep = {
789  .tv_sec = ms / 1000,
790  .tv_nsec = (ms % 1000) * 1000000UL,
791  };
792  nanosleep(&sleep, NULL);
793 }
time_t mutt_date_epoch(void)
Return the number of seconds since the Unix epoch.
Definition: date.c:410
#define TIME_T_MIN
Definition: date.h:31
int mutt_str_atoi(const char *str, int *dst)
Convert ASCII string to an integer.
Definition: string.c:262
size_t mutt_date_localtime_format(char *buf, size_t buflen, const char *format, time_t t)
Format localtime.
Definition: date.c:773
Memory management wrappers.
struct tm mutt_date_localtime(time_t t)
Converts calendar time to a broken-down time structure expressed in user timezone.
Definition: date.c:736
#define TIME_T_MAX
Definition: date.h:30
time_t mutt_date_add_timeout(time_t now, long timeout)
Safely add a timeout to a given time_t value.
Definition: date.c:718
static const char *const Months[]
Months of the year (abbreviated)
Definition: date.c:53
void mutt_date_normalize_time(struct tm *tm)
Fix the contents of a struct tm.
Definition: date.c:290
static const char * timestamp(time_t stamp)
Create a YYYY-MM-DD HH:MM:SS timestamp.
Definition: logging.c:77
#define MUTT_DATE_NOW
Constant representing the &#39;current time&#39;, see: mutt_date_gmtime(), mutt_date_localtime() ...
Definition: date.h:36
struct tm mutt_date_gmtime(time_t t)
Converts calendar time to a broken-down time structure expressed in UTC timezone. ...
Definition: date.c:754
time_t mutt_date_local_tz(time_t t)
Calculate the local timezone in seconds east of UTC.
Definition: date.c:201
time_t mutt_date_parse_date(const char *s, struct Tz *tz_out)
Parse a date string in RFC822 format.
Definition: date.c:459
static int is_leap_year_feb(struct tm *tm)
Is a given February in a leap year.
Definition: date.c:153
unsigned char zhours
Hours away from UTC.
Definition: date.h:44
char * mutt_date_make_date(char *buf, size_t buflen)
Write a date in RFC822 format to a buffer.
Definition: date.c:371
Logging Dispatcher.
time_t tv_sec
Definition: file.h:47
String manipulation functions.
#define mutt_array_size(x)
Definition: memory.h:33
int mutt_date_make_tls(char *buf, size_t buflen, time_t timestamp)
Format date in TLS certificate verification style.
Definition: date.c:643
bool zoccident
True if west of UTC, False if East.
Definition: date.h:46
size_t mutt_date_epoch_ms(void)
Return the number of milliseconds since the Unix epoch.
Definition: date.c:419
size_t mutt_str_strfcpy(char *dest, const char *src, size_t dsize)
Copy a string into a buffer (guaranteeing NUL-termination)
Definition: string.c:750
Ignore case when comparing strings.
Definition: string2.h:68
char * mutt_str_skip_email_wsp(const char *s)
Skip over whitespace as defined by RFC5322.
Definition: string.c:776
static time_t compute_tz(time_t g, struct tm *utc)
Calculate the number of seconds east of UTC.
Definition: date.c:124
size_t mutt_str_startswith(const char *str, const char *prefix, enum CaseSensitivity cs)
Check whether a string starts with a prefix.
Definition: string.c:168
unsigned char zminutes
Minutes away from UTC.
Definition: date.h:45
#define IS_SPACE(ch)
Definition: string2.h:38
List of recognised Timezones.
Definition: date.h:41
Log at debug level 1.
Definition: logging.h:56
static const char * uncomment_timezone(char *buf, size_t buflen, const char *tz)
Strip ()s from a timezone string.
Definition: date.c:174
int mutt_str_strcasecmp(const char *a, const char *b)
Compare two strings ignoring case, safely.
Definition: string.c:628
int mutt_date_check_month(const char *s)
Is the string a valid month name.
Definition: date.c:397
int mutt_date_make_imap(char *buf, size_t buflen, time_t timestamp)
Format date in IMAP style: DD-MMM-YYYY HH:MM:SS +ZZzz.
Definition: date.c:617
Time value with nanosecond precision.
Definition: file.h:45
#define mutt_debug(LEVEL,...)
Definition: logging.h:81
time_t mutt_date_parse_imap(const char *s)
Parse date of the form: DD-MMM-YYYY HH:MM:SS +ZZzz.
Definition: date.c:660
bool mutt_date_is_day_name(const char *s)
Is the string a valid day name.
Definition: date.c:436
static const char *const Weekdays[]
Day of the week (abbreviated)
Definition: date.c:46
Time and date handling routines.
time_t mutt_date_make_time(struct tm *t, bool local)
Convert struct tm to time_t
Definition: date.c:224
void mutt_date_sleep_ms(size_t ms)
Sleep for milliseconds.
Definition: date.c:786