Coverage Report

Created: 2025-12-28 06:16

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/tarantool/third_party/c-dt/dt_parse_iso.c
Line
Count
Source
1
/*
2
 * Copyright (c) 2012-2015 Christian Hansen <chansen@cpan.org>
3
 * <https://github.com/chansen/c-dt>
4
 * All rights reserved.
5
 * 
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met: 
8
 * 
9
 * 1. Redistributions of source code must retain the above copyright notice, this
10
 *    list of conditions and the following disclaimer. 
11
 * 2. Redistributions in binary form must reproduce the above copyright notice,
12
 *    this list of conditions and the following disclaimer in the documentation
13
 *    and/or other materials provided with the distribution. 
14
 * 
15
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
16
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
19
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
22
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25
 */
26
#include <stddef.h>
27
#include "dt_core.h"
28
#include "dt_valid.h"
29
30
static size_t
31
5.36k
count_digits(const unsigned char * const p, size_t i, const size_t len) {
32
5.36k
    const size_t n = i;
33
34
17.5M
    for(; i < len; i++) {
35
17.5M
        const unsigned char c = p[i] - '0';
36
17.5M
        if (c > 9)
37
4.93k
            break;
38
17.5M
    }
39
5.36k
    return i - n;
40
5.36k
}
41
42
static int
43
6.38k
parse_number(const unsigned char * const p, size_t i, const size_t len) {
44
6.38k
    int v = 0;
45
46
6.38k
    switch (len) {
47
94
        case 9: v += (p[i++] - '0') * 100000000;
48
102
        case 8: v += (p[i++] - '0') * 10000000;
49
327
        case 7: v += (p[i++] - '0') * 1000000;
50
384
        case 6: v += (p[i++] - '0') * 100000;
51
467
        case 5: v += (p[i++] - '0') * 10000;
52
1.45k
        case 4: v += (p[i++] - '0') * 1000;
53
3.38k
        case 3: v += (p[i++] - '0') * 100;
54
5.91k
        case 2: v += (p[i++] - '0') * 10;
55
6.38k
        case 1: v += (p[i++] - '0');
56
6.38k
    }
57
6.38k
    return v;
58
6.38k
}
59
60
static const int pow_10[10] = {
61
    1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000,
62
};
63
64
/*
65
 *  fffffffff
66
 */
67
68
static size_t
69
178
parse_fraction_digits(const unsigned char *p, size_t i, size_t len, int *fp) {
70
178
    size_t n, ndigits;
71
72
178
    ndigits = n = count_digits(p, i, len);
73
178
    if (ndigits < 1)
74
4
        return 0;
75
174
    if (ndigits > 9)
76
88
        ndigits = 9;
77
174
    if (fp)
78
174
        *fp = parse_number(p, i, ndigits) * pow_10[9 - ndigits];
79
174
    return n;
80
178
}
81
82
/*
83
 *  hh
84
 *  hhmm
85
 *  hhmmss
86
 *  hhmmss.fffffffff
87
 *  hhmmss,fffffffff
88
 */
89
90
size_t
91
1.08k
dt_parse_iso_time_basic(const char *str, size_t len, int *sp, int *fp) {
92
1.08k
    const unsigned char *p;
93
1.08k
    int h, m, s, f;
94
1.08k
    size_t n;
95
96
1.08k
    p = (const unsigned char *)str;
97
1.08k
    n = count_digits(p, 0, len);
98
1.08k
    m = s = f = 0;
99
1.08k
    switch (n) {
100
858
        case 2: /* hh */
101
858
            h = parse_number(p, 0, 2);
102
858
            goto hms;
103
53
        case 4: /* hhmm */
104
53
            h = parse_number(p, 0, 2);
105
53
            m = parse_number(p, 2, 2);
106
53
            goto hms;
107
148
        case 6: /* hhmmss */
108
148
            h = parse_number(p, 0, 2);
109
148
            m = parse_number(p, 2, 2);
110
148
            s = parse_number(p, 4, 2);
111
148
            break;
112
21
        default:
113
21
            return 0;
114
1.08k
    }
115
116
    /* hhmmss.fffffffff */
117
148
    if (n < len && (p[n] == '.' || p[n] == ',')) {
118
94
        size_t r = parse_fraction_digits(p, ++n, len, &f);
119
94
        if (!r)
120
2
            return 0;
121
92
        n += r;
122
92
    }
123
124
1.05k
  hms:
125
1.05k
    if (h > 23 || m > 59 || s > 59) {
126
141
        if (!(h == 24 && m == 0 && s == 0 && f == 0))
127
84
            return 0;
128
141
    }
129
130
973
    if (sp)
131
973
        *sp = h * 3600 + m * 60 + s;
132
973
    if (fp)
133
973
        *fp = f;
134
973
    return n;
135
1.05k
}
136
137
/*
138
 *  Z
139
 *  ±hh
140
 *  ±hhmm
141
 */
142
143
size_t
144
0
dt_parse_iso_zone_basic(const char *str, size_t len, int *op) {
145
0
    const unsigned char *p;
146
0
    int o, h, m, sign;
147
0
    size_t n;
148
149
0
    if (len < 1)
150
0
        return 0;
151
152
0
    p = (const unsigned char *)str;
153
0
    switch (*p) {
154
0
        case 'Z':
155
0
            o = 0;
156
0
            n = 1;
157
0
            goto zulu;
158
0
        case '+':
159
0
            sign = 1;
160
0
            break;
161
0
        case '-':
162
0
            sign = -1;
163
0
            break;
164
0
        default:
165
0
            return 0;
166
0
    }
167
168
0
    if (len < 3)
169
0
        return 0;
170
171
0
    n = count_digits(p, 1, len);
172
0
    m = 0;
173
0
    switch (n) {
174
0
        case 2: /* ±hh */
175
0
            h = parse_number(p, 1, 2);
176
0
            n = 3;
177
0
            break;
178
0
        case 4: /* ±hhmm */
179
0
            h = parse_number(p, 1, 2);
180
0
            m = parse_number(p, 3, 2);
181
0
            n = 5;
182
0
            break;
183
0
        default:
184
0
            return 0;
185
0
    }
186
187
0
    if (h > 23 || m > 59)
188
0
        return 0;
189
0
    o = sign * (h * 60 + m);
190
#ifdef DT_PARSE_ISO_STRICT
191
    if (o == 0 && sign < 0)
192
        return 0;
193
#endif
194
195
0
 zulu:
196
0
    if (op)
197
0
        *op = o;
198
0
    return n;
199
0
}
200
201
/*
202
 *  hh
203
 *  hh:mm
204
 *  hh:mm:ss
205
 *  hh:mm:ss.fffffffff
206
 *  hh:mm:ss,fffffffff
207
 */
208
209
size_t
210
189
dt_parse_iso_time_extended(const char *str, size_t len, int *sp, int *fp) {
211
189
    const unsigned char *p;
212
189
    int h, m, s, f;
213
189
    size_t n;
214
215
189
    p = (const unsigned char *)str;
216
189
    if (count_digits(p, 0, len) != 2)
217
2
        return 0;
218
219
187
    h = parse_number(p, 0, 2);
220
187
    m = s = f = 0;
221
187
    n = 2;
222
    
223
187
    if (len < 3 || p[2] != ':')
224
0
        goto hms;
225
226
187
    if (count_digits(p, 3, len) != 2)
227
6
        return 0;
228
229
181
    m = parse_number(p, 3, 2);
230
181
    n = 5;
231
232
181
    if (len < 6 || p[5] != ':')
233
39
        goto hms;
234
235
142
    if (count_digits(p, 6, len) != 2)
236
32
        return 0;
237
238
110
    s = parse_number(p, 6, 2);
239
110
    n = 8;
240
241
    /* hh:mm:ss.fffffffff */
242
110
    if (n < len && (p[n] == '.' || p[n] == ',')) {
243
84
        size_t r = parse_fraction_digits(p, ++n, len, &f);
244
84
        if (!r)
245
2
            return 0;
246
82
        n += r;
247
82
    }
248
249
147
  hms:
250
147
    if (h > 23 || m > 59 || s > 59) {
251
81
        if (!(h == 24 && m == 0 && s == 0 && f == 0))
252
61
            return 0;
253
81
    }
254
255
86
    if (sp)
256
86
        *sp = h * 3600 + m * 60 + s;
257
86
    if (fp)
258
86
        *fp = f;
259
86
    return n;
260
147
}
261
262
/*
263
 *  Z
264
 *  ±hh
265
 *  ±hh:mm
266
 */
267
268
size_t
269
0
dt_parse_iso_zone_extended(const char *str, size_t len, int *op) {
270
0
    const unsigned char *p;
271
0
    int o, h, m, sign;
272
0
    size_t n;
273
274
0
    if (len < 1)
275
0
        return 0;
276
277
0
    p = (const unsigned char *)str;
278
0
    switch (*p) {
279
0
        case 'Z':
280
0
            o = 0;
281
0
            n = 1;
282
0
            goto zulu;
283
0
        case '+':
284
0
            sign = 1;
285
0
            break;
286
0
        case '-':
287
0
            sign = -1;
288
0
            break;
289
0
        default:
290
0
            return 0;
291
0
    }
292
293
0
    if (len < 3 || count_digits(p, 1, len) != 2)
294
0
        return 0;
295
296
0
    h = parse_number(p, 1, 2);
297
0
    m = 0;
298
0
    n = 3;
299
300
0
    if (len < 4 || p[3] != ':')
301
0
        goto hm;
302
303
0
    if (count_digits(p, 4, len) != 2)
304
0
        return 0;
305
306
0
    m = parse_number(p, 4, 2);
307
0
    n = 6;
308
309
0
 hm:
310
0
    if (h > 23 || m > 59)
311
0
        return 0;
312
0
    o = sign * (h * 60 + m);
313
#ifdef DT_PARSE_ISO_STRICT
314
    if (o == 0 && sign < 0)
315
        return 0;
316
#endif
317
318
0
 zulu:
319
0
    if (op)
320
0
        *op = o;
321
0
    return n;
322
0
}
323
324
/*
325
 *  z
326
 *  Z
327
 *  GMT
328
 *  GMT±h
329
 *  GMT±hhmm
330
 *  GMT±h:mm
331
 *  GMT±hh:mm
332
 *  UTC
333
 *  UTC±h
334
 *  UTC±hhmm
335
 *  UTC±h:mm
336
 *  UTC±hh:mm
337
 *  ±h
338
 *  ±hh
339
 *  ±hhmm
340
 *  ±h:mm
341
 *  ±hh:mm
342
 */
343
344
size_t
345
234
dt_parse_iso_zone_lenient(const char *str, size_t len, int *op) {
346
234
    const unsigned char *p;
347
234
    int o, h, m, sign;
348
234
    size_t n;
349
350
234
    if (len < 1)
351
0
        return 0;
352
353
234
    p = (const unsigned char *)str;
354
234
    switch (*p) {
355
2
        case 'z':
356
3
        case 'Z':
357
3
            o = 0;
358
3
            n = 1;
359
3
            goto zulu;
360
34
        case 'G':
361
34
            if (len < 3 || p[1] != 'M' || p[2] != 'T')
362
34
                return 0;
363
0
            if (len > 3 && (p[3] == '+' || p[3] == '-')) {
364
0
                if (!(n = dt_parse_iso_zone_lenient(str + 3, len - 3, op)))
365
0
                    return 0;
366
0
                return n + 3;
367
0
            }
368
0
            o = 0;
369
0
            n = 3;
370
0
            goto zulu;
371
6
        case 'U':
372
6
            if (len < 3 || p[1] != 'T' || p[2] != 'C')
373
6
                return 0;
374
0
            if (len > 3 && (p[3] == '+' || p[3] == '-')) {
375
0
                if (!(n = dt_parse_iso_zone_lenient(str + 3, len - 3, op)))
376
0
                    return 0;
377
0
                return n + 3;
378
0
            }
379
0
            o = 0;
380
0
            n = 3;
381
0
            goto zulu;
382
19
        case '+':
383
19
            sign = 1;
384
19
            break;
385
107
        case '-':
386
107
            sign = -1;
387
107
            break;
388
65
        default:
389
65
            return 0;
390
234
    }
391
392
126
    if (len < 2)
393
3
        return 0;
394
395
123
    n = count_digits(p, 1, len);
396
123
    m = 0;
397
123
    switch (n) {
398
49
        case 1: /* ±h */
399
49
            h = parse_number(p, 1, 1);
400
49
            n = 2;
401
49
            break;
402
59
        case 2: /* ±hh */
403
59
            h = parse_number(p, 1, 2);
404
59
            n = 3;
405
59
            break;
406
10
        case 4: /* ±hhmm */
407
10
            h = parse_number(p, 1, 2);
408
10
            m = parse_number(p, 3, 2);
409
10
            n = 5;
410
10
            goto hm;
411
5
        default:
412
5
            return 0;
413
123
    }
414
    
415
108
    if (len < n + 1 || p[n] != ':')
416
49
        goto hm;
417
418
59
    if (count_digits(p, ++n, len) != 2)
419
16
        return 0;
420
421
43
    m = parse_number(p, n, 2);
422
43
    n += 2;
423
424
102
 hm:
425
102
    if (h > 23 || m > 59)
426
18
        return 0;
427
84
    o = sign * (h * 60 + m);
428
429
87
 zulu:
430
87
    if (op)
431
87
        *op = o;
432
87
    return n;
433
84
}
434
435
#ifdef DT_PARSE_ISO_TNT
436
/*
437
 * Count number of delimiting dashes, Ws or Qs in date string like
438
 *  5879611-07-11, or 2012-Q4-85, or 10000W521
439
 * Allows both ISO8601 and extended Tarantool datetime formats
440
 */
441
static size_t
442
2.10k
count_delims(const unsigned char *p, size_t i, size_t len) {
443
2.10k
    size_t n = 0;
444
445
15.0M
    for (; i < len; i++) {
446
15.0M
        switch (p[i]) {
447
11.6M
        case '0': case '1': case '2': case '3': case '4':
448
11.6M
        case '5': case '6': case '7': case '8': case '9':
449
11.6M
            continue;
450
78.8k
        case 'Q': case 'W':
451
3.33M
        case '-':
452
3.33M
            n++;
453
3.33M
            continue;
454
15.0M
        }
455
1.42k
        break;
456
15.0M
    }
457
2.10k
    return n;
458
2.10k
}
459
#endif
460
/*
461
 *  Basic      Extended
462
 *  20121224   2012-12-24   Calendar date   (ISO 8601)
463
 *  2012359    2012-359     Ordinal date    (ISO 8601)
464
 *  2012W521   2012-W52-1   Week date       (ISO 8601)
465
 *  2012Q485   2012-Q4-85   Quarter date
466
 *
467
 *  Tarantool extended ranges
468
 * #ifdef DT_PARSE_ISO_TNT
469
 *  -001-12-31      0000-01-01
470
 *  -5879610-06-22  5879611-07-11
471
 * #endif
472
 */
473
size_t
474
2.10k
dt_parse_iso_date(const char *str, size_t len, dt_t *dtp) {
475
2.10k
    const unsigned char *p = (const unsigned char *)str;
476
2.10k
    int y, x, d;
477
2.10k
    size_t n;
478
2.10k
    dt_t dt;
479
2.10k
    int head_n;
480
2.10k
#ifdef DT_PARSE_ISO_TNT
481
2.10k
    int sign = +1;
482
2.10k
    int dashes_n;
483
484
2.10k
    if (p[0] == '-') {
485
219
        sign = -1;
486
219
        p++;
487
219
        len--;
488
219
    }
489
2.10k
    dashes_n = count_delims(p, 0, len);
490
2.10k
#endif
491
492
2.10k
    head_n = n = count_digits(p, 0, len);
493
2.10k
    switch (n) {
494
0
#ifdef DT_PARSE_ISO_TNT
495
678
        case 3: /* -001-01-01 | 100W521 (extended Tarantool range) */
496
678
            if (!dashes_n)
497
10
                return 0;
498
668
            y = parse_number(p, 0, 3);
499
668
            break;
500
31
        case 4: /* -2001 (extended Tarantool range) | 2001-01-01 | 1000W521 */
501
31
            y = parse_number(p, 0, 4);
502
31
            break;
503
78
        case 5: /* 10000-01-01 | 10000W521 (extended Tarantool range) */
504
124
        case 6: /* 109000-01-01 | 109000W521 (extended Tarantool range) */
505
124
            if (!dashes_n)
506
2
                return 0;
507
122
            y = parse_number(p, 0, n);
508
122
            break;
509
1.12k
        case 7: /* 5879611-07-11 | 1000000W521 (extended Tarantool range) */
510
1.12k
            if (dashes_n > 0) {
511
218
                y = parse_number(p, 0, 7);
512
218
                break;
513
218
            }
514
            /* 2012359 (basic ordinal date) */
515
906
            y = parse_number(p, 0, 4);
516
906
            d = parse_number(p, 4, 3);
517
906
            p += 7;
518
906
            goto yd;
519
#else
520
        case 4: /* 2012 (year) */
521
            y = parse_number(p, 0, 4);
522
            break;
523
        case 7: /* 2012359 (basic ordinal date) */
524
            y = parse_number(p, 0, 4);
525
            d = parse_number(p, 4, 3);
526
            p += 7;
527
            goto yd;
528
#endif
529
45
        case 8: /* 20121224 (basic calendar date) */
530
45
            y = parse_number(p, 0, 4);
531
45
            x = parse_number(p, 4, 2);
532
45
            d = parse_number(p, 6, 2);
533
45
            p += 8;
534
45
            goto ymd;
535
102
        default:
536
102
            return 0;
537
2.10k
    }
538
539
1.03k
    if (len < (n + 4))
540
18
        return 0;
541
542
1.02k
    p += n;
543
1.02k
    n = count_digits(p, 1, len);
544
1.02k
    switch (p[0]) {
545
585
        case '-': /* 2012-359 | 2012-12-24 | 2012-W52-1 | 2012-Q4-85 */
546
585
            break;
547
0
#ifndef DT_PARSE_ISO_STRICT
548
187
        case 'Q': /* 2012Q485 */
549
187
            if (n != 3)
550
20
                return 0;
551
167
            x = parse_number(p, 1, 1);
552
167
            d = parse_number(p, 2, 2);
553
167
            p += 4;
554
167
            goto yqd;
555
0
#endif
556
248
        case 'W': /* 2012W521 */
557
248
            if (n != 3)
558
9
                return 0;
559
239
            x = parse_number(p, 1, 2);
560
239
            d = parse_number(p, 3, 1);
561
239
            p += 4;
562
239
            goto ywd;
563
1
        default:
564
1
            return 0;
565
1.02k
    }
566
567
585
    switch (n) {
568
207
        case 0: /* 2012-Q4-85 | 2012-W52-1 */
569
207
            break;
570
32
        case 2: /* 2012-12-24 */
571
32
            if (p[3] != '-' || count_digits(p, 4, len) != 2)
572
31
                return 0;
573
1
            x = parse_number(p, 1, 2);
574
1
            d = parse_number(p, 4, 2);
575
1
            p += 6;
576
1
            goto ymd;
577
344
        case 3: /* 2012-359 */
578
344
            d = parse_number(p, 1, 3);
579
344
            p += 4;
580
344
            goto yd;
581
2
        default:
582
2
            return 0;
583
585
    }
584
585
207
    if (len < (head_n + 6))
586
3
        return 0;
587
588
204
    n = count_digits(p, 2, len);
589
204
    switch (p[1]) {
590
0
#ifndef DT_PARSE_ISO_STRICT
591
58
        case 'Q': /* 2012-Q4-85 */
592
58
            if (n != 1 || p[3] != '-' || count_digits(p, 4, len) != 2)
593
57
                return 0;
594
1
            x = parse_number(p, 2, 1);
595
1
            d = parse_number(p, 4, 2);
596
1
            p += 6;
597
1
            goto yqd;
598
0
#endif
599
68
        case 'W': /* 2012-W52-1 */
600
68
            if (n != 2 || p[4] != '-' || count_digits(p, 5, len) != 1)
601
65
                return 0;
602
3
            x = parse_number(p, 2, 2);
603
3
            d = parse_number(p, 5, 1);
604
3
            p += 6;;
605
3
            goto ywd;
606
78
        default:
607
78
            return 0;
608
204
    }
609
610
1.25k
  yd:
611
1.25k
#ifdef DT_PARSE_ISO_TNT
612
1.25k
    if (!dt_from_yd_checked(sign * y, d, &dt))
613
37
        return 0;
614
#else
615
    if (!dt_valid_yd(y, d))
616
        return 0;
617
    dt = dt_from_yd(y, d);
618
#endif
619
1.21k
    goto finish;
620
621
1.21k
  ymd:
622
46
#ifdef DT_PARSE_ISO_TNT
623
46
    if (!dt_from_ymd_checked(sign * y, x, d, &dt))
624
26
        return 0;;
625
#else
626
    if (!dt_valid_ymd(y, x, d))
627
        return 0;
628
    dt = dt_from_ymd(y, x, d);
629
#endif
630
26
    goto finish;
631
632
0
#ifndef DT_PARSE_ISO_STRICT
633
168
  yqd:
634
168
#ifdef DT_PARSE_ISO_TNT
635
168
    if (!dt_from_yqd_checked(sign * y, x, d, &dt))
636
20
        return 0;
637
#else
638
    if (!dt_valid_yqd(y, x, d))
639
        return 0;
640
    dt = dt_from_yqd(y, x, d);
641
#endif
642
148
    goto finish;
643
148
#endif
644
645
242
  ywd:
646
242
#ifdef DT_PARSE_ISO_TNT
647
242
    if (!dt_from_ywd_checked(sign * y, x, d, &dt))
648
49
        return 0;
649
#else
650
    if (!dt_valid_ywd(y, x, d))
651
        return 0;
652
    dt = dt_from_ywd(y, x, d);
653
#endif
654
655
1.58k
  finish:
656
#ifndef DT_PARSE_ISO_YEAR0
657
    if (y < 1)
658
        return 0;
659
#endif
660
1.58k
    if (dtp)
661
1.58k
        *dtp = dt;
662
1.58k
    return (p - (const unsigned char *)str);
663
242
}
664
665
/*
666
 *  Basic               Extended
667
 *  T12                 N/A
668
 *  T1230               T12:30
669
 *  T123045             T12:30:45
670
 *  T123045.123456789   T12:30:45.123456789
671
 *  T123045,123456789   T12:30:45,123456789
672
 *
673
 *  The time designator [T] may be omitted.
674
 */
675
676
size_t
677
1.27k
dt_parse_iso_time(const char *str, size_t len, int *sod, int *nsec) {
678
1.27k
    size_t n, r;
679
680
1.27k
    if (len < 2)
681
1
        return 0;
682
683
1.26k
    if (str[0] == 'T')
684
4
        r = 1, ++str, --len;
685
1.26k
    else
686
1.26k
        r = 0;
687
688
1.26k
    if (len < 2 || str[2] == ':')
689
189
        n = dt_parse_iso_time_extended(str, len, sod, nsec);
690
1.08k
    else
691
1.08k
        n = dt_parse_iso_time_basic(str, len, sod, nsec);
692
693
1.26k
    if (!n)
694
210
        return 0;
695
1.05k
    return r + n;
696
1.26k
}
697
698
/* 
699
 *  Basic    Extended
700
 *  Z        N/A
701
 *  ±hh      N/A
702
 *  ±hhmm    ±hh:mm
703
 */
704
705
size_t
706
0
dt_parse_iso_zone(const char *str, size_t len, int *offset) {
707
0
    if (len < 3 || str[3] == ':')
708
0
        return dt_parse_iso_zone_extended(str, len, offset);
709
0
    else
710
0
        return dt_parse_iso_zone_basic(str, len, offset);
711
0
}
712