Line data Source code
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 : */
127 12 : static time_t compute_tz(time_t g, struct tm *utc)
128 : {
129 12 : struct tm lt = mutt_date_localtime(g);
130 :
131 12 : time_t t = (((lt.tm_hour - utc->tm_hour) * 60) + (lt.tm_min - utc->tm_min)) * 60;
132 :
133 12 : int yday = (lt.tm_yday - utc->tm_yday);
134 12 : if (yday != 0)
135 : {
136 : /* This code is optimized to negative timezones (West of Greenwich) */
137 0 : if ((yday == -1) || /* UTC passed midnight before localtime */
138 : (yday > 1)) /* UTC passed new year before localtime */
139 : {
140 0 : t -= (24 * 60 * 60);
141 : }
142 : else
143 : {
144 0 : t += (24 * 60 * 60);
145 : }
146 : }
147 :
148 12 : 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 num Timezone offset in seconds
158 : */
159 70 : static time_t add_tz_offset(time_t t, bool w, time_t h, time_t m)
160 : {
161 70 : if ((t != TIME_T_MAX) && (t != TIME_T_MIN))
162 70 : return t + (w ? 1 : -1) * (((time_t) h * 3600) + ((time_t) m * 60));
163 : else
164 0 : 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 : */
174 16 : static const struct Tz *find_tz(const char *s, size_t len)
175 : {
176 416 : for (size_t i = 0; i < mutt_array_size(TimeZones); i++)
177 : {
178 414 : if (mutt_istrn_equal(TimeZones[i].tzname, s, len))
179 14 : return &TimeZones[i];
180 : }
181 2 : 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 : */
189 30 : static int is_leap_year_feb(struct tm *tm)
190 : {
191 30 : if (tm->tm_mon != 1)
192 28 : return 0;
193 :
194 2 : int y = tm->tm_year + 1900;
195 2 : 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 : */
206 14 : time_t mutt_date_local_tz(time_t t)
207 : {
208 : /* Check we haven't overflowed the time (on 32-bit arches) */
209 14 : if ((t == TIME_T_MAX) || (t == TIME_T_MIN))
210 4 : return 0;
211 :
212 10 : if (t == 0)
213 2 : t = mutt_date_epoch();
214 :
215 10 : struct tm tm = mutt_date_gmtime(t);
216 10 : 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 : */
229 114 : time_t mutt_date_make_time(struct tm *t, bool local)
230 : {
231 114 : if (!t)
232 2 : 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 112 : if (t->tm_year > 10000)
240 2 : return TIME_T_MAX;
241 110 : if (t->tm_year < -10000)
242 2 : return TIME_T_MIN;
243 :
244 108 : if ((t->tm_mday < 1) || (t->tm_mday > 31))
245 4 : return TIME_T_MIN;
246 104 : if ((t->tm_hour < 0) || (t->tm_hour > 23) || (t->tm_min < 0) ||
247 98 : (t->tm_min > 59) || (t->tm_sec < 0) || (t->tm_sec > 60))
248 : {
249 12 : return TIME_T_MIN;
250 : }
251 92 : if (t->tm_year > 9999)
252 0 : return TIME_T_MAX;
253 :
254 : /* Compute the number of days since January 1 in the same year */
255 92 : 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 92 : g += t->tm_mday;
260 92 : if ((t->tm_year % 4) || (t->tm_mon < 2))
261 74 : g--;
262 92 : t->tm_yday = g;
263 :
264 : /* Compute the number of days since January 1, 1970 */
265 92 : g += (t->tm_year - 70) * (time_t) 365;
266 92 : g += (t->tm_year - 69) / 4;
267 :
268 : /* Compute the number of hours */
269 92 : g *= 24;
270 92 : g += t->tm_hour;
271 :
272 : /* Compute the number of minutes */
273 92 : g *= 60;
274 92 : g += t->tm_min;
275 :
276 : /* Compute the number of seconds */
277 92 : g *= 60;
278 92 : g += t->tm_sec;
279 :
280 92 : if (local)
281 2 : g -= compute_tz(g, t);
282 :
283 92 : 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 : */
295 20 : void mutt_date_normalize_time(struct tm *tm)
296 : {
297 20 : if (!tm)
298 2 : 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 22 : while (tm->tm_sec < 0)
306 : {
307 4 : tm->tm_sec += 60;
308 4 : tm->tm_min--;
309 : }
310 22 : while (tm->tm_sec >= 60)
311 : {
312 4 : tm->tm_sec -= 60;
313 4 : tm->tm_min++;
314 : }
315 22 : while (tm->tm_min < 0)
316 : {
317 4 : tm->tm_min += 60;
318 4 : tm->tm_hour--;
319 : }
320 22 : while (tm->tm_min >= 60)
321 : {
322 4 : tm->tm_min -= 60;
323 4 : tm->tm_hour++;
324 : }
325 22 : while (tm->tm_hour < 0)
326 : {
327 4 : tm->tm_hour += 24;
328 4 : tm->tm_mday--;
329 : }
330 22 : while (tm->tm_hour >= 24)
331 : {
332 4 : tm->tm_hour -= 24;
333 4 : tm->tm_mday++;
334 : }
335 : /* use loops on NNNdwmy user input values? */
336 20 : while (tm->tm_mon < 0)
337 : {
338 2 : tm->tm_mon += 12;
339 2 : tm->tm_year--;
340 : }
341 20 : while (tm->tm_mon >= 12)
342 : {
343 2 : tm->tm_mon -= 12;
344 2 : tm->tm_year++;
345 : }
346 24 : while (tm->tm_mday <= 0)
347 : {
348 6 : if (tm->tm_mon)
349 2 : tm->tm_mon--;
350 : else
351 : {
352 4 : tm->tm_mon = 11;
353 4 : tm->tm_year--;
354 : }
355 6 : tm->tm_mday += DaysPerMonth[tm->tm_mon] + is_leap_year_feb(tm);
356 : }
357 24 : while (tm->tm_mday > (DaysPerMonth[tm->tm_mon] + (leap = is_leap_year_feb(tm))))
358 : {
359 6 : tm->tm_mday -= DaysPerMonth[tm->tm_mon] + leap;
360 6 : if (tm->tm_mon < 11)
361 2 : tm->tm_mon++;
362 : else
363 : {
364 4 : tm->tm_mon = 0;
365 4 : 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 : */
378 8 : void mutt_date_make_date(struct Buffer *buf, bool local)
379 : {
380 8 : if (!buf)
381 4 : return;
382 :
383 : struct tm tm;
384 4 : time_t tz = 0;
385 :
386 4 : time_t t = mutt_date_epoch();
387 4 : if (local)
388 : {
389 2 : tm = mutt_date_localtime(t);
390 2 : tz = mutt_date_local_tz(t);
391 : }
392 : else
393 : {
394 2 : tm = mutt_date_gmtime(t);
395 : }
396 :
397 4 : tz /= 60;
398 :
399 4 : mutt_buffer_add_printf(buf, "%s, %d %s %d %02d:%02d:%02d %+03d%02d",
400 4 : Weekdays[tm.tm_wday], tm.tm_mday, Months[tm.tm_mon],
401 4 : tm.tm_year + 1900, tm.tm_hour, tm.tm_min, tm.tm_sec,
402 4 : (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 : */
414 112 : int mutt_date_check_month(const char *s)
415 : {
416 592 : for (int i = 0; i < mutt_array_size(Months); i++)
417 586 : if (mutt_istr_startswith(s, Months[i]))
418 106 : return i;
419 :
420 6 : return -1; /* error */
421 : }
422 :
423 : /**
424 : * mutt_date_epoch - Return the number of seconds since the Unix epoch
425 : * @retval num Number of seconds since the Unix epoch, or 0 on failure
426 : */
427 16 : time_t mutt_date_epoch(void)
428 : {
429 16 : 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 : */
436 16 : uint64_t mutt_date_epoch_ms(void)
437 : {
438 16 : struct timeval tv = { 0, 0 };
439 16 : 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 16 : 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 : */
456 100 : time_t mutt_date_parse_date(const char *s, struct Tz *tz_out)
457 : {
458 100 : if (!s)
459 2 : return -1;
460 :
461 98 : bool lax = false;
462 :
463 98 : const regmatch_t *match = mutt_prex_capture(PREX_RFC5322_DATE, s);
464 98 : if (!match)
465 : {
466 60 : match = mutt_prex_capture(PREX_RFC5322_DATE_LAX, s);
467 60 : if (!match)
468 : {
469 22 : mutt_debug(LL_DEBUG1, "Could not parse date: <%s>\n", s);
470 22 : return -1;
471 : }
472 38 : lax = true;
473 38 : mutt_debug(LL_DEBUG2, "Fallback regex for date: <%s>\n", s);
474 : }
475 :
476 76 : struct tm tm = { 0 };
477 :
478 : // clang-format off
479 76 : const regmatch_t *mday = &match[lax ? PREX_RFC5322_DATE_LAX_MATCH_DAY : PREX_RFC5322_DATE_MATCH_DAY];
480 76 : const regmatch_t *mmonth = &match[lax ? PREX_RFC5322_DATE_LAX_MATCH_MONTH : PREX_RFC5322_DATE_MATCH_MONTH];
481 76 : const regmatch_t *myear = &match[lax ? PREX_RFC5322_DATE_LAX_MATCH_YEAR : PREX_RFC5322_DATE_MATCH_YEAR];
482 76 : const regmatch_t *mhour = &match[lax ? PREX_RFC5322_DATE_LAX_MATCH_HOUR : PREX_RFC5322_DATE_MATCH_HOUR];
483 76 : const regmatch_t *mminute = &match[lax ? PREX_RFC5322_DATE_LAX_MATCH_MINUTE : PREX_RFC5322_DATE_MATCH_MINUTE];
484 76 : const regmatch_t *msecond = &match[lax ? PREX_RFC5322_DATE_LAX_MATCH_SECOND : PREX_RFC5322_DATE_MATCH_SECOND];
485 76 : const regmatch_t *mtz = &match[lax ? PREX_RFC5322_DATE_LAX_MATCH_TZ : PREX_RFC5322_DATE_MATCH_TZ];
486 76 : 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 76 : sscanf(s + mutt_regmatch_start(mday), "%d", &tm.tm_mday);
491 76 : if (tm.tm_mday > 31)
492 2 : return -1;
493 :
494 : /* Month */
495 74 : tm.tm_mon = mutt_date_check_month(s + mutt_regmatch_start(mmonth));
496 :
497 : /* Year */
498 74 : sscanf(s + mutt_regmatch_start(myear), "%d", &tm.tm_year);
499 74 : if (tm.tm_year < 50)
500 2 : tm.tm_year += 100;
501 72 : else if (tm.tm_year >= 1900)
502 72 : tm.tm_year -= 1900;
503 :
504 : /* Time */
505 74 : int hour, min, sec = 0;
506 74 : sscanf(s + mutt_regmatch_start(mhour), "%d", &hour);
507 74 : sscanf(s + mutt_regmatch_start(mminute), "%d", &min);
508 74 : if (mutt_regmatch_start(msecond) != -1)
509 68 : sscanf(s + mutt_regmatch_start(msecond), "%d", &sec);
510 74 : if ((hour > 23) || (min > 59) || (sec > 60))
511 10 : return -1;
512 64 : tm.tm_hour = hour;
513 64 : tm.tm_min = min;
514 64 : tm.tm_sec = sec;
515 :
516 : /* Time zone */
517 64 : int zhours = 0;
518 64 : int zminutes = 0;
519 64 : bool zoccident = false;
520 64 : if (mutt_regmatch_start(mtz) != -1)
521 : {
522 : char direction;
523 34 : sscanf(s + mutt_regmatch_start(mtz), "%c%02d%02d", &direction, &zhours, &zminutes);
524 34 : zoccident = (direction == '-');
525 : }
526 30 : else if (mutt_regmatch_start(mtzobs) != -1)
527 : {
528 : const struct Tz *tz =
529 16 : find_tz(s + mutt_regmatch_start(mtzobs), mutt_regmatch_len(mtzobs));
530 16 : if (tz)
531 : {
532 14 : zhours = tz->zhours;
533 14 : zminutes = tz->zminutes;
534 14 : zoccident = tz->zoccident;
535 : }
536 : }
537 :
538 64 : if (tz_out)
539 : {
540 64 : tz_out->zhours = zhours;
541 64 : tz_out->zminutes = zminutes;
542 64 : tz_out->zoccident = zoccident;
543 : }
544 :
545 64 : 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 : */
557 4 : int mutt_date_make_imap(char *buf, size_t buflen, time_t timestamp)
558 : {
559 4 : if (!buf)
560 2 : return -1;
561 :
562 2 : struct tm tm = mutt_date_localtime(timestamp);
563 2 : time_t tz = mutt_date_local_tz(timestamp);
564 :
565 2 : tz /= 60;
566 :
567 2 : return snprintf(buf, buflen, "%02d-%s-%d %02d:%02d:%02d %+03d%02d",
568 2 : tm.tm_mday, Months[tm.tm_mon], tm.tm_year + 1900, tm.tm_hour,
569 2 : 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 : */
583 4 : int mutt_date_make_tls(char *buf, size_t buflen, time_t timestamp)
584 : {
585 4 : if (!buf)
586 2 : return -1;
587 :
588 2 : struct tm tm = mutt_date_gmtime(timestamp);
589 2 : return snprintf(buf, buflen, "%s, %d %s %d %02d:%02d:%02d UTC",
590 2 : Weekdays[tm.tm_wday], tm.tm_mday, Months[tm.tm_mon],
591 2 : 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 : */
600 20 : time_t mutt_date_parse_imap(const char *s)
601 : {
602 20 : const regmatch_t *match = mutt_prex_capture(PREX_IMAP_DATE, s);
603 20 : if (!match)
604 14 : return 0;
605 :
606 6 : const regmatch_t *mday = &match[PREX_IMAP_DATE_MATCH_DAY];
607 6 : const regmatch_t *mmonth = &match[PREX_IMAP_DATE_MATCH_MONTH];
608 6 : const regmatch_t *myear = &match[PREX_IMAP_DATE_MATCH_YEAR];
609 6 : const regmatch_t *mtime = &match[PREX_IMAP_DATE_MATCH_TIME];
610 6 : const regmatch_t *mtz = &match[PREX_IMAP_DATE_MATCH_TZ];
611 :
612 : struct tm tm;
613 :
614 6 : sscanf(s + mutt_regmatch_start(mday), " %d", &tm.tm_mday);
615 6 : tm.tm_mon = mutt_date_check_month(s + mutt_regmatch_start(mmonth));
616 6 : sscanf(s + mutt_regmatch_start(myear), "%d", &tm.tm_year);
617 6 : tm.tm_year -= 1900;
618 6 : 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 6 : sscanf(s + mutt_regmatch_start(mtz), "%c%02d%02d", &direction, &zhours, &zminutes);
623 6 : bool zoccident = (direction == '-');
624 :
625 6 : 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 : */
636 18 : time_t mutt_date_add_timeout(time_t now, time_t timeout)
637 : {
638 18 : if (timeout < 0)
639 4 : return now;
640 :
641 14 : if ((TIME_T_MAX - now) < timeout)
642 4 : return TIME_T_MAX;
643 :
644 10 : 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 : */
654 24 : struct tm mutt_date_localtime(time_t t)
655 : {
656 24 : struct tm tm = { 0 };
657 :
658 24 : if (t == MUTT_DATE_NOW)
659 2 : t = mutt_date_epoch();
660 :
661 24 : localtime_r(&t, &tm);
662 24 : 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 : */
672 20 : struct tm mutt_date_gmtime(time_t t)
673 : {
674 20 : struct tm tm = { 0 };
675 :
676 20 : if (t == MUTT_DATE_NOW)
677 2 : t = mutt_date_epoch();
678 :
679 20 : gmtime_r(&t, &tm);
680 20 : 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 NUL byte
690 : */
691 6 : size_t mutt_date_localtime_format(char *buf, size_t buflen, const char *format, time_t t)
692 : {
693 6 : if (!buf || !format)
694 4 : return 0;
695 :
696 2 : struct tm tm = mutt_date_localtime(t);
697 2 : 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 : */
704 2 : void mutt_date_sleep_ms(size_t ms)
705 : {
706 2 : const struct timespec sleep = {
707 2 : .tv_sec = ms / 1000,
708 2 : .tv_nsec = (ms % 1000) * 1000000UL,
709 : };
710 2 : nanosleep(&sleep, NULL);
711 2 : }
|