Branch data Line data Source code
1 : : #ifndef Py_BUILD_CORE_BUILTIN
2 : : # define Py_BUILD_CORE_MODULE 1
3 : : #endif
4 : :
5 : : #include "Python.h"
6 : : #include "pycore_long.h" // _PyLong_GetOne()
7 : : #include "structmember.h"
8 : :
9 : : #include <ctype.h>
10 : : #include <stddef.h>
11 : : #include <stdint.h>
12 : :
13 : : #include "datetime.h"
14 : :
15 : : // Imports
16 : : static PyObject *io_open = NULL;
17 : : static PyObject *_tzpath_find_tzfile = NULL;
18 : : static PyObject *_common_mod = NULL;
19 : :
20 : : typedef struct TransitionRuleType TransitionRuleType;
21 : : typedef struct StrongCacheNode StrongCacheNode;
22 : :
23 : : typedef struct {
24 : : PyObject *utcoff;
25 : : PyObject *dstoff;
26 : : PyObject *tzname;
27 : : long utcoff_seconds;
28 : : } _ttinfo;
29 : :
30 : : typedef struct {
31 : : _ttinfo std;
32 : : _ttinfo dst;
33 : : int dst_diff;
34 : : TransitionRuleType *start;
35 : : TransitionRuleType *end;
36 : : unsigned char std_only;
37 : : } _tzrule;
38 : :
39 : : typedef struct {
40 : : PyDateTime_TZInfo base;
41 : : PyObject *key;
42 : : PyObject *file_repr;
43 : : PyObject *weakreflist;
44 : : size_t num_transitions;
45 : : size_t num_ttinfos;
46 : : int64_t *trans_list_utc;
47 : : int64_t *trans_list_wall[2];
48 : : _ttinfo **trans_ttinfos; // References to the ttinfo for each transition
49 : : _ttinfo *ttinfo_before;
50 : : _tzrule tzrule_after;
51 : : _ttinfo *_ttinfos; // Unique array of ttinfos for ease of deallocation
52 : : unsigned char fixed_offset;
53 : : unsigned char source;
54 : : } PyZoneInfo_ZoneInfo;
55 : :
56 : : struct TransitionRuleType {
57 : : int64_t (*year_to_timestamp)(TransitionRuleType *, int);
58 : : };
59 : :
60 : : typedef struct {
61 : : TransitionRuleType base;
62 : : uint8_t month;
63 : : uint8_t week;
64 : : uint8_t day;
65 : : int8_t hour;
66 : : int8_t minute;
67 : : int8_t second;
68 : : } CalendarRule;
69 : :
70 : : typedef struct {
71 : : TransitionRuleType base;
72 : : uint8_t julian;
73 : : unsigned int day;
74 : : int8_t hour;
75 : : int8_t minute;
76 : : int8_t second;
77 : : } DayRule;
78 : :
79 : : struct StrongCacheNode {
80 : : StrongCacheNode *next;
81 : : StrongCacheNode *prev;
82 : : PyObject *key;
83 : : PyObject *zone;
84 : : };
85 : :
86 : : static PyTypeObject PyZoneInfo_ZoneInfoType;
87 : :
88 : : // Globals
89 : : static PyObject *TIMEDELTA_CACHE = NULL;
90 : : static PyObject *ZONEINFO_WEAK_CACHE = NULL;
91 : : static StrongCacheNode *ZONEINFO_STRONG_CACHE = NULL;
92 : : static size_t ZONEINFO_STRONG_CACHE_MAX_SIZE = 8;
93 : :
94 : : static _ttinfo NO_TTINFO = {NULL, NULL, NULL, 0};
95 : :
96 : : // Constants
97 : : static const int EPOCHORDINAL = 719163;
98 : : static int DAYS_IN_MONTH[] = {
99 : : -1, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31,
100 : : };
101 : :
102 : : static int DAYS_BEFORE_MONTH[] = {
103 : : -1, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334,
104 : : };
105 : :
106 : : static const int SOURCE_NOCACHE = 0;
107 : : static const int SOURCE_CACHE = 1;
108 : : static const int SOURCE_FILE = 2;
109 : :
110 : : // Forward declarations
111 : : static int
112 : : load_data(PyZoneInfo_ZoneInfo *self, PyObject *file_obj);
113 : : static void
114 : : utcoff_to_dstoff(size_t *trans_idx, long *utcoffs, long *dstoffs,
115 : : unsigned char *isdsts, size_t num_transitions,
116 : : size_t num_ttinfos);
117 : : static int
118 : : ts_to_local(size_t *trans_idx, int64_t *trans_utc, long *utcoff,
119 : : int64_t *trans_local[2], size_t num_ttinfos,
120 : : size_t num_transitions);
121 : :
122 : : static int
123 : : parse_tz_str(PyObject *tz_str_obj, _tzrule *out);
124 : :
125 : : static Py_ssize_t
126 : : parse_abbr(const char *const p, PyObject **abbr);
127 : : static Py_ssize_t
128 : : parse_tz_delta(const char *const p, long *total_seconds);
129 : : static Py_ssize_t
130 : : parse_transition_time(const char *const p, int8_t *hour, int8_t *minute,
131 : : int8_t *second);
132 : : static Py_ssize_t
133 : : parse_transition_rule(const char *const p, TransitionRuleType **out);
134 : :
135 : : static _ttinfo *
136 : : find_tzrule_ttinfo(_tzrule *rule, int64_t ts, unsigned char fold, int year);
137 : : static _ttinfo *
138 : : find_tzrule_ttinfo_fromutc(_tzrule *rule, int64_t ts, int year,
139 : : unsigned char *fold);
140 : :
141 : : static int
142 : : build_ttinfo(long utcoffset, long dstoffset, PyObject *tzname, _ttinfo *out);
143 : : static void
144 : : xdecref_ttinfo(_ttinfo *ttinfo);
145 : : static int
146 : : ttinfo_eq(const _ttinfo *const tti0, const _ttinfo *const tti1);
147 : :
148 : : static int
149 : : build_tzrule(PyObject *std_abbr, PyObject *dst_abbr, long std_offset,
150 : : long dst_offset, TransitionRuleType *start,
151 : : TransitionRuleType *end, _tzrule *out);
152 : : static void
153 : : free_tzrule(_tzrule *tzrule);
154 : :
155 : : static PyObject *
156 : : load_timedelta(long seconds);
157 : :
158 : : static int
159 : : get_local_timestamp(PyObject *dt, int64_t *local_ts);
160 : : static _ttinfo *
161 : : find_ttinfo(PyZoneInfo_ZoneInfo *self, PyObject *dt);
162 : :
163 : : static int
164 : : ymd_to_ord(int y, int m, int d);
165 : : static int
166 : : is_leap_year(int year);
167 : :
168 : : static size_t
169 : : _bisect(const int64_t value, const int64_t *arr, size_t size);
170 : :
171 : : static int
172 : : eject_from_strong_cache(const PyTypeObject *const type, PyObject *key);
173 : : static void
174 : : clear_strong_cache(const PyTypeObject *const type);
175 : : static void
176 : : update_strong_cache(const PyTypeObject *const type, PyObject *key,
177 : : PyObject *zone);
178 : : static PyObject *
179 : : zone_from_strong_cache(const PyTypeObject *const type, PyObject *const key);
180 : :
181 : : static PyObject *
182 : 311 : zoneinfo_new_instance(PyTypeObject *type, PyObject *key)
183 : : {
184 : 311 : PyObject *file_obj = NULL;
185 : 311 : PyObject *file_path = NULL;
186 : :
187 : 311 : file_path = PyObject_CallFunctionObjArgs(_tzpath_find_tzfile, key, NULL);
188 [ + + ]: 311 : if (file_path == NULL) {
189 : 20 : return NULL;
190 : : }
191 [ + + ]: 291 : else if (file_path == Py_None) {
192 : 20 : file_obj = PyObject_CallMethod(_common_mod, "load_tzdata", "O", key);
193 [ + - ]: 20 : if (file_obj == NULL) {
194 : 20 : Py_DECREF(file_path);
195 : 20 : return NULL;
196 : : }
197 : : }
198 : :
199 : 271 : PyObject *self = (PyObject *)(type->tp_alloc(type, 0));
200 [ - + ]: 271 : if (self == NULL) {
201 : 0 : goto error;
202 : : }
203 : :
204 [ + - ]: 271 : if (file_obj == NULL) {
205 : 271 : file_obj = PyObject_CallFunction(io_open, "Os", file_path, "rb");
206 [ - + ]: 271 : if (file_obj == NULL) {
207 : 0 : goto error;
208 : : }
209 : : }
210 : :
211 [ - + ]: 271 : if (load_data((PyZoneInfo_ZoneInfo *)self, file_obj)) {
212 : 0 : goto error;
213 : : }
214 : :
215 : 271 : PyObject *rv = PyObject_CallMethod(file_obj, "close", NULL);
216 : 271 : Py_DECREF(file_obj);
217 : 271 : file_obj = NULL;
218 [ - + ]: 271 : if (rv == NULL) {
219 : 0 : goto error;
220 : : }
221 : 271 : Py_DECREF(rv);
222 : :
223 : 271 : ((PyZoneInfo_ZoneInfo *)self)->key = key;
224 : 271 : Py_INCREF(key);
225 : :
226 : 271 : goto cleanup;
227 : 0 : error:
228 : 0 : Py_XDECREF(self);
229 : 0 : self = NULL;
230 : 271 : cleanup:
231 [ - + ]: 271 : if (file_obj != NULL) {
232 : : PyObject *exc, *val, *tb;
233 : 0 : PyErr_Fetch(&exc, &val, &tb);
234 : 0 : PyObject *tmp = PyObject_CallMethod(file_obj, "close", NULL);
235 : 0 : _PyErr_ChainExceptions(exc, val, tb);
236 [ # # ]: 0 : if (tmp == NULL) {
237 [ # # ]: 0 : Py_CLEAR(self);
238 : : }
239 : 0 : Py_XDECREF(tmp);
240 : 0 : Py_DECREF(file_obj);
241 : : }
242 : 271 : Py_DECREF(file_path);
243 : 271 : return self;
244 : : }
245 : :
246 : : static PyObject *
247 : 478 : get_weak_cache(PyTypeObject *type)
248 : : {
249 [ + + ]: 478 : if (type == &PyZoneInfo_ZoneInfoType) {
250 : 284 : return ZONEINFO_WEAK_CACHE;
251 : : }
252 : : else {
253 : : PyObject *cache =
254 : 194 : PyObject_GetAttrString((PyObject *)type, "_weak_cache");
255 : : // We are assuming that the type lives at least as long as the function
256 : : // that calls get_weak_cache, and that it holds a reference to the
257 : : // cache, so we'll return a "borrowed reference".
258 : 194 : Py_XDECREF(cache);
259 : 194 : return cache;
260 : : }
261 : : }
262 : :
263 : : static PyObject *
264 : 773 : zoneinfo_new(PyTypeObject *type, PyObject *args, PyObject *kw)
265 : : {
266 : 773 : PyObject *key = NULL;
267 : : static char *kwlist[] = {"key", NULL};
268 [ - + ]: 773 : if (PyArg_ParseTupleAndKeywords(args, kw, "O", kwlist, &key) == 0) {
269 : 0 : return NULL;
270 : : }
271 : :
272 : 773 : PyObject *instance = zone_from_strong_cache(type, key);
273 [ + + - + ]: 773 : if (instance != NULL || PyErr_Occurred()) {
274 : 371 : return instance;
275 : : }
276 : :
277 : 402 : PyObject *weak_cache = get_weak_cache(type);
278 : 402 : instance = PyObject_CallMethod(weak_cache, "get", "O", key, Py_None);
279 [ - + ]: 402 : if (instance == NULL) {
280 : 0 : return NULL;
281 : : }
282 : :
283 [ + + ]: 402 : if (instance == Py_None) {
284 : 288 : Py_DECREF(instance);
285 : 288 : PyObject *tmp = zoneinfo_new_instance(type, key);
286 [ + + ]: 288 : if (tmp == NULL) {
287 : 40 : return NULL;
288 : : }
289 : :
290 : : instance =
291 : 248 : PyObject_CallMethod(weak_cache, "setdefault", "OO", key, tmp);
292 : 248 : Py_DECREF(tmp);
293 [ - + ]: 248 : if (instance == NULL) {
294 : 0 : return NULL;
295 : : }
296 : 248 : ((PyZoneInfo_ZoneInfo *)instance)->source = SOURCE_CACHE;
297 : : }
298 : :
299 : 362 : update_strong_cache(type, key, instance);
300 : 362 : return instance;
301 : : }
302 : :
303 : : static void
304 : 358 : zoneinfo_dealloc(PyObject *obj_self)
305 : : {
306 : 358 : PyZoneInfo_ZoneInfo *self = (PyZoneInfo_ZoneInfo *)obj_self;
307 : :
308 [ + + ]: 358 : if (self->weakreflist != NULL) {
309 : 86 : PyObject_ClearWeakRefs(obj_self);
310 : : }
311 : :
312 [ + + ]: 358 : if (self->trans_list_utc != NULL) {
313 : 322 : PyMem_Free(self->trans_list_utc);
314 : : }
315 : :
316 [ + + ]: 1074 : for (size_t i = 0; i < 2; i++) {
317 [ + + ]: 716 : if (self->trans_list_wall[i] != NULL) {
318 : 584 : PyMem_Free(self->trans_list_wall[i]);
319 : : }
320 : : }
321 : :
322 [ + + ]: 358 : if (self->_ttinfos != NULL) {
323 [ + + ]: 2076 : for (size_t i = 0; i < self->num_ttinfos; ++i) {
324 : 1754 : xdecref_ttinfo(&(self->_ttinfos[i]));
325 : : }
326 : 322 : PyMem_Free(self->_ttinfos);
327 : : }
328 : :
329 [ + + ]: 358 : if (self->trans_ttinfos != NULL) {
330 : 322 : PyMem_Free(self->trans_ttinfos);
331 : : }
332 : :
333 : 358 : free_tzrule(&(self->tzrule_after));
334 : :
335 : 358 : Py_XDECREF(self->key);
336 : 358 : Py_XDECREF(self->file_repr);
337 : :
338 : 358 : Py_TYPE(self)->tp_free((PyObject *)self);
339 : 358 : }
340 : :
341 : : static PyObject *
342 : 88 : zoneinfo_from_file(PyTypeObject *type, PyObject *args, PyObject *kwargs)
343 : : {
344 : 88 : PyObject *file_obj = NULL;
345 : 88 : PyObject *file_repr = NULL;
346 : 88 : PyObject *key = Py_None;
347 : 88 : PyZoneInfo_ZoneInfo *self = NULL;
348 : :
349 : : static char *kwlist[] = {"", "key", NULL};
350 [ + + ]: 88 : if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|O", kwlist, &file_obj,
351 : : &key)) {
352 : 1 : return NULL;
353 : : }
354 : :
355 : 87 : PyObject *obj_self = (PyObject *)(type->tp_alloc(type, 0));
356 : 87 : self = (PyZoneInfo_ZoneInfo *)obj_self;
357 [ - + ]: 87 : if (self == NULL) {
358 : 0 : return NULL;
359 : : }
360 : :
361 : 87 : file_repr = PyUnicode_FromFormat("%R", file_obj);
362 [ - + ]: 87 : if (file_repr == NULL) {
363 : 0 : goto error;
364 : : }
365 : :
366 [ + + ]: 87 : if (load_data(self, file_obj)) {
367 : 36 : goto error;
368 : : }
369 : :
370 : 51 : self->source = SOURCE_FILE;
371 : 51 : self->file_repr = file_repr;
372 : 51 : self->key = key;
373 : 51 : Py_INCREF(key);
374 : :
375 : 51 : return obj_self;
376 : 36 : error:
377 : 36 : Py_XDECREF(file_repr);
378 : 36 : Py_XDECREF(self);
379 : 36 : return NULL;
380 : : }
381 : :
382 : : static PyObject *
383 : 11 : zoneinfo_no_cache(PyTypeObject *cls, PyObject *args, PyObject *kwargs)
384 : : {
385 : : static char *kwlist[] = {"key", NULL};
386 : 11 : PyObject *key = NULL;
387 [ - + ]: 11 : if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O", kwlist, &key)) {
388 : 0 : return NULL;
389 : : }
390 : :
391 : 11 : PyObject *out = zoneinfo_new_instance(cls, key);
392 [ + - ]: 11 : if (out != NULL) {
393 : 11 : ((PyZoneInfo_ZoneInfo *)out)->source = SOURCE_NOCACHE;
394 : : }
395 : :
396 : 11 : return out;
397 : : }
398 : :
399 : : static PyObject *
400 : 77 : zoneinfo_clear_cache(PyObject *cls, PyObject *args, PyObject *kwargs)
401 : : {
402 : 77 : PyObject *only_keys = NULL;
403 : : static char *kwlist[] = {"only_keys", NULL};
404 : :
405 [ + + ]: 77 : if (!(PyArg_ParseTupleAndKeywords(args, kwargs, "|$O", kwlist,
406 : : &only_keys))) {
407 : 1 : return NULL;
408 : : }
409 : :
410 : 76 : PyTypeObject *type = (PyTypeObject *)cls;
411 : 76 : PyObject *weak_cache = get_weak_cache(type);
412 : :
413 [ + + + + ]: 150 : if (only_keys == NULL || only_keys == Py_None) {
414 : 74 : PyObject *rv = PyObject_CallMethod(weak_cache, "clear", NULL);
415 [ + - ]: 74 : if (rv != NULL) {
416 : 74 : Py_DECREF(rv);
417 : : }
418 : :
419 : 74 : clear_strong_cache(type);
420 : : }
421 : : else {
422 : 2 : PyObject *item = NULL;
423 : 2 : PyObject *pop = PyUnicode_FromString("pop");
424 [ - + ]: 2 : if (pop == NULL) {
425 : 0 : return NULL;
426 : : }
427 : :
428 : 2 : PyObject *iter = PyObject_GetIter(only_keys);
429 [ - + ]: 2 : if (iter == NULL) {
430 : 0 : Py_DECREF(pop);
431 : 0 : return NULL;
432 : : }
433 : :
434 [ + + ]: 5 : while ((item = PyIter_Next(iter))) {
435 : : // Remove from strong cache
436 [ - + ]: 3 : if (eject_from_strong_cache(type, item) < 0) {
437 : 0 : Py_DECREF(item);
438 : 0 : break;
439 : : }
440 : :
441 : : // Remove from weak cache
442 : 3 : PyObject *tmp = PyObject_CallMethodObjArgs(weak_cache, pop, item,
443 : : Py_None, NULL);
444 : :
445 : 3 : Py_DECREF(item);
446 [ - + ]: 3 : if (tmp == NULL) {
447 : 0 : break;
448 : : }
449 : 3 : Py_DECREF(tmp);
450 : : }
451 : 2 : Py_DECREF(iter);
452 : 2 : Py_DECREF(pop);
453 : : }
454 : :
455 [ - + ]: 76 : if (PyErr_Occurred()) {
456 : 0 : return NULL;
457 : : }
458 : :
459 : 76 : Py_RETURN_NONE;
460 : : }
461 : :
462 : : static PyObject *
463 : 3025 : zoneinfo_utcoffset(PyObject *self, PyObject *dt)
464 : : {
465 : 3025 : _ttinfo *tti = find_ttinfo((PyZoneInfo_ZoneInfo *)self, dt);
466 [ - + ]: 3025 : if (tti == NULL) {
467 : 0 : return NULL;
468 : : }
469 : 3025 : Py_INCREF(tti->utcoff);
470 : 3025 : return tti->utcoff;
471 : : }
472 : :
473 : : static PyObject *
474 : 2919 : zoneinfo_dst(PyObject *self, PyObject *dt)
475 : : {
476 : 2919 : _ttinfo *tti = find_ttinfo((PyZoneInfo_ZoneInfo *)self, dt);
477 [ - + ]: 2919 : if (tti == NULL) {
478 : 0 : return NULL;
479 : : }
480 : 2919 : Py_INCREF(tti->dstoff);
481 : 2919 : return tti->dstoff;
482 : : }
483 : :
484 : : static PyObject *
485 : 2919 : zoneinfo_tzname(PyObject *self, PyObject *dt)
486 : : {
487 : 2919 : _ttinfo *tti = find_ttinfo((PyZoneInfo_ZoneInfo *)self, dt);
488 [ - + ]: 2919 : if (tti == NULL) {
489 : 0 : return NULL;
490 : : }
491 : 2919 : Py_INCREF(tti->tzname);
492 : 2919 : return tti->tzname;
493 : : }
494 : :
495 : : #define GET_DT_TZINFO PyDateTime_DATE_GET_TZINFO
496 : :
497 : : static PyObject *
498 : 307 : zoneinfo_fromutc(PyObject *obj_self, PyObject *dt)
499 : : {
500 [ + + ]: 307 : if (!PyDateTime_Check(dt)) {
501 : 16 : PyErr_SetString(PyExc_TypeError,
502 : : "fromutc: argument must be a datetime");
503 : 16 : return NULL;
504 : : }
505 [ + + + + ]: 291 : if (GET_DT_TZINFO(dt) != obj_self) {
506 : 8 : PyErr_SetString(PyExc_ValueError,
507 : : "fromutc: dt.tzinfo "
508 : : "is not self");
509 : 8 : return NULL;
510 : : }
511 : :
512 : 283 : PyZoneInfo_ZoneInfo *self = (PyZoneInfo_ZoneInfo *)obj_self;
513 : :
514 : : int64_t timestamp;
515 [ - + ]: 283 : if (get_local_timestamp(dt, ×tamp)) {
516 : 0 : return NULL;
517 : : }
518 : 283 : size_t num_trans = self->num_transitions;
519 : :
520 : 283 : _ttinfo *tti = NULL;
521 : 283 : unsigned char fold = 0;
522 : :
523 [ + + + + ]: 283 : if (num_trans >= 1 && timestamp < self->trans_list_utc[0]) {
524 : 13 : tti = self->ttinfo_before;
525 : : }
526 [ + + ]: 270 : else if (num_trans == 0 ||
527 [ + + ]: 181 : timestamp > self->trans_list_utc[num_trans - 1]) {
528 : 124 : tti = find_tzrule_ttinfo_fromutc(&(self->tzrule_after), timestamp,
529 : 124 : PyDateTime_GET_YEAR(dt), &fold);
530 : :
531 : : // Immediately after the last manual transition, the fold/gap is
532 : : // between self->trans_ttinfos[num_transitions - 1] and whatever
533 : : // ttinfo applies immediately after the last transition, not between
534 : : // the STD and DST rules in the tzrule_after, so we may need to
535 : : // adjust the fold value.
536 [ + + ]: 124 : if (num_trans) {
537 : 35 : _ttinfo *tti_prev = NULL;
538 [ + + ]: 35 : if (num_trans == 1) {
539 : 1 : tti_prev = self->ttinfo_before;
540 : : }
541 : : else {
542 : 34 : tti_prev = self->trans_ttinfos[num_trans - 2];
543 : : }
544 : 35 : int64_t diff = tti_prev->utcoff_seconds - tti->utcoff_seconds;
545 [ + + ]: 35 : if (diff > 0 &&
546 [ + + ]: 16 : timestamp < (self->trans_list_utc[num_trans - 1] + diff)) {
547 : 4 : fold = 1;
548 : : }
549 : : }
550 : : }
551 : : else {
552 : 146 : size_t idx = _bisect(timestamp, self->trans_list_utc, num_trans);
553 : 146 : _ttinfo *tti_prev = NULL;
554 : :
555 [ + + ]: 146 : if (idx >= 2) {
556 : 127 : tti_prev = self->trans_ttinfos[idx - 2];
557 : 127 : tti = self->trans_ttinfos[idx - 1];
558 : : }
559 : : else {
560 : 19 : tti_prev = self->ttinfo_before;
561 : 19 : tti = self->trans_ttinfos[0];
562 : : }
563 : :
564 : : // Detect fold
565 : 146 : int64_t shift =
566 : 146 : (int64_t)(tti_prev->utcoff_seconds - tti->utcoff_seconds);
567 [ + + ]: 146 : if (shift > (timestamp - self->trans_list_utc[idx - 1])) {
568 : 76 : fold = 1;
569 : : }
570 : : }
571 : :
572 : 283 : PyObject *tmp = PyNumber_Add(dt, tti->utcoff);
573 [ - + ]: 283 : if (tmp == NULL) {
574 : 0 : return NULL;
575 : : }
576 : :
577 [ + + ]: 283 : if (fold) {
578 [ + + ]: 104 : if (PyDateTime_CheckExact(tmp)) {
579 : 76 : ((PyDateTime_DateTime *)tmp)->fold = 1;
580 : 76 : dt = tmp;
581 : : }
582 : : else {
583 : 28 : PyObject *replace = PyObject_GetAttrString(tmp, "replace");
584 : 28 : PyObject *args = PyTuple_New(0);
585 : 28 : PyObject *kwargs = PyDict_New();
586 : :
587 : 28 : Py_DECREF(tmp);
588 [ + - + - : 28 : if (args == NULL || kwargs == NULL || replace == NULL) {
- + ]
589 : 0 : Py_XDECREF(args);
590 : 0 : Py_XDECREF(kwargs);
591 : 0 : Py_XDECREF(replace);
592 : 0 : return NULL;
593 : : }
594 : :
595 : 28 : dt = NULL;
596 [ + - ]: 28 : if (!PyDict_SetItemString(kwargs, "fold", _PyLong_GetOne())) {
597 : 28 : dt = PyObject_Call(replace, args, kwargs);
598 : : }
599 : :
600 : 28 : Py_DECREF(args);
601 : 28 : Py_DECREF(kwargs);
602 : 28 : Py_DECREF(replace);
603 : :
604 [ - + ]: 28 : if (dt == NULL) {
605 : 0 : return NULL;
606 : : }
607 : : }
608 : : }
609 : : else {
610 : 179 : dt = tmp;
611 : : }
612 : 283 : return dt;
613 : : }
614 : :
615 : : static PyObject *
616 : 20 : zoneinfo_repr(PyZoneInfo_ZoneInfo *self)
617 : : {
618 : 20 : PyObject *rv = NULL;
619 : 20 : const char *type_name = Py_TYPE((PyObject *)self)->tp_name;
620 [ + + ]: 20 : if (!(self->key == Py_None)) {
621 : 8 : rv = PyUnicode_FromFormat("%s(key=%R)", type_name, self->key);
622 : : }
623 : : else {
624 : : assert(PyUnicode_Check(self->file_repr));
625 : 12 : rv = PyUnicode_FromFormat("%s.from_file(%U)", type_name,
626 : : self->file_repr);
627 : : }
628 : :
629 : 20 : return rv;
630 : : }
631 : :
632 : : static PyObject *
633 : 44 : zoneinfo_str(PyZoneInfo_ZoneInfo *self)
634 : : {
635 [ + + ]: 44 : if (!(self->key == Py_None)) {
636 : 40 : Py_INCREF(self->key);
637 : 40 : return self->key;
638 : : }
639 : : else {
640 : 4 : return zoneinfo_repr(self);
641 : : }
642 : : }
643 : :
644 : : /* Pickles the ZoneInfo object by key and source.
645 : : *
646 : : * ZoneInfo objects are pickled by reference to the TZif file that they came
647 : : * from, which means that the exact transitions may be different or the file
648 : : * may not un-pickle if the data has changed on disk in the interim.
649 : : *
650 : : * It is necessary to include a bit indicating whether or not the object
651 : : * was constructed from the cache, because from-cache objects will hit the
652 : : * unpickling process's cache, whereas no-cache objects will bypass it.
653 : : *
654 : : * Objects constructed from ZoneInfo.from_file cannot be pickled.
655 : : */
656 : : static PyObject *
657 : 54 : zoneinfo_reduce(PyObject *obj_self, PyObject *unused)
658 : : {
659 : 54 : PyZoneInfo_ZoneInfo *self = (PyZoneInfo_ZoneInfo *)obj_self;
660 [ + + ]: 54 : if (self->source == SOURCE_FILE) {
661 : : // Objects constructed from files cannot be pickled.
662 : : PyObject *pickle_error =
663 : 18 : _PyImport_GetModuleAttrString("pickle", "PicklingError");
664 [ - + ]: 18 : if (pickle_error == NULL) {
665 : 0 : return NULL;
666 : : }
667 : :
668 : 18 : PyErr_Format(pickle_error,
669 : : "Cannot pickle a ZoneInfo file from a file stream.");
670 : 18 : Py_DECREF(pickle_error);
671 : 18 : return NULL;
672 : : }
673 : :
674 : 36 : unsigned char from_cache = self->source == SOURCE_CACHE ? 1 : 0;
675 : 36 : PyObject *constructor = PyObject_GetAttrString(obj_self, "_unpickle");
676 : :
677 [ - + ]: 36 : if (constructor == NULL) {
678 : 0 : return NULL;
679 : : }
680 : :
681 : 36 : PyObject *rv = Py_BuildValue("O(OB)", constructor, self->key, from_cache);
682 : 36 : Py_DECREF(constructor);
683 : 36 : return rv;
684 : : }
685 : :
686 : : static PyObject *
687 : 54 : zoneinfo__unpickle(PyTypeObject *cls, PyObject *args)
688 : : {
689 : : PyObject *key;
690 : : unsigned char from_cache;
691 [ - + ]: 54 : if (!PyArg_ParseTuple(args, "OB", &key, &from_cache)) {
692 : 0 : return NULL;
693 : : }
694 : :
695 [ + + ]: 54 : if (from_cache) {
696 : 42 : PyObject *val_args = Py_BuildValue("(O)", key);
697 [ - + ]: 42 : if (val_args == NULL) {
698 : 0 : return NULL;
699 : : }
700 : :
701 : 42 : PyObject *rv = zoneinfo_new(cls, val_args, NULL);
702 : :
703 : 42 : Py_DECREF(val_args);
704 : 42 : return rv;
705 : : }
706 : : else {
707 : 12 : return zoneinfo_new_instance(cls, key);
708 : : }
709 : : }
710 : :
711 : : /* It is relatively expensive to construct new timedelta objects, and in most
712 : : * cases we're looking at a relatively small number of timedeltas, such as
713 : : * integer number of hours, etc. We will keep a cache so that we construct
714 : : * a minimal number of these.
715 : : *
716 : : * Possibly this should be replaced with an LRU cache so that it's not possible
717 : : * for the memory usage to explode from this, but in order for this to be a
718 : : * serious problem, one would need to deliberately craft a malicious time zone
719 : : * file with many distinct offsets. As of tzdb 2019c, loading every single zone
720 : : * fills the cache with ~450 timedeltas for a total size of ~12kB.
721 : : *
722 : : * This returns a new reference to the timedelta.
723 : : */
724 : : static PyObject *
725 : 4478 : load_timedelta(long seconds)
726 : : {
727 : : PyObject *rv;
728 : 4478 : PyObject *pyoffset = PyLong_FromLong(seconds);
729 [ - + ]: 4478 : if (pyoffset == NULL) {
730 : 0 : return NULL;
731 : : }
732 : 4478 : rv = PyDict_GetItemWithError(TIMEDELTA_CACHE, pyoffset);
733 [ + + ]: 4478 : if (rv == NULL) {
734 [ - + ]: 31 : if (PyErr_Occurred()) {
735 : 0 : goto error;
736 : : }
737 : 31 : PyObject *tmp = PyDateTimeAPI->Delta_FromDelta(
738 : 31 : 0, seconds, 0, 1, PyDateTimeAPI->DeltaType);
739 : :
740 [ - + ]: 31 : if (tmp == NULL) {
741 : 0 : goto error;
742 : : }
743 : :
744 : 31 : rv = PyDict_SetDefault(TIMEDELTA_CACHE, pyoffset, tmp);
745 : 31 : Py_DECREF(tmp);
746 : : }
747 : :
748 : 4478 : Py_XINCREF(rv);
749 : 4478 : Py_DECREF(pyoffset);
750 : 4478 : return rv;
751 : 0 : error:
752 : 0 : Py_DECREF(pyoffset);
753 : 0 : return NULL;
754 : : }
755 : :
756 : : /* Constructor for _ttinfo object - this starts by initializing the _ttinfo
757 : : * to { NULL, NULL, NULL }, so that Py_XDECREF will work on partially
758 : : * initialized _ttinfo objects.
759 : : */
760 : : static int
761 : 2239 : build_ttinfo(long utcoffset, long dstoffset, PyObject *tzname, _ttinfo *out)
762 : : {
763 : 2239 : out->utcoff = NULL;
764 : 2239 : out->dstoff = NULL;
765 : 2239 : out->tzname = NULL;
766 : :
767 : 2239 : out->utcoff_seconds = utcoffset;
768 : 2239 : out->utcoff = load_timedelta(utcoffset);
769 [ - + ]: 2239 : if (out->utcoff == NULL) {
770 : 0 : return -1;
771 : : }
772 : :
773 : 2239 : out->dstoff = load_timedelta(dstoffset);
774 [ - + ]: 2239 : if (out->dstoff == NULL) {
775 : 0 : return -1;
776 : : }
777 : :
778 : 2239 : out->tzname = tzname;
779 : 2239 : Py_INCREF(tzname);
780 : :
781 : 2239 : return 0;
782 : : }
783 : :
784 : : /* Decrease reference count on any non-NULL members of a _ttinfo */
785 : : static void
786 : 2314 : xdecref_ttinfo(_ttinfo *ttinfo)
787 : : {
788 [ + - ]: 2314 : if (ttinfo != NULL) {
789 : 2314 : Py_XDECREF(ttinfo->utcoff);
790 : 2314 : Py_XDECREF(ttinfo->dstoff);
791 : 2314 : Py_XDECREF(ttinfo->tzname);
792 : : }
793 : 2314 : }
794 : :
795 : : /* Equality function for _ttinfo. */
796 : : static int
797 : 11 : ttinfo_eq(const _ttinfo *const tti0, const _ttinfo *const tti1)
798 : : {
799 : : int rv;
800 [ - + ]: 11 : if ((rv = PyObject_RichCompareBool(tti0->utcoff, tti1->utcoff, Py_EQ)) <
801 : : 1) {
802 : 0 : goto end;
803 : : }
804 : :
805 [ - + ]: 11 : if ((rv = PyObject_RichCompareBool(tti0->dstoff, tti1->dstoff, Py_EQ)) <
806 : : 1) {
807 : 0 : goto end;
808 : : }
809 : :
810 [ + - ]: 11 : if ((rv = PyObject_RichCompareBool(tti0->tzname, tti1->tzname, Py_EQ)) <
811 : : 1) {
812 : 0 : goto end;
813 : : }
814 : 11 : end:
815 : 11 : return rv;
816 : : }
817 : :
818 : : /* Given a file-like object, this populates a ZoneInfo object
819 : : *
820 : : * The current version calls into a Python function to read the data from
821 : : * file into Python objects, and this translates those Python objects into
822 : : * C values and calculates derived values (e.g. dstoff) in C.
823 : : *
824 : : * This returns 0 on success and -1 on failure.
825 : : *
826 : : * The function will never return while `self` is partially initialized —
827 : : * the object only needs to be freed / deallocated if this succeeds.
828 : : */
829 : : static int
830 : 358 : load_data(PyZoneInfo_ZoneInfo *self, PyObject *file_obj)
831 : : {
832 : 358 : PyObject *data_tuple = NULL;
833 : :
834 : 358 : long *utcoff = NULL;
835 : 358 : long *dstoff = NULL;
836 : 358 : size_t *trans_idx = NULL;
837 : 358 : unsigned char *isdst = NULL;
838 : :
839 : 358 : self->trans_list_utc = NULL;
840 : 358 : self->trans_list_wall[0] = NULL;
841 : 358 : self->trans_list_wall[1] = NULL;
842 : 358 : self->trans_ttinfos = NULL;
843 : 358 : self->_ttinfos = NULL;
844 : 358 : self->file_repr = NULL;
845 : :
846 : 358 : size_t ttinfos_allocated = 0;
847 : :
848 : 358 : data_tuple = PyObject_CallMethod(_common_mod, "load_data", "O", file_obj);
849 : :
850 [ + + ]: 358 : if (data_tuple == NULL) {
851 : 8 : goto error;
852 : : }
853 : :
854 [ - + ]: 350 : if (!PyTuple_CheckExact(data_tuple)) {
855 : 0 : PyErr_Format(PyExc_TypeError, "Invalid data result type: %r",
856 : : data_tuple);
857 : 0 : goto error;
858 : : }
859 : :
860 : : // Unpack the data tuple
861 : 350 : PyObject *trans_idx_list = PyTuple_GetItem(data_tuple, 0);
862 [ - + ]: 350 : if (trans_idx_list == NULL) {
863 : 0 : goto error;
864 : : }
865 : :
866 : 350 : PyObject *trans_utc = PyTuple_GetItem(data_tuple, 1);
867 [ - + ]: 350 : if (trans_utc == NULL) {
868 : 0 : goto error;
869 : : }
870 : :
871 : 350 : PyObject *utcoff_list = PyTuple_GetItem(data_tuple, 2);
872 [ - + ]: 350 : if (utcoff_list == NULL) {
873 : 0 : goto error;
874 : : }
875 : :
876 : 350 : PyObject *isdst_list = PyTuple_GetItem(data_tuple, 3);
877 [ - + ]: 350 : if (isdst_list == NULL) {
878 : 0 : goto error;
879 : : }
880 : :
881 : 350 : PyObject *abbr = PyTuple_GetItem(data_tuple, 4);
882 [ - + ]: 350 : if (abbr == NULL) {
883 : 0 : goto error;
884 : : }
885 : :
886 : 350 : PyObject *tz_str = PyTuple_GetItem(data_tuple, 5);
887 [ - + ]: 350 : if (tz_str == NULL) {
888 : 0 : goto error;
889 : : }
890 : :
891 : : // Load the relevant sizes
892 : 350 : Py_ssize_t num_transitions = PyTuple_Size(trans_utc);
893 [ - + ]: 350 : if (num_transitions < 0) {
894 : 0 : goto error;
895 : : }
896 : :
897 : 350 : Py_ssize_t num_ttinfos = PyTuple_Size(utcoff_list);
898 [ - + ]: 350 : if (num_ttinfos < 0) {
899 : 0 : goto error;
900 : : }
901 : :
902 : 350 : self->num_transitions = (size_t)num_transitions;
903 : 350 : self->num_ttinfos = (size_t)num_ttinfos;
904 : :
905 : : // Load the transition indices and list
906 : 350 : self->trans_list_utc =
907 : 350 : PyMem_Malloc(self->num_transitions * sizeof(int64_t));
908 [ - + ]: 350 : if (self->trans_list_utc == NULL) {
909 : 0 : goto error;
910 : : }
911 : 350 : trans_idx = PyMem_Malloc(self->num_transitions * sizeof(Py_ssize_t));
912 [ - + ]: 350 : if (trans_idx == NULL) {
913 : 0 : goto error;
914 : : }
915 : :
916 [ + + ]: 39649 : for (size_t i = 0; i < self->num_transitions; ++i) {
917 : 39299 : PyObject *num = PyTuple_GetItem(trans_utc, i);
918 [ - + ]: 39299 : if (num == NULL) {
919 : 0 : goto error;
920 : : }
921 : 39299 : self->trans_list_utc[i] = PyLong_AsLongLong(num);
922 [ - + - - ]: 39299 : if (self->trans_list_utc[i] == -1 && PyErr_Occurred()) {
923 : 0 : goto error;
924 : : }
925 : :
926 : 39299 : num = PyTuple_GetItem(trans_idx_list, i);
927 [ - + ]: 39299 : if (num == NULL) {
928 : 0 : goto error;
929 : : }
930 : :
931 : 39299 : Py_ssize_t cur_trans_idx = PyLong_AsSsize_t(num);
932 [ - + ]: 39299 : if (cur_trans_idx == -1) {
933 : 0 : goto error;
934 : : }
935 : :
936 : 39299 : trans_idx[i] = (size_t)cur_trans_idx;
937 [ - + ]: 39299 : if (trans_idx[i] > self->num_ttinfos) {
938 : 0 : PyErr_Format(
939 : : PyExc_ValueError,
940 : : "Invalid transition index found while reading TZif: %zd",
941 : : cur_trans_idx);
942 : :
943 : 0 : goto error;
944 : : }
945 : : }
946 : :
947 : : // Load UTC offsets and isdst (size num_ttinfos)
948 : 350 : utcoff = PyMem_Malloc(self->num_ttinfos * sizeof(long));
949 : 350 : isdst = PyMem_Malloc(self->num_ttinfos * sizeof(unsigned char));
950 : :
951 [ + - - + ]: 350 : if (utcoff == NULL || isdst == NULL) {
952 : 0 : goto error;
953 : : }
954 [ + + ]: 2104 : for (size_t i = 0; i < self->num_ttinfos; ++i) {
955 : 1754 : PyObject *num = PyTuple_GetItem(utcoff_list, i);
956 [ - + ]: 1754 : if (num == NULL) {
957 : 0 : goto error;
958 : : }
959 : :
960 : 1754 : utcoff[i] = PyLong_AsLong(num);
961 [ - + - - ]: 1754 : if (utcoff[i] == -1 && PyErr_Occurred()) {
962 : 0 : goto error;
963 : : }
964 : :
965 : 1754 : num = PyTuple_GetItem(isdst_list, i);
966 [ - + ]: 1754 : if (num == NULL) {
967 : 0 : goto error;
968 : : }
969 : :
970 : 1754 : int isdst_with_error = PyObject_IsTrue(num);
971 [ - + ]: 1754 : if (isdst_with_error == -1) {
972 : 0 : goto error;
973 : : }
974 : : else {
975 : 1754 : isdst[i] = (unsigned char)isdst_with_error;
976 : : }
977 : : }
978 : :
979 : 350 : dstoff = PyMem_Calloc(self->num_ttinfos, sizeof(long));
980 [ - + ]: 350 : if (dstoff == NULL) {
981 : 0 : goto error;
982 : : }
983 : :
984 : : // Derive dstoff and trans_list_wall from the information we've loaded
985 : 350 : utcoff_to_dstoff(trans_idx, utcoff, dstoff, isdst, self->num_transitions,
986 : : self->num_ttinfos);
987 : :
988 [ - + ]: 350 : if (ts_to_local(trans_idx, self->trans_list_utc, utcoff,
989 : 350 : self->trans_list_wall, self->num_ttinfos,
990 : : self->num_transitions)) {
991 : 0 : goto error;
992 : : }
993 : :
994 : : // Build _ttinfo objects from utcoff, dstoff and abbr
995 : 350 : self->_ttinfos = PyMem_Malloc(self->num_ttinfos * sizeof(_ttinfo));
996 [ - + ]: 350 : if (self->_ttinfos == NULL) {
997 : 0 : goto error;
998 : : }
999 [ + + ]: 2104 : for (size_t i = 0; i < self->num_ttinfos; ++i) {
1000 : 1754 : PyObject *tzname = PyTuple_GetItem(abbr, i);
1001 [ - + ]: 1754 : if (tzname == NULL) {
1002 : 0 : goto error;
1003 : : }
1004 : :
1005 : 1754 : ttinfos_allocated++;
1006 [ - + ]: 1754 : if (build_ttinfo(utcoff[i], dstoff[i], tzname, &(self->_ttinfos[i]))) {
1007 : 0 : goto error;
1008 : : }
1009 : : }
1010 : :
1011 : : // Build our mapping from transition to the ttinfo that applies
1012 : 350 : self->trans_ttinfos =
1013 : 350 : PyMem_Calloc(self->num_transitions, sizeof(_ttinfo *));
1014 [ - + ]: 350 : if (self->trans_ttinfos == NULL) {
1015 : 0 : goto error;
1016 : : }
1017 [ + + ]: 39649 : for (size_t i = 0; i < self->num_transitions; ++i) {
1018 : 39299 : size_t ttinfo_idx = trans_idx[i];
1019 : : assert(ttinfo_idx < self->num_ttinfos);
1020 : 39299 : self->trans_ttinfos[i] = &(self->_ttinfos[ttinfo_idx]);
1021 : : }
1022 : :
1023 : : // Set ttinfo_before to the first non-DST transition
1024 [ + + ]: 352 : for (size_t i = 0; i < self->num_ttinfos; ++i) {
1025 [ + + ]: 302 : if (!isdst[i]) {
1026 : 300 : self->ttinfo_before = &(self->_ttinfos[i]);
1027 : 300 : break;
1028 : : }
1029 : : }
1030 : :
1031 : : // If there are only DST ttinfos, pick the first one, if there are no
1032 : : // ttinfos at all, set ttinfo_before to NULL
1033 [ + + + + ]: 350 : if (self->ttinfo_before == NULL && self->num_ttinfos > 0) {
1034 : 2 : self->ttinfo_before = &(self->_ttinfos[0]);
1035 : : }
1036 : :
1037 [ + + + + ]: 350 : if (tz_str != Py_None && PyObject_IsTrue(tz_str)) {
1038 [ + + ]: 286 : if (parse_tz_str(tz_str, &(self->tzrule_after))) {
1039 : 27 : goto error;
1040 : : }
1041 : : }
1042 : : else {
1043 [ + + ]: 64 : if (!self->num_ttinfos) {
1044 : 1 : PyErr_Format(PyExc_ValueError, "No time zone information found.");
1045 : 1 : goto error;
1046 : : }
1047 : :
1048 : : size_t idx;
1049 [ + + ]: 63 : if (!self->num_transitions) {
1050 : 4 : idx = self->num_ttinfos - 1;
1051 : : }
1052 : : else {
1053 : 59 : idx = trans_idx[self->num_transitions - 1];
1054 : : }
1055 : :
1056 : 63 : _ttinfo *tti = &(self->_ttinfos[idx]);
1057 : 63 : build_tzrule(tti->tzname, NULL, tti->utcoff_seconds, 0, NULL, NULL,
1058 : : &(self->tzrule_after));
1059 : :
1060 : : // We've abused the build_tzrule constructor to construct an STD-only
1061 : : // rule mimicking whatever ttinfo we've picked up, but it's possible
1062 : : // that the one we've picked up is a DST zone, so we need to make sure
1063 : : // that the dstoff is set correctly in that case.
1064 [ + + ]: 63 : if (PyObject_IsTrue(tti->dstoff)) {
1065 : 16 : _ttinfo *tti_after = &(self->tzrule_after.std);
1066 : 16 : Py_DECREF(tti_after->dstoff);
1067 : 16 : tti_after->dstoff = tti->dstoff;
1068 : 16 : Py_INCREF(tti_after->dstoff);
1069 : : }
1070 : : }
1071 : :
1072 : : // Determine if this is a "fixed offset" zone, meaning that the output of
1073 : : // the utcoffset, dst and tzname functions does not depend on the specific
1074 : : // datetime passed.
1075 : : //
1076 : : // We make three simplifying assumptions here:
1077 : : //
1078 : : // 1. If tzrule_after is not std_only, it has transitions that might occur
1079 : : // (it is possible to construct TZ strings that specify STD and DST but
1080 : : // no transitions ever occur, such as AAA0BBB,0/0,J365/25).
1081 : : // 2. If self->_ttinfos contains more than one _ttinfo object, the objects
1082 : : // represent different offsets.
1083 : : // 3. self->ttinfos contains no unused _ttinfos (in which case an otherwise
1084 : : // fixed-offset zone with extra _ttinfos defined may appear to *not* be
1085 : : // a fixed offset zone).
1086 : : //
1087 : : // Violations to these assumptions would be fairly exotic, and exotic
1088 : : // zones should almost certainly not be used with datetime.time (the
1089 : : // only thing that would be affected by this).
1090 [ + + + + ]: 322 : if (self->num_ttinfos > 1 || !self->tzrule_after.std_only) {
1091 : 309 : self->fixed_offset = 0;
1092 : : }
1093 [ + + ]: 13 : else if (self->num_ttinfos == 0) {
1094 : 2 : self->fixed_offset = 1;
1095 : : }
1096 : : else {
1097 : : int constant_offset =
1098 : 11 : ttinfo_eq(&(self->_ttinfos[0]), &self->tzrule_after.std);
1099 [ - + ]: 11 : if (constant_offset < 0) {
1100 : 0 : goto error;
1101 : : }
1102 : : else {
1103 : 11 : self->fixed_offset = constant_offset;
1104 : : }
1105 : : }
1106 : :
1107 : 322 : int rv = 0;
1108 : 322 : goto cleanup;
1109 : 36 : error:
1110 : : // These resources only need to be freed if we have failed, if we succeed
1111 : : // in initializing a PyZoneInfo_ZoneInfo object, we can rely on its dealloc
1112 : : // method to free the relevant resources.
1113 [ + + ]: 36 : if (self->trans_list_utc != NULL) {
1114 : 28 : PyMem_Free(self->trans_list_utc);
1115 : 28 : self->trans_list_utc = NULL;
1116 : : }
1117 : :
1118 [ + + ]: 108 : for (size_t i = 0; i < 2; ++i) {
1119 [ - + ]: 72 : if (self->trans_list_wall[i] != NULL) {
1120 : 0 : PyMem_Free(self->trans_list_wall[i]);
1121 : 0 : self->trans_list_wall[i] = NULL;
1122 : : }
1123 : : }
1124 : :
1125 [ + + ]: 36 : if (self->_ttinfos != NULL) {
1126 [ - + ]: 28 : for (size_t i = 0; i < ttinfos_allocated; ++i) {
1127 : 0 : xdecref_ttinfo(&(self->_ttinfos[i]));
1128 : : }
1129 : 28 : PyMem_Free(self->_ttinfos);
1130 : 28 : self->_ttinfos = NULL;
1131 : : }
1132 : :
1133 [ + + ]: 36 : if (self->trans_ttinfos != NULL) {
1134 : 28 : PyMem_Free(self->trans_ttinfos);
1135 : 28 : self->trans_ttinfos = NULL;
1136 : : }
1137 : :
1138 : 36 : rv = -1;
1139 : 358 : cleanup:
1140 : 358 : Py_XDECREF(data_tuple);
1141 : :
1142 [ + + ]: 358 : if (utcoff != NULL) {
1143 : 350 : PyMem_Free(utcoff);
1144 : : }
1145 : :
1146 [ + + ]: 358 : if (dstoff != NULL) {
1147 : 350 : PyMem_Free(dstoff);
1148 : : }
1149 : :
1150 [ + + ]: 358 : if (isdst != NULL) {
1151 : 350 : PyMem_Free(isdst);
1152 : : }
1153 : :
1154 [ + + ]: 358 : if (trans_idx != NULL) {
1155 : 350 : PyMem_Free(trans_idx);
1156 : : }
1157 : :
1158 : 358 : return rv;
1159 : : }
1160 : :
1161 : : /* Function to calculate the local timestamp of a transition from the year. */
1162 : : int64_t
1163 : 3264 : calendarrule_year_to_timestamp(TransitionRuleType *base_self, int year)
1164 : : {
1165 : 3264 : CalendarRule *self = (CalendarRule *)base_self;
1166 : :
1167 : : // We want (year, month, day of month); we have year and month, but we
1168 : : // need to turn (week, day-of-week) into day-of-month
1169 : : //
1170 : : // Week 1 is the first week in which day `day` (where 0 = Sunday) appears.
1171 : : // Week 5 represents the last occurrence of day `day`, so we need to know
1172 : : // the first weekday of the month and the number of days in the month.
1173 : 3264 : int8_t first_day = (ymd_to_ord(year, self->month, 1) + 6) % 7;
1174 : 3264 : uint8_t days_in_month = DAYS_IN_MONTH[self->month];
1175 [ - + - - ]: 3264 : if (self->month == 2 && is_leap_year(year)) {
1176 : 0 : days_in_month += 1;
1177 : : }
1178 : :
1179 : : // This equation seems magical, so I'll break it down:
1180 : : // 1. calendar says 0 = Monday, POSIX says 0 = Sunday so we need first_day
1181 : : // + 1 to get 1 = Monday -> 7 = Sunday, which is still equivalent
1182 : : // because this math is mod 7
1183 : : // 2. Get first day - desired day mod 7 (adjusting by 7 for negative
1184 : : // numbers so that -1 % 7 = 6).
1185 : : // 3. Add 1 because month days are a 1-based index.
1186 : 3264 : int8_t month_day = ((int8_t)(self->day) - (first_day + 1)) % 7;
1187 [ + + ]: 3264 : if (month_day < 0) {
1188 : 2327 : month_day += 7;
1189 : : }
1190 : 3264 : month_day += 1;
1191 : :
1192 : : // Now use a 0-based index version of `week` to calculate the w-th
1193 : : // occurrence of `day`
1194 : 3264 : month_day += ((int8_t)(self->week) - 1) * 7;
1195 : :
1196 : : // month_day will only be > days_in_month if w was 5, and `w` means "last
1197 : : // occurrence of `d`", so now we just check if we over-shot the end of the
1198 : : // month and if so knock off 1 week.
1199 [ + + ]: 3264 : if (month_day > days_in_month) {
1200 : 678 : month_day -= 7;
1201 : : }
1202 : :
1203 : 3264 : int64_t ordinal = ymd_to_ord(year, self->month, month_day) - EPOCHORDINAL;
1204 : 3264 : return ((ordinal * 86400) + (int64_t)(self->hour * 3600) +
1205 : 3264 : (int64_t)(self->minute * 60) + (int64_t)(self->second));
1206 : : }
1207 : :
1208 : : /* Constructor for CalendarRule. */
1209 : : int
1210 : 330 : calendarrule_new(uint8_t month, uint8_t week, uint8_t day, int8_t hour,
1211 : : int8_t minute, int8_t second, CalendarRule *out)
1212 : : {
1213 : : // These bounds come from the POSIX standard, which describes an Mm.n.d
1214 : : // rule as:
1215 : : //
1216 : : // The d'th day (0 <= d <= 6) of week n of month m of the year (1 <= n <=
1217 : : // 5, 1 <= m <= 12, where week 5 means "the last d day in month m" which
1218 : : // may occur in either the fourth or the fifth week). Week 1 is the first
1219 : : // week in which the d'th day occurs. Day zero is Sunday.
1220 [ + + + + ]: 330 : if (month <= 0 || month > 12) {
1221 : 4 : PyErr_Format(PyExc_ValueError, "Month must be in (0, 12]");
1222 : 4 : return -1;
1223 : : }
1224 : :
1225 [ + - + + ]: 326 : if (week <= 0 || week > 5) {
1226 : 2 : PyErr_Format(PyExc_ValueError, "Week must be in (0, 5]");
1227 : 2 : return -1;
1228 : : }
1229 : :
1230 : : // If the 'day' parameter type is changed to a signed type,
1231 : : // "day < 0" check must be added.
1232 [ + + ]: 324 : if (/* day < 0 || */ day > 6) {
1233 : 2 : PyErr_Format(PyExc_ValueError, "Day must be in [0, 6]");
1234 : 2 : return -1;
1235 : : }
1236 : :
1237 : 322 : TransitionRuleType base = {&calendarrule_year_to_timestamp};
1238 : :
1239 : 322 : CalendarRule new_offset = {
1240 : : .base = base,
1241 : : .month = month,
1242 : : .week = week,
1243 : : .day = day,
1244 : : .hour = hour,
1245 : : .minute = minute,
1246 : : .second = second,
1247 : : };
1248 : :
1249 : 322 : *out = new_offset;
1250 : 322 : return 0;
1251 : : }
1252 : :
1253 : : /* Function to calculate the local timestamp of a transition from the year.
1254 : : *
1255 : : * This translates the day of the year into a local timestamp — either a
1256 : : * 1-based Julian day, not including leap days, or the 0-based year-day,
1257 : : * including leap days.
1258 : : * */
1259 : : int64_t
1260 : 316 : dayrule_year_to_timestamp(TransitionRuleType *base_self, int year)
1261 : : {
1262 : : // The function signature requires a TransitionRuleType pointer, but this
1263 : : // function is only applicable to DayRule* objects.
1264 : 316 : DayRule *self = (DayRule *)base_self;
1265 : :
1266 : : // ymd_to_ord calculates the number of days since 0001-01-01, but we want
1267 : : // to know the number of days since 1970-01-01, so we must subtract off
1268 : : // the equivalent of ymd_to_ord(1970, 1, 1).
1269 : : //
1270 : : // We subtract off an additional 1 day to account for January 1st (we want
1271 : : // the number of full days *before* the date of the transition - partial
1272 : : // days are accounted for in the hour, minute and second portions.
1273 : 316 : int64_t days_before_year = ymd_to_ord(year, 1, 1) - EPOCHORDINAL - 1;
1274 : :
1275 : : // The Julian day specification skips over February 29th in leap years,
1276 : : // from the POSIX standard:
1277 : : //
1278 : : // Leap days shall not be counted. That is, in all years-including leap
1279 : : // years-February 28 is day 59 and March 1 is day 60. It is impossible to
1280 : : // refer explicitly to the occasional February 29.
1281 : : //
1282 : : // This is actually more useful than you'd think — if you want a rule that
1283 : : // always transitions on a given calendar day (other than February 29th),
1284 : : // you would use a Julian day, e.g. J91 always refers to April 1st and J365
1285 : : // always refers to December 31st.
1286 : 316 : unsigned int day = self->day;
1287 [ + + + - : 316 : if (self->julian && day >= 59 && is_leap_year(year)) {
+ + ]
1288 : 139 : day += 1;
1289 : : }
1290 : :
1291 : 316 : return ((days_before_year + day) * 86400) + (self->hour * 3600) +
1292 : 316 : (self->minute * 60) + self->second;
1293 : : }
1294 : :
1295 : : /* Constructor for DayRule. */
1296 : : static int
1297 : 18 : dayrule_new(uint8_t julian, unsigned int day, int8_t hour, int8_t minute,
1298 : : int8_t second, DayRule *out)
1299 : : {
1300 : : // The POSIX standard specifies that Julian days must be in the range (1 <=
1301 : : // n <= 365) and that non-Julian (they call it "0-based Julian") days must
1302 : : // be in the range (0 <= n <= 365).
1303 [ + + + + ]: 18 : if (day < julian || day > 365) {
1304 : 4 : PyErr_Format(PyExc_ValueError, "day must be in [%u, 365], not: %u",
1305 : : julian, day);
1306 : 4 : return -1;
1307 : : }
1308 : :
1309 : 14 : TransitionRuleType base = {
1310 : : &dayrule_year_to_timestamp,
1311 : : };
1312 : :
1313 : 14 : DayRule tmp = {
1314 : : .base = base,
1315 : : .julian = julian,
1316 : : .day = day,
1317 : : .hour = hour,
1318 : : .minute = minute,
1319 : : .second = second,
1320 : : };
1321 : :
1322 : 14 : *out = tmp;
1323 : :
1324 : 14 : return 0;
1325 : : }
1326 : :
1327 : : /* Calculate the start and end rules for a _tzrule in the given year. */
1328 : : static void
1329 : 1790 : tzrule_transitions(_tzrule *rule, int year, int64_t *start, int64_t *end)
1330 : : {
1331 : : assert(rule->start != NULL);
1332 : : assert(rule->end != NULL);
1333 : 1790 : *start = rule->start->year_to_timestamp(rule->start, year);
1334 : 1790 : *end = rule->end->year_to_timestamp(rule->end, year);
1335 : 1790 : }
1336 : :
1337 : : /* Calculate the _ttinfo that applies at a given local time from a _tzrule.
1338 : : *
1339 : : * This takes a local timestamp and fold for disambiguation purposes; the year
1340 : : * could technically be calculated from the timestamp, but given that the
1341 : : * callers of this function already have the year information accessible from
1342 : : * the datetime struct, it is taken as an additional parameter to reduce
1343 : : * unnecessary calculation.
1344 : : * */
1345 : : static _ttinfo *
1346 : 1860 : find_tzrule_ttinfo(_tzrule *rule, int64_t ts, unsigned char fold, int year)
1347 : : {
1348 [ + + ]: 1860 : if (rule->std_only) {
1349 : 188 : return &(rule->std);
1350 : : }
1351 : :
1352 : : int64_t start, end;
1353 : : uint8_t isdst;
1354 : :
1355 : 1672 : tzrule_transitions(rule, year, &start, &end);
1356 : :
1357 : : // With fold = 0, the period (denominated in local time) with the smaller
1358 : : // offset starts at the end of the gap and ends at the end of the fold;
1359 : : // with fold = 1, it runs from the start of the gap to the beginning of the
1360 : : // fold.
1361 : : //
1362 : : // So in order to determine the DST boundaries we need to know both the
1363 : : // fold and whether DST is positive or negative (rare), and it turns out
1364 : : // that this boils down to fold XOR is_positive.
1365 [ + + ]: 1672 : if (fold == (rule->dst_diff >= 0)) {
1366 : 695 : end -= rule->dst_diff;
1367 : : }
1368 : : else {
1369 : 977 : start += rule->dst_diff;
1370 : : }
1371 : :
1372 [ + + ]: 1672 : if (start < end) {
1373 [ + + + + ]: 802 : isdst = (ts >= start) && (ts < end);
1374 : : }
1375 : : else {
1376 [ + + + + ]: 870 : isdst = (ts < end) || (ts >= start);
1377 : : }
1378 : :
1379 [ + + ]: 1672 : if (isdst) {
1380 : 847 : return &(rule->dst);
1381 : : }
1382 : : else {
1383 : 825 : return &(rule->std);
1384 : : }
1385 : : }
1386 : :
1387 : : /* Calculate the ttinfo and fold that applies for a _tzrule at an epoch time.
1388 : : *
1389 : : * This function can determine the _ttinfo that applies at a given epoch time,
1390 : : * (analogous to trans_list_utc), and whether or not the datetime is in a fold.
1391 : : * This is to be used in the .fromutc() function.
1392 : : *
1393 : : * The year is technically a redundant parameter, because it can be calculated
1394 : : * from the timestamp, but all callers of this function should have the year
1395 : : * in the datetime struct anyway, so taking it as a parameter saves unnecessary
1396 : : * calculation.
1397 : : **/
1398 : : static _ttinfo *
1399 : 124 : find_tzrule_ttinfo_fromutc(_tzrule *rule, int64_t ts, int year,
1400 : : unsigned char *fold)
1401 : : {
1402 [ + + ]: 124 : if (rule->std_only) {
1403 : 6 : *fold = 0;
1404 : 6 : return &(rule->std);
1405 : : }
1406 : :
1407 : : int64_t start, end;
1408 : : uint8_t isdst;
1409 : 118 : tzrule_transitions(rule, year, &start, &end);
1410 : 118 : start -= rule->std.utcoff_seconds;
1411 : 118 : end -= rule->dst.utcoff_seconds;
1412 : :
1413 [ + + ]: 118 : if (start < end) {
1414 [ + + + + ]: 73 : isdst = (ts >= start) && (ts < end);
1415 : : }
1416 : : else {
1417 [ + + + + ]: 45 : isdst = (ts < end) || (ts >= start);
1418 : : }
1419 : :
1420 : : // For positive DST, the ambiguous period is one dst_diff after the end of
1421 : : // DST; for negative DST, the ambiguous period is one dst_diff before the
1422 : : // start of DST.
1423 : : int64_t ambig_start, ambig_end;
1424 [ + + ]: 118 : if (rule->dst_diff > 0) {
1425 : 99 : ambig_start = end;
1426 : 99 : ambig_end = end + rule->dst_diff;
1427 : : }
1428 : : else {
1429 : 19 : ambig_start = start;
1430 : 19 : ambig_end = start - rule->dst_diff;
1431 : : }
1432 : :
1433 [ + + + + ]: 118 : *fold = (ts >= ambig_start) && (ts < ambig_end);
1434 : :
1435 [ + + ]: 118 : if (isdst) {
1436 : 61 : return &(rule->dst);
1437 : : }
1438 : : else {
1439 : 57 : return &(rule->std);
1440 : : }
1441 : : }
1442 : :
1443 : : /* Parse a TZ string in the format specified by the POSIX standard:
1444 : : *
1445 : : * std offset[dst[offset],start[/time],end[/time]]
1446 : : *
1447 : : * std and dst must be 3 or more characters long and must not contain a
1448 : : * leading colon, embedded digits, commas, nor a plus or minus signs; The
1449 : : * spaces between "std" and "offset" are only for display and are not actually
1450 : : * present in the string.
1451 : : *
1452 : : * The format of the offset is ``[+|-]hh[:mm[:ss]]``
1453 : : *
1454 : : * See the POSIX.1 spec: IEE Std 1003.1-2018 §8.3:
1455 : : *
1456 : : * https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html
1457 : : */
1458 : : static int
1459 : 286 : parse_tz_str(PyObject *tz_str_obj, _tzrule *out)
1460 : : {
1461 : 286 : PyObject *std_abbr = NULL;
1462 : 286 : PyObject *dst_abbr = NULL;
1463 : 286 : TransitionRuleType *start = NULL;
1464 : 286 : TransitionRuleType *end = NULL;
1465 : : // Initialize offsets to invalid value (> 24 hours)
1466 : 286 : long std_offset = 1 << 20;
1467 : 286 : long dst_offset = 1 << 20;
1468 : :
1469 : 286 : const char *tz_str = PyBytes_AsString(tz_str_obj);
1470 [ - + ]: 286 : if (tz_str == NULL) {
1471 : 0 : return -1;
1472 : : }
1473 : 286 : const char *p = tz_str;
1474 : :
1475 : : // Read the `std` abbreviation, which must be at least 3 characters long.
1476 : 286 : Py_ssize_t num_chars = parse_abbr(p, &std_abbr);
1477 [ + + ]: 286 : if (num_chars < 1) {
1478 : 1 : PyErr_Format(PyExc_ValueError, "Invalid STD format in %R", tz_str_obj);
1479 : 1 : goto error;
1480 : : }
1481 : :
1482 : 285 : p += num_chars;
1483 : :
1484 : : // Now read the STD offset, which is required
1485 : 285 : num_chars = parse_tz_delta(p, &std_offset);
1486 [ + + ]: 285 : if (num_chars < 0) {
1487 : 5 : PyErr_Format(PyExc_ValueError, "Invalid STD offset in %R", tz_str_obj);
1488 : 5 : goto error;
1489 : : }
1490 : 280 : p += num_chars;
1491 : :
1492 : : // If the string ends here, there is no DST, otherwise we must parse the
1493 : : // DST abbreviation and start and end dates and times.
1494 [ + + ]: 280 : if (*p == '\0') {
1495 : 96 : goto complete;
1496 : : }
1497 : :
1498 : 184 : num_chars = parse_abbr(p, &dst_abbr);
1499 [ + + ]: 184 : if (num_chars < 1) {
1500 : 1 : PyErr_Format(PyExc_ValueError, "Invalid DST format in %R", tz_str_obj);
1501 : 1 : goto error;
1502 : : }
1503 : 183 : p += num_chars;
1504 : :
1505 [ + + ]: 183 : if (*p == ',') {
1506 : : // From the POSIX standard:
1507 : : //
1508 : : // If no offset follows dst, the alternative time is assumed to be one
1509 : : // hour ahead of standard time.
1510 : 121 : dst_offset = std_offset + 3600;
1511 : : }
1512 : : else {
1513 : 62 : num_chars = parse_tz_delta(p, &dst_offset);
1514 [ + + ]: 62 : if (num_chars < 0) {
1515 : 3 : PyErr_Format(PyExc_ValueError, "Invalid DST offset in %R",
1516 : : tz_str_obj);
1517 : 3 : goto error;
1518 : : }
1519 : :
1520 : 59 : p += num_chars;
1521 : : }
1522 : :
1523 : 180 : TransitionRuleType **transitions[2] = {&start, &end};
1524 [ + + ]: 516 : for (size_t i = 0; i < 2; ++i) {
1525 [ + + ]: 352 : if (*p != ',') {
1526 : 1 : PyErr_Format(PyExc_ValueError,
1527 : : "Missing transition rules in TZ string: %R",
1528 : : tz_str_obj);
1529 : 1 : goto error;
1530 : : }
1531 : 351 : p++;
1532 : :
1533 : 351 : num_chars = parse_transition_rule(p, transitions[i]);
1534 [ + + ]: 351 : if (num_chars < 0) {
1535 : 15 : PyErr_Format(PyExc_ValueError,
1536 : : "Malformed transition rule in TZ string: %R",
1537 : : tz_str_obj);
1538 : 15 : goto error;
1539 : : }
1540 : 336 : p += num_chars;
1541 : : }
1542 : :
1543 [ + + ]: 164 : if (*p != '\0') {
1544 : 1 : PyErr_Format(PyExc_ValueError,
1545 : : "Extraneous characters at end of TZ string: %R",
1546 : : tz_str_obj);
1547 : 1 : goto error;
1548 : : }
1549 : :
1550 : 163 : complete:
1551 : 259 : build_tzrule(std_abbr, dst_abbr, std_offset, dst_offset, start, end, out);
1552 : 259 : Py_DECREF(std_abbr);
1553 : 259 : Py_XDECREF(dst_abbr);
1554 : :
1555 : 259 : return 0;
1556 : 27 : error:
1557 : 27 : Py_XDECREF(std_abbr);
1558 [ + + + - ]: 27 : if (dst_abbr != NULL && dst_abbr != Py_None) {
1559 : 21 : Py_DECREF(dst_abbr);
1560 : : }
1561 : :
1562 [ + + ]: 27 : if (start != NULL) {
1563 : 9 : PyMem_Free(start);
1564 : : }
1565 : :
1566 [ + + ]: 27 : if (end != NULL) {
1567 : 1 : PyMem_Free(end);
1568 : : }
1569 : :
1570 : 27 : return -1;
1571 : : }
1572 : :
1573 : : static int
1574 : 1135 : parse_uint(const char *const p, uint8_t *value)
1575 : : {
1576 [ - + ]: 1135 : if (!isdigit(*p)) {
1577 : 0 : return -1;
1578 : : }
1579 : :
1580 : 1135 : *value = (*p) - '0';
1581 : 1135 : return 0;
1582 : : }
1583 : :
1584 : : /* Parse the STD and DST abbreviations from a TZ string. */
1585 : : static Py_ssize_t
1586 : 470 : parse_abbr(const char *const p, PyObject **abbr)
1587 : : {
1588 : 470 : const char *ptr = p;
1589 : 470 : char buff = *ptr;
1590 : : const char *str_start;
1591 : : const char *str_end;
1592 : :
1593 [ + + ]: 470 : if (*ptr == '<') {
1594 : 70 : ptr++;
1595 : 70 : str_start = ptr;
1596 [ + + ]: 280 : while ((buff = *ptr) != '>') {
1597 : : // From the POSIX standard:
1598 : : //
1599 : : // In the quoted form, the first character shall be the less-than
1600 : : // ( '<' ) character and the last character shall be the
1601 : : // greater-than ( '>' ) character. All characters between these
1602 : : // quoting characters shall be alphanumeric characters from the
1603 : : // portable character set in the current locale, the plus-sign (
1604 : : // '+' ) character, or the minus-sign ( '-' ) character. The std
1605 : : // and dst fields in this case shall not include the quoting
1606 : : // characters.
1607 [ + - + + : 210 : if (!isalpha(buff) && !isdigit(buff) && buff != '+' &&
+ + - + ]
1608 : : buff != '-') {
1609 : 0 : return -1;
1610 : : }
1611 : 210 : ptr++;
1612 : : }
1613 : 70 : str_end = ptr;
1614 : 70 : ptr++;
1615 : : }
1616 : : else {
1617 : 400 : str_start = p;
1618 : : // From the POSIX standard:
1619 : : //
1620 : : // In the unquoted form, all characters in these fields shall be
1621 : : // alphabetic characters from the portable character set in the
1622 : : // current locale.
1623 [ + + ]: 1647 : while (isalpha(*ptr)) {
1624 : 1247 : ptr++;
1625 : : }
1626 : 400 : str_end = ptr;
1627 : : }
1628 : :
1629 : 470 : *abbr = PyUnicode_FromStringAndSize(str_start, str_end - str_start);
1630 [ - + ]: 470 : if (*abbr == NULL) {
1631 : 0 : return -1;
1632 : : }
1633 : :
1634 : 470 : return ptr - p;
1635 : : }
1636 : :
1637 : : /* Parse a UTC offset from a TZ str. */
1638 : : static Py_ssize_t
1639 : 347 : parse_tz_delta(const char *const p, long *total_seconds)
1640 : : {
1641 : : // From the POSIX spec:
1642 : : //
1643 : : // Indicates the value added to the local time to arrive at Coordinated
1644 : : // Universal Time. The offset has the form:
1645 : : //
1646 : : // hh[:mm[:ss]]
1647 : : //
1648 : : // One or more digits may be used; the value is always interpreted as a
1649 : : // decimal number.
1650 : : //
1651 : : // The POSIX spec says that the values for `hour` must be between 0 and 24
1652 : : // hours, but RFC 8536 §3.3.1 specifies that the hours part of the
1653 : : // transition times may be signed and range from -167 to 167.
1654 : 347 : long sign = -1;
1655 : 347 : long hours = 0;
1656 : 347 : long minutes = 0;
1657 : 347 : long seconds = 0;
1658 : :
1659 : 347 : const char *ptr = p;
1660 : 347 : char buff = *ptr;
1661 [ + + + + ]: 347 : if (buff == '-' || buff == '+') {
1662 : : // Negative numbers correspond to *positive* offsets, from the spec:
1663 : : //
1664 : : // If preceded by a '-', the timezone shall be east of the Prime
1665 : : // Meridian; otherwise, it shall be west (which may be indicated by
1666 : : // an optional preceding '+' ).
1667 [ + + ]: 133 : if (buff == '-') {
1668 : 128 : sign = 1;
1669 : : }
1670 : :
1671 : 133 : ptr++;
1672 : : }
1673 : :
1674 : : // The hour can be 1 or 2 numeric characters
1675 [ + + ]: 735 : for (size_t i = 0; i < 2; ++i) {
1676 : 692 : buff = *ptr;
1677 [ + + ]: 692 : if (!isdigit(buff)) {
1678 [ + + ]: 304 : if (i == 0) {
1679 : 2 : return -1;
1680 : : }
1681 : : else {
1682 : 302 : break;
1683 : : }
1684 : : }
1685 : :
1686 : 388 : hours *= 10;
1687 : 388 : hours += buff - '0';
1688 : 388 : ptr++;
1689 : : }
1690 : :
1691 [ + + - + ]: 345 : if (hours > 24 || hours < 0) {
1692 : 6 : return -1;
1693 : : }
1694 : :
1695 : : // Minutes and seconds always of the format ":dd"
1696 : 339 : long *outputs[2] = {&minutes, &seconds};
1697 [ + - ]: 339 : for (size_t i = 0; i < 2; ++i) {
1698 [ + - ]: 339 : if (*ptr != ':') {
1699 : 339 : goto complete;
1700 : : }
1701 : 0 : ptr++;
1702 : :
1703 [ # # ]: 0 : for (size_t j = 0; j < 2; ++j) {
1704 : 0 : buff = *ptr;
1705 [ # # ]: 0 : if (!isdigit(buff)) {
1706 : 0 : return -1;
1707 : : }
1708 : 0 : *(outputs[i]) *= 10;
1709 : 0 : *(outputs[i]) += buff - '0';
1710 : 0 : ptr++;
1711 : : }
1712 : : }
1713 : :
1714 : 0 : complete:
1715 : 339 : *total_seconds = sign * ((hours * 3600) + (minutes * 60) + seconds);
1716 : :
1717 : 339 : return ptr - p;
1718 : : }
1719 : :
1720 : : /* Parse the date portion of a transition rule. */
1721 : : static Py_ssize_t
1722 : 351 : parse_transition_rule(const char *const p, TransitionRuleType **out)
1723 : : {
1724 : : // The full transition rule indicates when to change back and forth between
1725 : : // STD and DST, and has the form:
1726 : : //
1727 : : // date[/time],date[/time]
1728 : : //
1729 : : // This function parses an individual date[/time] section, and returns
1730 : : // the number of characters that contributed to the transition rule. This
1731 : : // does not include the ',' at the end of the first rule.
1732 : : //
1733 : : // The POSIX spec states that if *time* is not given, the default is 02:00.
1734 : 351 : const char *ptr = p;
1735 : 351 : int8_t hour = 2;
1736 : 351 : int8_t minute = 0;
1737 : 351 : int8_t second = 0;
1738 : :
1739 : : // Rules come in one of three flavors:
1740 : : //
1741 : : // 1. Jn: Julian day n, with no leap days.
1742 : : // 2. n: Day of year (0-based, with leap days)
1743 : : // 3. Mm.n.d: Specifying by month, week and day-of-week.
1744 : :
1745 [ + + ]: 351 : if (*ptr == 'M') {
1746 : : uint8_t month, week, day;
1747 : 331 : ptr++;
1748 [ - + ]: 331 : if (parse_uint(ptr, &month)) {
1749 : 9 : return -1;
1750 : : }
1751 : 331 : ptr++;
1752 [ + + ]: 331 : if (*ptr != '.') {
1753 : : uint8_t tmp;
1754 [ - + ]: 144 : if (parse_uint(ptr, &tmp)) {
1755 : 0 : return -1;
1756 : : }
1757 : :
1758 : 144 : month *= 10;
1759 : 144 : month += tmp;
1760 : 144 : ptr++;
1761 : : }
1762 : :
1763 : 331 : uint8_t *values[2] = {&week, &day};
1764 [ + + ]: 991 : for (size_t i = 0; i < 2; ++i) {
1765 [ + + ]: 661 : if (*ptr != '.') {
1766 : 1 : return -1;
1767 : : }
1768 : 660 : ptr++;
1769 : :
1770 [ - + ]: 660 : if (parse_uint(ptr, values[i])) {
1771 : 0 : return -1;
1772 : : }
1773 : 660 : ptr++;
1774 : : }
1775 : :
1776 [ + + ]: 330 : if (*ptr == '/') {
1777 : 176 : ptr++;
1778 : : Py_ssize_t num_chars =
1779 : 176 : parse_transition_time(ptr, &hour, &minute, &second);
1780 [ - + ]: 176 : if (num_chars < 0) {
1781 : 0 : return -1;
1782 : : }
1783 : 176 : ptr += num_chars;
1784 : : }
1785 : :
1786 : 330 : CalendarRule *rv = PyMem_Calloc(1, sizeof(CalendarRule));
1787 [ - + ]: 330 : if (rv == NULL) {
1788 : 0 : return -1;
1789 : : }
1790 : :
1791 [ + + ]: 330 : if (calendarrule_new(month, week, day, hour, minute, second, rv)) {
1792 : 8 : PyMem_Free(rv);
1793 : 8 : return -1;
1794 : : }
1795 : :
1796 : 322 : *out = (TransitionRuleType *)rv;
1797 : : }
1798 : : else {
1799 : 20 : uint8_t julian = 0;
1800 : 20 : unsigned int day = 0;
1801 [ + + ]: 20 : if (*ptr == 'J') {
1802 : 10 : julian = 1;
1803 : 10 : ptr++;
1804 : : }
1805 : :
1806 [ + + ]: 59 : for (size_t i = 0; i < 3; ++i) {
1807 [ + + ]: 50 : if (!isdigit(*ptr)) {
1808 [ + + ]: 11 : if (i == 0) {
1809 : 2 : return -1;
1810 : : }
1811 : 9 : break;
1812 : : }
1813 : 39 : day *= 10;
1814 : 39 : day += (*ptr) - '0';
1815 : 39 : ptr++;
1816 : : }
1817 : :
1818 [ + + ]: 18 : if (*ptr == '/') {
1819 : 16 : ptr++;
1820 : : Py_ssize_t num_chars =
1821 : 16 : parse_transition_time(ptr, &hour, &minute, &second);
1822 [ - + ]: 16 : if (num_chars < 0) {
1823 : 0 : return -1;
1824 : : }
1825 : 16 : ptr += num_chars;
1826 : : }
1827 : :
1828 : 18 : DayRule *rv = PyMem_Calloc(1, sizeof(DayRule));
1829 [ - + ]: 18 : if (rv == NULL) {
1830 : 0 : return -1;
1831 : : }
1832 : :
1833 [ + + ]: 18 : if (dayrule_new(julian, day, hour, minute, second, rv)) {
1834 : 4 : PyMem_Free(rv);
1835 : 4 : return -1;
1836 : : }
1837 : 14 : *out = (TransitionRuleType *)rv;
1838 : : }
1839 : :
1840 : 336 : return ptr - p;
1841 : : }
1842 : :
1843 : : /* Parse the time portion of a transition rule (e.g. following an /) */
1844 : : static Py_ssize_t
1845 : 192 : parse_transition_time(const char *const p, int8_t *hour, int8_t *minute,
1846 : : int8_t *second)
1847 : : {
1848 : : // From the spec:
1849 : : //
1850 : : // The time has the same format as offset except that no leading sign
1851 : : // ( '-' or '+' ) is allowed.
1852 : : //
1853 : : // The format for the offset is:
1854 : : //
1855 : : // h[h][:mm[:ss]]
1856 : : //
1857 : : // RFC 8536 also allows transition times to be signed and to range from
1858 : : // -167 to +167, but the current version only supports [0, 99].
1859 : : //
1860 : : // TODO: Support the full range of transition hours.
1861 : 192 : int8_t *components[3] = {hour, minute, second};
1862 : 192 : const char *ptr = p;
1863 : 192 : int8_t sign = 1;
1864 : :
1865 [ + + - + ]: 192 : if (*ptr == '-' || *ptr == '+') {
1866 [ + - ]: 4 : if (*ptr == '-') {
1867 : 4 : sign = -1;
1868 : : }
1869 : 4 : ptr++;
1870 : : }
1871 : :
1872 [ + + ]: 398 : for (size_t i = 0; i < 3; ++i) {
1873 [ + + ]: 396 : if (i > 0) {
1874 [ + + ]: 204 : if (*ptr != ':') {
1875 : 190 : break;
1876 : : }
1877 : 14 : ptr++;
1878 : : }
1879 : :
1880 : 206 : uint8_t buff = 0;
1881 [ + + ]: 471 : for (size_t j = 0; j < 2; j++) {
1882 [ + + ]: 412 : if (!isdigit(*ptr)) {
1883 [ + - + - ]: 147 : if (i == 0 && j > 0) {
1884 : 147 : break;
1885 : : }
1886 : 0 : return -1;
1887 : : }
1888 : :
1889 : 265 : buff *= 10;
1890 : 265 : buff += (*ptr) - '0';
1891 : 265 : ptr++;
1892 : : }
1893 : :
1894 : 206 : *(components[i]) = sign * buff;
1895 : : }
1896 : :
1897 : 192 : return ptr - p;
1898 : : }
1899 : :
1900 : : /* Constructor for a _tzrule.
1901 : : *
1902 : : * If `dst_abbr` is NULL, this will construct an "STD-only" _tzrule, in which
1903 : : * case `dst_offset` will be ignored and `start` and `end` are expected to be
1904 : : * NULL as well.
1905 : : *
1906 : : * Returns 0 on success.
1907 : : */
1908 : : static int
1909 : 322 : build_tzrule(PyObject *std_abbr, PyObject *dst_abbr, long std_offset,
1910 : : long dst_offset, TransitionRuleType *start,
1911 : : TransitionRuleType *end, _tzrule *out)
1912 : : {
1913 : 322 : _tzrule rv = {{0}};
1914 : :
1915 : 322 : rv.start = start;
1916 : 322 : rv.end = end;
1917 : :
1918 [ - + ]: 322 : if (build_ttinfo(std_offset, 0, std_abbr, &rv.std)) {
1919 : 0 : goto error;
1920 : : }
1921 : :
1922 [ + + ]: 322 : if (dst_abbr != NULL) {
1923 : 163 : rv.dst_diff = dst_offset - std_offset;
1924 [ - + ]: 163 : if (build_ttinfo(dst_offset, rv.dst_diff, dst_abbr, &rv.dst)) {
1925 : 0 : goto error;
1926 : : }
1927 : : }
1928 : : else {
1929 : 159 : rv.std_only = 1;
1930 : : }
1931 : :
1932 : 322 : *out = rv;
1933 : :
1934 : 322 : return 0;
1935 : 0 : error:
1936 : 0 : xdecref_ttinfo(&rv.std);
1937 : 0 : xdecref_ttinfo(&rv.dst);
1938 : 0 : return -1;
1939 : : }
1940 : :
1941 : : /* Destructor for _tzrule. */
1942 : : static void
1943 : 358 : free_tzrule(_tzrule *tzrule)
1944 : : {
1945 : 358 : xdecref_ttinfo(&(tzrule->std));
1946 [ + + ]: 358 : if (!tzrule->std_only) {
1947 : 199 : xdecref_ttinfo(&(tzrule->dst));
1948 : : }
1949 : :
1950 [ + + ]: 358 : if (tzrule->start != NULL) {
1951 : 163 : PyMem_Free(tzrule->start);
1952 : : }
1953 : :
1954 [ + + ]: 358 : if (tzrule->end != NULL) {
1955 : 163 : PyMem_Free(tzrule->end);
1956 : : }
1957 : 358 : }
1958 : :
1959 : : /* Calculate DST offsets from transitions and UTC offsets
1960 : : *
1961 : : * This is necessary because each C `ttinfo` only contains the UTC offset,
1962 : : * time zone abbreviation and an isdst boolean - it does not include the
1963 : : * amount of the DST offset, but we need the amount for the dst() function.
1964 : : *
1965 : : * Thus function uses heuristics to infer what the offset should be, so it
1966 : : * is not guaranteed that this will work for all zones. If we cannot assign
1967 : : * a value for a given DST offset, we'll assume it's 1H rather than 0H, so
1968 : : * bool(dt.dst()) will always match ttinfo.isdst.
1969 : : */
1970 : : static void
1971 : 350 : utcoff_to_dstoff(size_t *trans_idx, long *utcoffs, long *dstoffs,
1972 : : unsigned char *isdsts, size_t num_transitions,
1973 : : size_t num_ttinfos)
1974 : : {
1975 : 350 : size_t dst_count = 0;
1976 : 350 : size_t dst_found = 0;
1977 [ + + ]: 2104 : for (size_t i = 0; i < num_ttinfos; ++i) {
1978 : 1754 : dst_count++;
1979 : : }
1980 : :
1981 [ + + ]: 39357 : for (size_t i = 1; i < num_transitions; ++i) {
1982 [ - + ]: 39007 : if (dst_count == dst_found) {
1983 : 0 : break;
1984 : : }
1985 : :
1986 : 39007 : size_t idx = trans_idx[i];
1987 : 39007 : size_t comp_idx = trans_idx[i - 1];
1988 : :
1989 : : // Only look at DST offsets that have nto been assigned already
1990 [ + + + + ]: 39007 : if (!isdsts[idx] || dstoffs[idx] != 0) {
1991 : 38155 : continue;
1992 : : }
1993 : :
1994 : 852 : long dstoff = 0;
1995 : 852 : long utcoff = utcoffs[idx];
1996 : :
1997 [ + + ]: 852 : if (!isdsts[comp_idx]) {
1998 : 592 : dstoff = utcoff - utcoffs[comp_idx];
1999 : : }
2000 : :
2001 [ + + + + ]: 852 : if (!dstoff && idx < (num_ttinfos - 1)) {
2002 : 254 : comp_idx = trans_idx[i + 1];
2003 : :
2004 : : // If the following transition is also DST and we couldn't find
2005 : : // the DST offset by this point, we're going to have to skip it
2006 : : // and hope this transition gets assigned later
2007 [ + + ]: 254 : if (isdsts[comp_idx]) {
2008 : 232 : continue;
2009 : : }
2010 : :
2011 : 22 : dstoff = utcoff - utcoffs[comp_idx];
2012 : : }
2013 : :
2014 [ + + ]: 620 : if (dstoff) {
2015 : 592 : dst_found++;
2016 : 592 : dstoffs[idx] = dstoff;
2017 : : }
2018 : : }
2019 : :
2020 [ + + ]: 350 : if (dst_found < dst_count) {
2021 : : // If there are time zones we didn't find a value for, we'll end up
2022 : : // with dstoff = 0 for something where isdst=1. This is obviously
2023 : : // wrong — one hour will be a much better guess than 0.
2024 [ + + ]: 2056 : for (size_t idx = 0; idx < num_ttinfos; ++idx) {
2025 [ + + + + ]: 1754 : if (isdsts[idx] && !dstoffs[idx]) {
2026 : 76 : dstoffs[idx] = 3600;
2027 : : }
2028 : : }
2029 : : }
2030 : 350 : }
2031 : :
2032 : : #define _swap(x, y, buffer) \
2033 : : buffer = x; \
2034 : : x = y; \
2035 : : y = buffer;
2036 : :
2037 : : /* Calculate transitions in local time from UTC time and offsets.
2038 : : *
2039 : : * We want to know when each transition occurs, denominated in the number of
2040 : : * nominal wall-time seconds between 1970-01-01T00:00:00 and the transition in
2041 : : * *local time* (note: this is *not* equivalent to the output of
2042 : : * datetime.timestamp, which is the total number of seconds actual elapsed
2043 : : * since 1970-01-01T00:00:00Z in UTC).
2044 : : *
2045 : : * This is an ambiguous question because "local time" can be ambiguous — but it
2046 : : * is disambiguated by the `fold` parameter, so we allocate two arrays:
2047 : : *
2048 : : * trans_local[0]: The wall-time transitions for fold=0
2049 : : * trans_local[1]: The wall-time transitions for fold=1
2050 : : *
2051 : : * This returns 0 on success and a negative number of failure. The trans_local
2052 : : * arrays must be freed if they are not NULL.
2053 : : */
2054 : : static int
2055 : 350 : ts_to_local(size_t *trans_idx, int64_t *trans_utc, long *utcoff,
2056 : : int64_t *trans_local[2], size_t num_ttinfos,
2057 : : size_t num_transitions)
2058 : : {
2059 [ + + ]: 350 : if (num_transitions == 0) {
2060 : 58 : return 0;
2061 : : }
2062 : :
2063 : : // Copy the UTC transitions into each array to be modified in place later
2064 [ + + ]: 876 : for (size_t i = 0; i < 2; ++i) {
2065 : 584 : trans_local[i] = PyMem_Malloc(num_transitions * sizeof(int64_t));
2066 [ - + ]: 584 : if (trans_local[i] == NULL) {
2067 : 0 : return -1;
2068 : : }
2069 : :
2070 : 584 : memcpy(trans_local[i], trans_utc, num_transitions * sizeof(int64_t));
2071 : : }
2072 : :
2073 : : int64_t offset_0, offset_1, buff;
2074 [ + + ]: 292 : if (num_ttinfos > 1) {
2075 : 290 : offset_0 = utcoff[0];
2076 : 290 : offset_1 = utcoff[trans_idx[0]];
2077 : :
2078 [ + + ]: 290 : if (offset_1 > offset_0) {
2079 : 98 : _swap(offset_0, offset_1, buff);
2080 : : }
2081 : : }
2082 : : else {
2083 : 2 : offset_0 = utcoff[0];
2084 : 2 : offset_1 = utcoff[0];
2085 : : }
2086 : :
2087 : 292 : trans_local[0][0] += offset_0;
2088 : 292 : trans_local[1][0] += offset_1;
2089 : :
2090 [ + + ]: 39299 : for (size_t i = 1; i < num_transitions; ++i) {
2091 : 39007 : offset_0 = utcoff[trans_idx[i - 1]];
2092 : 39007 : offset_1 = utcoff[trans_idx[i]];
2093 : :
2094 [ + + ]: 39007 : if (offset_1 > offset_0) {
2095 : 19467 : _swap(offset_1, offset_0, buff);
2096 : : }
2097 : :
2098 : 39007 : trans_local[0][i] += offset_0;
2099 : 39007 : trans_local[1][i] += offset_1;
2100 : : }
2101 : :
2102 : 292 : return 0;
2103 : : }
2104 : :
2105 : : /* Simple bisect_right binary search implementation */
2106 : : static size_t
2107 : 6647 : _bisect(const int64_t value, const int64_t *arr, size_t size)
2108 : : {
2109 : 6647 : size_t lo = 0;
2110 : 6647 : size_t hi = size;
2111 : : size_t m;
2112 : :
2113 [ + + ]: 53087 : while (lo < hi) {
2114 : 46440 : m = (lo + hi) / 2;
2115 [ + + ]: 46440 : if (arr[m] > value) {
2116 : 31414 : hi = m;
2117 : : }
2118 : : else {
2119 : 15026 : lo = m + 1;
2120 : : }
2121 : : }
2122 : :
2123 : 6647 : return hi;
2124 : : }
2125 : :
2126 : : /* Find the ttinfo rules that apply at a given local datetime. */
2127 : : static _ttinfo *
2128 : 8863 : find_ttinfo(PyZoneInfo_ZoneInfo *self, PyObject *dt)
2129 : : {
2130 : : // datetime.time has a .tzinfo attribute that passes None as the dt
2131 : : // argument; it only really has meaning for fixed-offset zones.
2132 [ + + ]: 8863 : if (dt == Py_None) {
2133 [ + + ]: 138 : if (self->fixed_offset) {
2134 : 15 : return &(self->tzrule_after.std);
2135 : : }
2136 : : else {
2137 : 123 : return &NO_TTINFO;
2138 : : }
2139 : : }
2140 : :
2141 : : int64_t ts;
2142 [ - + ]: 8725 : if (get_local_timestamp(dt, &ts)) {
2143 : 0 : return NULL;
2144 : : }
2145 : :
2146 : 8725 : unsigned char fold = PyDateTime_DATE_GET_FOLD(dt);
2147 : : assert(fold < 2);
2148 : 8725 : int64_t *local_transitions = self->trans_list_wall[fold];
2149 : 8725 : size_t num_trans = self->num_transitions;
2150 : :
2151 [ + + + + ]: 8725 : if (num_trans && ts < local_transitions[0]) {
2152 : 364 : return self->ttinfo_before;
2153 : : }
2154 [ + + + + ]: 8361 : else if (!num_trans || ts > local_transitions[self->num_transitions - 1]) {
2155 : 1860 : return find_tzrule_ttinfo(&(self->tzrule_after), ts, fold,
2156 : 1860 : PyDateTime_GET_YEAR(dt));
2157 : : }
2158 : : else {
2159 : 6501 : size_t idx = _bisect(ts, local_transitions, self->num_transitions) - 1;
2160 : : assert(idx < self->num_transitions);
2161 : 6501 : return self->trans_ttinfos[idx];
2162 : : }
2163 : : }
2164 : :
2165 : : static int
2166 : 12775 : is_leap_year(int year)
2167 : : {
2168 : 12775 : const unsigned int ayear = (unsigned int)year;
2169 [ + + + + : 12775 : return ayear % 4 == 0 && (ayear % 100 != 0 || ayear % 400 == 0);
+ + ]
2170 : : }
2171 : :
2172 : : /* Calculates ordinal datetime from year, month and day. */
2173 : : static int
2174 : 13585 : ymd_to_ord(int y, int m, int d)
2175 : : {
2176 : 13585 : y -= 1;
2177 : 13585 : int days_before_year = (y * 365) + (y / 4) - (y / 100) + (y / 400);
2178 : 13585 : int yearday = DAYS_BEFORE_MONTH[m];
2179 [ + + + + ]: 13585 : if (m > 2 && is_leap_year(y + 1)) {
2180 : 5099 : yearday += 1;
2181 : : }
2182 : :
2183 : 13585 : return days_before_year + yearday + d;
2184 : : }
2185 : :
2186 : : /* Calculate the number of seconds since 1970-01-01 in local time.
2187 : : *
2188 : : * This gets a datetime in the same "units" as self->trans_list_wall so that we
2189 : : * can easily determine which transitions a datetime falls between. See the
2190 : : * comment above ts_to_local for more information.
2191 : : * */
2192 : : static int
2193 : 9008 : get_local_timestamp(PyObject *dt, int64_t *local_ts)
2194 : : {
2195 : : assert(local_ts != NULL);
2196 : :
2197 : : int hour, minute, second;
2198 : : int ord;
2199 [ + + ]: 9008 : if (PyDateTime_CheckExact(dt)) {
2200 : 6741 : int y = PyDateTime_GET_YEAR(dt);
2201 : 6741 : int m = PyDateTime_GET_MONTH(dt);
2202 : 6741 : int d = PyDateTime_GET_DAY(dt);
2203 : 6741 : hour = PyDateTime_DATE_GET_HOUR(dt);
2204 : 6741 : minute = PyDateTime_DATE_GET_MINUTE(dt);
2205 : 6741 : second = PyDateTime_DATE_GET_SECOND(dt);
2206 : :
2207 : 6741 : ord = ymd_to_ord(y, m, d);
2208 : : }
2209 : : else {
2210 : 2267 : PyObject *num = PyObject_CallMethod(dt, "toordinal", NULL);
2211 [ - + ]: 2267 : if (num == NULL) {
2212 : 0 : return -1;
2213 : : }
2214 : :
2215 : 2267 : ord = PyLong_AsLong(num);
2216 : 2267 : Py_DECREF(num);
2217 [ - + - - ]: 2267 : if (ord == -1 && PyErr_Occurred()) {
2218 : 0 : return -1;
2219 : : }
2220 : :
2221 : 2267 : num = PyObject_GetAttrString(dt, "hour");
2222 [ - + ]: 2267 : if (num == NULL) {
2223 : 0 : return -1;
2224 : : }
2225 : 2267 : hour = PyLong_AsLong(num);
2226 : 2267 : Py_DECREF(num);
2227 [ - + ]: 2267 : if (hour == -1) {
2228 : 0 : return -1;
2229 : : }
2230 : :
2231 : 2267 : num = PyObject_GetAttrString(dt, "minute");
2232 [ - + ]: 2267 : if (num == NULL) {
2233 : 0 : return -1;
2234 : : }
2235 : 2267 : minute = PyLong_AsLong(num);
2236 : 2267 : Py_DECREF(num);
2237 [ - + ]: 2267 : if (minute == -1) {
2238 : 0 : return -1;
2239 : : }
2240 : :
2241 : 2267 : num = PyObject_GetAttrString(dt, "second");
2242 [ - + ]: 2267 : if (num == NULL) {
2243 : 0 : return -1;
2244 : : }
2245 : 2267 : second = PyLong_AsLong(num);
2246 : 2267 : Py_DECREF(num);
2247 [ - + ]: 2267 : if (second == -1) {
2248 : 0 : return -1;
2249 : : }
2250 : : }
2251 : :
2252 : 9008 : *local_ts = (int64_t)(ord - EPOCHORDINAL) * 86400 +
2253 : 9008 : (int64_t)(hour * 3600 + minute * 60 + second);
2254 : :
2255 : 9008 : return 0;
2256 : : }
2257 : :
2258 : : /////
2259 : : // Functions for cache handling
2260 : :
2261 : : /* Constructor for StrongCacheNode */
2262 : : static StrongCacheNode *
2263 : 192 : strong_cache_node_new(PyObject *key, PyObject *zone)
2264 : : {
2265 : 192 : StrongCacheNode *node = PyMem_Malloc(sizeof(StrongCacheNode));
2266 [ - + ]: 192 : if (node == NULL) {
2267 : 0 : return NULL;
2268 : : }
2269 : :
2270 : 192 : Py_INCREF(key);
2271 : 192 : Py_INCREF(zone);
2272 : :
2273 : 192 : node->next = NULL;
2274 : 192 : node->prev = NULL;
2275 : 192 : node->key = key;
2276 : 192 : node->zone = zone;
2277 : :
2278 : 192 : return node;
2279 : : }
2280 : :
2281 : : /* Destructor for StrongCacheNode */
2282 : : void
2283 : 192 : strong_cache_node_free(StrongCacheNode *node)
2284 : : {
2285 : 192 : Py_XDECREF(node->key);
2286 : 192 : Py_XDECREF(node->zone);
2287 : :
2288 : 192 : PyMem_Free(node);
2289 : 192 : }
2290 : :
2291 : : /* Frees all nodes at or after a specified root in the strong cache.
2292 : : *
2293 : : * This can be used on the root node to free the entire cache or it can be used
2294 : : * to clear all nodes that have been expired (which, if everything is going
2295 : : * right, will actually only be 1 node at a time).
2296 : : */
2297 : : void
2298 : 93 : strong_cache_free(StrongCacheNode *root)
2299 : : {
2300 : 93 : StrongCacheNode *node = root;
2301 : : StrongCacheNode *next_node;
2302 [ + + ]: 282 : while (node != NULL) {
2303 : 189 : next_node = node->next;
2304 : 189 : strong_cache_node_free(node);
2305 : :
2306 : 189 : node = next_node;
2307 : : }
2308 : 93 : }
2309 : :
2310 : : /* Removes a node from the cache and update its neighbors.
2311 : : *
2312 : : * This is used both when ejecting a node from the cache and when moving it to
2313 : : * the front of the cache.
2314 : : */
2315 : : static void
2316 : 197 : remove_from_strong_cache(StrongCacheNode *node)
2317 : : {
2318 [ - + ]: 197 : if (ZONEINFO_STRONG_CACHE == node) {
2319 : 0 : ZONEINFO_STRONG_CACHE = node->next;
2320 : : }
2321 : :
2322 [ + + ]: 197 : if (node->prev != NULL) {
2323 : 5 : node->prev->next = node->next;
2324 : : }
2325 : :
2326 [ - + ]: 197 : if (node->next != NULL) {
2327 : 0 : node->next->prev = node->prev;
2328 : : }
2329 : :
2330 : 197 : node->next = NULL;
2331 : 197 : node->prev = NULL;
2332 : 197 : }
2333 : :
2334 : : /* Retrieves the node associated with a key, if it exists.
2335 : : *
2336 : : * This traverses the strong cache until it finds a matching key and returns a
2337 : : * pointer to the relevant node if found. Returns NULL if no node is found.
2338 : : *
2339 : : * root may be NULL, indicating an empty cache.
2340 : : */
2341 : : static StrongCacheNode *
2342 : 596 : find_in_strong_cache(const StrongCacheNode *const root, PyObject *const key)
2343 : : {
2344 : 596 : const StrongCacheNode *node = root;
2345 [ + + ]: 1272 : while (node != NULL) {
2346 : 1050 : int rv = PyObject_RichCompareBool(key, node->key, Py_EQ);
2347 [ - + ]: 1050 : if (rv < 0) {
2348 : 0 : return NULL;
2349 : : }
2350 [ + + ]: 1050 : if (rv) {
2351 : 374 : return (StrongCacheNode *)node;
2352 : : }
2353 : :
2354 : 676 : node = node->next;
2355 : : }
2356 : :
2357 : 222 : return NULL;
2358 : : }
2359 : :
2360 : : /* Ejects a given key from the class's strong cache, if applicable.
2361 : : *
2362 : : * This function is used to enable the per-key functionality in clear_cache.
2363 : : */
2364 : : static int
2365 : 3 : eject_from_strong_cache(const PyTypeObject *const type, PyObject *key)
2366 : : {
2367 [ - + ]: 3 : if (type != &PyZoneInfo_ZoneInfoType) {
2368 : 0 : return 0;
2369 : : }
2370 : :
2371 : 3 : StrongCacheNode *node = find_in_strong_cache(ZONEINFO_STRONG_CACHE, key);
2372 [ + - ]: 3 : if (node != NULL) {
2373 : 3 : remove_from_strong_cache(node);
2374 : :
2375 : 3 : strong_cache_node_free(node);
2376 : : }
2377 [ # # ]: 0 : else if (PyErr_Occurred()) {
2378 : 0 : return -1;
2379 : : }
2380 : 3 : return 0;
2381 : : }
2382 : :
2383 : : /* Moves a node to the front of the LRU cache.
2384 : : *
2385 : : * The strong cache is an LRU cache, so whenever a given node is accessed, if
2386 : : * it is not at the front of the cache, it needs to be moved there.
2387 : : */
2388 : : static void
2389 : 563 : move_strong_cache_node_to_front(StrongCacheNode **root, StrongCacheNode *node)
2390 : : {
2391 : 563 : StrongCacheNode *root_p = *root;
2392 [ + + ]: 563 : if (root_p == node) {
2393 : 369 : return;
2394 : : }
2395 : :
2396 : 194 : remove_from_strong_cache(node);
2397 : :
2398 : 194 : node->prev = NULL;
2399 : 194 : node->next = root_p;
2400 : :
2401 [ + + ]: 194 : if (root_p != NULL) {
2402 : 144 : root_p->prev = node;
2403 : : }
2404 : :
2405 : 194 : *root = node;
2406 : : }
2407 : :
2408 : : /* Retrieves a ZoneInfo from the strong cache if it's present.
2409 : : *
2410 : : * This function finds the ZoneInfo by key and if found will move the node to
2411 : : * the front of the LRU cache and return a new reference to it. It returns NULL
2412 : : * if the key is not in the cache.
2413 : : *
2414 : : * The strong cache is currently only implemented for the base class, so this
2415 : : * always returns a cache miss for subclasses.
2416 : : */
2417 : : static PyObject *
2418 : 773 : zone_from_strong_cache(const PyTypeObject *const type, PyObject *const key)
2419 : : {
2420 [ + + ]: 773 : if (type != &PyZoneInfo_ZoneInfoType) {
2421 : 180 : return NULL; // Strong cache currently only implemented for base class
2422 : : }
2423 : :
2424 : 593 : StrongCacheNode *node = find_in_strong_cache(ZONEINFO_STRONG_CACHE, key);
2425 : :
2426 [ + + ]: 593 : if (node != NULL) {
2427 : 371 : move_strong_cache_node_to_front(&ZONEINFO_STRONG_CACHE, node);
2428 : 371 : Py_INCREF(node->zone);
2429 : 371 : return node->zone;
2430 : : }
2431 : :
2432 : 222 : return NULL; // Cache miss
2433 : : }
2434 : :
2435 : : /* Inserts a new key into the strong LRU cache.
2436 : : *
2437 : : * This function is only to be used after a cache miss — it creates a new node
2438 : : * at the front of the cache and ejects any stale entries (keeping the size of
2439 : : * the cache to at most ZONEINFO_STRONG_CACHE_MAX_SIZE).
2440 : : */
2441 : : static void
2442 : 362 : update_strong_cache(const PyTypeObject *const type, PyObject *key,
2443 : : PyObject *zone)
2444 : : {
2445 [ + + ]: 362 : if (type != &PyZoneInfo_ZoneInfoType) {
2446 : 170 : return;
2447 : : }
2448 : :
2449 : 192 : StrongCacheNode *new_node = strong_cache_node_new(key, zone);
2450 : :
2451 : 192 : move_strong_cache_node_to_front(&ZONEINFO_STRONG_CACHE, new_node);
2452 : :
2453 : 192 : StrongCacheNode *node = new_node->next;
2454 [ + + ]: 831 : for (size_t i = 1; i < ZONEINFO_STRONG_CACHE_MAX_SIZE; ++i) {
2455 [ + + ]: 786 : if (node == NULL) {
2456 : 147 : return;
2457 : : }
2458 : 639 : node = node->next;
2459 : : }
2460 : :
2461 : : // Everything beyond this point needs to be freed
2462 [ + + ]: 45 : if (node != NULL) {
2463 [ + - ]: 30 : if (node->prev != NULL) {
2464 : 30 : node->prev->next = NULL;
2465 : : }
2466 : 30 : strong_cache_free(node);
2467 : : }
2468 : : }
2469 : :
2470 : : /* Clears all entries into a type's strong cache.
2471 : : *
2472 : : * Because the strong cache is not implemented for subclasses, this is a no-op
2473 : : * for everything except the base class.
2474 : : */
2475 : : void
2476 : 77 : clear_strong_cache(const PyTypeObject *const type)
2477 : : {
2478 [ + + ]: 77 : if (type != &PyZoneInfo_ZoneInfoType) {
2479 : 14 : return;
2480 : : }
2481 : :
2482 : 63 : strong_cache_free(ZONEINFO_STRONG_CACHE);
2483 : 63 : ZONEINFO_STRONG_CACHE = NULL;
2484 : : }
2485 : :
2486 : : static PyObject *
2487 : 4 : new_weak_cache(void)
2488 : : {
2489 : : PyObject *WeakValueDictionary =
2490 : 4 : _PyImport_GetModuleAttrString("weakref", "WeakValueDictionary");
2491 [ - + ]: 4 : if (WeakValueDictionary == NULL) {
2492 : 0 : return NULL;
2493 : : }
2494 : 4 : PyObject *weak_cache = PyObject_CallNoArgs(WeakValueDictionary);
2495 : 4 : Py_DECREF(WeakValueDictionary);
2496 : 4 : return weak_cache;
2497 : : }
2498 : :
2499 : : static int
2500 : 3 : initialize_caches(void)
2501 : : {
2502 : : // TODO: Move to a PyModule_GetState / PEP 573 based caching system.
2503 [ + - ]: 3 : if (TIMEDELTA_CACHE == NULL) {
2504 : 3 : TIMEDELTA_CACHE = PyDict_New();
2505 : : }
2506 : : else {
2507 : 0 : Py_INCREF(TIMEDELTA_CACHE);
2508 : : }
2509 : :
2510 [ - + ]: 3 : if (TIMEDELTA_CACHE == NULL) {
2511 : 0 : return -1;
2512 : : }
2513 : :
2514 [ + - ]: 3 : if (ZONEINFO_WEAK_CACHE == NULL) {
2515 : 3 : ZONEINFO_WEAK_CACHE = new_weak_cache();
2516 : : }
2517 : : else {
2518 : 0 : Py_INCREF(ZONEINFO_WEAK_CACHE);
2519 : : }
2520 : :
2521 [ - + ]: 3 : if (ZONEINFO_WEAK_CACHE == NULL) {
2522 : 0 : return -1;
2523 : : }
2524 : :
2525 : 3 : return 0;
2526 : : }
2527 : :
2528 : : static PyObject *
2529 : 1 : zoneinfo_init_subclass(PyTypeObject *cls, PyObject *args, PyObject **kwargs)
2530 : : {
2531 : 1 : PyObject *weak_cache = new_weak_cache();
2532 [ - + ]: 1 : if (weak_cache == NULL) {
2533 : 0 : return NULL;
2534 : : }
2535 : :
2536 [ - + ]: 1 : if (PyObject_SetAttrString((PyObject *)cls, "_weak_cache",
2537 : : weak_cache) < 0) {
2538 : 0 : Py_DECREF(weak_cache);
2539 : 0 : return NULL;
2540 : : }
2541 : 1 : Py_DECREF(weak_cache);
2542 : 1 : Py_RETURN_NONE;
2543 : : }
2544 : :
2545 : : /////
2546 : : // Specify the ZoneInfo type
2547 : : static PyMethodDef zoneinfo_methods[] = {
2548 : : {"clear_cache", (PyCFunction)(void (*)(void))zoneinfo_clear_cache,
2549 : : METH_VARARGS | METH_KEYWORDS | METH_CLASS,
2550 : : PyDoc_STR("Clear the ZoneInfo cache.")},
2551 : : {"no_cache", (PyCFunction)(void (*)(void))zoneinfo_no_cache,
2552 : : METH_VARARGS | METH_KEYWORDS | METH_CLASS,
2553 : : PyDoc_STR("Get a new instance of ZoneInfo, bypassing the cache.")},
2554 : : {"from_file", (PyCFunction)(void (*)(void))zoneinfo_from_file,
2555 : : METH_VARARGS | METH_KEYWORDS | METH_CLASS,
2556 : : PyDoc_STR("Create a ZoneInfo file from a file object.")},
2557 : : {"utcoffset", (PyCFunction)zoneinfo_utcoffset, METH_O,
2558 : : PyDoc_STR("Retrieve a timedelta representing the UTC offset in a zone at "
2559 : : "the given datetime.")},
2560 : : {"dst", (PyCFunction)zoneinfo_dst, METH_O,
2561 : : PyDoc_STR("Retrieve a timedelta representing the amount of DST applied "
2562 : : "in a zone at the given datetime.")},
2563 : : {"tzname", (PyCFunction)zoneinfo_tzname, METH_O,
2564 : : PyDoc_STR("Retrieve a string containing the abbreviation for the time "
2565 : : "zone that applies in a zone at a given datetime.")},
2566 : : {"fromutc", (PyCFunction)zoneinfo_fromutc, METH_O,
2567 : : PyDoc_STR("Given a datetime with local time in UTC, retrieve an adjusted "
2568 : : "datetime in local time.")},
2569 : : {"__reduce__", (PyCFunction)zoneinfo_reduce, METH_NOARGS,
2570 : : PyDoc_STR("Function for serialization with the pickle protocol.")},
2571 : : {"_unpickle", (PyCFunction)zoneinfo__unpickle, METH_VARARGS | METH_CLASS,
2572 : : PyDoc_STR("Private method used in unpickling.")},
2573 : : {"__init_subclass__", (PyCFunction)(void (*)(void))zoneinfo_init_subclass,
2574 : : METH_VARARGS | METH_KEYWORDS | METH_CLASS,
2575 : : PyDoc_STR("Function to initialize subclasses.")},
2576 : : {NULL} /* Sentinel */
2577 : : };
2578 : :
2579 : : static PyMemberDef zoneinfo_members[] = {
2580 : : {.name = "key",
2581 : : .offset = offsetof(PyZoneInfo_ZoneInfo, key),
2582 : : .type = T_OBJECT_EX,
2583 : : .flags = READONLY,
2584 : : .doc = NULL},
2585 : : {NULL}, /* Sentinel */
2586 : : };
2587 : :
2588 : : static PyTypeObject PyZoneInfo_ZoneInfoType = {
2589 : : PyVarObject_HEAD_INIT(NULL, 0) //
2590 : : .tp_name = "zoneinfo.ZoneInfo",
2591 : : .tp_basicsize = sizeof(PyZoneInfo_ZoneInfo),
2592 : : .tp_weaklistoffset = offsetof(PyZoneInfo_ZoneInfo, weakreflist),
2593 : : .tp_repr = (reprfunc)zoneinfo_repr,
2594 : : .tp_str = (reprfunc)zoneinfo_str,
2595 : : .tp_getattro = PyObject_GenericGetAttr,
2596 : : .tp_flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE),
2597 : : /* .tp_doc = zoneinfo_doc, */
2598 : : .tp_methods = zoneinfo_methods,
2599 : : .tp_members = zoneinfo_members,
2600 : : .tp_new = zoneinfo_new,
2601 : : .tp_dealloc = zoneinfo_dealloc,
2602 : : };
2603 : :
2604 : : /////
2605 : : // Specify the _zoneinfo module
2606 : : static PyMethodDef module_methods[] = {{NULL, NULL}};
2607 : : static void
2608 : 3 : module_free(void *m)
2609 : : {
2610 : 3 : Py_XDECREF(_tzpath_find_tzfile);
2611 : 3 : _tzpath_find_tzfile = NULL;
2612 : :
2613 : 3 : Py_XDECREF(_common_mod);
2614 : 3 : _common_mod = NULL;
2615 : :
2616 : 3 : Py_XDECREF(io_open);
2617 : 3 : io_open = NULL;
2618 : :
2619 : 3 : xdecref_ttinfo(&NO_TTINFO);
2620 : :
2621 [ + - - + ]: 3 : if (TIMEDELTA_CACHE != NULL && Py_REFCNT(TIMEDELTA_CACHE) > 1) {
2622 : 0 : Py_DECREF(TIMEDELTA_CACHE);
2623 : : } else {
2624 [ + - ]: 3 : Py_CLEAR(TIMEDELTA_CACHE);
2625 : : }
2626 : :
2627 [ + - - + ]: 3 : if (ZONEINFO_WEAK_CACHE != NULL && Py_REFCNT(ZONEINFO_WEAK_CACHE) > 1) {
2628 : 0 : Py_DECREF(ZONEINFO_WEAK_CACHE);
2629 : : } else {
2630 [ + - ]: 3 : Py_CLEAR(ZONEINFO_WEAK_CACHE);
2631 : : }
2632 : :
2633 : 3 : clear_strong_cache(&PyZoneInfo_ZoneInfoType);
2634 : 3 : }
2635 : :
2636 : : static int
2637 : 3 : zoneinfomodule_exec(PyObject *m)
2638 : : {
2639 : 3 : PyDateTime_IMPORT;
2640 [ - + ]: 3 : if (PyDateTimeAPI == NULL) {
2641 : 0 : goto error;
2642 : : }
2643 : 3 : PyZoneInfo_ZoneInfoType.tp_base = PyDateTimeAPI->TZInfoType;
2644 [ - + ]: 3 : if (PyType_Ready(&PyZoneInfo_ZoneInfoType) < 0) {
2645 : 0 : goto error;
2646 : : }
2647 : :
2648 : 3 : Py_INCREF(&PyZoneInfo_ZoneInfoType);
2649 : 3 : PyModule_AddObject(m, "ZoneInfo", (PyObject *)&PyZoneInfo_ZoneInfoType);
2650 : :
2651 : : /* Populate imports */
2652 : 3 : _tzpath_find_tzfile =
2653 : 3 : _PyImport_GetModuleAttrString("zoneinfo._tzpath", "find_tzfile");
2654 [ - + ]: 3 : if (_tzpath_find_tzfile == NULL) {
2655 : 0 : goto error;
2656 : : }
2657 : :
2658 : 3 : io_open = _PyImport_GetModuleAttrString("io", "open");
2659 [ - + ]: 3 : if (io_open == NULL) {
2660 : 0 : goto error;
2661 : : }
2662 : :
2663 : 3 : _common_mod = PyImport_ImportModule("zoneinfo._common");
2664 [ - + ]: 3 : if (_common_mod == NULL) {
2665 : 0 : goto error;
2666 : : }
2667 : :
2668 [ + - ]: 3 : if (NO_TTINFO.utcoff == NULL) {
2669 : 3 : NO_TTINFO.utcoff = Py_None;
2670 : 3 : NO_TTINFO.dstoff = Py_None;
2671 : 3 : NO_TTINFO.tzname = Py_None;
2672 : :
2673 [ + + ]: 12 : for (size_t i = 0; i < 3; ++i) {
2674 : 9 : Py_INCREF(Py_None);
2675 : : }
2676 : : }
2677 : :
2678 [ - + ]: 3 : if (initialize_caches()) {
2679 : 0 : goto error;
2680 : : }
2681 : :
2682 : 3 : return 0;
2683 : :
2684 : 0 : error:
2685 : 0 : return -1;
2686 : : }
2687 : :
2688 : : static PyModuleDef_Slot zoneinfomodule_slots[] = {
2689 : : {Py_mod_exec, zoneinfomodule_exec}, {0, NULL}};
2690 : :
2691 : : static struct PyModuleDef zoneinfomodule = {
2692 : : PyModuleDef_HEAD_INIT,
2693 : : .m_name = "_zoneinfo",
2694 : : .m_doc = "C implementation of the zoneinfo module",
2695 : : .m_size = 0,
2696 : : .m_methods = module_methods,
2697 : : .m_slots = zoneinfomodule_slots,
2698 : : .m_free = (freefunc)module_free};
2699 : :
2700 : : PyMODINIT_FUNC
2701 : 3 : PyInit__zoneinfo(void)
2702 : : {
2703 : 3 : return PyModuleDef_Init(&zoneinfomodule);
2704 : : }
|