$darkmode
Libical API Documentation 4.0 STABLE VERSION Visit the v3.0 documentation
vcardtime.c
Go to the documentation of this file.
1 /*======================================================================
2  FILE: vcardtime.c
3  CREATOR: Ken Murchison 24 Aug 2022
4 
5  SPDX-FileCopyrightText: 2022, Fastmail Pty. Ltd. (https://fastmail.com)
6  SPDX-License-Identifier: LGPL-2.1-only OR MPL-2.0
7  ======================================================================*/
8 
14 #ifdef HAVE_CONFIG_H
15 #include <config.h>
16 #endif
17 
18 #include "vcardtime.h"
19 #include "icalmemory.h"
20 #include "icaltime.h"
21 
22 #include <limits.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <time.h>
27 
28 #define TIME_BUF_SIZE 21
29 
30 vcardtimetype vcardtime_null_datetime(void)
31 {
32  vcardtimetype t = {-1, -1, -1, -1, -1, -1, -1};
33 
34  return t;
35 }
36 
37 vcardtimetype vcardtime_current_utc_time(void)
38 {
39  time_t now = time(0);
40  struct tm t;
41  vcardtimetype tt;
42 
43  memset(&t, 0, sizeof(struct tm));
44  if (!icalgmtime_r(&now, &t)) {
45  return vcardtime_null_datetime();
46  }
47 
48  tt.year = t.tm_year + 1900;
49  tt.month = t.tm_mon + 1;
50  tt.day = t.tm_mday;
51  tt.hour = t.tm_hour;
52  tt.minute = t.tm_min;
53  tt.second = t.tm_sec;
54  tt.utcoffset = 0;
55 
56  return tt;
57 }
58 
59 bool vcardtime_is_time(const vcardtimetype t)
60 {
61  return (t.year == -1 && t.month == -1 && t.day == -1);
62 }
63 
64 bool vcardtime_is_date(const vcardtimetype t)
65 {
66  return (t.hour == -1 && t.minute == -1 && t.second == -1);
67 }
68 
69 bool vcardtime_is_null_datetime(const vcardtimetype t)
70 {
71  return (vcardtime_is_time(t) && vcardtime_is_date(t));
72 }
73 
74 bool vcardtime_is_datetime(const vcardtimetype t)
75 {
76  return (t.day != -1 && t.hour != -1);
77 }
78 
79 bool vcardtime_is_timestamp(const vcardtimetype t)
80 {
81  return (t.year != -1 && t.month != -1 && t.day != -1 &&
82  t.hour != -1 && t.minute != -1 && t.second != -1 &&
83  t.utcoffset != -1);
84 }
85 
86 bool vcardtime_is_utc(const vcardtimetype t)
87 {
88  return (t.utcoffset == 0 && !vcardtime_is_date(t));
89 }
90 
91 bool vcardtime_is_leap_year(const int year)
92 {
93  if (year == -1) {
94  return true;
95  } else if (year <= 1752) {
96  return (year % 4 == 0);
97  } else {
98  return ((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0);
99  }
100 }
101 
102 static const int days_in_month[] =
103  {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
104 
105 bool vcardtime_is_valid_time(const struct vcardtimetype t)
106 {
107  int days;
108 
109  if (t.year > 3000 || t.day == 0 ||
110  t.hour > 23 || t.minute > 59 || t.second > 60 ||
111  t.utcoffset < -720 || t.utcoffset > 840) {
112  return false;
113  }
114 
115  if (t.minute == -1 && t.hour != -1 && t.second != -1) {
116  return false;
117  }
118 
119  switch (t.month) {
120  case 0:
121  return false;
122 
123  case -1:
124  if (t.year != -1 && t.day != -1) {
125  return false;
126  }
127 
128  days = 31;
129  break;
130 
131  case 2:
132  days = 28 + vcardtime_is_leap_year(t.year);
133  break;
134 
135  default:
136  if (t.month > 12) {
137  return false;
138  }
139 
140  days = days_in_month[t.month];
141  break;
142  }
143 
144  if (t.day > days) {
145  return false;
146  }
147 
148  return true;
149 }
150 
151 #pragma GCC diagnostic push
152 #pragma GCC diagnostic ignored "-Wformat-nonliteral"
153 static int sprintf_date(const vcardtimetype t, unsigned flags,
154  char *buf, size_t size)
155 {
156  /*
157  date = year [month day]
158  / year "-" month
159  / "--" month [day]
160  / "--" "-" day
161  */
162  const char *fmt;
163 
164  if (!(flags & VCARDTIME_AS_V4)) {
165  return snprintf(buf, size, "%04d%02d%02d",
166  t.year == -1 ? 0 : t.year,
167  t.month == -1 ? 1 : t.month,
168  t.day == -1 ? 1 : t.day);
169  } else if (t.year != -1) {
170  if (t.month == -1) {
171  fmt = "%04d";
172  } else if (t.day == -1) {
173  fmt = "%04d-%02d";
174  } else {
175  fmt = "%04d%02d%02d";
176  }
177 
178  /* cppcheck-suppress wrongPrintfScanfArgNum */
179  return snprintf(buf, size, fmt, t.year, t.month, t.day);
180  } else if (t.month != -1) {
181  if (t.day == -1) {
182  fmt = "--%02d";
183  } else {
184  fmt = "--%02d%02d";
185  }
186 
187  /* cppcheck-suppress wrongPrintfScanfArgNum */
188  return snprintf(buf, size, fmt, t.month, t.day);
189  } else {
190  return snprintf(buf, size, "---%02d", t.day);
191  }
192 }
193 
194 static int sprintf_time(const vcardtimetype t, unsigned flags,
195  char *buf, size_t size)
196 {
197  /*
198  time = ["T"] hour [minute [second]] [zone]
199  / ["T"] "-" minute [second]
200  / ["T"] "-" "-" second
201  */
202  const char *fmt;
203  int n;
204 
205  if (!(flags & VCARDTIME_BARE_TIME)) {
206  strncat(buf, "T", size);
207  buf++;
208  size--;
209  }
210 
211  if (!(flags & VCARDTIME_AS_V4)) {
212  n = snprintf(buf, size, "%02d%02d%02d",
213  t.hour == -1 ? 0 : t.hour,
214  t.minute == -1 ? 0 : t.minute,
215  t.second == -1 ? 0 : t.second);
216  } else if (t.hour != -1) {
217  /* hour [minute [second]] [zone] */
218  if (t.minute == -1) {
219  /* hour */
220  fmt = "%02d";
221  } else if (t.second == -1) {
222  /* hour minute */
223  fmt = "%02d%02d";
224  } else {
225  /* hour minute second */
226  fmt = "%02d%02d%02d";
227  }
228 
229  /* cppcheck-suppress wrongPrintfScanfArgNum */
230  n = snprintf(buf, size, fmt, t.hour, t.minute, t.second);
231  } else if (t.minute != -1) {
232  /* "-" minute [second] */
233  if (t.second == -1) {
234  /* "-" minute */
235  fmt = "-%02d";
236  } else {
237  /* "-" minute second */
238  fmt = "-%02d%02d";
239  }
240 
241  /* cppcheck-suppress wrongPrintfScanfArgNum */
242  return snprintf(buf, size, fmt, t.minute, t.second);
243  } else {
244  /* "-" "-" second */
245  return snprintf(buf, size, "--%02d", t.second);
246  }
247 
248  if (t.utcoffset != -1) {
249  /* zone = "Z" / ( sign hour minute ) */
250  buf += n;
251  size -= (size_t)n;
252 
253  if (t.utcoffset == 0) {
254  strncpy(buf, "Z", size);
255  n++;
256  } else {
257  n += snprintf(buf, size, "%+03d%02d",
258  t.utcoffset / 60, abs(t.utcoffset % 60));
259  }
260  }
261 
262  return n;
263 }
264 #pragma GCC diagnostic pop
265 
266 char *vcardtime_as_vcard_string_r(const vcardtimetype t, unsigned flags)
267 {
268  size_t size = TIME_BUF_SIZE;
269  char *ptr, *buf;
270 
271  ptr = buf = icalmemory_new_buffer(size);
272 
273  if (!(flags & VCARDTIME_AS_V4) || !vcardtime_is_time(t)) {
274  int n = sprintf_date(t, flags, ptr, size);
275  ptr += n;
276  size -= (size_t)n;
277  }
278  if (!vcardtime_is_date(t)) {
279  (void)sprintf_time(t, flags, ptr, size);
280  }
281 
282  return buf;
283 }
284 
285 const char *vcardtime_as_vcard_string(const vcardtimetype t, unsigned flags)
286 {
287  char *buf;
288 
289  buf = vcardtime_as_vcard_string_r(t, flags);
291  return buf;
292 }
293 
294 #define num_digits(s) strspn(s, "0123456789")
295 
296 static const char *sscanf_date(const char *str, vcardtimetype *t)
297 {
298  /*
299  date = year [month day]
300  / year "-" month
301  / "--" month [day]
302  / "--" "-" day
303  */
304  const char *month;
305  size_t ndig;
306  int nchar = 0;
307  char *newstr;
308 
309  if (!str || !*str) {
310  /* empty string */
311  return NULL;
312  } else if (!strncmp(str, "--", 2)) {
313  month = str + 2;
314 
315  if (*month == '-') {
316  ndig = num_digits(month + 1);
317 
318  if (ndig == 2) {
319  sscanf(str, "---%2u%n", (unsigned *)&t->day, &nchar);
320  }
321  } else {
322  ndig = num_digits(month);
323 
324  if (ndig == 4) {
325  sscanf(str, "--%2u%2u%n",
326  (unsigned *)&t->month, (unsigned *)&t->day, &nchar);
327  } else if (ndig == 2) {
328  sscanf(str, "--%2u%n", (unsigned *)&t->month, &nchar);
329  }
330  }
331  } else {
332  ndig = num_digits(str);
333 
334  if (ndig == 8) {
335  sscanf(str, "%4u%2u%2u%n",
336  (unsigned *)&t->year, (unsigned *)&t->month,
337  (unsigned *)&t->day, &nchar);
338  } else if (ndig == 4) {
339  month = str + 4;
340 
341  if (!*month) {
342  sscanf(str, "%4u%n", (unsigned *)&t->year, &nchar);
343  } else if (*month == '-') {
344  ndig = num_digits(++month);
345 
346  if (ndig == 2) {
347  if (month[2] == '-') {
348  sscanf(str, "%4u-%2u-%2u%n",
349  (unsigned *)&t->year, (unsigned *)&t->month,
350  (unsigned *)&t->day, &nchar);
351  } else {
352  sscanf(str, "%4u-%2u%n",
353  (unsigned *)&t->year, (unsigned *)&t->month,
354  &nchar);
355  }
356  }
357  }
358  }
359  }
360 
361  if (!nchar) {
362  /* invalid time */
363  return NULL;
364  }
365 
366  newstr = (char *)str + nchar;
367  return newstr;
368 }
369 
370 static const char *sscanf_zone(const char *str, vcardtimetype *t)
371 {
372  /*
373  zone = "Z"
374  / ( "+" / "-" ) hour [minute]
375  */
376  unsigned offset_h = 0, offset_m = 0;
377  char sign[2] = "";
378  char *newstr;
379  int nchar = 0;
380 
381  if (!str || !*str) {
382  /* empty string */
383  return NULL;
384  } else if (*str == 'Z') {
385  nchar = 1;
386  } else if (strchr("+-", *str)) {
387  size_t ndig = num_digits(str + 1);
388 
389  if (ndig == 4) {
390  sscanf(str, "%1[+-]%2u%2u%n", sign, &offset_h, &offset_m, &nchar);
391  } else if (ndig == 2) {
392  sscanf(str, "%1[+-]%2u%n", sign, &offset_h, &nchar);
393  }
394  }
395 
396  if (!nchar) {
397  /* invalid zone */
398  return NULL;
399  }
400 
401  t->utcoffset = (int)(60 * offset_h + offset_m);
402  if (*sign == '-') {
403  t->utcoffset = -t->utcoffset;
404  }
405 
406  newstr = (char *)str + nchar;
407  return newstr;
408 }
409 
410 static const char *sscanf_time(const char *str, vcardtimetype *t)
411 {
412  /*
413  time = hour [ ":" minute ":" second [ "." secfrac ] ]
414  / hour [ minute [second]] [zone]
415  / "-" minute [second]
416  / "-" "-" second
417  */
418  unsigned secfrac;
419  size_t ndig;
420  int nchar = 0;
421 
422  if (!str || !*str) {
423  /* empty string */
424  return NULL;
425  } else if (*str == '-') {
426  if (str[1] == '-') {
427  ndig = num_digits(str + 2);
428 
429  if (ndig == 2) {
430  sscanf(str, "--%2u%n", (unsigned *)&t->second, &nchar);
431  }
432  } else {
433  ndig = num_digits(str + 1);
434 
435  if (ndig == 4) {
436  sscanf(str, "-%2u%2u%n",
437  (unsigned *)&t->minute, (unsigned *)&t->second, &nchar);
438  } else if (ndig == 2) {
439  sscanf(str, "-%2u%n", (unsigned *)&t->minute, &nchar);
440  t->second = 0;
441  }
442  }
443  } else {
444  ndig = num_digits(str);
445 
446  if (ndig == 6) {
447  sscanf(str, "%2u%2u%2u%n",
448  (unsigned *)&t->hour, (unsigned *)&t->minute,
449  (unsigned *)&t->second, &nchar);
450  } else if (ndig == 4) {
451  sscanf(str, "%2u%2u%n",
452  (unsigned *)&t->hour, (unsigned *)&t->minute, &nchar);
453  t->second = 0;
454  } else if (ndig == 2) {
455  if (str[2] == ':') {
456  if (strlen(str) > 8 && str[8] == '.') {
457  sscanf(str, "%2u:%2u:%2u.%u%n",
458  (unsigned *)&t->hour, (unsigned *)&t->minute,
459  (unsigned *)&t->second, (unsigned *)&secfrac, &nchar);
460  } else {
461  sscanf(str, "%2u:%2u:%2u%n",
462  (unsigned *)&t->hour, (unsigned *)&t->minute,
463  (unsigned *)&t->second, &nchar);
464  }
465  } else {
466  sscanf(str, "%2u%n", (unsigned *)&t->hour, &nchar);
467  }
468  }
469  }
470 
471  if (!nchar) {
472  /* invalid time */
473  return NULL;
474  }
475 
476  str += nchar;
477 
478  if (t->hour != -1 && *str) {
479  str = sscanf_zone(str, t);
480  }
481 
482  return str;
483 }
484 
485 vcardtimetype vcardtime_from_string(const char *str, int is_bare_time)
486 {
487  vcardtimetype t = {-1, -1, -1, -1, -1, -1, -1};
488 
489  if (!is_bare_time && str && *str != 'T') {
490  str = sscanf_date(str, &t);
491  }
492  if (str && *str) {
493  if (*str == 'T') {
494  str++;
495  }
496  str = sscanf_time(str, &t);
497  }
498 
499  return (str && !*str) ? t : vcardtime_null_datetime();
500 }
Common memory management routines.
Defines the data structure for representing date-times.
Defines the data structure representing vCard date-times.
void * icalmemory_new_buffer(size_t size)
Creates new buffer with the specified size.
Definition: icalmemory.c:315
void icalmemory_add_tmp_buffer(void *buf)
Adds an externally allocated buffer to the ring.
Definition: icalmemory.c:158