1 /* ------------------------------------------------------------------------- */
2
3 #include <Python.h>
4 #include <datetime.h>
5 #include <structmember.h>
6 #include <math.h>
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <string.h>
10 #include <time.h>
11
12 /* ------------------------------------------------------------------------- */
13
14 #define EPOCH_YEAR 1970
15
16 #define DAYS_PER_N_YEAR 365
17 #define DAYS_PER_L_YEAR 366
18
19 #define USECS_PER_SEC 1000000
20
21 #define SECS_PER_MIN 60
22 #define SECS_PER_HOUR (60 * SECS_PER_MIN)
23 #define SECS_PER_DAY (SECS_PER_HOUR * 24)
24
25 // 400-year chunks always have 146097 days (20871 weeks).
26 #define DAYS_PER_400_YEARS 146097L
27 #define SECS_PER_400_YEARS ((int64_t)DAYS_PER_400_YEARS * (int64_t)SECS_PER_DAY)
28
29 // The number of seconds in an aligned 100-year chunk, for those that
30 // do not begin with a leap year and those that do respectively.
31 const int64_t SECS_PER_100_YEARS[2] = {
32 (uint64_t)(76L * DAYS_PER_N_YEAR + 24L * DAYS_PER_L_YEAR) * SECS_PER_DAY,
33 (uint64_t)(75L * DAYS_PER_N_YEAR + 25L * DAYS_PER_L_YEAR) * SECS_PER_DAY
34 };
35
36 // The number of seconds in an aligned 4-year chunk, for those that
37 // do not begin with a leap year and those that do respectively.
38 const int32_t SECS_PER_4_YEARS[2] = {
39 (4 * DAYS_PER_N_YEAR + 0 * DAYS_PER_L_YEAR) * SECS_PER_DAY,
40 (3 * DAYS_PER_N_YEAR + 1 * DAYS_PER_L_YEAR) * SECS_PER_DAY
41 };
42
43 // The number of seconds in non-leap and leap years respectively.
44 const int32_t SECS_PER_YEAR[2] = {
45 DAYS_PER_N_YEAR * SECS_PER_DAY,
46 DAYS_PER_L_YEAR * SECS_PER_DAY
47 };
48
49 #define MONTHS_PER_YEAR 12
50
51 // The month lengths in non-leap and leap years respectively.
52 const int32_t DAYS_PER_MONTHS[2][13] = {
53 {-1, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
54 {-1, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
55 };
56
57 // The day offsets of the beginning of each (1-based) month in non-leap
58 // and leap years respectively.
59 // For example, in a leap year there are 335 days before December.
60 const int32_t MONTHS_OFFSETS[2][14] = {
61 {-1, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365},
62 {-1, 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366}
63 };
64
65 const int DAY_OF_WEEK_TABLE[12] = {
66 0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4
67 };
68
69 #define TM_SUNDAY 0
70 #define TM_MONDAY 1
71 #define TM_TUESDAY 2
72 #define TM_WEDNESDAY 3
73 #define TM_THURSDAY 4
74 #define TM_FRIDAY 5
75 #define TM_SATURDAY 6
76
77 #define TM_JANUARY 0
78 #define TM_FEBRUARY 1
79 #define TM_MARCH 2
80 #define TM_APRIL 3
81 #define TM_MAY 4
82 #define TM_JUNE 5
83 #define TM_JULY 6
84 #define TM_AUGUST 7
85 #define TM_SEPTEMBER 8
86 #define TM_OCTOBER 9
87 #define TM_NOVEMBER 10
88 #define TM_DECEMBER 11
89
90 /* ------------------------------------------------------------------------- */
91
_p(int y)92 int _p(int y) {
93 return y + y/4 - y/100 + y /400;
94 }
95
_is_leap(int year)96 int _is_leap(int year) {
97 return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
98 }
99
_is_long_year(int year)100 int _is_long_year(int year) {
101 return (_p(year) % 7 == 4) || (_p(year - 1) % 7 == 3);
102 }
103
_week_day(int year,int month,int day)104 int _week_day(int year, int month, int day) {
105 int y;
106 int w;
107
108 y = year - (month < 3);
109
110 w = (_p(y) + DAY_OF_WEEK_TABLE[month - 1] + day) % 7;
111
112 if (!w) {
113 w = 7;
114 }
115
116 return w;
117 }
118
_days_in_year(int year)119 int _days_in_year(int year) {
120 if (_is_leap(year)) {
121 return DAYS_PER_L_YEAR;
122 }
123
124 return DAYS_PER_N_YEAR;
125 }
126
_day_number(int year,int month,int day)127 int _day_number(int year, int month, int day) {
128 month = (month + 9) % 12;
129 year = year - month / 10;
130
131 return (
132 365 * year
133 + year / 4 - year / 100 + year / 400
134 + (month * 306 + 5) / 10
135 + (day - 1)
136 );
137 }
138
_get_offset(PyObject * dt)139 int _get_offset(PyObject *dt) {
140 PyObject *tzinfo;
141 PyObject *offset;
142
143 tzinfo = ((PyDateTime_DateTime *)(dt))->tzinfo;
144
145 if (tzinfo != Py_None) {
146 offset = PyObject_CallMethod(tzinfo, "utcoffset", "O", dt);
147
148 return
149 PyDateTime_DELTA_GET_DAYS(offset) * SECS_PER_DAY
150 + PyDateTime_DELTA_GET_SECONDS(offset);
151 }
152
153 return 0;
154 }
155
_has_tzinfo(PyObject * dt)156 int _has_tzinfo(PyObject *dt) {
157 return ((_PyDateTime_BaseTZInfo *)(dt))->hastzinfo;
158 }
159
_get_tz_name(PyObject * dt)160 char* _get_tz_name(PyObject *dt) {
161 PyObject *tzinfo;
162 char *tz = "";
163
164 tzinfo = ((PyDateTime_DateTime *)(dt))->tzinfo;
165
166 if (tzinfo != Py_None) {
167 if (PyObject_HasAttrString(tzinfo, "name")) {
168 // Pendulum timezone
169 tz = PyUnicode_AsUTF8(
170 PyObject_GetAttrString(tzinfo, "name")
171 );
172 } else if (PyObject_HasAttrString(tzinfo, "zone")) {
173 // pytz timezone
174 tz = PyUnicode_AsUTF8(
175 PyObject_GetAttrString(tzinfo, "zone")
176 );
177 }
178 }
179
180 return tz;
181 }
182
183 /* ------------------------ Custom Types ------------------------------- */
184
185 /*
186 * class Diff():
187 */
188 typedef struct {
189 PyObject_HEAD
190 int years;
191 int months;
192 int days;
193 int hours;
194 int minutes;
195 int seconds;
196 int microseconds;
197 int total_days;
198 } Diff;
199
200 /*
201 * def __init__(self, years, months, days, hours, minutes, seconds, microseconds, total_days):
202 * self.years = years
203 * # ...
204 */
Diff_init(Diff * self,PyObject * args,PyObject * kwargs)205 static int Diff_init(Diff *self, PyObject *args, PyObject *kwargs) {
206 int years;
207 int months;
208 int days;
209 int hours;
210 int minutes;
211 int seconds;
212 int microseconds;
213 int total_days;
214
215 if (!PyArg_ParseTuple(args, "iiiiiii", &years, &months, &days, &hours, &minutes, &seconds, µseconds, &total_days))
216 return -1;
217
218 self->years = years;
219 self->months = months;
220 self->days = days;
221 self->hours = hours;
222 self->minutes = minutes;
223 self->seconds = seconds;
224 self->microseconds = microseconds;
225 self->total_days = total_days;
226
227 return 0;
228 }
229
230 /*
231 * def __repr__(self):
232 * return '{} years {} months {} days {} hours {} minutes {} seconds {} microseconds'.format(
233 * self.years, self.months, self.days, self.minutes, self.hours, self.seconds, self.microseconds
234 * )
235 */
Diff_repr(Diff * self)236 static PyObject *Diff_repr(Diff *self) {
237 char repr[82] = {0};
238
239 sprintf(
240 repr,
241 "%d years %d months %d days %d hours %d minutes %d seconds %d microseconds",
242 self->years,
243 self->months,
244 self->days,
245 self->hours,
246 self->minutes,
247 self->seconds,
248 self->microseconds
249 );
250
251 return PyUnicode_FromString(repr);
252 }
253
254 /*
255 * Instantiate new Diff_type object
256 * Skip overhead of calling PyObject_New and PyObject_Init.
257 * Directly allocate object.
258 */
new_diff_ex(int years,int months,int days,int hours,int minutes,int seconds,int microseconds,int total_days,PyTypeObject * type)259 static PyObject *new_diff_ex(int years, int months, int days, int hours, int minutes, int seconds, int microseconds, int total_days, PyTypeObject *type) {
260 Diff *self = (Diff *) (type->tp_alloc(type, 0));
261
262 if (self != NULL) {
263 self->years = years;
264 self->months = months;
265 self->days = days;
266 self->hours = hours;
267 self->minutes = minutes;
268 self->seconds = seconds;
269 self->microseconds = microseconds;
270 self->total_days = total_days;
271 }
272
273 return (PyObject *) self;
274 }
275
276 /*
277 * Class member / class attributes
278 */
279 static PyMemberDef Diff_members[] = {
280 {"years", T_INT, offsetof(Diff, years), 0, "years in diff"},
281 {"months", T_INT, offsetof(Diff, months), 0, "months in diff"},
282 {"days", T_INT, offsetof(Diff, days), 0, "days in diff"},
283 {"hours", T_INT, offsetof(Diff, hours), 0, "hours in diff"},
284 {"minutes", T_INT, offsetof(Diff, minutes), 0, "minutes in diff"},
285 {"seconds", T_INT, offsetof(Diff, seconds), 0, "seconds in diff"},
286 {"microseconds", T_INT, offsetof(Diff, microseconds), 0, "microseconds in diff"},
287 {"total_days", T_INT, offsetof(Diff, total_days), 0, "total days in diff"},
288 {NULL}
289 };
290
291 static PyTypeObject Diff_type = {
292 PyVarObject_HEAD_INIT(NULL, 0)
293 "PreciseDiff", /* tp_name */
294 sizeof(Diff), /* tp_basicsize */
295 0, /* tp_itemsize */
296 0, /* tp_dealloc */
297 0, /* tp_print */
298 0, /* tp_getattr */
299 0, /* tp_setattr */
300 0, /* tp_as_async */
301 (reprfunc)Diff_repr, /* tp_repr */
302 0, /* tp_as_number */
303 0, /* tp_as_sequence */
304 0, /* tp_as_mapping */
305 0, /* tp_hash */
306 0, /* tp_call */
307 (reprfunc)Diff_repr, /* tp_str */
308 0, /* tp_getattro */
309 0, /* tp_setattro */
310 0, /* tp_as_buffer */
311 Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /* tp_flags */
312 "Precise difference between two datetime objects", /* tp_doc */
313 };
314
315 #define new_diff(years, months, days, hours, minutes, seconds, microseconds, total_days) new_diff_ex(years, months, days, hours, minutes, seconds, microseconds, total_days, &Diff_type)
316
317
318 /* -------------------------- Functions --------------------------*/
319
is_leap(PyObject * self,PyObject * args)320 PyObject* is_leap(PyObject *self, PyObject *args) {
321 PyObject *leap;
322 int year;
323
324 if (!PyArg_ParseTuple(args, "i", &year)) {
325 PyErr_SetString(
326 PyExc_ValueError, "Invalid parameters"
327 );
328 return NULL;
329 }
330
331 leap = PyBool_FromLong(_is_leap(year));
332
333 return leap;
334 }
335
is_long_year(PyObject * self,PyObject * args)336 PyObject* is_long_year(PyObject *self, PyObject *args) {
337 PyObject *is_long;
338 int year;
339
340 if (!PyArg_ParseTuple(args, "i", &year)) {
341 PyErr_SetString(
342 PyExc_ValueError, "Invalid parameters"
343 );
344 return NULL;
345 }
346
347 is_long = PyBool_FromLong(_is_long_year(year));
348
349 return is_long;
350 }
351
week_day(PyObject * self,PyObject * args)352 PyObject* week_day(PyObject *self, PyObject *args) {
353 PyObject *wd;
354 int year;
355 int month;
356 int day;
357
358 if (!PyArg_ParseTuple(args, "iii", &year, &month, &day)) {
359 PyErr_SetString(
360 PyExc_ValueError, "Invalid parameters"
361 );
362 return NULL;
363 }
364
365 wd = PyLong_FromLong(_week_day(year, month, day));
366
367 return wd;
368 }
369
days_in_year(PyObject * self,PyObject * args)370 PyObject* days_in_year(PyObject *self, PyObject *args) {
371 PyObject *ndays;
372 int year;
373
374 if (!PyArg_ParseTuple(args, "i", &year)) {
375 PyErr_SetString(
376 PyExc_ValueError, "Invalid parameters"
377 );
378 return NULL;
379 }
380
381 ndays = PyLong_FromLong(_days_in_year(year));
382
383 return ndays;
384 }
385
timestamp(PyObject * self,PyObject * args)386 PyObject* timestamp(PyObject *self, PyObject *args) {
387 int64_t result;
388 PyObject* dt;
389
390 if (!PyArg_ParseTuple(args, "O", &dt)) {
391 PyErr_SetString(
392 PyExc_ValueError, "Invalid parameters"
393 );
394 return NULL;
395 }
396
397 int year = (double) PyDateTime_GET_YEAR(dt);
398 int month = PyDateTime_GET_MONTH(dt);
399 int day = PyDateTime_GET_DAY(dt);
400 int hour = PyDateTime_DATE_GET_HOUR(dt);
401 int minute = PyDateTime_DATE_GET_MINUTE(dt);
402 int second = PyDateTime_DATE_GET_SECOND(dt);
403
404 result = (year - 1970) * 365 + MONTHS_OFFSETS[0][month];
405 result += (int) floor((double) (year - 1968) / 4);
406 result -= (year - 1900) / 100;
407 result += (year - 1600) / 400;
408
409 if (_is_leap(year) && month < 3) {
410 result -= 1;
411 }
412
413 result += day - 1;
414 result *= 24;
415 result += hour;
416 result *= 60;
417 result += minute;
418 result *= 60;
419 result += second;
420
421 return PyLong_FromSsize_t(result);
422 }
423
local_time(PyObject * self,PyObject * args)424 PyObject* local_time(PyObject *self, PyObject *args) {
425 double unix_time;
426 int32_t utc_offset;
427 int32_t year;
428 int32_t microsecond;
429 int64_t seconds;
430 int32_t leap_year;
431 int64_t sec_per_100years;
432 int64_t sec_per_4years;
433 int32_t sec_per_year;
434 int32_t month;
435 int32_t day;
436 int32_t month_offset;
437 int32_t hour;
438 int32_t minute;
439 int32_t second;
440
441 if (!PyArg_ParseTuple(args, "dii", &unix_time, &utc_offset, µsecond)) {
442 PyErr_SetString(
443 PyExc_ValueError, "Invalid parameters"
444 );
445 return NULL;
446 }
447
448 year = EPOCH_YEAR;
449 seconds = (int64_t) floor(unix_time);
450
451 // Shift to a base year that is 400-year aligned.
452 if (seconds >= 0) {
453 seconds -= 10957L * SECS_PER_DAY;
454 year += 30; // == 2000;
455 } else {
456 seconds += (int64_t)(146097L - 10957L) * SECS_PER_DAY;
457 year -= 370; // == 1600;
458 }
459
460 seconds += utc_offset;
461
462 // Handle years in chunks of 400/100/4/1
463 year += 400 * (seconds / SECS_PER_400_YEARS);
464 seconds %= SECS_PER_400_YEARS;
465 if (seconds < 0) {
466 seconds += SECS_PER_400_YEARS;
467 year -= 400;
468 }
469
470 leap_year = 1; // 4-century aligned
471
472 sec_per_100years = SECS_PER_100_YEARS[leap_year];
473
474 while (seconds >= sec_per_100years) {
475 seconds -= sec_per_100years;
476 year += 100;
477 leap_year = 0; // 1-century, non 4-century aligned
478 sec_per_100years = SECS_PER_100_YEARS[leap_year];
479 }
480
481 sec_per_4years = SECS_PER_4_YEARS[leap_year];
482 while (seconds >= sec_per_4years) {
483 seconds -= sec_per_4years;
484 year += 4;
485 leap_year = 1; // 4-year, non century aligned
486 sec_per_4years = SECS_PER_4_YEARS[leap_year];
487 }
488
489 sec_per_year = SECS_PER_YEAR[leap_year];
490 while (seconds >= sec_per_year) {
491 seconds -= sec_per_year;
492 year += 1;
493 leap_year = 0; // non 4-year aligned
494 sec_per_year = SECS_PER_YEAR[leap_year];
495 }
496
497 // Handle months and days
498 month = TM_DECEMBER + 1;
499 day = seconds / SECS_PER_DAY + 1;
500 seconds %= SECS_PER_DAY;
501 while (month != TM_JANUARY + 1) {
502 month_offset = MONTHS_OFFSETS[leap_year][month];
503 if (day > month_offset) {
504 day -= month_offset;
505 break;
506 }
507
508 month -= 1;
509 }
510
511 // Handle hours, minutes and seconds
512 hour = seconds / SECS_PER_HOUR;
513 seconds %= SECS_PER_HOUR;
514 minute = seconds / SECS_PER_MIN;
515 second = seconds % SECS_PER_MIN;
516
517 return Py_BuildValue("NNNNNNN",
518 PyLong_FromLong(year),
519 PyLong_FromLong(month),
520 PyLong_FromLong(day),
521 PyLong_FromLong(hour),
522 PyLong_FromLong(minute),
523 PyLong_FromLong(second),
524 PyLong_FromLong(microsecond)
525 );
526 }
527
528
529 // Calculate a precise difference between two datetimes.
precise_diff(PyObject * self,PyObject * args)530 PyObject* precise_diff(PyObject *self, PyObject *args) {
531 PyObject* dt1;
532 PyObject* dt2;
533
534 if (!PyArg_ParseTuple(args, "OO", &dt1, &dt2)) {
535 PyErr_SetString(
536 PyExc_ValueError, "Invalid parameters"
537 );
538 return NULL;
539 }
540
541 int year_diff = 0;
542 int month_diff = 0;
543 int day_diff = 0;
544 int hour_diff = 0;
545 int minute_diff = 0;
546 int second_diff = 0;
547 int microsecond_diff = 0;
548 int sign = 1;
549 int year;
550 int month;
551 int leap;
552 int days_in_last_month;
553 int days_in_month;
554 int dt1_year = PyDateTime_GET_YEAR(dt1);
555 int dt2_year = PyDateTime_GET_YEAR(dt2);
556 int dt1_month = PyDateTime_GET_MONTH(dt1);
557 int dt2_month = PyDateTime_GET_MONTH(dt2);
558 int dt1_day = PyDateTime_GET_DAY(dt1);
559 int dt2_day = PyDateTime_GET_DAY(dt2);
560 int dt1_hour = 0;
561 int dt2_hour = 0;
562 int dt1_minute = 0;
563 int dt2_minute = 0;
564 int dt1_second = 0;
565 int dt2_second = 0;
566 int dt1_microsecond = 0;
567 int dt2_microsecond = 0;
568 int dt1_total_seconds = 0;
569 int dt2_total_seconds = 0;
570 int dt1_offset = 0;
571 int dt2_offset = 0;
572 int dt1_is_datetime = PyDateTime_Check(dt1);
573 int dt2_is_datetime = PyDateTime_Check(dt2);
574 char *tz1 = "";
575 char *tz2 = "";
576 int in_same_tz = 0;
577 int total_days = (
578 _day_number(dt2_year, dt2_month, dt2_day)
579 - _day_number(dt1_year, dt1_month, dt1_day)
580 );
581
582 // If both dates are datetimes, we check
583 // If we are in the same timezone
584 if (dt1_is_datetime && dt2_is_datetime) {
585 if (_has_tzinfo(dt1)) {
586 tz1 = _get_tz_name(dt1);
587 dt1_offset = _get_offset(dt1);
588 }
589
590 if (_has_tzinfo(dt2)) {
591 tz2 = _get_tz_name(dt2);
592 dt2_offset = _get_offset(dt2);
593 }
594
595 in_same_tz = tz1 == tz2 && strncmp(tz1, "", 1);
596 }
597
598 // If we have datetimes (and not only dates)
599 // we get the information we need
600 if (dt1_is_datetime) {
601 dt1_hour = PyDateTime_DATE_GET_HOUR(dt1);
602 dt1_minute = PyDateTime_DATE_GET_MINUTE(dt1);
603 dt1_second = PyDateTime_DATE_GET_SECOND(dt1);
604 dt1_microsecond = PyDateTime_DATE_GET_MICROSECOND(dt1);
605
606 if ((!in_same_tz && dt1_offset != 0) || total_days == 0) {
607 dt1_hour -= dt1_offset / SECS_PER_HOUR;
608 dt1_offset %= SECS_PER_HOUR;
609 dt1_minute -= dt1_offset / SECS_PER_MIN;
610 dt1_offset %= SECS_PER_MIN;
611 dt1_second -= dt1_offset;
612
613 if (dt1_second < 0) {
614 dt1_second += 60;
615 dt1_minute -= 1;
616 } else if (dt1_second > 60) {
617 dt1_second -= 60;
618 dt1_minute += 1;
619 }
620
621 if (dt1_minute < 0) {
622 dt1_minute += 60;
623 dt1_hour -= 1;
624 } else if (dt1_minute > 60) {
625 dt1_minute -= 60;
626 dt1_hour += 1;
627 }
628
629 if (dt1_hour < 0) {
630 dt1_hour += 24;
631 dt1_day -= 1;
632 } else if (dt1_hour > 24) {
633 dt1_hour -= 24;
634 dt1_day += 1;
635 }
636 }
637
638 dt1_total_seconds = (
639 dt1_hour * SECS_PER_HOUR
640 + dt1_minute * SECS_PER_MIN
641 + dt1_second
642 );
643 }
644
645 if (dt2_is_datetime) {
646 dt2_hour = PyDateTime_DATE_GET_HOUR(dt2);
647 dt2_minute = PyDateTime_DATE_GET_MINUTE(dt2);
648 dt2_second = PyDateTime_DATE_GET_SECOND(dt2);
649 dt2_microsecond = PyDateTime_DATE_GET_MICROSECOND(dt2);
650
651 if ((!in_same_tz && dt2_offset != 0) || total_days == 0) {
652 dt2_hour -= dt2_offset / SECS_PER_HOUR;
653 dt2_offset %= SECS_PER_HOUR;
654 dt2_minute -= dt2_offset / SECS_PER_MIN;
655 dt2_offset %= SECS_PER_MIN;
656 dt2_second -= dt2_offset;
657
658 if (dt2_second < 0) {
659 dt2_second += 60;
660 dt2_minute -= 1;
661 } else if (dt2_second > 60) {
662 dt2_second -= 60;
663 dt2_minute += 1;
664 }
665
666 if (dt2_minute < 0) {
667 dt2_minute += 60;
668 dt2_hour -= 1;
669 } else if (dt2_minute > 60) {
670 dt2_minute -= 60;
671 dt2_hour += 1;
672 }
673
674 if (dt2_hour < 0) {
675 dt2_hour += 24;
676 dt2_day -= 1;
677 } else if (dt2_hour > 24) {
678 dt2_hour -= 24;
679 dt2_day += 1;
680 }
681 }
682
683 dt2_total_seconds = (
684 dt2_hour * SECS_PER_HOUR
685 + dt2_minute * SECS_PER_MIN
686 + dt2_second
687 );
688 }
689
690 // Direct comparison between two datetimes does not work
691 // so we need to check by properties
692 int dt1_gt_dt2 = (
693 dt1_year > dt2_year
694 || (dt1_year == dt2_year && dt1_month > dt2_month)
695 || (
696 dt1_year == dt2_year
697 && dt1_month == dt2_month
698 && dt1_day > dt2_day
699 )
700 || (
701 dt1_year == dt2_year
702 && dt1_month == dt2_month
703 && dt1_day == dt2_day
704 && dt1_total_seconds > dt2_total_seconds
705 )
706 || (
707 dt1_year == dt2_year
708 && dt1_month == dt2_month
709 && dt1_day == dt2_day
710 && dt1_total_seconds == dt2_total_seconds
711 && dt1_microsecond > dt2_microsecond
712 )
713 );
714
715 if (dt1_gt_dt2) {
716 PyObject* temp;
717 temp = dt1;
718 dt1 = dt2;
719 dt2 = temp;
720 sign = -1;
721
722 // Retrieving properties
723 dt1_year = PyDateTime_GET_YEAR(dt1);
724 dt2_year = PyDateTime_GET_YEAR(dt2);
725 dt1_month = PyDateTime_GET_MONTH(dt1);
726 dt2_month = PyDateTime_GET_MONTH(dt2);
727 dt1_day = PyDateTime_GET_DAY(dt1);
728 dt2_day = PyDateTime_GET_DAY(dt2);
729
730 if (dt2_is_datetime) {
731 dt1_hour = PyDateTime_DATE_GET_HOUR(dt1);
732 dt1_minute = PyDateTime_DATE_GET_MINUTE(dt1);
733 dt1_second = PyDateTime_DATE_GET_SECOND(dt1);
734 dt1_microsecond = PyDateTime_DATE_GET_MICROSECOND(dt1);
735 }
736
737 if (dt1_is_datetime) {
738 dt2_hour = PyDateTime_DATE_GET_HOUR(dt2);
739 dt2_minute = PyDateTime_DATE_GET_MINUTE(dt2);
740 dt2_second = PyDateTime_DATE_GET_SECOND(dt2);
741 dt2_microsecond = PyDateTime_DATE_GET_MICROSECOND(dt2);
742 }
743
744 total_days = (
745 _day_number(dt2_year, dt2_month, dt2_day)
746 - _day_number(dt1_year, dt1_month, dt1_day)
747 );
748 }
749
750 year_diff = dt2_year - dt1_year;
751 month_diff = dt2_month - dt1_month;
752 day_diff = dt2_day - dt1_day;
753 hour_diff = dt2_hour - dt1_hour;
754 minute_diff = dt2_minute - dt1_minute;
755 second_diff = dt2_second - dt1_second;
756 microsecond_diff = dt2_microsecond - dt1_microsecond;
757
758 if (microsecond_diff < 0) {
759 microsecond_diff += 1e6;
760 second_diff -= 1;
761 }
762
763 if (second_diff < 0) {
764 second_diff += 60;
765 minute_diff -= 1;
766 }
767
768 if (minute_diff < 0) {
769 minute_diff += 60;
770 hour_diff -= 1;
771 }
772
773 if (hour_diff < 0) {
774 hour_diff += 24;
775 day_diff -= 1;
776 }
777
778 if (day_diff < 0) {
779 // If we have a difference in days,
780 // we have to check if they represent months
781 year = dt2_year;
782 month = dt2_month;
783
784 if (month == 1) {
785 month = 12;
786 year -= 1;
787 } else {
788 month -= 1;
789 }
790
791 leap = _is_leap(year);
792
793 days_in_last_month = DAYS_PER_MONTHS[leap][month];
794 days_in_month = DAYS_PER_MONTHS[_is_leap(dt2_year)][dt2_month];
795
796 if (day_diff < days_in_month - days_in_last_month) {
797 // We don't have a full month, we calculate days
798 if (days_in_last_month < dt1_day) {
799 day_diff += dt1_day;
800 } else {
801 day_diff += days_in_last_month;
802 }
803 } else if (day_diff == days_in_month - days_in_last_month) {
804 // We have exactly a full month
805 // We remove the days difference
806 // and add one to the months difference
807 day_diff = 0;
808 month_diff += 1;
809 } else {
810 // We have a full month
811 day_diff += days_in_last_month;
812 }
813
814 month_diff -= 1;
815 }
816
817 if (month_diff < 0) {
818 month_diff += 12;
819 year_diff -= 1;
820 }
821
822 return new_diff(
823 year_diff * sign,
824 month_diff * sign,
825 day_diff * sign,
826 hour_diff * sign,
827 minute_diff * sign,
828 second_diff * sign,
829 microsecond_diff * sign,
830 total_days * sign
831 );
832 }
833
834 /* ------------------------------------------------------------------------- */
835
836 static PyMethodDef helpers_methods[] = {
837 {
838 "is_leap",
839 (PyCFunction) is_leap,
840 METH_VARARGS,
841 PyDoc_STR("Checks if a year is a leap year.")
842 },
843 {
844 "is_long_year",
845 (PyCFunction) is_long_year,
846 METH_VARARGS,
847 PyDoc_STR("Checks if a year is a long year.")
848 },
849 {
850 "week_day",
851 (PyCFunction) week_day,
852 METH_VARARGS,
853 PyDoc_STR("Returns the weekday number.")
854 },
855 {
856 "days_in_year",
857 (PyCFunction) days_in_year,
858 METH_VARARGS,
859 PyDoc_STR("Returns the number of days in the given year.")
860 },
861 {
862 "timestamp",
863 (PyCFunction) timestamp,
864 METH_VARARGS,
865 PyDoc_STR("Returns the timestamp of the given datetime.")
866 },
867 {
868 "local_time",
869 (PyCFunction) local_time,
870 METH_VARARGS,
871 PyDoc_STR("Returns a UNIX time as a broken down time for a particular transition type.")
872 },
873 {
874 "precise_diff",
875 (PyCFunction) precise_diff,
876 METH_VARARGS,
877 PyDoc_STR("Calculate a precise difference between two datetimes.")
878 },
879 {NULL}
880 };
881
882 /* ------------------------------------------------------------------------- */
883
884 static struct PyModuleDef moduledef = {
885 PyModuleDef_HEAD_INIT,
886 "_helpers",
887 NULL,
888 -1,
889 helpers_methods,
890 NULL,
891 NULL,
892 NULL,
893 NULL,
894 };
895
896 PyMODINIT_FUNC
PyInit__helpers(void)897 PyInit__helpers(void)
898 {
899 PyObject *module;
900
901 PyDateTime_IMPORT;
902
903 module = PyModule_Create(&moduledef);
904
905 if (module == NULL)
906 return NULL;
907
908 // Diff declaration
909 Diff_type.tp_new = PyType_GenericNew;
910 Diff_type.tp_members = Diff_members;
911 Diff_type.tp_init = (initproc)Diff_init;
912
913 if (PyType_Ready(&Diff_type) < 0)
914 return NULL;
915
916 PyModule_AddObject(module, "PreciseDiff", (PyObject *)&Diff_type);
917
918 return module;
919 }
920