/* ------------------------------------------------------------------------- */ #include #include #include #include #include #include #include #include /* ------------------------------------------------------------------------- */ #define EPOCH_YEAR 1970 #define DAYS_PER_N_YEAR 365 #define DAYS_PER_L_YEAR 366 #define USECS_PER_SEC 1000000 #define SECS_PER_MIN 60 #define SECS_PER_HOUR (60 * SECS_PER_MIN) #define SECS_PER_DAY (SECS_PER_HOUR * 24) // 400-year chunks always have 146097 days (20871 weeks). #define DAYS_PER_400_YEARS 146097L #define SECS_PER_400_YEARS ((int64_t)DAYS_PER_400_YEARS * (int64_t)SECS_PER_DAY) // The number of seconds in an aligned 100-year chunk, for those that // do not begin with a leap year and those that do respectively. const int64_t SECS_PER_100_YEARS[2] = { (uint64_t)(76L * DAYS_PER_N_YEAR + 24L * DAYS_PER_L_YEAR) * SECS_PER_DAY, (uint64_t)(75L * DAYS_PER_N_YEAR + 25L * DAYS_PER_L_YEAR) * SECS_PER_DAY}; // The number of seconds in an aligned 4-year chunk, for those that // do not begin with a leap year and those that do respectively. const int32_t SECS_PER_4_YEARS[2] = { (4 * DAYS_PER_N_YEAR + 0 * DAYS_PER_L_YEAR) * SECS_PER_DAY, (3 * DAYS_PER_N_YEAR + 1 * DAYS_PER_L_YEAR) * SECS_PER_DAY}; // The number of seconds in non-leap and leap years respectively. const int32_t SECS_PER_YEAR[2] = { DAYS_PER_N_YEAR * SECS_PER_DAY, DAYS_PER_L_YEAR *SECS_PER_DAY}; #define MONTHS_PER_YEAR 12 // The month lengths in non-leap and leap years respectively. const int32_t DAYS_PER_MONTHS[2][13] = { {-1, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, {-1, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}}; // The day offsets of the beginning of each (1-based) month in non-leap // and leap years respectively. // For example, in a leap year there are 335 days before December. const int32_t MONTHS_OFFSETS[2][14] = { {-1, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365}, {-1, 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366}}; const int DAY_OF_WEEK_TABLE[12] = { 0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4}; #define TM_SUNDAY 0 #define TM_MONDAY 1 #define TM_TUESDAY 2 #define TM_WEDNESDAY 3 #define TM_THURSDAY 4 #define TM_FRIDAY 5 #define TM_SATURDAY 6 #define TM_JANUARY 0 #define TM_FEBRUARY 1 #define TM_MARCH 2 #define TM_APRIL 3 #define TM_MAY 4 #define TM_JUNE 5 #define TM_JULY 6 #define TM_AUGUST 7 #define TM_SEPTEMBER 8 #define TM_OCTOBER 9 #define TM_NOVEMBER 10 #define TM_DECEMBER 11 /* ------------------------------------------------------------------------- */ int _p(int y) { return y + y / 4 - y / 100 + y / 400; } int _is_leap(int year) { return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0); } int _is_long_year(int year) { return (_p(year) % 7 == 4) || (_p(year - 1) % 7 == 3); } int _week_day(int year, int month, int day) { int y; int w; y = year - (month < 3); w = (_p(y) + DAY_OF_WEEK_TABLE[month - 1] + day) % 7; if (!w) { w = 7; } return w; } int _days_in_year(int year) { if (_is_leap(year)) { return DAYS_PER_L_YEAR; } return DAYS_PER_N_YEAR; } int _day_number(int year, int month, int day) { month = (month + 9) % 12; year = year - month / 10; return ( 365 * year + year / 4 - year / 100 + year / 400 + (month * 306 + 5) / 10 + (day - 1)); } int _get_offset(PyObject *dt) { PyObject *tzinfo; PyObject *offset; tzinfo = ((PyDateTime_DateTime *)(dt))->tzinfo; if (tzinfo != Py_None) { offset = PyObject_CallMethod(tzinfo, "utcoffset", "O", dt); return PyDateTime_DELTA_GET_DAYS(offset) * SECS_PER_DAY + PyDateTime_DELTA_GET_SECONDS(offset); } return 0; } int _has_tzinfo(PyObject *dt) { return ((_PyDateTime_BaseTZInfo *)(dt))->hastzinfo; } char *_get_tz_name(PyObject *dt) { PyObject *tzinfo; char *tz = ""; tzinfo = ((PyDateTime_DateTime *)(dt))->tzinfo; if (tzinfo != Py_None) { if (PyObject_HasAttrString(tzinfo, "key")) { // zoneinfo timezone tz = (char *)PyUnicode_AsUTF8( PyObject_GetAttrString(tzinfo, "name")); } else if (PyObject_HasAttrString(tzinfo, "name")) { // Pendulum timezone tz = (char *)PyUnicode_AsUTF8( PyObject_GetAttrString(tzinfo, "name")); } else if (PyObject_HasAttrString(tzinfo, "zone")) { // pytz timezone tz = (char *)PyUnicode_AsUTF8( PyObject_GetAttrString(tzinfo, "zone")); } } return tz; } /* ------------------------ Custom Types ------------------------------- */ /* * class Diff(): */ typedef struct { PyObject_HEAD int years; int months; int days; int hours; int minutes; int seconds; int microseconds; int total_days; } Diff; /* * def __init__(self, years, months, days, hours, minutes, seconds, microseconds, total_days): * self.years = years * # ... */ static int Diff_init(Diff *self, PyObject *args, PyObject *kwargs) { int years; int months; int days; int hours; int minutes; int seconds; int microseconds; int total_days; if (!PyArg_ParseTuple(args, "iiiiiii", &years, &months, &days, &hours, &minutes, &seconds, µseconds, &total_days)) return -1; self->years = years; self->months = months; self->days = days; self->hours = hours; self->minutes = minutes; self->seconds = seconds; self->microseconds = microseconds; self->total_days = total_days; return 0; } /* * def __repr__(self): * return '{} years {} months {} days {} hours {} minutes {} seconds {} microseconds'.format( * self.years, self.months, self.days, self.minutes, self.hours, self.seconds, self.microseconds * ) */ static PyObject *Diff_repr(Diff *self) { return PyUnicode_FromFormat( "%d years %d months %d days %d hours %d minutes %d seconds %d microseconds", self->years, self->months, self->days, self->hours, self->minutes, self->seconds, self->microseconds); } /* * Instantiate new Diff_type object * Skip overhead of calling PyObject_New and PyObject_Init. * Directly allocate object. */ static PyObject *new_diff_ex(int years, int months, int days, int hours, int minutes, int seconds, int microseconds, int total_days, PyTypeObject *type) { Diff *self = (Diff *)(type->tp_alloc(type, 0)); if (self != NULL) { self->years = years; self->months = months; self->days = days; self->hours = hours; self->minutes = minutes; self->seconds = seconds; self->microseconds = microseconds; self->total_days = total_days; } return (PyObject *)self; } /* * Class member / class attributes */ static PyMemberDef Diff_members[] = { {"years", T_INT, offsetof(Diff, years), 0, "years in diff"}, {"months", T_INT, offsetof(Diff, months), 0, "months in diff"}, {"days", T_INT, offsetof(Diff, days), 0, "days in diff"}, {"hours", T_INT, offsetof(Diff, hours), 0, "hours in diff"}, {"minutes", T_INT, offsetof(Diff, minutes), 0, "minutes in diff"}, {"seconds", T_INT, offsetof(Diff, seconds), 0, "seconds in diff"}, {"microseconds", T_INT, offsetof(Diff, microseconds), 0, "microseconds in diff"}, {"total_days", T_INT, offsetof(Diff, total_days), 0, "total days in diff"}, {NULL}}; static PyTypeObject Diff_type = { PyVarObject_HEAD_INIT(NULL, 0) "PreciseDiff", /* tp_name */ sizeof(Diff), /* tp_basicsize */ 0, /* tp_itemsize */ 0, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_as_async */ (reprfunc)Diff_repr, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ (reprfunc)Diff_repr, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ "Precise difference between two datetime objects", /* tp_doc */ }; #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) /* -------------------------- Functions --------------------------*/ PyObject *is_leap(PyObject *self, PyObject *args) { PyObject *leap; int year; if (!PyArg_ParseTuple(args, "i", &year)) { PyErr_SetString( PyExc_ValueError, "Invalid parameters"); return NULL; } leap = PyBool_FromLong(_is_leap(year)); return leap; } PyObject *is_long_year(PyObject *self, PyObject *args) { PyObject *is_long; int year; if (!PyArg_ParseTuple(args, "i", &year)) { PyErr_SetString( PyExc_ValueError, "Invalid parameters"); return NULL; } is_long = PyBool_FromLong(_is_long_year(year)); return is_long; } PyObject *week_day(PyObject *self, PyObject *args) { PyObject *wd; int year; int month; int day; if (!PyArg_ParseTuple(args, "iii", &year, &month, &day)) { PyErr_SetString( PyExc_ValueError, "Invalid parameters"); return NULL; } wd = PyLong_FromLong(_week_day(year, month, day)); return wd; } PyObject *days_in_year(PyObject *self, PyObject *args) { PyObject *ndays; int year; if (!PyArg_ParseTuple(args, "i", &year)) { PyErr_SetString( PyExc_ValueError, "Invalid parameters"); return NULL; } ndays = PyLong_FromLong(_days_in_year(year)); return ndays; } PyObject *timestamp(PyObject *self, PyObject *args) { int64_t result; PyObject *dt; if (!PyArg_ParseTuple(args, "O", &dt)) { PyErr_SetString( PyExc_ValueError, "Invalid parameters"); return NULL; } int year = (double)PyDateTime_GET_YEAR(dt); int month = PyDateTime_GET_MONTH(dt); int day = PyDateTime_GET_DAY(dt); int hour = PyDateTime_DATE_GET_HOUR(dt); int minute = PyDateTime_DATE_GET_MINUTE(dt); int second = PyDateTime_DATE_GET_SECOND(dt); result = (year - 1970) * 365 + MONTHS_OFFSETS[0][month]; result += (int)floor((double)(year - 1968) / 4); result -= (year - 1900) / 100; result += (year - 1600) / 400; if (_is_leap(year) && month < 3) { result -= 1; } result += day - 1; result *= 24; result += hour; result *= 60; result += minute; result *= 60; result += second; return PyLong_FromSsize_t(result); } PyObject *local_time(PyObject *self, PyObject *args) { double unix_time; int32_t utc_offset; int32_t year; int32_t microsecond; int64_t seconds; int32_t leap_year; int64_t sec_per_100years; int64_t sec_per_4years; int32_t sec_per_year; int32_t month; int32_t day; int32_t month_offset; int32_t hour; int32_t minute; int32_t second; if (!PyArg_ParseTuple(args, "dii", &unix_time, &utc_offset, µsecond)) { PyErr_SetString( PyExc_ValueError, "Invalid parameters"); return NULL; } year = EPOCH_YEAR; seconds = (int64_t)floor(unix_time); // Shift to a base year that is 400-year aligned. if (seconds >= 0) { seconds -= 10957L * SECS_PER_DAY; year += 30; // == 2000; } else { seconds += (int64_t)(146097L - 10957L) * SECS_PER_DAY; year -= 370; // == 1600; } seconds += utc_offset; // Handle years in chunks of 400/100/4/1 year += 400 * (seconds / SECS_PER_400_YEARS); seconds %= SECS_PER_400_YEARS; if (seconds < 0) { seconds += SECS_PER_400_YEARS; year -= 400; } leap_year = 1; // 4-century aligned sec_per_100years = SECS_PER_100_YEARS[leap_year]; while (seconds >= sec_per_100years) { seconds -= sec_per_100years; year += 100; leap_year = 0; // 1-century, non 4-century aligned sec_per_100years = SECS_PER_100_YEARS[leap_year]; } sec_per_4years = SECS_PER_4_YEARS[leap_year]; while (seconds >= sec_per_4years) { seconds -= sec_per_4years; year += 4; leap_year = 1; // 4-year, non century aligned sec_per_4years = SECS_PER_4_YEARS[leap_year]; } sec_per_year = SECS_PER_YEAR[leap_year]; while (seconds >= sec_per_year) { seconds -= sec_per_year; year += 1; leap_year = 0; // non 4-year aligned sec_per_year = SECS_PER_YEAR[leap_year]; } // Handle months and days month = TM_DECEMBER + 1; day = seconds / SECS_PER_DAY + 1; seconds %= SECS_PER_DAY; while (month != TM_JANUARY + 1) { month_offset = MONTHS_OFFSETS[leap_year][month]; if (day > month_offset) { day -= month_offset; break; } month -= 1; } // Handle hours, minutes and seconds hour = seconds / SECS_PER_HOUR; seconds %= SECS_PER_HOUR; minute = seconds / SECS_PER_MIN; second = seconds % SECS_PER_MIN; return Py_BuildValue("NNNNNNN", PyLong_FromLong(year), PyLong_FromLong(month), PyLong_FromLong(day), PyLong_FromLong(hour), PyLong_FromLong(minute), PyLong_FromLong(second), PyLong_FromLong(microsecond)); } // Calculate a precise difference between two datetimes. PyObject *precise_diff(PyObject *self, PyObject *args) { PyObject *dt1; PyObject *dt2; if (!PyArg_ParseTuple(args, "OO", &dt1, &dt2)) { PyErr_SetString( PyExc_ValueError, "Invalid parameters"); return NULL; } int year_diff = 0; int month_diff = 0; int day_diff = 0; int hour_diff = 0; int minute_diff = 0; int second_diff = 0; int microsecond_diff = 0; int sign = 1; int year; int month; int leap; int days_in_last_month; int days_in_month; int dt1_year = PyDateTime_GET_YEAR(dt1); int dt2_year = PyDateTime_GET_YEAR(dt2); int dt1_month = PyDateTime_GET_MONTH(dt1); int dt2_month = PyDateTime_GET_MONTH(dt2); int dt1_day = PyDateTime_GET_DAY(dt1); int dt2_day = PyDateTime_GET_DAY(dt2); int dt1_hour = 0; int dt2_hour = 0; int dt1_minute = 0; int dt2_minute = 0; int dt1_second = 0; int dt2_second = 0; int dt1_microsecond = 0; int dt2_microsecond = 0; int dt1_total_seconds = 0; int dt2_total_seconds = 0; int dt1_offset = 0; int dt2_offset = 0; int dt1_is_datetime = PyDateTime_Check(dt1); int dt2_is_datetime = PyDateTime_Check(dt2); char *tz1 = ""; char *tz2 = ""; int in_same_tz = 0; int total_days = (_day_number(dt2_year, dt2_month, dt2_day) - _day_number(dt1_year, dt1_month, dt1_day)); // If both dates are datetimes, we check // If we are in the same timezone if (dt1_is_datetime && dt2_is_datetime) { if (_has_tzinfo(dt1)) { tz1 = _get_tz_name(dt1); dt1_offset = _get_offset(dt1); } if (_has_tzinfo(dt2)) { tz2 = _get_tz_name(dt2); dt2_offset = _get_offset(dt2); } in_same_tz = tz1 == tz2 && strncmp(tz1, "", 1); } // If we have datetimes (and not only dates) // we get the information we need if (dt1_is_datetime) { dt1_hour = PyDateTime_DATE_GET_HOUR(dt1); dt1_minute = PyDateTime_DATE_GET_MINUTE(dt1); dt1_second = PyDateTime_DATE_GET_SECOND(dt1); dt1_microsecond = PyDateTime_DATE_GET_MICROSECOND(dt1); if ((!in_same_tz && dt1_offset != 0) || total_days == 0) { dt1_hour -= dt1_offset / SECS_PER_HOUR; dt1_offset %= SECS_PER_HOUR; dt1_minute -= dt1_offset / SECS_PER_MIN; dt1_offset %= SECS_PER_MIN; dt1_second -= dt1_offset; if (dt1_second < 0) { dt1_second += 60; dt1_minute -= 1; } else if (dt1_second > 60) { dt1_second -= 60; dt1_minute += 1; } if (dt1_minute < 0) { dt1_minute += 60; dt1_hour -= 1; } else if (dt1_minute > 60) { dt1_minute -= 60; dt1_hour += 1; } if (dt1_hour < 0) { dt1_hour += 24; dt1_day -= 1; } else if (dt1_hour > 24) { dt1_hour -= 24; dt1_day += 1; } } dt1_total_seconds = (dt1_hour * SECS_PER_HOUR + dt1_minute * SECS_PER_MIN + dt1_second); } if (dt2_is_datetime) { dt2_hour = PyDateTime_DATE_GET_HOUR(dt2); dt2_minute = PyDateTime_DATE_GET_MINUTE(dt2); dt2_second = PyDateTime_DATE_GET_SECOND(dt2); dt2_microsecond = PyDateTime_DATE_GET_MICROSECOND(dt2); if ((!in_same_tz && dt2_offset != 0) || total_days == 0) { dt2_hour -= dt2_offset / SECS_PER_HOUR; dt2_offset %= SECS_PER_HOUR; dt2_minute -= dt2_offset / SECS_PER_MIN; dt2_offset %= SECS_PER_MIN; dt2_second -= dt2_offset; if (dt2_second < 0) { dt2_second += 60; dt2_minute -= 1; } else if (dt2_second > 60) { dt2_second -= 60; dt2_minute += 1; } if (dt2_minute < 0) { dt2_minute += 60; dt2_hour -= 1; } else if (dt2_minute > 60) { dt2_minute -= 60; dt2_hour += 1; } if (dt2_hour < 0) { dt2_hour += 24; dt2_day -= 1; } else if (dt2_hour > 24) { dt2_hour -= 24; dt2_day += 1; } } dt2_total_seconds = (dt2_hour * SECS_PER_HOUR + dt2_minute * SECS_PER_MIN + dt2_second); } // Direct comparison between two datetimes does not work // so we need to check by properties int dt1_gt_dt2 = (dt1_year > dt2_year || (dt1_year == dt2_year && dt1_month > dt2_month) || (dt1_year == dt2_year && dt1_month == dt2_month && dt1_day > dt2_day) || (dt1_year == dt2_year && dt1_month == dt2_month && dt1_day == dt2_day && dt1_total_seconds > dt2_total_seconds) || (dt1_year == dt2_year && dt1_month == dt2_month && dt1_day == dt2_day && dt1_total_seconds == dt2_total_seconds && dt1_microsecond > dt2_microsecond)); if (dt1_gt_dt2) { PyObject *temp; temp = dt1; dt1 = dt2; dt2 = temp; sign = -1; // Retrieving properties dt1_year = PyDateTime_GET_YEAR(dt1); dt2_year = PyDateTime_GET_YEAR(dt2); dt1_month = PyDateTime_GET_MONTH(dt1); dt2_month = PyDateTime_GET_MONTH(dt2); dt1_day = PyDateTime_GET_DAY(dt1); dt2_day = PyDateTime_GET_DAY(dt2); if (dt2_is_datetime) { dt1_hour = PyDateTime_DATE_GET_HOUR(dt1); dt1_minute = PyDateTime_DATE_GET_MINUTE(dt1); dt1_second = PyDateTime_DATE_GET_SECOND(dt1); dt1_microsecond = PyDateTime_DATE_GET_MICROSECOND(dt1); } if (dt1_is_datetime) { dt2_hour = PyDateTime_DATE_GET_HOUR(dt2); dt2_minute = PyDateTime_DATE_GET_MINUTE(dt2); dt2_second = PyDateTime_DATE_GET_SECOND(dt2); dt2_microsecond = PyDateTime_DATE_GET_MICROSECOND(dt2); } total_days = (_day_number(dt2_year, dt2_month, dt2_day) - _day_number(dt1_year, dt1_month, dt1_day)); } year_diff = dt2_year - dt1_year; month_diff = dt2_month - dt1_month; day_diff = dt2_day - dt1_day; hour_diff = dt2_hour - dt1_hour; minute_diff = dt2_minute - dt1_minute; second_diff = dt2_second - dt1_second; microsecond_diff = dt2_microsecond - dt1_microsecond; if (microsecond_diff < 0) { microsecond_diff += 1e6; second_diff -= 1; } if (second_diff < 0) { second_diff += 60; minute_diff -= 1; } if (minute_diff < 0) { minute_diff += 60; hour_diff -= 1; } if (hour_diff < 0) { hour_diff += 24; day_diff -= 1; } if (day_diff < 0) { // If we have a difference in days, // we have to check if they represent months year = dt2_year; month = dt2_month; if (month == 1) { month = 12; year -= 1; } else { month -= 1; } leap = _is_leap(year); days_in_last_month = DAYS_PER_MONTHS[leap][month]; days_in_month = DAYS_PER_MONTHS[_is_leap(dt2_year)][dt2_month]; if (day_diff < days_in_month - days_in_last_month) { // We don't have a full month, we calculate days if (days_in_last_month < dt1_day) { day_diff += dt1_day; } else { day_diff += days_in_last_month; } } else if (day_diff == days_in_month - days_in_last_month) { // We have exactly a full month // We remove the days difference // and add one to the months difference day_diff = 0; month_diff += 1; } else { // We have a full month day_diff += days_in_last_month; } month_diff -= 1; } if (month_diff < 0) { month_diff += 12; year_diff -= 1; } return new_diff( year_diff * sign, month_diff * sign, day_diff * sign, hour_diff * sign, minute_diff * sign, second_diff * sign, microsecond_diff * sign, total_days * sign); } /* ------------------------------------------------------------------------- */ static PyMethodDef helpers_methods[] = { {"is_leap", (PyCFunction)is_leap, METH_VARARGS, PyDoc_STR("Checks if a year is a leap year.")}, {"is_long_year", (PyCFunction)is_long_year, METH_VARARGS, PyDoc_STR("Checks if a year is a long year.")}, {"week_day", (PyCFunction)week_day, METH_VARARGS, PyDoc_STR("Returns the weekday number.")}, {"days_in_year", (PyCFunction)days_in_year, METH_VARARGS, PyDoc_STR("Returns the number of days in the given year.")}, {"timestamp", (PyCFunction)timestamp, METH_VARARGS, PyDoc_STR("Returns the timestamp of the given datetime.")}, {"local_time", (PyCFunction)local_time, METH_VARARGS, PyDoc_STR("Returns a UNIX time as a broken down time for a particular transition type.")}, {"precise_diff", (PyCFunction)precise_diff, METH_VARARGS, PyDoc_STR("Calculate a precise difference between two datetimes.")}, {NULL}}; /* ------------------------------------------------------------------------- */ static struct PyModuleDef moduledef = { PyModuleDef_HEAD_INIT, "_helpers", NULL, -1, helpers_methods, NULL, NULL, NULL, NULL, }; PyMODINIT_FUNC PyInit__helpers(void) { PyObject *module; PyDateTime_IMPORT; module = PyModule_Create(&moduledef); if (module == NULL) return NULL; // Diff declaration Diff_type.tp_new = PyType_GenericNew; Diff_type.tp_members = Diff_members; Diff_type.tp_init = (initproc)Diff_init; if (PyType_Ready(&Diff_type) < 0) return NULL; PyModule_AddObject(module, "PreciseDiff", (PyObject *)&Diff_type); return module; }