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