Coverage Report

Created: 2025-09-27 06:26

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/php-src/ext/date/lib/parse_posix.c
Line
Count
Source
1
/*
2
 * The MIT License (MIT)
3
 *
4
 * Copyright (c) 2021 MongoDB, Inc.
5
 *
6
 * Permission is hereby granted, free of charge, to any person obtaining a copy
7
 * of this software and associated documentation files (the "Software"), to deal
8
 * in the Software without restriction, including without limitation the rights
9
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
 * copies of the Software, and to permit persons to whom the Software is
11
 * furnished to do so, subject to the following conditions:
12
 *
13
 * The above copyright notice and this permission notice shall be included in
14
 * all copies or substantial portions of the Software.
15
 *
16
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
 * THE SOFTWARE.
23
 */
24
25
#include "timelib.h"
26
#include "timelib_private.h"
27
28
// This section adds the missing 'strndup' implementation on Windows.
29
#if TIMELIB_USE_BUILTIN_STRNDUP == 1
30
# include <stdlib.h>
31
# include <string.h>
32
33
/**
34
 * char* timelib_strndup(const char* s, size_t n)
35
 *
36
 * Returns a pointer to a copy of 's' with at most 'n' characters
37
 * in memory obtained from 'malloc', or 'NULL' if insufficient
38
 * memory was available.  The result is always 'NULL' terminated.
39
 */
40
static char* timelib_strndup(const char* s, size_t n)
41
{
42
  char* result;
43
  size_t len = strlen(s);
44
45
  if (n < len) {
46
    len = n;
47
  }
48
49
  result = (char*)malloc(len + 1);
50
  if (!result) {
51
    return 0;
52
  }
53
54
  result[len] = '\0';
55
  return (char*)memcpy(result, s, len);
56
}
57
#endif
58
59
/* Forwards declrations */
60
static timelib_posix_trans_info *timelib_posix_trans_info_ctor(void);
61
static void timelib_posix_trans_info_dtor(timelib_posix_trans_info* ts);
62
63
/* "<" [+-]? .+? ">" */
64
static char *read_description_numeric_abbr(char **ptr)
65
4
{
66
4
  const char *begin = *ptr + 1;
67
68
  // skip '<'
69
4
  (*ptr)++;
70
71
24
  while (**ptr != '\0' && **ptr != '>') {
72
20
    (*ptr)++;
73
20
  }
74
75
4
  if (**ptr == '\0') {
76
0
    return NULL;
77
0
  }
78
79
4
  if (**ptr == '>') {
80
4
    (*ptr)++;
81
4
  }
82
83
  // Abbreviation may not be empty
84
4
  if (*ptr - begin - 1 < 1) {
85
0
    return NULL;
86
0
  }
87
88
4
  return timelib_strndup(begin, *ptr - begin - 1);
89
4
}
90
91
/* [A-Z]+ */
92
static char *read_description_abbr(char **ptr)
93
6.40k
{
94
6.40k
  const char *begin = *ptr;
95
96
  // Find the end
97
25.9k
  while ((**ptr >= 'A' && **ptr <= 'Z') || (**ptr >= 'a' && **ptr <= 'z')) {
98
19.5k
    (*ptr)++;
99
19.5k
  }
100
101
  // Abbreviation may not be empty
102
6.40k
  if (*ptr - begin < 1) {
103
0
    return NULL;
104
0
  }
105
106
6.40k
  return timelib_strndup(begin, *ptr - begin);
107
6.40k
}
108
109
/* "<" [+-]? .+? ">" | [A-Z]+ */
110
static char *read_description(char **ptr)
111
6.41k
{
112
6.41k
  if (**ptr == '<') {
113
4
    return read_description_numeric_abbr(ptr);
114
6.40k
  } else {
115
6.40k
    return read_description_abbr(ptr);
116
6.40k
  }
117
6.41k
}
118
119
/* [+-]? */
120
static int read_sign(char **ptr)
121
6.41k
{
122
6.41k
  int bias = 1;
123
124
6.41k
  if (**ptr == '+') {
125
0
    (*ptr)++;
126
6.41k
  } else if (**ptr == '-') {
127
198
    bias = -1;
128
198
    (*ptr)++;
129
198
  }
130
131
6.41k
  return bias;
132
6.41k
}
133
134
/* [0-9]+ */
135
static timelib_sll read_number(char **ptr)
136
11.6k
{
137
11.6k
  const char *begin = *ptr;
138
11.6k
  int acc = 0;
139
140
  // skip leading 0's
141
18.6k
  while (**ptr == '0') {
142
7.08k
    (*ptr)++;
143
7.08k
  }
144
145
17.0k
  while (**ptr >= '0' && **ptr <= '9') {
146
5.40k
    acc = acc * 10;
147
5.40k
    acc += (**ptr) - '0';
148
5.40k
    (*ptr)++;
149
5.40k
  }
150
151
11.6k
  if (begin == *ptr) {
152
0
    return TIMELIB_UNSET;
153
0
  }
154
155
11.6k
  return acc;
156
11.6k
}
157
158
/* [+-]? [0-9]+ ( ":" [0-9]+ ( ":" [0-9]+ )? )? */
159
static timelib_sll read_offset(char **ptr)
160
6.41k
{
161
6.41k
  const char *begin;
162
6.41k
  int bias = read_sign(ptr);
163
6.41k
  int hours = 0;
164
6.41k
  int minutes = 0;
165
6.41k
  int seconds = 0;
166
167
6.41k
  begin = *ptr;
168
169
  // read through to : or non-digit for hours
170
6.41k
  hours = read_number(ptr);
171
6.41k
  if (hours == TIMELIB_UNSET) {
172
0
    return hours;
173
0
  }
174
175
  // check for optional minutes
176
6.41k
  if (**ptr == ':') {
177
4
    (*ptr)++; // skip ':'
178
4
    minutes = read_number(ptr);
179
4
    if (minutes == TIMELIB_UNSET) {
180
0
      return minutes;
181
0
    }
182
4
  }
183
184
  // check for optional seconds
185
6.41k
  if (**ptr == ':') {
186
0
    (*ptr)++; // skip ':'
187
0
    seconds = read_number(ptr);
188
0
    if (seconds == TIMELIB_UNSET) {
189
0
      return seconds;
190
0
    }
191
0
  }
192
193
6.41k
  if (begin == *ptr) {
194
0
    return TIMELIB_UNSET;
195
0
  }
196
197
  // multiplication with -1, because the offset in the identifier is the
198
  // 'wrong' way around as for example EST5 is UTC-5 (and not +5)
199
6.41k
  return -1 * bias * (hours * 3600 + minutes * 60 + seconds);
200
6.41k
}
201
202
203
// Mw.m.d
204
static timelib_posix_trans_info* read_trans_spec_mwd(char **ptr)
205
1.73k
{
206
1.73k
  timelib_posix_trans_info *tmp = timelib_posix_trans_info_ctor();
207
208
1.73k
  tmp->type = TIMELIB_POSIX_TRANS_TYPE_MWD;
209
210
  // Skip 'M'
211
1.73k
  (*ptr)++;
212
213
1.73k
  tmp->mwd.month = read_number(ptr);
214
1.73k
  if (tmp->mwd.month == TIMELIB_UNSET) {
215
0
    goto fail;
216
0
  }
217
218
  // check for '.' and skip it
219
1.73k
  if (**ptr != '.') {
220
0
    goto fail;
221
0
  }
222
1.73k
  (*ptr)++;
223
224
1.73k
  tmp->mwd.week = read_number(ptr);
225
1.73k
  if (tmp->mwd.week == TIMELIB_UNSET) {
226
0
    goto fail;
227
0
  }
228
229
  // check for '.' and skip it
230
1.73k
  if (**ptr != '.') {
231
0
    goto fail;
232
0
  }
233
1.73k
  (*ptr)++;
234
235
1.73k
  tmp->mwd.dow = read_number(ptr);
236
1.73k
  if (tmp->mwd.dow == TIMELIB_UNSET) {
237
0
    goto fail;
238
0
  }
239
240
1.73k
  return tmp;
241
242
0
fail:
243
0
  timelib_posix_trans_info_dtor(tmp);
244
0
  return NULL;
245
1.73k
}
246
247
// (Jn | n | Mw.m.d) ( /time )?
248
static timelib_posix_trans_info* read_transition_spec(char **ptr)
249
1.73k
{
250
1.73k
  timelib_posix_trans_info *tmp;
251
252
1.73k
  if (**ptr == 'M') {
253
1.73k
    tmp = read_trans_spec_mwd(ptr);
254
1.73k
    if (!tmp) {
255
0
      return NULL;
256
0
    }
257
1.73k
  } else {
258
0
    tmp = timelib_posix_trans_info_ctor();
259
260
0
    if (**ptr == 'J') {
261
0
      tmp->type = TIMELIB_POSIX_TRANS_TYPE_JULIAN_NO_FEB29;
262
0
      (*ptr)++;
263
0
    }
264
265
0
    tmp->days = read_number(ptr);
266
0
    if (tmp->days == TIMELIB_UNSET) {
267
0
      goto fail;
268
0
    }
269
0
  }
270
271
  // Check for the optional hour
272
1.73k
  if (**ptr == '/') {
273
867
    (*ptr)++;
274
867
    tmp->hour = read_offset(ptr);
275
867
    if (tmp->hour == TIMELIB_UNSET) {
276
0
      goto fail;
277
0
    }
278
    // as the bias for normal offsets = -1, we need to reverse it here
279
867
    tmp->hour = -tmp->hour;
280
867
  }
281
282
1.73k
  return tmp;
283
284
0
fail:
285
0
  timelib_posix_trans_info_dtor(tmp);
286
0
  return NULL;
287
1.73k
}
288
289
static timelib_posix_trans_info* timelib_posix_trans_info_ctor(void)
290
1.73k
{
291
1.73k
  timelib_posix_trans_info *tmp;
292
293
1.73k
  tmp = timelib_calloc(1, sizeof(timelib_posix_trans_info));
294
1.73k
  tmp->type = TIMELIB_POSIX_TRANS_TYPE_JULIAN_FEB29;
295
1.73k
  tmp->hour = 2 * 3600;
296
297
1.73k
  return tmp;
298
1.73k
}
299
300
static void timelib_posix_trans_info_dtor(timelib_posix_trans_info* ts)
301
1.73k
{
302
1.73k
  timelib_free(ts);
303
1.73k
}
304
305
void timelib_posix_str_dtor(timelib_posix_str *ps)
306
5.54k
{
307
5.54k
  if (ps->std) {
308
5.54k
    timelib_free(ps->std);
309
5.54k
  }
310
5.54k
  if (ps->dst) {
311
867
    timelib_free(ps->dst);
312
867
  }
313
5.54k
  if (ps->dst_begin) {
314
867
    timelib_posix_trans_info_dtor(ps->dst_begin);
315
867
  }
316
5.54k
  if (ps->dst_end) {
317
867
    timelib_posix_trans_info_dtor(ps->dst_end);
318
867
  }
319
320
5.54k
  timelib_free(ps);
321
5.54k
}
322
323
timelib_posix_str* timelib_parse_posix_str(const char *posix)
324
5.54k
{
325
5.54k
  timelib_posix_str *tmp = timelib_calloc(1, sizeof(timelib_posix_str));
326
5.54k
  char *ptr = (char*) posix;
327
328
  // read standard description (ie. EST or <-03>)
329
5.54k
  tmp->std = read_description(&ptr);
330
5.54k
  if (!tmp->std) {
331
0
    timelib_posix_str_dtor(tmp);
332
0
    return NULL;
333
0
  }
334
335
  // read required offset
336
5.54k
  tmp->std_offset = read_offset(&ptr);
337
5.54k
  if (tmp->std_offset == TIMELIB_UNSET) {
338
0
    timelib_posix_str_dtor(tmp);
339
0
    return NULL;
340
0
  }
341
342
  // if we're at the end return, otherwise we'll continue to try to parse
343
  // the dst abbreviation and spec
344
5.54k
  if (*ptr == '\0') {
345
4.67k
    return tmp;
346
4.67k
  }
347
348
  // assume dst is there, and initialise offset
349
867
  tmp->dst_offset = tmp->std_offset + 3600;
350
351
867
  tmp->dst = read_description(&ptr);
352
867
  if (!tmp->dst) {
353
0
    timelib_posix_str_dtor(tmp);
354
0
    return NULL;
355
0
  }
356
357
  // if we have a "," here, then the dst offset is the standard offset +
358
  // 3600 seconds, otherwise, try to parse the dst offset
359
867
  if (*ptr != ',' && *ptr != '\0') {
360
1
    tmp->dst_offset = read_offset(&ptr);
361
1
    if (tmp->dst_offset == TIMELIB_UNSET) {
362
0
      timelib_posix_str_dtor(tmp);
363
0
      return NULL;
364
0
    }
365
1
  }
366
367
  // if we *don't* have a "," here, we're missing the dst transitions
368
  // ,start[/time],end[/time]
369
867
  if (*ptr != ',') {
370
0
    timelib_posix_str_dtor(tmp);
371
0
    return NULL;
372
0
  }
373
374
867
  ptr++; // skip ','
375
376
  // start[/time]
377
867
  tmp->dst_begin = read_transition_spec(&ptr);
378
867
  if (!tmp->dst_begin) {
379
0
    timelib_posix_str_dtor(tmp);
380
0
    return NULL;
381
0
  }
382
383
  // if we *don't* have a "," here, we're missing the dst end transition
384
  // ,end[/time]
385
867
  if (*ptr != ',') {
386
0
    timelib_posix_str_dtor(tmp);
387
0
    return NULL;
388
0
  }
389
390
867
  ptr++; // skip ','
391
392
  // end[/time]
393
867
  tmp->dst_end = read_transition_spec(&ptr);
394
867
  if (!tmp->dst_end) {
395
0
    timelib_posix_str_dtor(tmp);
396
0
    return NULL;
397
0
  }
398
399
  // make sure there is no trailing data
400
867
  if (*ptr != '\0') {
401
0
    timelib_posix_str_dtor(tmp);
402
0
    return NULL;
403
0
  }
404
405
867
  return tmp;
406
867
}
407
408
static const int month_lengths[2][MONTHS_PER_YEAR] = {
409
  { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }, // normal year
410
  { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }  // leap year
411
};
412
413
/* This function is adapted from the 'localtime.c' function 'transtime' as bundled with the 'tzcode' project
414
 * from IANA, and is public domain licensed. */
415
static timelib_sll calc_transition(timelib_posix_trans_info *psi, timelib_sll year)
416
36
{
417
36
  int leap_year = timelib_is_leap(year);
418
419
36
  switch (psi->type) {
420
0
    case TIMELIB_POSIX_TRANS_TYPE_JULIAN_NO_FEB29: {
421
0
      timelib_sll value = (psi->days - 1);
422
423
0
      if (leap_year && psi->days >= 60) {
424
0
        value++;
425
0
      }
426
427
0
      return value * SECS_PER_DAY;
428
0
    }
429
430
0
    case TIMELIB_POSIX_TRANS_TYPE_JULIAN_FEB29: {
431
0
      return psi->days * SECS_PER_DAY;
432
0
    }
433
434
36
    case TIMELIB_POSIX_TRANS_TYPE_MWD: {
435
      /*
436
       * Mm.n.d - nth "dth day" of month m.
437
       */
438
439
36
      int i, d, m1, yy0, yy1, yy2, dow;
440
36
      timelib_sll value = 0;
441
442
      /* Use Zeller's Congruence to get day-of-week of first day of
443
       * month. */
444
36
      m1 = (psi->mwd.month + 9) % 12 + 1;
445
36
      yy0 = (psi->mwd.month <= 2) ? (year - 1) : year;
446
36
      yy1 = yy0 / 100;
447
36
      yy2 = yy0 % 100;
448
36
      dow = ((26 * m1 - 2) / 10 + 1 + yy2 + yy2 / 4 + yy1 / 4 - 2 * yy1) % 7;
449
36
      if (dow < 0) {
450
0
        dow += DAYS_PER_WEEK;
451
0
      }
452
453
      /* "dow" is the day-of-week of the first day of the month. Get the
454
       * day-of-month (zero-origin) of the first "dow" day of the month. */
455
36
      d = psi->mwd.dow - dow;
456
36
      if (d < 0) {
457
30
        d += DAYS_PER_WEEK;
458
30
      }
459
96
      for (i = 1; i < psi->mwd.week; ++i) {
460
72
        if (d + DAYS_PER_WEEK >= month_lengths[leap_year][psi->mwd.month - 1]) {
461
12
          break;
462
12
        }
463
60
        d += DAYS_PER_WEEK;
464
60
      }
465
466
      /* "d" is the day-of-month (zero-origin) of the day we want. */
467
36
      value = d * SECS_PER_DAY;
468
234
      for (i = 0; i < psi->mwd.month - 1; ++i) {
469
198
        value += month_lengths[leap_year][i] * SECS_PER_DAY;
470
198
      }
471
472
36
      return value;
473
0
    } break;
474
36
  }
475
476
0
  return 0;
477
36
}
478
479
static timelib_sll count_leap_years(timelib_sll y)
480
36
{
481
  /* Because we want this for Jan 1, the leap day hasn't happend yet, so
482
   * subtract one of year before we calculate */
483
36
  y--;
484
485
36
  return (y/4) - (y/100) + (y/400);
486
36
}
487
488
timelib_sll timelib_ts_at_start_of_year(timelib_sll year)
489
18
{
490
18
  timelib_sll epoch_leap_years = count_leap_years(1970);
491
18
  timelib_sll current_leap_years = count_leap_years(year);
492
493
18
  return SECS_PER_DAY * (
494
18
    ((year-1970) * DAYS_PER_YEAR)
495
18
    + current_leap_years
496
18
    - epoch_leap_years
497
18
  );
498
18
}
499
500
void timelib_get_transitions_for_year(timelib_tzinfo *tz, timelib_sll year, timelib_posix_transitions *transitions)
501
18
{
502
18
  timelib_sll trans_begin; /* Since start of the year */
503
18
  timelib_sll trans_end;
504
18
  timelib_sll year_begin_ts = timelib_ts_at_start_of_year(year);
505
506
18
  trans_begin = year_begin_ts;
507
18
  trans_begin += calc_transition(tz->posix_info->dst_begin, year);
508
18
  trans_begin += tz->posix_info->dst_begin->hour;
509
18
  trans_begin -= tz->posix_info->std_offset;
510
511
18
  trans_end = year_begin_ts;
512
18
  trans_end += calc_transition(tz->posix_info->dst_end, year);
513
18
  trans_end += tz->posix_info->dst_end->hour;
514
18
  trans_end -= tz->posix_info->dst_offset;
515
516
18
  if (trans_begin < trans_end) {
517
0
    transitions->times[transitions->count  ] = trans_begin;
518
0
    transitions->times[transitions->count+1] = trans_end;
519
0
    transitions->types[transitions->count  ] = tz->posix_info->type_index_dst_type;
520
0
    transitions->types[transitions->count+1] = tz->posix_info->type_index_std_type;
521
18
  } else {
522
18
    transitions->times[transitions->count+1] = trans_begin;
523
18
    transitions->times[transitions->count  ] = trans_end;
524
18
    transitions->types[transitions->count+1] = tz->posix_info->type_index_dst_type;
525
18
    transitions->types[transitions->count  ] = tz->posix_info->type_index_std_type;
526
18
  }
527
528
18
  transitions->count += 2;
529
18
}
530
531
ttinfo* timelib_fetch_posix_timezone_offset(timelib_tzinfo *tz, timelib_sll ts, timelib_sll *transition_time)
532
5.34k
{
533
5.34k
  timelib_sll               year;
534
5.34k
  timelib_time              dummy;
535
5.34k
  timelib_posix_transitions transitions = { 0 };
536
5.34k
  size_t            i;
537
538
  /* If there is no second (dst_end) information, the UTC offset is valid for the whole year, so no need to
539
   * do clever logic */
540
5.34k
  if (!tz->posix_info->dst_end) {
541
5.33k
    if (transition_time) {
542
0
      *transition_time = tz->trans[tz->bit64.timecnt - 1];
543
0
    }
544
5.33k
    return &(tz->type[tz->posix_info->type_index_std_type]);
545
5.33k
  }
546
547
  /* Find 'year' (UTC) for 'ts' */
548
6
  timelib_unixtime2gmt(&dummy, ts);
549
6
  year = dummy.y;
550
551
  /* Calculate transition times for 'year-1', 'year', and 'year+1' */
552
6
  timelib_get_transitions_for_year(tz, year - 1, &transitions);
553
6
  timelib_get_transitions_for_year(tz, year,     &transitions);
554
6
  timelib_get_transitions_for_year(tz, year + 1, &transitions);
555
556
  /* Check where the 'ts' falls in the 4 transitions */
557
18
  for (i = 1; i < transitions.count; i++) {
558
18
    if (ts < transitions.times[i]) {
559
6
      if (transition_time) {
560
6
        *transition_time = transitions.times[i - 1];
561
6
      }
562
6
      return &(tz->type[transitions.types[i - 1]]);
563
6
    }
564
18
  }
565
566
0
  return NULL;
567
6
}