• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

neomutt / neomutt / 17366932035

31 Aug 2025 12:06PM UTC coverage: 50.099% (+0.05%) from 50.049%
17366932035

push

github

web-flow
tweak observer event types (#4023)

9132 of 18228 relevant lines covered (50.1%)

272.36 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.43
/mutt/date.c
1
/**
2
 * @file
3
 * Time and date handling routines
4
 *
5
 * @authors
6
 * Copyright (C) 2017-2024 Richard Russon <rich@flatcap.org>
7
 * Copyright (C) 2018-2020 Pietro Cerutti <gahr@gahr.ch>
8
 * Copyright (C) 2019 Victor Fernandes <criw@pm.me>
9
 * Copyright (C) 2021 Reto Brunner <reto@slightlybroken.com>
10
 * Copyright (C) 2023 Dennis Schön <mail@dennis-schoen.de>
11
 * Copyright (C) 2023 Rayford Shireman
12
 * Copyright (C) 2023 Steinar H Gunderson <steinar+neomutt@gunderson.no>
13
 * Copyright (C) 2023 наб <nabijaczleweli@nabijaczleweli.xyz>
14
 *
15
 * @copyright
16
 * This program is free software: you can redistribute it and/or modify it under
17
 * the terms of the GNU General Public License as published by the Free Software
18
 * Foundation, either version 2 of the License, or (at your option) any later
19
 * version.
20
 *
21
 * This program is distributed in the hope that it will be useful, but WITHOUT
22
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
23
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
24
 * details.
25
 *
26
 * You should have received a copy of the GNU General Public License along with
27
 * this program.  If not, see <http://www.gnu.org/licenses/>.
28
 */
29

30
/**
31
 * @page mutt_date Time and date handling routines
32
 *
33
 * Some commonly used time and date functions.
34
 */
35

36
#include "config.h"
37
#include <locale.h>
38
#include <stdbool.h>
39
#include <stdint.h>
40
#include <stdio.h>
41
#include <stdlib.h>
42
#include <string.h>
43
#include <sys/time.h>
44
#include <time.h>
45
#include "date.h"
46
#include "buffer.h"
47
#include "eqi.h"
48
#include "logging2.h"
49
#include "memory.h"
50
#include "prex.h"
51
#include "regex3.h"
52
#include "string2.h"
53

54
struct timespec;
55

56
/**
57
 * Weekdays - Day of the week (abbreviated)
58
 */
59
static const char *const Weekdays[] = {
60
  "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat",
61
};
62

63
/**
64
 * Months - Months of the year (abbreviated)
65
 */
66
static const char *const Months[] = {
67
  "Jan", "Feb", "Mar", "Apr", "May", "Jun",
68
  "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
69
};
70

71
/**
72
 * TimeZones - Lookup table of Time Zones
73
 *
74
 * @note Keep in alphabetical order
75
 */
76
static const struct Tz TimeZones[] = {
77
  // clang-format off
78
  { "aat",     1,  0, true  }, /* Atlantic Africa Time */
79
  { "adt",     4,  0, false }, /* Arabia DST */
80
  { "ast",     3,  0, false }, /* Arabia */
81
//{ "ast",     4,  0, true  }, /* Atlantic */
82
  { "bst",     1,  0, false }, /* British DST */
83
  { "cat",     1,  0, false }, /* Central Africa */
84
  { "cdt",     5,  0, true  },
85
  { "cest",    2,  0, false }, /* Central Europe DST */
86
  { "cet",     1,  0, false }, /* Central Europe */
87
  { "cst",     6,  0, true  },
88
//{ "cst",     8,  0, false }, /* China */
89
//{ "cst",     9, 30, false }, /* Australian Central Standard Time */
90
  { "eat",     3,  0, false }, /* East Africa */
91
  { "edt",     4,  0, true  },
92
  { "eest",    3,  0, false }, /* Eastern Europe DST */
93
  { "eet",     2,  0, false }, /* Eastern Europe */
94
  { "egst",    0,  0, false }, /* Eastern Greenland DST */
95
  { "egt",     1,  0, true  }, /* Eastern Greenland */
96
  { "est",     5,  0, true  },
97
  { "gmt",     0,  0, false },
98
  { "gst",     4,  0, false }, /* Presian Gulf */
99
  { "hkt",     8,  0, false }, /* Hong Kong */
100
  { "ict",     7,  0, false }, /* Indochina */
101
  { "idt",     3,  0, false }, /* Israel DST */
102
  { "ist",     2,  0, false }, /* Israel */
103
//{ "ist",     5, 30, false }, /* India */
104
  { "jst",     9,  0, false }, /* Japan */
105
  { "kst",     9,  0, false }, /* Korea */
106
  { "mdt",     6,  0, true  },
107
  { "met",     1,  0, false }, /* This is now officially CET */
108
  { "met dst", 2,  0, false }, /* MET in Daylight Saving Time */
109
  { "msd",     4,  0, false }, /* Moscow DST */
110
  { "msk",     3,  0, false }, /* Moscow */
111
  { "mst",     7,  0, true  },
112
  { "nzdt",   13,  0, false }, /* New Zealand DST */
113
  { "nzst",   12,  0, false }, /* New Zealand */
114
  { "pdt",     7,  0, true  },
115
  { "pst",     8,  0, true  },
116
  { "sat",     2,  0, false }, /* South Africa */
117
  { "smt",     4,  0, false }, /* Seychelles */
118
  { "sst",    11,  0, true  }, /* Samoa */
119
//{ "sst",     8,  0, false }, /* Singapore */
120
  { "utc",     0,  0, false },
121
  { "wat",     0,  0, false }, /* West Africa */
122
  { "west",    1,  0, false }, /* Western Europe DST */
123
  { "wet",     0,  0, false }, /* Western Europe */
124
  { "wgst",    2,  0, true  }, /* Western Greenland DST */
125
  { "wgt",     3,  0, true  }, /* Western Greenland */
126
  { "wst",     8,  0, false }, /* Western Australia */
127
  // clang-format on
128
};
129

130
/**
131
 * compute_tz - Calculate the number of seconds east of UTC
132
 * @param g   Local time
133
 * @param utc UTC time
134
 * @retval num Seconds east of UTC
135
 *
136
 * returns the seconds east of UTC given 'g' and its corresponding gmtime()
137
 * representation
138
 */
139
static int compute_tz(time_t g, struct tm *utc)
32✔
140
{
141
  struct tm lt = mutt_date_localtime(g);
32✔
142

143
  int tz = (((lt.tm_hour - utc->tm_hour) * 60) + (lt.tm_min - utc->tm_min)) * 60;
32✔
144

145
  int yday = (lt.tm_yday - utc->tm_yday);
32✔
146
  if (yday != 0)
32✔
147
  {
148
    /* This code is optimized to negative timezones (West of Greenwich) */
149
    if ((yday == -1) || /* UTC passed midnight before localtime */
×
150
        (yday > 1))     /* UTC passed new year before localtime */
×
151
    {
152
      tz -= (24 * 60 * 60);
×
153
    }
154
    else
155
    {
156
      tz += (24 * 60 * 60);
×
157
    }
158
  }
159

160
  return tz;
32✔
161
}
162

163
/**
164
 * add_tz_offset - Compute and add a timezone offset to an UTC time
165
 * @param t UTC time
166
 * @param w True if west of UTC, false if east
167
 * @param h Number of hours in the timezone
168
 * @param m Number of minutes in the timezone
169
 * @retval num Timezone offset in seconds
170
 */
171
static time_t add_tz_offset(time_t t, bool w, time_t h, time_t m)
172
{
173
  if ((t != TIME_T_MAX) && (t != TIME_T_MIN))
36✔
174
    return t + (w ? 1 : -1) * (((time_t) h * 3600) + ((time_t) m * 60));
66✔
175
  else
176
    return t;
177
}
178

179
/**
180
 * find_tz - Look up a timezone
181
 * @param s Timezone to lookup
182
 * @param len Length of the s string
183
 * @retval ptr Pointer to the Tz struct
184
 * @retval NULL Not found
185
 */
186
static const struct Tz *find_tz(const char *s, size_t len)
8✔
187
{
188
  for (size_t i = 0; i < countof(TimeZones); i++)
208✔
189
  {
190
    if (mutt_istrn_equal(TimeZones[i].tzname, s, len))
207✔
191
      return &TimeZones[i];
7✔
192
  }
193
  return NULL;
194
}
195

196
/**
197
 * is_leap_year_feb - Is a given February in a leap year
198
 * @param tm Date to be tested
199
 * @retval true It's a leap year
200
 */
201
static int is_leap_year_feb(struct tm *tm)
30✔
202
{
203
  if (tm->tm_mon != 1)
30✔
204
    return 0;
205

206
  int y = tm->tm_year + 1900;
1✔
207
  return ((y & 3) == 0) && (((y % 100) != 0) || ((y % 400) == 0));
1✔
208
}
209

210
/**
211
 * mutt_date_local_tz - Calculate the local timezone in seconds east of UTC
212
 * @param t Time to examine
213
 * @retval num Seconds east of UTC
214
 *
215
 * Returns the local timezone in seconds east of UTC for the time t,
216
 * or for the current time if t is zero.
217
 */
218
int mutt_date_local_tz(time_t t)
7✔
219
{
220
  /* Check we haven't overflowed the time (on 32-bit arches) */
221
  if ((t == TIME_T_MAX) || (t == TIME_T_MIN))
7✔
222
    return 0;
223

224
  if (t == 0)
5✔
225
    t = mutt_date_now();
226

227
  struct tm tm = mutt_date_gmtime(t);
5✔
228
  return compute_tz(t, &tm);
5✔
229
}
230

231
/**
232
 * mutt_date_make_time - Convert `struct tm` to `time_t`
233
 * @param t     Time to convert
234
 * @param local Should the local timezone be considered
235
 * @retval num        Time in Unix format
236
 * @retval #TIME_T_MIN Error
237
 *
238
 * Convert a struct tm to time_t, but don't take the local timezone into
239
 * account unless "local" is nonzero
240
 */
241
time_t mutt_date_make_time(struct tm *t, bool local)
85✔
242
{
243
  if (!t)
85✔
244
    return TIME_T_MIN;
245

246
  static const int AccumDaysPerMonth[countof(Months)] = {
247
    0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334,
248
  };
249

250
  /* Prevent an integer overflow, with some arbitrary limits. */
251
  if (t->tm_year > 10000)
84✔
252
    return TIME_T_MAX;
253
  if (t->tm_year < -10000)
83✔
254
    return TIME_T_MIN;
255

256
  if ((t->tm_mday < 1) || (t->tm_mday > 31))
82✔
257
    return TIME_T_MIN;
258
  if ((t->tm_hour < 0) || (t->tm_hour > 23) || (t->tm_min < 0) ||
80✔
259
      (t->tm_min > 59) || (t->tm_sec < 0) || (t->tm_sec > 60))
76✔
260
  {
261
    return TIME_T_MIN;
262
  }
263
  if (t->tm_year > 9999)
74✔
264
    return TIME_T_MAX;
265

266
  /* Compute the number of days since January 1 in the same year */
267
  int yday = AccumDaysPerMonth[t->tm_mon % countof(Months)];
74✔
268

269
  /* The leap years are 1972 and every 4. year until 2096,
270
   * but this algorithm will fail after year 2099 */
271
  yday += t->tm_mday;
74✔
272
  if ((t->tm_year % 4) || (t->tm_mon < 2))
74✔
273
    yday--;
64✔
274
  t->tm_yday = yday;
74✔
275

276
  time_t g = yday;
74✔
277

278
  /* Compute the number of days since January 1, 1970 */
279
  g += (t->tm_year - 70) * (time_t) 365;
74✔
280
  g += (t->tm_year - 69) / 4;
74✔
281

282
  /* Compute the number of hours */
283
  g *= 24;
74✔
284
  g += t->tm_hour;
74✔
285

286
  /* Compute the number of minutes */
287
  g *= 60;
74✔
288
  g += t->tm_min;
74✔
289

290
  /* Compute the number of seconds */
291
  g *= 60;
74✔
292
  g += t->tm_sec;
74✔
293

294
  if (local)
74✔
295
    g -= compute_tz(g, t);
27✔
296

297
  return g;
298
}
299

300
/**
301
 * mutt_date_normalize_time - Fix the contents of a struct tm
302
 * @param tm Time to correct
303
 *
304
 * If values have been added/subtracted from a struct tm, it can lead to
305
 * invalid dates, e.g.  Adding 10 days to the 25th of a month.
306
 *
307
 * This function will correct any over/under-flow.
308
 */
309
void mutt_date_normalize_time(struct tm *tm)
21✔
310
{
311
  if (!tm)
21✔
312
    return;
313

314
  static const char DaysPerMonth[countof(Months)] = {
315
    31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31,
316
  };
317
  int leap;
318

319
  while (tm->tm_sec < 0)
22✔
320
  {
321
    tm->tm_sec += 60;
2✔
322
    tm->tm_min--;
2✔
323
  }
324
  while (tm->tm_sec >= 60)
22✔
325
  {
326
    tm->tm_sec -= 60;
2✔
327
    tm->tm_min++;
2✔
328
  }
329
  while (tm->tm_min < 0)
22✔
330
  {
331
    tm->tm_min += 60;
2✔
332
    tm->tm_hour--;
2✔
333
  }
334
  while (tm->tm_min >= 60)
22✔
335
  {
336
    tm->tm_min -= 60;
2✔
337
    tm->tm_hour++;
2✔
338
  }
339
  while (tm->tm_hour < 0)
22✔
340
  {
341
    tm->tm_hour += 24;
2✔
342
    tm->tm_mday--;
2✔
343
  }
344
  while (tm->tm_hour >= 24)
22✔
345
  {
346
    tm->tm_hour -= 24;
2✔
347
    tm->tm_mday++;
2✔
348
  }
349
  /* use loops on NNNdwmy user input values? */
350
  while (tm->tm_mon < 0)
21✔
351
  {
352
    tm->tm_mon += 12;
1✔
353
    tm->tm_year--;
1✔
354
  }
355
  while (tm->tm_mon >= 12)
21✔
356
  {
357
    tm->tm_mon -= 12;
1✔
358
    tm->tm_year++;
1✔
359
  }
360
  while (tm->tm_mday <= 0)
27✔
361
  {
362
    if (tm->tm_mon)
7✔
363
    {
364
      tm->tm_mon--;
4✔
365
    }
366
    else
367
    {
368
      tm->tm_mon = 11;
3✔
369
      tm->tm_year--;
3✔
370
    }
371
    tm->tm_mday += DaysPerMonth[tm->tm_mon] + is_leap_year_feb(tm);
7✔
372
  }
373
  while (tm->tm_mday > (DaysPerMonth[tm->tm_mon] + (leap = is_leap_year_feb(tm))))
23✔
374
  {
375
    tm->tm_mday -= DaysPerMonth[tm->tm_mon] + leap;
3✔
376
    if (tm->tm_mon < 11)
3✔
377
    {
378
      tm->tm_mon++;
1✔
379
    }
380
    else
381
    {
382
      tm->tm_mon = 0;
2✔
383
      tm->tm_year++;
2✔
384
    }
385
  }
386
}
387

388
/**
389
 * mutt_date_make_date - Write a date in RFC822 format to a buffer
390
 * @param buf   Buffer for result
391
 * @param local If true, use the local timezone.  Otherwise use UTC.
392
 *
393
 * Appends the date to the passed in buffer.
394
 * The buffer is not cleared because some callers prepend quotes.
395
 */
396
void mutt_date_make_date(struct Buffer *buf, bool local)
4✔
397
{
398
  if (!buf)
4✔
399
    return;
2✔
400

401
  struct tm tm = { 0 };
402
  int tz = 0;
403

404
  time_t t = mutt_date_now();
405
  if (local)
2✔
406
  {
407
    tm = mutt_date_localtime(t);
1✔
408
    tz = mutt_date_local_tz(t);
1✔
409
  }
410
  else
411
  {
412
    tm = mutt_date_gmtime(t);
1✔
413
  }
414

415
  tz /= 60;
2✔
416

417
  buf_add_printf(buf, "%s, %d %s %d %02d:%02d:%02d %+03d%02d", Weekdays[tm.tm_wday],
2✔
418
                 tm.tm_mday, Months[tm.tm_mon], tm.tm_year + 1900, tm.tm_hour,
2✔
419
                 tm.tm_min, tm.tm_sec, tz / 60, abs(tz) % 60);
2✔
420
}
421

422
/**
423
 * mutt_date_check_month - Is the string a valid month name
424
 * @param s String to check (must be at least 3 bytes long)
425
 * @retval num Index into Months array (0-based)
426
 * @retval -1  Error
427
 *
428
 * @note Only the first three characters are checked
429
 * @note The comparison is case insensitive
430
 */
431
int mutt_date_check_month(const char *s)
86✔
432
{
433
  if (!s)
86✔
434
    return -1;
435

436
  char buf[4] = { 0 };
85✔
437
  memcpy(buf, s, 3);
438
  uint32_t sv;
439
  memcpy(&sv, buf, sizeof(sv));
440
  for (int i = 0; i < countof(Months); i++)
514✔
441
  {
442
    uint32_t mv;
443
    memcpy(&mv, Months[i], sizeof(mv));
506✔
444
    if (sv == mv)
506✔
445
      return i;
446
  }
447

448
  return -1; /* error */
449
}
450

451
/**
452
 * mutt_date_now - Return the number of seconds since the Unix epoch
453
 * @retval num Number of seconds since the Unix epoch, or 0 on failure
454
 */
455
time_t mutt_date_now(void)
72✔
456
{
457
  return mutt_date_now_ms() / 1000;
75✔
458
}
459

460
/**
461
 * mutt_date_now_ms - Return the number of milliseconds since the Unix epoch
462
 * @retval num The number of ms since the Unix epoch, or 0 on failure
463
 */
464
uint64_t mutt_date_now_ms(void)
75✔
465
{
466
  struct timeval tv = { 0, 0 };
75✔
467
  gettimeofday(&tv, NULL);
75✔
468
  /* We assume that gettimeofday doesn't modify its first argument on failure.
469
   * We also kind of assume that gettimeofday does not fail. */
470
  return ((uint64_t) tv.tv_sec * 1000) + (tv.tv_usec / 1000);
75✔
471
}
472

473
/**
474
 * mutt_time_now - Set the provided time field to the current time
475
 * @param[out] tp Field to set
476
 *
477
 * Uses nanosecond precision if available, if not we fallback to microseconds.
478
 */
479
void mutt_time_now(struct timespec *tp)
×
480
{
481
#ifdef HAVE_CLOCK_GETTIME
482
  if (clock_gettime(CLOCK_REALTIME, tp) != 0)
×
483
    mutt_perror("clock_gettime");
×
484
#else
485
  struct timeval tv = { 0, 0 };
486
  if (gettimeofday(&tv, NULL) != 0)
487
    mutt_perror("gettimeofday");
488
  tp->tv_sec = tv.tv_sec;
489
  tp->tv_nsec = tv.tv_usec * 1000;
490
#endif
491
}
×
492

493
/**
494
 * parse_small_uint - Parse a positive integer of at most 5 digits
495
 * @param[in]  str String to parse
496
 * @param[in]  end End of the string
497
 * @param[out] val Value
498
 * @retval num Number of chars parsed
499
 *
500
 * Leaves val untouched if the given string does not start with an integer;
501
 * ignores junk after it or any digits beyond the first five (this is so
502
 * that the function can never overflow, yet check if the integer is
503
 * larger than the maximum 4 digits supported in a year).
504
 * and does not support negative numbers. Empty strings are parsed as zero.
505
 */
506
static int parse_small_uint(const char *str, const char *end, int *val)
81✔
507
{
508
  const char *ptr = str;
509
  int v = 0;
510
  while ((ptr < end) && (ptr < (str + 5)) && (*ptr >= '0') && (*ptr <= '9'))
305✔
511
  {
512
    v = (v * 10) + (*ptr - '0');
224✔
513
    ptr++;
224✔
514
  }
515
  *val = v;
81✔
516
  return ptr - str;
81✔
517
}
518

519
/**
520
 * mutt_date_parse_rfc5322_strict - Parse a date string in RFC822 format
521
 * @param[in]  s      String to parse
522
 * @param[out] tz_out Timezone info (OPTIONAL)
523
 * @retval num Unix time in seconds
524
 *
525
 * Parse a date string in RFC822 format, without any comments or extra
526
 * whitespace (except a comment at the very end, since that is very common for
527
 * time zones).
528
 *
529
 * This is a fairly straightforward implementation in the hope of extracting
530
 * the valid cases quickly, i.e., without having to resort to a regex.
531
 * The hard cases are left to a regex implementation further down in
532
 * mutt_date_parse_date() (which calls us).
533
 *
534
 * Spec: https://tools.ietf.org/html/rfc5322#section-3.3
535
 */
536
static time_t mutt_date_parse_rfc5322_strict(const char *s, struct Tz *tz_out)
50✔
537
{
538
  size_t len = strlen(s);
50✔
539

540
  /* Skip over the weekday, if any. */
541
  if ((len >= 5) && (s[4] == ' ') &&
50✔
542
      (eqi4(s, "Mon,") || eqi4(s, "Tue,") || eqi4(s, "Wed,") ||
43✔
543
       eqi4(s, "Thu,") || eqi4(s, "Fri,") || eqi4(s, "Sat,") || eqi4(s, "Sun,")))
6✔
544
  {
545
    s += 5;
43✔
546
    len -= 5;
43✔
547
  }
548

549
  while ((len > 0) && (*s == ' '))
51✔
550
  {
551
    s++;
1✔
552
    len--;
1✔
553
  }
554

555
  if ((len == 0) || (*s < '0') || (*s > '9'))
50✔
556
    return -1;
557

558
  struct tm tm = { 0 };
43✔
559

560
  /* Day */
561
  int mday_len = parse_small_uint(s, s + len, &tm.tm_mday);
43✔
562
  if ((mday_len == 0) || (mday_len > 2) || (tm.tm_mday > 31))
43✔
563
    return -1;
564
  s += mday_len;
42✔
565
  len -= mday_len;
42✔
566

567
  if ((len == 0) || (*s != ' '))
42✔
568
    return -1;
569
  s++;
42✔
570
  len--;
42✔
571

572
  /* Month */
573
  if (len < 3)
42✔
574
    return -1;
575
  tm.tm_mon = mutt_date_check_month(s);
42✔
576
  if (tm.tm_mon == -1)
42✔
577
    return -1;
578
  s += 3;
579
  len -= 3;
580

581
  if ((len == 0) || (*s != ' '))
38✔
582
    return -1;
583
  s++;
38✔
584
  len--;
38✔
585

586
  /* Year */
587
  int year_len = parse_small_uint(s, s + len, &tm.tm_year);
38✔
588
  if ((year_len != 2) && (year_len != 4))
38✔
589
    return -1;
590
  if (tm.tm_year < 50)
35✔
591
    tm.tm_year += 100;
1✔
592
  else if (tm.tm_year >= 1900)
34✔
593
    tm.tm_year -= 1900;
34✔
594
  s += year_len;
35✔
595
  len -= year_len;
35✔
596

597
  if ((len == 0) || (*s != ' '))
35✔
598
    return -1;
599
  s++;
600
  len--;
34✔
601

602
  /* Hour */
603
  if ((len < 3) || (s[0] < '0') || (s[0] > '2') || (s[1] < '0') ||
34✔
604
      (s[1] > '9') || (s[2] != ':'))
31✔
605
  {
606
    return -1;
607
  }
608
  tm.tm_hour = ((s[0] - '0') * 10) + (s[1] - '0');
30✔
609
  if (tm.tm_hour > 23)
30✔
610
    return -1;
611
  s += 3;
612
  len -= 3;
29✔
613

614
  /* Minute */
615
  if ((len < 2) || (s[0] < '0') || (s[0] > '5') || (s[1] < '0') || (s[1] > '9'))
29✔
616
    return -1;
617
  tm.tm_min = ((s[0] - '0') * 10) + (s[1] - '0');
25✔
618
  if (tm.tm_min > 59)
619
    return -1;
620
  s += 2;
25✔
621
  len -= 2;
25✔
622

623
  /* Second (optional) */
624
  if ((len > 0) && (s[0] == ':'))
25✔
625
  {
626
    s++;
627
    len--;
24✔
628
    if ((len < 2) || (s[0] < '0') || (s[0] > '5') || (s[1] < '0') || (s[1] > '9'))
24✔
629
      return -1;
630
    tm.tm_sec = ((s[0] - '0') * 10) + (s[1] - '0');
21✔
631
    if (tm.tm_sec > 60)
632
      return -1;
633
    s += 2;
21✔
634
    len -= 2;
21✔
635
  }
636

637
  while ((len > 0) && (*s == ' '))
42✔
638
  {
639
    s++;
20✔
640
    len--;
20✔
641
  }
642

643
  /* Strip optional time zone comment and white space from the end
644
   * (this is the only one that is very common) */
645
  while ((len > 0) && (s[len - 1] == ' '))
22✔
646
    len--;
647
  if ((len >= 2) && (s[len - 1] == ')'))
22✔
648
  {
649
    for (int i = len - 2; i >= 0; i--)
24✔
650
    {
651
      if (s[i] == '(')
24✔
652
      {
653
        len = i;
654
        break;
655
      }
656
      if (!mutt_isalpha(s[i]) && (s[i] != ' '))
18✔
657
        return -1; /* give up more complex comment parsing */
658
    }
659
  }
660
  while ((len > 0) && (s[len - 1] == ' '))
27✔
661
    len--;
662

663
  /* Time zone (optional) */
664
  int zhours = 0;
665
  int zminutes = 0;
666
  bool zoccident = false;
667
  if (len > 0)
22✔
668
  {
669
    if ((len == 5) && ((s[0] == '+') || (s[0] == '-')) && (s[1] >= '0') &&
20✔
670
        (s[1] <= '9') && (s[2] >= '0') && (s[2] <= '9') && (s[3] >= '0') &&
7✔
671
        (s[3] <= '9') && (s[4] >= '0') && (s[4] <= '9'))
7✔
672
    {
673
      zoccident = (s[0] == '-');
674
      zhours = ((s[1] - '0') * 10) + (s[2] - '0');
7✔
675
      zminutes = ((s[3] - '0') * 10) + (s[4] - '0');
7✔
676
    }
677
    else
678
    {
679
      for (int i = 0; i < len; i++)
35✔
680
      {
681
        if (!mutt_isalpha(s[i]))
30✔
682
          return -1;
683
      }
684
      const struct Tz *tz = find_tz(s, len);
5✔
685
      if (tz)
5✔
686
      {
687
        zhours = tz->zhours;
4✔
688
        zminutes = tz->zminutes;
4✔
689
        zoccident = tz->zoccident;
4✔
690
      }
691
    }
692
  }
693

694
  if (tz_out)
14✔
695
  {
696
    tz_out->zhours = zhours;
14✔
697
    tz_out->zminutes = zminutes;
14✔
698
    tz_out->zoccident = zoccident;
14✔
699
  }
700

701
  return add_tz_offset(mutt_date_make_time(&tm, false), zoccident, zhours, zminutes);
14✔
702
}
703

704
/**
705
 * mutt_date_parse_date - Parse a date string in RFC822 format
706
 * @param[in]  s      String to parse
707
 * @param[out] tz_out Pointer to timezone (optional)
708
 * @retval num Unix time in seconds, or -1 on failure
709
 *
710
 * Parse a date of the form:
711
 * `[ weekday , ] day-of-month month year hour:minute:second [ timezone ]`
712
 *
713
 * The 'timezone' field is optional; it defaults to +0000 if missing.
714
 */
715
time_t mutt_date_parse_date(const char *s, struct Tz *tz_out)
51✔
716
{
717
  if (!s)
51✔
718
    return -1;
719

720
  const time_t strict_t = mutt_date_parse_rfc5322_strict(s, tz_out);
50✔
721
  if (strict_t != -1)
50✔
722
    return strict_t;
723

724
  const regmatch_t *match = mutt_prex_capture(PREX_RFC5322_DATE_LAX, s);
36✔
725
  if (!match)
36✔
726
  {
727
    mutt_debug(LL_DEBUG1, "Could not parse date: <%s>\n", s);
11✔
728
    return -1;
11✔
729
  }
730
  mutt_debug(LL_DEBUG2, "Fallback regex for date: <%s>\n", s);
25✔
731

732
  struct tm tm = { 0 };
25✔
733

734
  // clang-format off
735
  const regmatch_t *mday    = &match[PREX_RFC5322_DATE_LAX_MATCH_DAY];
736
  const regmatch_t *mmonth  = &match[PREX_RFC5322_DATE_LAX_MATCH_MONTH];
737
  const regmatch_t *myear   = &match[PREX_RFC5322_DATE_LAX_MATCH_YEAR];
738
  const regmatch_t *mhour   = &match[PREX_RFC5322_DATE_LAX_MATCH_HOUR];
739
  const regmatch_t *mminute = &match[PREX_RFC5322_DATE_LAX_MATCH_MINUTE];
740
  const regmatch_t *msecond = &match[PREX_RFC5322_DATE_LAX_MATCH_SECOND];
741
  const regmatch_t *mtz     = &match[PREX_RFC5322_DATE_LAX_MATCH_TZ];
742
  const regmatch_t *mtzobs  = &match[PREX_RFC5322_DATE_LAX_MATCH_TZ_OBS];
743
  // clang-format on
744

745
  /* Day */
746
  sscanf(s + mutt_regmatch_start(mday), "%d", &tm.tm_mday);
25✔
747
  if (tm.tm_mday > 31)
25✔
748
    return -1;
749

750
  /* Month */
751
  tm.tm_mon = mutt_date_check_month(s + mutt_regmatch_start(mmonth));
24✔
752

753
  /* Year */
754
  sscanf(s + mutt_regmatch_start(myear), "%d", &tm.tm_year);
24✔
755
  if (tm.tm_year < 50)
24✔
756
    tm.tm_year += 100;
×
757
  else if (tm.tm_year >= 1900)
24✔
758
    tm.tm_year -= 1900;
24✔
759

760
  /* Time */
761
  int hour = 0, min = 0, sec = 0;
24✔
762
  sscanf(s + mutt_regmatch_start(mhour), "%d", &hour);
24✔
763
  sscanf(s + mutt_regmatch_start(mminute), "%d", &min);
24✔
764
  if (mutt_regmatch_start(msecond) != -1)
24✔
765
    sscanf(s + mutt_regmatch_start(msecond), "%d", &sec);
22✔
766
  if ((hour > 23) || (min > 59) || (sec > 60))
24✔
767
    return -1;
768
  tm.tm_hour = hour;
19✔
769
  tm.tm_min = min;
19✔
770
  tm.tm_sec = sec;
19✔
771

772
  /* Time zone */
773
  int zhours = 0;
19✔
774
  int zminutes = 0;
19✔
775
  bool zoccident = false;
776
  if (mutt_regmatch_start(mtz) != -1)
19✔
777
  {
778
    char direction = '\0';
11✔
779
    sscanf(s + mutt_regmatch_start(mtz), "%c%02d%02d", &direction, &zhours, &zminutes);
11✔
780
    zoccident = (direction == '-');
11✔
781
  }
782
  else if (mutt_regmatch_start(mtzobs) != -1)
8✔
783
  {
784
    const struct Tz *tz = find_tz(s + mutt_regmatch_start(mtzobs),
3✔
785
                                  mutt_regmatch_len(mtzobs));
786
    if (tz)
3✔
787
    {
788
      zhours = tz->zhours;
3✔
789
      zminutes = tz->zminutes;
3✔
790
      zoccident = tz->zoccident;
3✔
791
    }
792
  }
793

794
  if (tz_out)
19✔
795
  {
796
    tz_out->zhours = zhours;
19✔
797
    tz_out->zminutes = zminutes;
19✔
798
    tz_out->zoccident = zoccident;
19✔
799
  }
800

801
  return add_tz_offset(mutt_date_make_time(&tm, false), zoccident, zhours, zminutes);
19✔
802
}
803

804
/**
805
 * mutt_date_make_imap - Format date in IMAP style: DD-MMM-YYYY HH:MM:SS +ZZzz
806
 * @param buf       Buffer to store the results
807
 * @param timestamp Time to format
808
 * @retval num Characters written to buf
809
 */
810
int mutt_date_make_imap(struct Buffer *buf, time_t timestamp)
2✔
811
{
812
  if (!buf)
2✔
813
    return -1;
814

815
  struct tm tm = mutt_date_localtime(timestamp);
1✔
816
  int tz = mutt_date_local_tz(timestamp);
1✔
817

818
  tz /= 60;
1✔
819

820
  return buf_printf(buf, "%02d-%s-%d %02d:%02d:%02d %+03d%02d", tm.tm_mday,
1✔
821
                    Months[tm.tm_mon], tm.tm_year + 1900, tm.tm_hour, tm.tm_min,
1✔
822
                    tm.tm_sec, tz / 60, abs(tz) % 60);
1✔
823
}
824

825
/**
826
 * mutt_date_make_tls - Format date in TLS certificate verification style
827
 * @param buf       Buffer to store the results
828
 * @param buflen    Length of buffer
829
 * @param timestamp Time to format
830
 * @retval num Characters written to buf
831
 *
832
 * e.g., Mar 17 16:40:46 2016 UTC. The time is always in UTC.
833
 *
834
 * Caller should provide a buffer of at least 27 bytes.
835
 */
836
int mutt_date_make_tls(char *buf, size_t buflen, time_t timestamp)
2✔
837
{
838
  if (!buf)
2✔
839
    return -1;
840

841
  struct tm tm = mutt_date_gmtime(timestamp);
1✔
842
  return snprintf(buf, buflen, "%s, %d %s %d %02d:%02d:%02d UTC",
1✔
843
                  Weekdays[tm.tm_wday], tm.tm_mday, Months[tm.tm_mon],
1✔
844
                  tm.tm_year + 1900, tm.tm_hour, tm.tm_min, tm.tm_sec);
1✔
845
}
846

847
/**
848
 * mutt_date_parse_imap - Parse date of the form: DD-MMM-YYYY HH:MM:SS +ZZzz
849
 * @param s Date in string form
850
 * @retval num Unix time
851
 * @retval 0   Error
852
 */
853
time_t mutt_date_parse_imap(const char *s)
10✔
854
{
855
  const regmatch_t *match = mutt_prex_capture(PREX_IMAP_DATE, s);
10✔
856
  if (!match)
10✔
857
    return 0;
858

859
  const regmatch_t *mday = &match[PREX_IMAP_DATE_MATCH_DAY];
860
  const regmatch_t *mmonth = &match[PREX_IMAP_DATE_MATCH_MONTH];
861
  const regmatch_t *myear = &match[PREX_IMAP_DATE_MATCH_YEAR];
862
  const regmatch_t *mtime = &match[PREX_IMAP_DATE_MATCH_TIME];
863
  const regmatch_t *mtz = &match[PREX_IMAP_DATE_MATCH_TZ];
864

865
  struct tm tm = { 0 };
3✔
866

867
  sscanf(s + mutt_regmatch_start(mday), " %d", &tm.tm_mday);
3✔
868
  tm.tm_mon = mutt_date_check_month(s + mutt_regmatch_start(mmonth));
3✔
869
  sscanf(s + mutt_regmatch_start(myear), "%d", &tm.tm_year);
3✔
870
  tm.tm_year -= 1900;
3✔
871
  sscanf(s + mutt_regmatch_start(mtime), "%d:%d:%d", &tm.tm_hour, &tm.tm_min, &tm.tm_sec);
3✔
872

873
  char direction = '\0';
3✔
874
  int zhours = 0, zminutes = 0;
3✔
875
  sscanf(s + mutt_regmatch_start(mtz), "%c%02d%02d", &direction, &zhours, &zminutes);
3✔
876
  bool zoccident = (direction == '-');
3✔
877

878
  return add_tz_offset(mutt_date_make_time(&tm, false), zoccident, zhours, zminutes);
3✔
879
}
880

881
/**
882
 * mutt_date_add_timeout - Safely add a timeout to a given time_t value
883
 * @param now     Time now
884
 * @param timeout Timeout in seconds
885
 * @retval num Unix time to timeout
886
 *
887
 * This will truncate instead of overflowing.
888
 */
889
time_t mutt_date_add_timeout(time_t now, time_t timeout)
9✔
890
{
891
  if (timeout < 0)
9✔
892
    return now;
893

894
  if ((TIME_T_MAX - now) < timeout)
7✔
895
    return TIME_T_MAX;
896

897
  return now + timeout;
5✔
898
}
899

900
/**
901
 * mutt_date_localtime - Converts calendar time to a broken-down time structure expressed in user timezone
902
 * @param  t  Time
903
 * @retval obj Broken-down time representation
904
 */
905
struct tm mutt_date_localtime(time_t t)
64✔
906
{
907
  struct tm tm = { 0 };
64✔
908

909
  struct tm *ret = localtime_r(&t, &tm);
64✔
910
  if (!ret)
64✔
911
  {
912
    mutt_debug(LL_DEBUG1, "Could not convert time_t via localtime_r() to struct tm: time_t = %jd\n",
×
913
               (intmax_t) t);
914
    struct tm default_tm = { 0 }; // 1970-01-01 00:00:00
×
915
    mktime(&default_tm); // update derived fields making tm into a valid tm.
×
916
    tm = default_tm;
×
917
  }
918
  return tm;
64✔
919
}
920

921
/**
922
 * mutt_date_gmtime - Converts calendar time to a broken-down time structure expressed in UTC timezone
923
 * @param  t  Time
924
 * @retval obj Broken-down time representation
925
 */
926
struct tm mutt_date_gmtime(time_t t)
10✔
927
{
928
  struct tm tm = { 0 };
10✔
929

930
  struct tm *ret = gmtime_r(&t, &tm);
10✔
931
  if (!ret)
10✔
932
  {
933
    mutt_debug(LL_DEBUG1, "Could not convert time_t via gmtime_r() to struct tm: time_t = %jd\n",
×
934
               (intmax_t) t);
935
    struct tm default_tm = { 0 }; // 1970-01-01 00:00:00
×
936
    mktime(&default_tm); // update derived fields making tm into a valid tm.
×
937
    tm = default_tm;
×
938
  }
939
  return tm;
10✔
940
}
941

942
/**
943
 * mutt_date_localtime_format - Format localtime
944
 * @param buf    Buffer to store formatted time
945
 * @param buflen Buffer size
946
 * @param format Format to apply
947
 * @param t      Time to format
948
 * @retval num   Number of Bytes added to buffer, excluding NUL byte
949
 */
950
size_t mutt_date_localtime_format(char *buf, size_t buflen, const char *format, time_t t)
3✔
951
{
952
  if (!buf || !format)
3✔
953
    return 0;
954

955
  struct tm tm = mutt_date_localtime(t);
1✔
956
  return strftime(buf, buflen, format, &tm);
1✔
957
}
958

959
/**
960
 * mutt_date_localtime_format_locale - Format localtime using a given locale
961
 * @param buf    Buffer to store formatted time
962
 * @param buflen Buffer size
963
 * @param format Format to apply
964
 * @param t      Time to format
965
 * @param loc    Locale to use
966
 * @retval num   Number of Bytes added to buffer, excluding NUL byte
967
 */
968
size_t mutt_date_localtime_format_locale(char *buf, size_t buflen,
3✔
969
                                         const char *format, time_t t, locale_t loc)
970
{
971
  if (!buf || !format)
3✔
972
    return 0;
973

974
  struct tm tm = mutt_date_localtime(t);
1✔
975
  return strftime_l(buf, buflen, format, &tm, loc);
1✔
976
}
977

978
/**
979
 * mutt_date_sleep_ms - Sleep for milliseconds
980
 * @param ms Number of milliseconds to sleep
981
 */
982
void mutt_date_sleep_ms(size_t ms)
1✔
983
{
984
  const struct timespec sleep = {
1✔
985
    .tv_sec = ms / 1000,
1✔
986
    .tv_nsec = (ms % 1000) * 1000000UL,
1✔
987
  };
988
  nanosleep(&sleep, NULL);
1✔
989
}
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc