Libical API Documentation 4.0 UNRELEASED Go to the stable 3.0 documentation
Loading...
Searching...
No Matches
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
13
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
30vcardtimetype vcardtime_null_datetime(void)
31{
32 vcardtimetype t = {-1, -1, -1, -1, -1, -1, -1};
33
34 return t;
35}
36
37vcardtimetype vcardtime_current_utc_time(void)
38{
39 time_t now = time(0);
40 struct tm t;
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
59bool vcardtime_is_time(const vcardtimetype t)
60{
61 return (t.year == -1 && t.month == -1 && t.day == -1);
62}
63
64bool vcardtime_is_date(const vcardtimetype t)
65{
66 return (t.hour == -1 && t.minute == -1 && t.second == -1);
67}
68
69bool vcardtime_is_null_datetime(const vcardtimetype t)
70{
71 return (vcardtime_is_time(t) && vcardtime_is_date(t));
72}
73
74bool vcardtime_is_datetime(const vcardtimetype t)
75{
76 return (t.day != -1 && t.hour != -1);
77}
78
79bool 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
86bool vcardtime_is_utc(const vcardtimetype t)
87{
88 return (t.utcoffset == 0 && !vcardtime_is_date(t));
89}
90
91bool 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
102static const int days_in_month[] =
103 {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
104
105bool 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"
153static 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
194static 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
266char *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
285const 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
296static 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
370static 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
410static 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 (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
485vcardtimetype 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}
void * icalmemory_new_buffer(size_t size)
Creates new buffer with the specified size.
Definition icalmemory.c:313
void icalmemory_add_tmp_buffer(void *buf)
Adds an externally allocated buffer to the ring.
Definition icalmemory.c:156
Common memory management routines.
Defines the data structure for representing date-times.
Defines the data structure representing vCard date-times.