mirror of
https://github.com/aykhans/AzSuicideDataVisualization.git
synced 2025-07-03 06:31:28 +00:00
first commit
This commit is contained in:
@ -0,0 +1,774 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2016, PyData Development Team
|
||||
All rights reserved.
|
||||
|
||||
Distributed under the terms of the BSD Simplified License.
|
||||
|
||||
The full license is in the LICENSE file, distributed with this software.
|
||||
|
||||
Copyright (c) 2005-2011, NumPy Developers
|
||||
All rights reserved.
|
||||
|
||||
This file is derived from NumPy 1.7. See NUMPY_LICENSE.txt
|
||||
|
||||
*/
|
||||
|
||||
#define NO_IMPORT
|
||||
|
||||
#ifndef NPY_NO_DEPRECATED_API
|
||||
#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
|
||||
#endif // NPY_NO_DEPRECATED_API
|
||||
|
||||
#include <Python.h>
|
||||
|
||||
#include <numpy/arrayobject.h>
|
||||
#include <numpy/arrayscalars.h>
|
||||
#include <numpy/ndarraytypes.h>
|
||||
#include "np_datetime.h"
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
#define PyInt_AsLong PyLong_AsLong
|
||||
#endif // PyInt_AsLong
|
||||
|
||||
const npy_datetimestruct _NS_MIN_DTS = {
|
||||
1677, 9, 21, 0, 12, 43, 145224, 193000, 0};
|
||||
const npy_datetimestruct _NS_MAX_DTS = {
|
||||
2262, 4, 11, 23, 47, 16, 854775, 807000, 0};
|
||||
|
||||
|
||||
const int days_per_month_table[2][12] = {
|
||||
{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
|
||||
{31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}};
|
||||
|
||||
/*
|
||||
* Returns 1 if the given year is a leap year, 0 otherwise.
|
||||
*/
|
||||
int is_leapyear(npy_int64 year) {
|
||||
return (year & 0x3) == 0 && /* year % 4 == 0 */
|
||||
((year % 100) != 0 || (year % 400) == 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Adjusts a datetimestruct based on a minutes offset. Assumes
|
||||
* the current values are valid.g
|
||||
*/
|
||||
void add_minutes_to_datetimestruct(npy_datetimestruct *dts, int minutes) {
|
||||
int isleap;
|
||||
|
||||
/* MINUTES */
|
||||
dts->min += minutes;
|
||||
while (dts->min < 0) {
|
||||
dts->min += 60;
|
||||
dts->hour--;
|
||||
}
|
||||
while (dts->min >= 60) {
|
||||
dts->min -= 60;
|
||||
dts->hour++;
|
||||
}
|
||||
|
||||
/* HOURS */
|
||||
while (dts->hour < 0) {
|
||||
dts->hour += 24;
|
||||
dts->day--;
|
||||
}
|
||||
while (dts->hour >= 24) {
|
||||
dts->hour -= 24;
|
||||
dts->day++;
|
||||
}
|
||||
|
||||
/* DAYS */
|
||||
if (dts->day < 1) {
|
||||
dts->month--;
|
||||
if (dts->month < 1) {
|
||||
dts->year--;
|
||||
dts->month = 12;
|
||||
}
|
||||
isleap = is_leapyear(dts->year);
|
||||
dts->day += days_per_month_table[isleap][dts->month - 1];
|
||||
} else if (dts->day > 28) {
|
||||
isleap = is_leapyear(dts->year);
|
||||
if (dts->day > days_per_month_table[isleap][dts->month - 1]) {
|
||||
dts->day -= days_per_month_table[isleap][dts->month - 1];
|
||||
dts->month++;
|
||||
if (dts->month > 12) {
|
||||
dts->year++;
|
||||
dts->month = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Calculates the days offset from the 1970 epoch.
|
||||
*/
|
||||
npy_int64 get_datetimestruct_days(const npy_datetimestruct *dts) {
|
||||
int i, month;
|
||||
npy_int64 year, days = 0;
|
||||
const int *month_lengths;
|
||||
|
||||
year = dts->year - 1970;
|
||||
days = year * 365;
|
||||
|
||||
/* Adjust for leap years */
|
||||
if (days >= 0) {
|
||||
/*
|
||||
* 1968 is the closest leap year before 1970.
|
||||
* Exclude the current year, so add 1.
|
||||
*/
|
||||
year += 1;
|
||||
/* Add one day for each 4 years */
|
||||
days += year / 4;
|
||||
/* 1900 is the closest previous year divisible by 100 */
|
||||
year += 68;
|
||||
/* Subtract one day for each 100 years */
|
||||
days -= year / 100;
|
||||
/* 1600 is the closest previous year divisible by 400 */
|
||||
year += 300;
|
||||
/* Add one day for each 400 years */
|
||||
days += year / 400;
|
||||
} else {
|
||||
/*
|
||||
* 1972 is the closest later year after 1970.
|
||||
* Include the current year, so subtract 2.
|
||||
*/
|
||||
year -= 2;
|
||||
/* Subtract one day for each 4 years */
|
||||
days += year / 4;
|
||||
/* 2000 is the closest later year divisible by 100 */
|
||||
year -= 28;
|
||||
/* Add one day for each 100 years */
|
||||
days -= year / 100;
|
||||
/* 2000 is also the closest later year divisible by 400 */
|
||||
/* Subtract one day for each 400 years */
|
||||
days += year / 400;
|
||||
}
|
||||
|
||||
month_lengths = days_per_month_table[is_leapyear(dts->year)];
|
||||
month = dts->month - 1;
|
||||
|
||||
/* Add the months */
|
||||
for (i = 0; i < month; ++i) {
|
||||
days += month_lengths[i];
|
||||
}
|
||||
|
||||
/* Add the days */
|
||||
days += dts->day - 1;
|
||||
|
||||
return days;
|
||||
}
|
||||
|
||||
/*
|
||||
* Modifies '*days_' to be the day offset within the year,
|
||||
* and returns the year.
|
||||
*/
|
||||
static npy_int64 days_to_yearsdays(npy_int64 *days_) {
|
||||
const npy_int64 days_per_400years = (400 * 365 + 100 - 4 + 1);
|
||||
/* Adjust so it's relative to the year 2000 (divisible by 400) */
|
||||
npy_int64 days = (*days_) - (365 * 30 + 7);
|
||||
npy_int64 year;
|
||||
|
||||
/* Break down the 400 year cycle to get the year and day within the year */
|
||||
if (days >= 0) {
|
||||
year = 400 * (days / days_per_400years);
|
||||
days = days % days_per_400years;
|
||||
} else {
|
||||
year = 400 * ((days - (days_per_400years - 1)) / days_per_400years);
|
||||
days = days % days_per_400years;
|
||||
if (days < 0) {
|
||||
days += days_per_400years;
|
||||
}
|
||||
}
|
||||
|
||||
/* Work out the year/day within the 400 year cycle */
|
||||
if (days >= 366) {
|
||||
year += 100 * ((days - 1) / (100 * 365 + 25 - 1));
|
||||
days = (days - 1) % (100 * 365 + 25 - 1);
|
||||
if (days >= 365) {
|
||||
year += 4 * ((days + 1) / (4 * 365 + 1));
|
||||
days = (days + 1) % (4 * 365 + 1);
|
||||
if (days >= 366) {
|
||||
year += (days - 1) / 365;
|
||||
days = (days - 1) % 365;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*days_ = days;
|
||||
return year + 2000;
|
||||
}
|
||||
|
||||
/*
|
||||
* Adjusts a datetimestruct based on a seconds offset. Assumes
|
||||
* the current values are valid.
|
||||
*/
|
||||
NPY_NO_EXPORT void add_seconds_to_datetimestruct(npy_datetimestruct *dts,
|
||||
int seconds) {
|
||||
int minutes;
|
||||
|
||||
dts->sec += seconds;
|
||||
if (dts->sec < 0) {
|
||||
minutes = dts->sec / 60;
|
||||
dts->sec = dts->sec % 60;
|
||||
if (dts->sec < 0) {
|
||||
--minutes;
|
||||
dts->sec += 60;
|
||||
}
|
||||
add_minutes_to_datetimestruct(dts, minutes);
|
||||
} else if (dts->sec >= 60) {
|
||||
minutes = dts->sec / 60;
|
||||
dts->sec = dts->sec % 60;
|
||||
add_minutes_to_datetimestruct(dts, minutes);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Fills in the year, month, day in 'dts' based on the days
|
||||
* offset from 1970.
|
||||
*/
|
||||
static void set_datetimestruct_days(npy_int64 days, npy_datetimestruct *dts) {
|
||||
const int *month_lengths;
|
||||
int i;
|
||||
|
||||
dts->year = days_to_yearsdays(&days);
|
||||
month_lengths = days_per_month_table[is_leapyear(dts->year)];
|
||||
|
||||
for (i = 0; i < 12; ++i) {
|
||||
if (days < month_lengths[i]) {
|
||||
dts->month = i + 1;
|
||||
dts->day = days + 1;
|
||||
return;
|
||||
} else {
|
||||
days -= month_lengths[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Compares two npy_datetimestruct objects chronologically
|
||||
*/
|
||||
int cmp_npy_datetimestruct(const npy_datetimestruct *a,
|
||||
const npy_datetimestruct *b) {
|
||||
if (a->year > b->year) {
|
||||
return 1;
|
||||
} else if (a->year < b->year) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (a->month > b->month) {
|
||||
return 1;
|
||||
} else if (a->month < b->month) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (a->day > b->day) {
|
||||
return 1;
|
||||
} else if (a->day < b->day) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (a->hour > b->hour) {
|
||||
return 1;
|
||||
} else if (a->hour < b->hour) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (a->min > b->min) {
|
||||
return 1;
|
||||
} else if (a->min < b->min) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (a->sec > b->sec) {
|
||||
return 1;
|
||||
} else if (a->sec < b->sec) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (a->us > b->us) {
|
||||
return 1;
|
||||
} else if (a->us < b->us) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (a->ps > b->ps) {
|
||||
return 1;
|
||||
} else if (a->ps < b->ps) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (a->as > b->as) {
|
||||
return 1;
|
||||
} else if (a->as < b->as) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
* Converts a Python datetime.datetime or datetime.date
|
||||
* object into a NumPy npy_datetimestruct. Uses tzinfo (if present)
|
||||
* to convert to UTC time.
|
||||
*
|
||||
* The following implementation just asks for attributes, and thus
|
||||
* supports datetime duck typing. The tzinfo time zone conversion
|
||||
* requires this style of access as well.
|
||||
*
|
||||
* Returns -1 on error, 0 on success, and 1 (with no error set)
|
||||
* if obj doesn't have the needed date or datetime attributes.
|
||||
*/
|
||||
int convert_pydatetime_to_datetimestruct(PyObject *dtobj,
|
||||
npy_datetimestruct *out) {
|
||||
// Assumes that obj is a valid datetime object
|
||||
PyObject *tmp;
|
||||
PyObject *obj = (PyObject*)dtobj;
|
||||
|
||||
/* Initialize the output to all zeros */
|
||||
memset(out, 0, sizeof(npy_datetimestruct));
|
||||
out->month = 1;
|
||||
out->day = 1;
|
||||
|
||||
out->year = PyInt_AsLong(PyObject_GetAttrString(obj, "year"));
|
||||
out->month = PyInt_AsLong(PyObject_GetAttrString(obj, "month"));
|
||||
out->day = PyInt_AsLong(PyObject_GetAttrString(obj, "day"));
|
||||
|
||||
// TODO(anyone): If we can get PyDateTime_IMPORT to work, we could use
|
||||
// PyDateTime_Check here, and less verbose attribute lookups.
|
||||
|
||||
/* Check for time attributes (if not there, return success as a date) */
|
||||
if (!PyObject_HasAttrString(obj, "hour") ||
|
||||
!PyObject_HasAttrString(obj, "minute") ||
|
||||
!PyObject_HasAttrString(obj, "second") ||
|
||||
!PyObject_HasAttrString(obj, "microsecond")) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
out->hour = PyInt_AsLong(PyObject_GetAttrString(obj, "hour"));
|
||||
out->min = PyInt_AsLong(PyObject_GetAttrString(obj, "minute"));
|
||||
out->sec = PyInt_AsLong(PyObject_GetAttrString(obj, "second"));
|
||||
out->us = PyInt_AsLong(PyObject_GetAttrString(obj, "microsecond"));
|
||||
|
||||
/* Apply the time zone offset if datetime obj is tz-aware */
|
||||
if (PyObject_HasAttrString((PyObject*)obj, "tzinfo")) {
|
||||
tmp = PyObject_GetAttrString(obj, "tzinfo");
|
||||
if (tmp == NULL) {
|
||||
return -1;
|
||||
}
|
||||
if (tmp == Py_None) {
|
||||
Py_DECREF(tmp);
|
||||
} else {
|
||||
PyObject *offset;
|
||||
PyObject *tmp_int;
|
||||
int seconds_offset, minutes_offset;
|
||||
|
||||
/* The utcoffset function should return a timedelta */
|
||||
offset = PyObject_CallMethod(tmp, "utcoffset", "O", obj);
|
||||
if (offset == NULL) {
|
||||
Py_DECREF(tmp);
|
||||
return -1;
|
||||
}
|
||||
Py_DECREF(tmp);
|
||||
|
||||
/*
|
||||
* The timedelta should have a function "total_seconds"
|
||||
* which contains the value we want.
|
||||
*/
|
||||
tmp = PyObject_CallMethod(offset, "total_seconds", "");
|
||||
if (tmp == NULL) {
|
||||
return -1;
|
||||
}
|
||||
tmp_int = PyNumber_Long(tmp);
|
||||
if (tmp_int == NULL) {
|
||||
Py_DECREF(tmp);
|
||||
return -1;
|
||||
}
|
||||
seconds_offset = PyInt_AsLong(tmp_int);
|
||||
if (seconds_offset == -1 && PyErr_Occurred()) {
|
||||
Py_DECREF(tmp_int);
|
||||
Py_DECREF(tmp);
|
||||
return -1;
|
||||
}
|
||||
Py_DECREF(tmp_int);
|
||||
Py_DECREF(tmp);
|
||||
|
||||
/* Convert to a minutes offset and apply it */
|
||||
minutes_offset = seconds_offset / 60;
|
||||
|
||||
add_minutes_to_datetimestruct(out, -minutes_offset);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Converts a datetime from a datetimestruct to a datetime based
|
||||
* on a metadata unit. The date is assumed to be valid.
|
||||
*/
|
||||
npy_datetime npy_datetimestruct_to_datetime(NPY_DATETIMEUNIT base,
|
||||
const npy_datetimestruct *dts) {
|
||||
npy_datetime ret;
|
||||
|
||||
if (base == NPY_FR_Y) {
|
||||
/* Truncate to the year */
|
||||
ret = dts->year - 1970;
|
||||
} else if (base == NPY_FR_M) {
|
||||
/* Truncate to the month */
|
||||
ret = 12 * (dts->year - 1970) + (dts->month - 1);
|
||||
} else {
|
||||
/* Otherwise calculate the number of days to start */
|
||||
npy_int64 days = get_datetimestruct_days(dts);
|
||||
|
||||
switch (base) {
|
||||
case NPY_FR_W:
|
||||
/* Truncate to weeks */
|
||||
if (days >= 0) {
|
||||
ret = days / 7;
|
||||
} else {
|
||||
ret = (days - 6) / 7;
|
||||
}
|
||||
break;
|
||||
case NPY_FR_D:
|
||||
ret = days;
|
||||
break;
|
||||
case NPY_FR_h:
|
||||
ret = days * 24 + dts->hour;
|
||||
break;
|
||||
case NPY_FR_m:
|
||||
ret = (days * 24 + dts->hour) * 60 + dts->min;
|
||||
break;
|
||||
case NPY_FR_s:
|
||||
ret = ((days * 24 + dts->hour) * 60 + dts->min) * 60 + dts->sec;
|
||||
break;
|
||||
case NPY_FR_ms:
|
||||
ret = (((days * 24 + dts->hour) * 60 + dts->min) * 60 +
|
||||
dts->sec) *
|
||||
1000 +
|
||||
dts->us / 1000;
|
||||
break;
|
||||
case NPY_FR_us:
|
||||
ret = (((days * 24 + dts->hour) * 60 + dts->min) * 60 +
|
||||
dts->sec) *
|
||||
1000000 +
|
||||
dts->us;
|
||||
break;
|
||||
case NPY_FR_ns:
|
||||
ret = ((((days * 24 + dts->hour) * 60 + dts->min) * 60 +
|
||||
dts->sec) *
|
||||
1000000 +
|
||||
dts->us) *
|
||||
1000 +
|
||||
dts->ps / 1000;
|
||||
break;
|
||||
case NPY_FR_ps:
|
||||
ret = ((((days * 24 + dts->hour) * 60 + dts->min) * 60 +
|
||||
dts->sec) *
|
||||
1000000 +
|
||||
dts->us) *
|
||||
1000000 +
|
||||
dts->ps;
|
||||
break;
|
||||
case NPY_FR_fs:
|
||||
/* only 2.6 hours */
|
||||
ret = (((((days * 24 + dts->hour) * 60 + dts->min) * 60 +
|
||||
dts->sec) *
|
||||
1000000 +
|
||||
dts->us) *
|
||||
1000000 +
|
||||
dts->ps) *
|
||||
1000 +
|
||||
dts->as / 1000;
|
||||
break;
|
||||
case NPY_FR_as:
|
||||
/* only 9.2 secs */
|
||||
ret = (((((days * 24 + dts->hour) * 60 + dts->min) * 60 +
|
||||
dts->sec) *
|
||||
1000000 +
|
||||
dts->us) *
|
||||
1000000 +
|
||||
dts->ps) *
|
||||
1000000 +
|
||||
dts->as;
|
||||
break;
|
||||
default:
|
||||
/* Something got corrupted */
|
||||
PyErr_SetString(
|
||||
PyExc_ValueError,
|
||||
"NumPy datetime metadata with corrupt unit value");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Port numpy#13188 https://github.com/numpy/numpy/pull/13188/
|
||||
*
|
||||
* Computes the python `ret, d = divmod(d, unit)`.
|
||||
*
|
||||
* Note that GCC is smart enough at -O2 to eliminate the `if(*d < 0)` branch
|
||||
* for subsequent calls to this command - it is able to deduce that `*d >= 0`.
|
||||
*/
|
||||
npy_int64 extract_unit(npy_datetime *d, npy_datetime unit) {
|
||||
assert(unit > 0);
|
||||
npy_int64 div = *d / unit;
|
||||
npy_int64 mod = *d % unit;
|
||||
if (mod < 0) {
|
||||
mod += unit;
|
||||
div -= 1;
|
||||
}
|
||||
assert(mod >= 0);
|
||||
*d = mod;
|
||||
return div;
|
||||
}
|
||||
|
||||
/*
|
||||
* Converts a datetime based on the given metadata into a datetimestruct
|
||||
*/
|
||||
void pandas_datetime_to_datetimestruct(npy_datetime dt,
|
||||
NPY_DATETIMEUNIT base,
|
||||
npy_datetimestruct *out) {
|
||||
npy_int64 perday;
|
||||
|
||||
/* Initialize the output to all zeros */
|
||||
memset(out, 0, sizeof(npy_datetimestruct));
|
||||
out->year = 1970;
|
||||
out->month = 1;
|
||||
out->day = 1;
|
||||
|
||||
/*
|
||||
* Note that care must be taken with the / and % operators
|
||||
* for negative values.
|
||||
*/
|
||||
switch (base) {
|
||||
case NPY_FR_Y:
|
||||
out->year = 1970 + dt;
|
||||
break;
|
||||
|
||||
case NPY_FR_M:
|
||||
out->year = 1970 + extract_unit(&dt, 12);
|
||||
out->month = dt + 1;
|
||||
break;
|
||||
|
||||
case NPY_FR_W:
|
||||
/* A week is 7 days */
|
||||
set_datetimestruct_days(dt * 7, out);
|
||||
break;
|
||||
|
||||
case NPY_FR_D:
|
||||
set_datetimestruct_days(dt, out);
|
||||
break;
|
||||
|
||||
case NPY_FR_h:
|
||||
perday = 24LL;
|
||||
|
||||
set_datetimestruct_days(extract_unit(&dt, perday), out);
|
||||
out->hour = dt;
|
||||
break;
|
||||
|
||||
case NPY_FR_m:
|
||||
perday = 24LL * 60;
|
||||
|
||||
set_datetimestruct_days(extract_unit(&dt, perday), out);
|
||||
out->hour = (int)extract_unit(&dt, 60);
|
||||
out->min = (int)dt;
|
||||
break;
|
||||
|
||||
case NPY_FR_s:
|
||||
perday = 24LL * 60 * 60;
|
||||
|
||||
set_datetimestruct_days(extract_unit(&dt, perday), out);
|
||||
out->hour = (int)extract_unit(&dt, 60 * 60);
|
||||
out->min = (int)extract_unit(&dt, 60);
|
||||
out->sec = (int)dt;
|
||||
break;
|
||||
|
||||
case NPY_FR_ms:
|
||||
perday = 24LL * 60 * 60 * 1000;
|
||||
|
||||
set_datetimestruct_days(extract_unit(&dt, perday), out);
|
||||
out->hour = (int)extract_unit(&dt, 1000LL * 60 * 60);
|
||||
out->min = (int)extract_unit(&dt, 1000LL * 60);
|
||||
out->sec = (int)extract_unit(&dt, 1000LL);
|
||||
out->us = (int)(dt * 1000);
|
||||
break;
|
||||
|
||||
case NPY_FR_us:
|
||||
perday = 24LL * 60LL * 60LL * 1000LL * 1000LL;
|
||||
|
||||
set_datetimestruct_days(extract_unit(&dt, perday), out);
|
||||
out->hour = (int)extract_unit(&dt, 1000LL * 1000 * 60 * 60);
|
||||
out->min = (int)extract_unit(&dt, 1000LL * 1000 * 60);
|
||||
out->sec = (int)extract_unit(&dt, 1000LL * 1000);
|
||||
out->us = (int)dt;
|
||||
break;
|
||||
|
||||
case NPY_FR_ns:
|
||||
perday = 24LL * 60LL * 60LL * 1000LL * 1000LL * 1000LL;
|
||||
|
||||
set_datetimestruct_days(extract_unit(&dt, perday), out);
|
||||
out->hour = (int)extract_unit(&dt, 1000LL * 1000 * 1000 * 60 * 60);
|
||||
out->min = (int)extract_unit(&dt, 1000LL * 1000 * 1000 * 60);
|
||||
out->sec = (int)extract_unit(&dt, 1000LL * 1000 * 1000);
|
||||
out->us = (int)extract_unit(&dt, 1000LL);
|
||||
out->ps = (int)(dt * 1000);
|
||||
break;
|
||||
|
||||
case NPY_FR_ps:
|
||||
perday = 24LL * 60 * 60 * 1000 * 1000 * 1000 * 1000;
|
||||
|
||||
set_datetimestruct_days(extract_unit(&dt, perday), out);
|
||||
out->hour = (int)extract_unit(&dt, 1000LL * 1000 * 1000 * 60 * 60);
|
||||
out->min = (int)extract_unit(&dt, 1000LL * 1000 * 1000 * 60);
|
||||
out->sec = (int)extract_unit(&dt, 1000LL * 1000 * 1000);
|
||||
out->us = (int)extract_unit(&dt, 1000LL);
|
||||
out->ps = (int)(dt * 1000);
|
||||
break;
|
||||
|
||||
case NPY_FR_fs:
|
||||
/* entire range is only +- 2.6 hours */
|
||||
out->hour = (int)extract_unit(&dt, 1000LL * 1000 * 1000 * 1000 *
|
||||
1000 * 60 * 60);
|
||||
if (out->hour < 0) {
|
||||
out->year = 1969;
|
||||
out->month = 12;
|
||||
out->day = 31;
|
||||
out->hour += 24;
|
||||
assert(out->hour >= 0);
|
||||
}
|
||||
out->min = (int)extract_unit(&dt, 1000LL * 1000 * 1000 * 1000 *
|
||||
1000 * 60);
|
||||
out->sec = (int)extract_unit(&dt, 1000LL * 1000 * 1000 * 1000 *
|
||||
1000);
|
||||
out->us = (int)extract_unit(&dt, 1000LL * 1000 * 1000);
|
||||
out->ps = (int)extract_unit(&dt, 1000LL);
|
||||
out->as = (int)(dt * 1000);
|
||||
break;
|
||||
|
||||
case NPY_FR_as:
|
||||
/* entire range is only +- 9.2 seconds */
|
||||
out->sec = (int)extract_unit(&dt, 1000LL * 1000 * 1000 * 1000 *
|
||||
1000 * 1000);
|
||||
if (out->sec < 0) {
|
||||
out->year = 1969;
|
||||
out->month = 12;
|
||||
out->day = 31;
|
||||
out->hour = 23;
|
||||
out->min = 59;
|
||||
out->sec += 60;
|
||||
assert(out->sec >= 0);
|
||||
}
|
||||
out->us = (int)extract_unit(&dt, 1000LL * 1000 * 1000 * 1000);
|
||||
out->ps = (int)extract_unit(&dt, 1000LL * 1000);
|
||||
out->as = (int)dt;
|
||||
break;
|
||||
|
||||
default:
|
||||
PyErr_SetString(PyExc_RuntimeError,
|
||||
"NumPy datetime metadata is corrupted with invalid "
|
||||
"base unit");
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Converts a timedelta from a timedeltastruct to a timedelta based
|
||||
* on a metadata unit. The timedelta is assumed to be valid.
|
||||
*
|
||||
* Returns 0 on success, -1 on failure.
|
||||
*/
|
||||
void pandas_timedelta_to_timedeltastruct(npy_timedelta td,
|
||||
NPY_DATETIMEUNIT base,
|
||||
pandas_timedeltastruct *out) {
|
||||
npy_int64 frac;
|
||||
npy_int64 sfrac;
|
||||
npy_int64 ifrac;
|
||||
int sign;
|
||||
npy_int64 DAY_NS = 86400000000000LL;
|
||||
|
||||
/* Initialize the output to all zeros */
|
||||
memset(out, 0, sizeof(pandas_timedeltastruct));
|
||||
|
||||
switch (base) {
|
||||
case NPY_FR_ns:
|
||||
|
||||
// put frac in seconds
|
||||
if (td < 0 && td % (1000LL * 1000LL * 1000LL) != 0)
|
||||
frac = td / (1000LL * 1000LL * 1000LL) - 1;
|
||||
else
|
||||
frac = td / (1000LL * 1000LL * 1000LL);
|
||||
|
||||
if (frac < 0) {
|
||||
sign = -1;
|
||||
|
||||
// even fraction
|
||||
if ((-frac % 86400LL) != 0) {
|
||||
out->days = -frac / 86400LL + 1;
|
||||
frac += 86400LL * out->days;
|
||||
} else {
|
||||
frac = -frac;
|
||||
}
|
||||
} else {
|
||||
sign = 1;
|
||||
out->days = 0;
|
||||
}
|
||||
|
||||
if (frac >= 86400) {
|
||||
out->days += frac / 86400LL;
|
||||
frac -= out->days * 86400LL;
|
||||
}
|
||||
|
||||
if (frac >= 3600) {
|
||||
out->hrs = frac / 3600LL;
|
||||
frac -= out->hrs * 3600LL;
|
||||
} else {
|
||||
out->hrs = 0;
|
||||
}
|
||||
|
||||
if (frac >= 60) {
|
||||
out->min = frac / 60LL;
|
||||
frac -= out->min * 60LL;
|
||||
} else {
|
||||
out->min = 0;
|
||||
}
|
||||
|
||||
if (frac >= 0) {
|
||||
out->sec = frac;
|
||||
frac -= out->sec;
|
||||
} else {
|
||||
out->sec = 0;
|
||||
}
|
||||
|
||||
sfrac = (out->hrs * 3600LL + out->min * 60LL
|
||||
+ out->sec) * (1000LL * 1000LL * 1000LL);
|
||||
|
||||
if (sign < 0)
|
||||
out->days = -out->days;
|
||||
|
||||
ifrac = td - (out->days * DAY_NS + sfrac);
|
||||
|
||||
if (ifrac != 0) {
|
||||
out->ms = ifrac / (1000LL * 1000LL);
|
||||
ifrac -= out->ms * 1000LL * 1000LL;
|
||||
out->us = ifrac / 1000LL;
|
||||
ifrac -= out->us * 1000LL;
|
||||
out->ns = ifrac;
|
||||
} else {
|
||||
out->ms = 0;
|
||||
out->us = 0;
|
||||
out->ns = 0;
|
||||
}
|
||||
|
||||
out->seconds = out->hrs * 3600 + out->min * 60 + out->sec;
|
||||
out->microseconds = out->ms * 1000 + out->us;
|
||||
out->nanoseconds = out->ns;
|
||||
break;
|
||||
|
||||
default:
|
||||
PyErr_SetString(PyExc_RuntimeError,
|
||||
"NumPy timedelta metadata is corrupted with "
|
||||
"invalid base unit");
|
||||
}
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2016, PyData Development Team
|
||||
All rights reserved.
|
||||
|
||||
Distributed under the terms of the BSD Simplified License.
|
||||
|
||||
The full license is in the LICENSE file, distributed with this software.
|
||||
|
||||
Copyright (c) 2005-2011, NumPy Developers
|
||||
All rights reserved.
|
||||
|
||||
This file is derived from NumPy 1.7. See NUMPY_LICENSE.txt
|
||||
|
||||
*/
|
||||
|
||||
#ifndef PANDAS__LIBS_TSLIBS_SRC_DATETIME_NP_DATETIME_H_
|
||||
#define PANDAS__LIBS_TSLIBS_SRC_DATETIME_NP_DATETIME_H_
|
||||
|
||||
#ifndef NPY_NO_DEPRECATED_API
|
||||
#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
|
||||
#endif // NPY_NO_DEPRECATED_API
|
||||
|
||||
#include <numpy/ndarraytypes.h>
|
||||
|
||||
typedef struct {
|
||||
npy_int64 days;
|
||||
npy_int32 hrs, min, sec, ms, us, ns, seconds, microseconds, nanoseconds;
|
||||
} pandas_timedeltastruct;
|
||||
|
||||
extern const npy_datetimestruct _NS_MIN_DTS;
|
||||
extern const npy_datetimestruct _NS_MAX_DTS;
|
||||
|
||||
// stuff pandas needs
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
int convert_pydatetime_to_datetimestruct(PyObject *dtobj,
|
||||
npy_datetimestruct *out);
|
||||
|
||||
npy_datetime npy_datetimestruct_to_datetime(NPY_DATETIMEUNIT base,
|
||||
const npy_datetimestruct *dts);
|
||||
|
||||
void pandas_datetime_to_datetimestruct(npy_datetime val, NPY_DATETIMEUNIT fr,
|
||||
npy_datetimestruct *result);
|
||||
|
||||
void pandas_timedelta_to_timedeltastruct(npy_timedelta val,
|
||||
NPY_DATETIMEUNIT fr,
|
||||
pandas_timedeltastruct *result);
|
||||
|
||||
extern const int days_per_month_table[2][12];
|
||||
|
||||
// stuff numpy-derived code needs in header
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
int is_leapyear(npy_int64 year);
|
||||
|
||||
/*
|
||||
* Calculates the days offset from the 1970 epoch.
|
||||
*/
|
||||
npy_int64
|
||||
get_datetimestruct_days(const npy_datetimestruct *dts);
|
||||
|
||||
|
||||
/*
|
||||
* Compares two npy_datetimestruct objects chronologically
|
||||
*/
|
||||
int cmp_npy_datetimestruct(const npy_datetimestruct *a,
|
||||
const npy_datetimestruct *b);
|
||||
|
||||
|
||||
/*
|
||||
* Adjusts a datetimestruct based on a minutes offset. Assumes
|
||||
* the current values are valid.
|
||||
*/
|
||||
void
|
||||
add_minutes_to_datetimestruct(npy_datetimestruct *dts, int minutes);
|
||||
|
||||
|
||||
#endif // PANDAS__LIBS_TSLIBS_SRC_DATETIME_NP_DATETIME_H_
|
@ -0,0 +1,941 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2016, PyData Development Team
|
||||
All rights reserved.
|
||||
|
||||
Distributed under the terms of the BSD Simplified License.
|
||||
|
||||
The full license is in the LICENSE file, distributed with this software.
|
||||
|
||||
Written by Mark Wiebe (mwwiebe@gmail.com)
|
||||
Copyright (c) 2011 by Enthought, Inc.
|
||||
|
||||
Copyright (c) 2005-2011, NumPy Developers
|
||||
All rights reserved.
|
||||
|
||||
See NUMPY_LICENSE.txt for the license.
|
||||
|
||||
This file implements string parsing and creation for NumPy datetime.
|
||||
|
||||
*/
|
||||
|
||||
#define PY_SSIZE_T_CLEAN
|
||||
#define NO_IMPORT
|
||||
|
||||
#ifndef NPY_NO_DEPRECATED_API
|
||||
#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
|
||||
#endif // NPY_NO_DEPRECATED_API
|
||||
|
||||
#include <Python.h>
|
||||
|
||||
#include <time.h>
|
||||
|
||||
#include <numpy/arrayobject.h>
|
||||
#include <numpy/arrayscalars.h>
|
||||
#include <numpy/ndarraytypes.h>
|
||||
|
||||
#include "np_datetime.h"
|
||||
#include "np_datetime_strings.h"
|
||||
|
||||
|
||||
/*
|
||||
* Parses (almost) standard ISO 8601 date strings. The differences are:
|
||||
*
|
||||
* + Only seconds may have a decimal point, with up to 18 digits after it
|
||||
* (maximum attoseconds precision).
|
||||
* + Either a 'T' as in ISO 8601 or a ' ' may be used to separate
|
||||
* the date and the time. Both are treated equivalently.
|
||||
* + Doesn't (yet) handle the "YYYY-DDD" or "YYYY-Www" formats.
|
||||
* + Doesn't handle leap seconds (seconds value has 60 in these cases).
|
||||
* + Doesn't handle 24:00:00 as synonym for midnight (00:00:00) tomorrow
|
||||
* + Accepts special values "NaT" (not a time), "Today", (current
|
||||
* day according to local time) and "Now" (current time in UTC).
|
||||
* + ':' separator between hours, minutes, and seconds is optional. When
|
||||
* omitted, each component must be 2 digits if it appears. (GH-10041)
|
||||
*
|
||||
* 'str' must be a NULL-terminated string, and 'len' must be its length.
|
||||
*
|
||||
* 'out' gets filled with the parsed date-time.
|
||||
* 'out_local' gets set to 1 if the parsed time contains timezone,
|
||||
* to 0 otherwise.
|
||||
* 'out_tzoffset' gets set to timezone offset by minutes
|
||||
* if the parsed time was in local time,
|
||||
* to 0 otherwise. The values 'now' and 'today' don't get counted
|
||||
* as local, and neither do UTC +/-#### timezone offsets, because
|
||||
* they aren't using the computer's local timezone offset.
|
||||
*
|
||||
* Returns 0 on success, -1 on failure.
|
||||
*/
|
||||
int parse_iso_8601_datetime(const char *str, int len, int want_exc,
|
||||
npy_datetimestruct *out,
|
||||
int *out_local, int *out_tzoffset) {
|
||||
int year_leap = 0;
|
||||
int i, numdigits;
|
||||
const char *substr;
|
||||
int sublen;
|
||||
|
||||
/* If year-month-day are separated by a valid separator,
|
||||
* months/days without leading zeroes will be parsed
|
||||
* (though not iso8601). If the components aren't separated,
|
||||
* 4 (YYYY) or 8 (YYYYMMDD) digits are expected. 6 digits are
|
||||
* forbidden here (but parsed as YYMMDD elsewhere).
|
||||
*/
|
||||
int has_ymd_sep = 0;
|
||||
char ymd_sep = '\0';
|
||||
char valid_ymd_sep[] = {'-', '.', '/', '\\', ' '};
|
||||
int valid_ymd_sep_len = sizeof(valid_ymd_sep);
|
||||
|
||||
/* hour-minute-second may or may not separated by ':'. If not, then
|
||||
* each component must be 2 digits. */
|
||||
int has_hms_sep = 0;
|
||||
int hour_was_2_digits = 0;
|
||||
|
||||
/* Initialize the output to all zeros */
|
||||
memset(out, 0, sizeof(npy_datetimestruct));
|
||||
out->month = 1;
|
||||
out->day = 1;
|
||||
|
||||
substr = str;
|
||||
sublen = len;
|
||||
|
||||
/* Skip leading whitespace */
|
||||
while (sublen > 0 && isspace(*substr)) {
|
||||
++substr;
|
||||
--sublen;
|
||||
}
|
||||
|
||||
/* Leading '-' sign for negative year */
|
||||
if (*substr == '-') {
|
||||
++substr;
|
||||
--sublen;
|
||||
}
|
||||
|
||||
if (sublen == 0) {
|
||||
goto parse_error;
|
||||
}
|
||||
|
||||
/* PARSE THE YEAR (4 digits) */
|
||||
out->year = 0;
|
||||
if (sublen >= 4 && isdigit(substr[0]) && isdigit(substr[1]) &&
|
||||
isdigit(substr[2]) && isdigit(substr[3])) {
|
||||
out->year = 1000 * (substr[0] - '0') + 100 * (substr[1] - '0') +
|
||||
10 * (substr[2] - '0') + (substr[3] - '0');
|
||||
|
||||
substr += 4;
|
||||
sublen -= 4;
|
||||
}
|
||||
|
||||
/* Negate the year if necessary */
|
||||
if (str[0] == '-') {
|
||||
out->year = -out->year;
|
||||
}
|
||||
/* Check whether it's a leap-year */
|
||||
year_leap = is_leapyear(out->year);
|
||||
|
||||
/* Next character must be a separator, start of month, or end of string */
|
||||
if (sublen == 0) {
|
||||
if (out_local != NULL) {
|
||||
*out_local = 0;
|
||||
}
|
||||
goto finish;
|
||||
}
|
||||
|
||||
if (!isdigit(*substr)) {
|
||||
for (i = 0; i < valid_ymd_sep_len; ++i) {
|
||||
if (*substr == valid_ymd_sep[i]) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == valid_ymd_sep_len) {
|
||||
goto parse_error;
|
||||
}
|
||||
has_ymd_sep = 1;
|
||||
ymd_sep = valid_ymd_sep[i];
|
||||
++substr;
|
||||
--sublen;
|
||||
/* Cannot have trailing separator */
|
||||
if (sublen == 0 || !isdigit(*substr)) {
|
||||
goto parse_error;
|
||||
}
|
||||
}
|
||||
|
||||
/* PARSE THE MONTH */
|
||||
/* First digit required */
|
||||
out->month = (*substr - '0');
|
||||
++substr;
|
||||
--sublen;
|
||||
/* Second digit optional if there was a separator */
|
||||
if (isdigit(*substr)) {
|
||||
out->month = 10 * out->month + (*substr - '0');
|
||||
++substr;
|
||||
--sublen;
|
||||
} else if (!has_ymd_sep) {
|
||||
goto parse_error;
|
||||
}
|
||||
if (out->month < 1 || out->month > 12) {
|
||||
if (want_exc) {
|
||||
PyErr_Format(PyExc_ValueError,
|
||||
"Month out of range in datetime string \"%s\"", str);
|
||||
}
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* Next character must be the separator, start of day, or end of string */
|
||||
if (sublen == 0) {
|
||||
/* Forbid YYYYMM. Parsed instead as YYMMDD by someone else. */
|
||||
if (!has_ymd_sep) {
|
||||
goto parse_error;
|
||||
}
|
||||
if (out_local != NULL) {
|
||||
*out_local = 0;
|
||||
}
|
||||
goto finish;
|
||||
}
|
||||
|
||||
if (has_ymd_sep) {
|
||||
/* Must have separator, but cannot be trailing */
|
||||
if (*substr != ymd_sep || sublen == 1) {
|
||||
goto parse_error;
|
||||
}
|
||||
++substr;
|
||||
--sublen;
|
||||
}
|
||||
|
||||
/* PARSE THE DAY */
|
||||
/* First digit required */
|
||||
if (!isdigit(*substr)) {
|
||||
goto parse_error;
|
||||
}
|
||||
out->day = (*substr - '0');
|
||||
++substr;
|
||||
--sublen;
|
||||
/* Second digit optional if there was a separator */
|
||||
if (isdigit(*substr)) {
|
||||
out->day = 10 * out->day + (*substr - '0');
|
||||
++substr;
|
||||
--sublen;
|
||||
} else if (!has_ymd_sep) {
|
||||
goto parse_error;
|
||||
}
|
||||
if (out->day < 1 ||
|
||||
out->day > days_per_month_table[year_leap][out->month - 1]) {
|
||||
if (want_exc) {
|
||||
PyErr_Format(PyExc_ValueError,
|
||||
"Day out of range in datetime string \"%s\"", str);
|
||||
}
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* Next character must be a 'T', ' ', or end of string */
|
||||
if (sublen == 0) {
|
||||
if (out_local != NULL) {
|
||||
*out_local = 0;
|
||||
}
|
||||
goto finish;
|
||||
}
|
||||
|
||||
if ((*substr != 'T' && *substr != ' ') || sublen == 1) {
|
||||
goto parse_error;
|
||||
}
|
||||
++substr;
|
||||
--sublen;
|
||||
|
||||
/* PARSE THE HOURS */
|
||||
/* First digit required */
|
||||
if (!isdigit(*substr)) {
|
||||
goto parse_error;
|
||||
}
|
||||
out->hour = (*substr - '0');
|
||||
++substr;
|
||||
--sublen;
|
||||
/* Second digit optional */
|
||||
if (isdigit(*substr)) {
|
||||
hour_was_2_digits = 1;
|
||||
out->hour = 10 * out->hour + (*substr - '0');
|
||||
++substr;
|
||||
--sublen;
|
||||
if (out->hour >= 24) {
|
||||
if (want_exc) {
|
||||
PyErr_Format(PyExc_ValueError,
|
||||
"Hours out of range in datetime string \"%s\"",
|
||||
str);
|
||||
}
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
/* Next character must be a ':' or the end of the string */
|
||||
if (sublen == 0) {
|
||||
if (!hour_was_2_digits) {
|
||||
goto parse_error;
|
||||
}
|
||||
goto finish;
|
||||
}
|
||||
|
||||
if (*substr == ':') {
|
||||
has_hms_sep = 1;
|
||||
++substr;
|
||||
--sublen;
|
||||
/* Cannot have a trailing separator */
|
||||
if (sublen == 0 || !isdigit(*substr)) {
|
||||
goto parse_error;
|
||||
}
|
||||
} else if (!isdigit(*substr)) {
|
||||
if (!hour_was_2_digits) {
|
||||
goto parse_error;
|
||||
}
|
||||
goto parse_timezone;
|
||||
}
|
||||
|
||||
/* PARSE THE MINUTES */
|
||||
/* First digit required */
|
||||
out->min = (*substr - '0');
|
||||
++substr;
|
||||
--sublen;
|
||||
/* Second digit optional if there was a separator */
|
||||
if (isdigit(*substr)) {
|
||||
out->min = 10 * out->min + (*substr - '0');
|
||||
++substr;
|
||||
--sublen;
|
||||
if (out->min >= 60) {
|
||||
if (want_exc) {
|
||||
PyErr_Format(PyExc_ValueError,
|
||||
"Minutes out of range in datetime string \"%s\"",
|
||||
str);
|
||||
}
|
||||
goto error;
|
||||
}
|
||||
} else if (!has_hms_sep) {
|
||||
goto parse_error;
|
||||
}
|
||||
|
||||
if (sublen == 0) {
|
||||
goto finish;
|
||||
}
|
||||
|
||||
/* If we make it through this condition block, then the next
|
||||
* character is a digit. */
|
||||
if (has_hms_sep && *substr == ':') {
|
||||
++substr;
|
||||
--sublen;
|
||||
/* Cannot have a trailing ':' */
|
||||
if (sublen == 0 || !isdigit(*substr)) {
|
||||
goto parse_error;
|
||||
}
|
||||
} else if (!has_hms_sep && isdigit(*substr)) {
|
||||
} else {
|
||||
goto parse_timezone;
|
||||
}
|
||||
|
||||
/* PARSE THE SECONDS */
|
||||
/* First digit required */
|
||||
out->sec = (*substr - '0');
|
||||
++substr;
|
||||
--sublen;
|
||||
/* Second digit optional if there was a separator */
|
||||
if (isdigit(*substr)) {
|
||||
out->sec = 10 * out->sec + (*substr - '0');
|
||||
++substr;
|
||||
--sublen;
|
||||
if (out->sec >= 60) {
|
||||
if (want_exc) {
|
||||
PyErr_Format(PyExc_ValueError,
|
||||
"Seconds out of range in datetime string \"%s\"",
|
||||
str);
|
||||
}
|
||||
goto error;
|
||||
}
|
||||
} else if (!has_hms_sep) {
|
||||
goto parse_error;
|
||||
}
|
||||
|
||||
/* Next character may be a '.' indicating fractional seconds */
|
||||
if (sublen > 0 && *substr == '.') {
|
||||
++substr;
|
||||
--sublen;
|
||||
} else {
|
||||
goto parse_timezone;
|
||||
}
|
||||
|
||||
/* PARSE THE MICROSECONDS (0 to 6 digits) */
|
||||
numdigits = 0;
|
||||
for (i = 0; i < 6; ++i) {
|
||||
out->us *= 10;
|
||||
if (sublen > 0 && isdigit(*substr)) {
|
||||
out->us += (*substr - '0');
|
||||
++substr;
|
||||
--sublen;
|
||||
++numdigits;
|
||||
}
|
||||
}
|
||||
|
||||
if (sublen == 0 || !isdigit(*substr)) {
|
||||
goto parse_timezone;
|
||||
}
|
||||
|
||||
/* PARSE THE PICOSECONDS (0 to 6 digits) */
|
||||
numdigits = 0;
|
||||
for (i = 0; i < 6; ++i) {
|
||||
out->ps *= 10;
|
||||
if (sublen > 0 && isdigit(*substr)) {
|
||||
out->ps += (*substr - '0');
|
||||
++substr;
|
||||
--sublen;
|
||||
++numdigits;
|
||||
}
|
||||
}
|
||||
|
||||
if (sublen == 0 || !isdigit(*substr)) {
|
||||
goto parse_timezone;
|
||||
}
|
||||
|
||||
/* PARSE THE ATTOSECONDS (0 to 6 digits) */
|
||||
numdigits = 0;
|
||||
for (i = 0; i < 6; ++i) {
|
||||
out->as *= 10;
|
||||
if (sublen > 0 && isdigit(*substr)) {
|
||||
out->as += (*substr - '0');
|
||||
++substr;
|
||||
--sublen;
|
||||
++numdigits;
|
||||
}
|
||||
}
|
||||
|
||||
parse_timezone:
|
||||
/* trim any whitespace between time/timeezone */
|
||||
while (sublen > 0 && isspace(*substr)) {
|
||||
++substr;
|
||||
--sublen;
|
||||
}
|
||||
|
||||
if (sublen == 0) {
|
||||
// Unlike NumPy, treating no time zone as naive
|
||||
goto finish;
|
||||
}
|
||||
|
||||
/* UTC specifier */
|
||||
if (*substr == 'Z') {
|
||||
/* "Z" should be equivalent to tz offset "+00:00" */
|
||||
if (out_local != NULL) {
|
||||
*out_local = 1;
|
||||
}
|
||||
|
||||
if (out_tzoffset != NULL) {
|
||||
*out_tzoffset = 0;
|
||||
}
|
||||
|
||||
if (sublen == 1) {
|
||||
goto finish;
|
||||
} else {
|
||||
++substr;
|
||||
--sublen;
|
||||
}
|
||||
} else if (*substr == '-' || *substr == '+') {
|
||||
/* Time zone offset */
|
||||
int offset_neg = 0, offset_hour = 0, offset_minute = 0;
|
||||
|
||||
/*
|
||||
* Since "local" means local with respect to the current
|
||||
* machine, we say this is non-local.
|
||||
*/
|
||||
|
||||
if (*substr == '-') {
|
||||
offset_neg = 1;
|
||||
}
|
||||
++substr;
|
||||
--sublen;
|
||||
|
||||
/* The hours offset */
|
||||
if (sublen >= 2 && isdigit(substr[0]) && isdigit(substr[1])) {
|
||||
offset_hour = 10 * (substr[0] - '0') + (substr[1] - '0');
|
||||
substr += 2;
|
||||
sublen -= 2;
|
||||
if (offset_hour >= 24) {
|
||||
if (want_exc) {
|
||||
PyErr_Format(PyExc_ValueError,
|
||||
"Timezone hours offset out of range "
|
||||
"in datetime string \"%s\"",
|
||||
str);
|
||||
}
|
||||
goto error;
|
||||
}
|
||||
} else if (sublen >= 1 && isdigit(substr[0])) {
|
||||
offset_hour = substr[0] - '0';
|
||||
++substr;
|
||||
--sublen;
|
||||
} else {
|
||||
goto parse_error;
|
||||
}
|
||||
|
||||
/* The minutes offset is optional */
|
||||
if (sublen > 0) {
|
||||
/* Optional ':' */
|
||||
if (*substr == ':') {
|
||||
++substr;
|
||||
--sublen;
|
||||
}
|
||||
|
||||
/* The minutes offset (at the end of the string) */
|
||||
if (sublen >= 2 && isdigit(substr[0]) && isdigit(substr[1])) {
|
||||
offset_minute = 10 * (substr[0] - '0') + (substr[1] - '0');
|
||||
substr += 2;
|
||||
sublen -= 2;
|
||||
if (offset_minute >= 60) {
|
||||
if (want_exc) {
|
||||
PyErr_Format(PyExc_ValueError,
|
||||
"Timezone minutes offset out of range "
|
||||
"in datetime string \"%s\"",
|
||||
str);
|
||||
}
|
||||
goto error;
|
||||
}
|
||||
} else if (sublen >= 1 && isdigit(substr[0])) {
|
||||
offset_minute = substr[0] - '0';
|
||||
++substr;
|
||||
--sublen;
|
||||
} else {
|
||||
goto parse_error;
|
||||
}
|
||||
}
|
||||
|
||||
/* Apply the time zone offset */
|
||||
if (offset_neg) {
|
||||
offset_hour = -offset_hour;
|
||||
offset_minute = -offset_minute;
|
||||
}
|
||||
if (out_local != NULL) {
|
||||
*out_local = 1;
|
||||
// Unlike NumPy, do not change internal value to local time
|
||||
*out_tzoffset = 60 * offset_hour + offset_minute;
|
||||
}
|
||||
}
|
||||
|
||||
/* Skip trailing whitespace */
|
||||
while (sublen > 0 && isspace(*substr)) {
|
||||
++substr;
|
||||
--sublen;
|
||||
}
|
||||
|
||||
if (sublen != 0) {
|
||||
goto parse_error;
|
||||
}
|
||||
|
||||
finish:
|
||||
return 0;
|
||||
|
||||
parse_error:
|
||||
if (want_exc) {
|
||||
PyErr_Format(PyExc_ValueError,
|
||||
"Error parsing datetime string \"%s\" at position %d", str,
|
||||
(int)(substr - str));
|
||||
}
|
||||
return -1;
|
||||
|
||||
error:
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Provides a string length to use for converting datetime
|
||||
* objects with the given local and unit settings.
|
||||
*/
|
||||
int get_datetime_iso_8601_strlen(int local, NPY_DATETIMEUNIT base) {
|
||||
int len = 0;
|
||||
|
||||
switch (base) {
|
||||
/* Generic units can only be used to represent NaT */
|
||||
/* return 4;*/
|
||||
case NPY_FR_as:
|
||||
len += 3; /* "###" */
|
||||
case NPY_FR_fs:
|
||||
len += 3; /* "###" */
|
||||
case NPY_FR_ps:
|
||||
len += 3; /* "###" */
|
||||
case NPY_FR_ns:
|
||||
len += 3; /* "###" */
|
||||
case NPY_FR_us:
|
||||
len += 3; /* "###" */
|
||||
case NPY_FR_ms:
|
||||
len += 4; /* ".###" */
|
||||
case NPY_FR_s:
|
||||
len += 3; /* ":##" */
|
||||
case NPY_FR_m:
|
||||
len += 3; /* ":##" */
|
||||
case NPY_FR_h:
|
||||
len += 3; /* "T##" */
|
||||
case NPY_FR_D:
|
||||
case NPY_FR_W:
|
||||
len += 3; /* "-##" */
|
||||
case NPY_FR_M:
|
||||
len += 3; /* "-##" */
|
||||
case NPY_FR_Y:
|
||||
len += 21; /* 64-bit year */
|
||||
break;
|
||||
default:
|
||||
len += 3; /* handle the now defunct NPY_FR_B */
|
||||
break;
|
||||
}
|
||||
|
||||
if (base >= NPY_FR_h) {
|
||||
if (local) {
|
||||
len += 5; /* "+####" or "-####" */
|
||||
} else {
|
||||
len += 1; /* "Z" */
|
||||
}
|
||||
}
|
||||
|
||||
len += 1; /* NULL terminator */
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Converts an npy_datetimestruct to an (almost) ISO 8601
|
||||
* NULL-terminated string using timezone Z (UTC). If the string fits in
|
||||
* the space exactly, it leaves out the NULL terminator and returns success.
|
||||
*
|
||||
* The differences from ISO 8601 are the 'NaT' string, and
|
||||
* the number of year digits is >= 4 instead of strictly 4.
|
||||
*
|
||||
* 'base' restricts the output to that unit. Set 'base' to
|
||||
* -1 to auto-detect a base after which all the values are zero.
|
||||
*
|
||||
* Returns 0 on success, -1 on failure (for example if the output
|
||||
* string was too short).
|
||||
*/
|
||||
int make_iso_8601_datetime(npy_datetimestruct *dts, char *outstr, int outlen,
|
||||
NPY_DATETIMEUNIT base) {
|
||||
char *substr = outstr;
|
||||
int sublen = outlen;
|
||||
int tmplen;
|
||||
|
||||
/*
|
||||
* Print weeks with the same precision as days.
|
||||
*
|
||||
* TODO: Could print weeks with YYYY-Www format if the week
|
||||
* epoch is a Monday.
|
||||
*/
|
||||
if (base == NPY_FR_W) {
|
||||
base = NPY_FR_D;
|
||||
}
|
||||
|
||||
/* YEAR */
|
||||
/*
|
||||
* Can't use PyOS_snprintf, because it always produces a '\0'
|
||||
* character at the end, and NumPy string types are permitted
|
||||
* to have data all the way to the end of the buffer.
|
||||
*/
|
||||
#ifdef _WIN32
|
||||
tmplen = _snprintf(substr, sublen, "%04" NPY_INT64_FMT, dts->year);
|
||||
#else
|
||||
tmplen = snprintf(substr, sublen, "%04" NPY_INT64_FMT, dts->year);
|
||||
#endif // _WIN32
|
||||
/* If it ran out of space or there isn't space for the NULL terminator */
|
||||
if (tmplen < 0 || tmplen > sublen) {
|
||||
goto string_too_short;
|
||||
}
|
||||
substr += tmplen;
|
||||
sublen -= tmplen;
|
||||
|
||||
/* Stop if the unit is years */
|
||||
if (base == NPY_FR_Y) {
|
||||
if (sublen > 0) {
|
||||
*substr = '\0';
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* MONTH */
|
||||
if (sublen < 1) {
|
||||
goto string_too_short;
|
||||
}
|
||||
substr[0] = '-';
|
||||
if (sublen < 2) {
|
||||
goto string_too_short;
|
||||
}
|
||||
substr[1] = (char)((dts->month / 10) + '0');
|
||||
if (sublen < 3) {
|
||||
goto string_too_short;
|
||||
}
|
||||
substr[2] = (char)((dts->month % 10) + '0');
|
||||
substr += 3;
|
||||
sublen -= 3;
|
||||
|
||||
/* Stop if the unit is months */
|
||||
if (base == NPY_FR_M) {
|
||||
if (sublen > 0) {
|
||||
*substr = '\0';
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* DAY */
|
||||
if (sublen < 1) {
|
||||
goto string_too_short;
|
||||
}
|
||||
substr[0] = '-';
|
||||
if (sublen < 2) {
|
||||
goto string_too_short;
|
||||
}
|
||||
substr[1] = (char)((dts->day / 10) + '0');
|
||||
if (sublen < 3) {
|
||||
goto string_too_short;
|
||||
}
|
||||
substr[2] = (char)((dts->day % 10) + '0');
|
||||
substr += 3;
|
||||
sublen -= 3;
|
||||
|
||||
/* Stop if the unit is days */
|
||||
if (base == NPY_FR_D) {
|
||||
if (sublen > 0) {
|
||||
*substr = '\0';
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* HOUR */
|
||||
if (sublen < 1) {
|
||||
goto string_too_short;
|
||||
}
|
||||
substr[0] = 'T';
|
||||
if (sublen < 2) {
|
||||
goto string_too_short;
|
||||
}
|
||||
substr[1] = (char)((dts->hour / 10) + '0');
|
||||
if (sublen < 3) {
|
||||
goto string_too_short;
|
||||
}
|
||||
substr[2] = (char)((dts->hour % 10) + '0');
|
||||
substr += 3;
|
||||
sublen -= 3;
|
||||
|
||||
/* Stop if the unit is hours */
|
||||
if (base == NPY_FR_h) {
|
||||
goto add_time_zone;
|
||||
}
|
||||
|
||||
/* MINUTE */
|
||||
if (sublen < 1) {
|
||||
goto string_too_short;
|
||||
}
|
||||
substr[0] = ':';
|
||||
if (sublen < 2) {
|
||||
goto string_too_short;
|
||||
}
|
||||
substr[1] = (char)((dts->min / 10) + '0');
|
||||
if (sublen < 3) {
|
||||
goto string_too_short;
|
||||
}
|
||||
substr[2] = (char)((dts->min % 10) + '0');
|
||||
substr += 3;
|
||||
sublen -= 3;
|
||||
|
||||
/* Stop if the unit is minutes */
|
||||
if (base == NPY_FR_m) {
|
||||
goto add_time_zone;
|
||||
}
|
||||
|
||||
/* SECOND */
|
||||
if (sublen < 1) {
|
||||
goto string_too_short;
|
||||
}
|
||||
substr[0] = ':';
|
||||
if (sublen < 2) {
|
||||
goto string_too_short;
|
||||
}
|
||||
substr[1] = (char)((dts->sec / 10) + '0');
|
||||
if (sublen < 3) {
|
||||
goto string_too_short;
|
||||
}
|
||||
substr[2] = (char)((dts->sec % 10) + '0');
|
||||
substr += 3;
|
||||
sublen -= 3;
|
||||
|
||||
/* Stop if the unit is seconds */
|
||||
if (base == NPY_FR_s) {
|
||||
goto add_time_zone;
|
||||
}
|
||||
|
||||
/* MILLISECOND */
|
||||
if (sublen < 1) {
|
||||
goto string_too_short;
|
||||
}
|
||||
substr[0] = '.';
|
||||
if (sublen < 2) {
|
||||
goto string_too_short;
|
||||
}
|
||||
substr[1] = (char)((dts->us / 100000) % 10 + '0');
|
||||
if (sublen < 3) {
|
||||
goto string_too_short;
|
||||
}
|
||||
substr[2] = (char)((dts->us / 10000) % 10 + '0');
|
||||
if (sublen < 4) {
|
||||
goto string_too_short;
|
||||
}
|
||||
substr[3] = (char)((dts->us / 1000) % 10 + '0');
|
||||
substr += 4;
|
||||
sublen -= 4;
|
||||
|
||||
/* Stop if the unit is milliseconds */
|
||||
if (base == NPY_FR_ms) {
|
||||
goto add_time_zone;
|
||||
}
|
||||
|
||||
/* MICROSECOND */
|
||||
if (sublen < 1) {
|
||||
goto string_too_short;
|
||||
}
|
||||
substr[0] = (char)((dts->us / 100) % 10 + '0');
|
||||
if (sublen < 2) {
|
||||
goto string_too_short;
|
||||
}
|
||||
substr[1] = (char)((dts->us / 10) % 10 + '0');
|
||||
if (sublen < 3) {
|
||||
goto string_too_short;
|
||||
}
|
||||
substr[2] = (char)(dts->us % 10 + '0');
|
||||
substr += 3;
|
||||
sublen -= 3;
|
||||
|
||||
/* Stop if the unit is microseconds */
|
||||
if (base == NPY_FR_us) {
|
||||
goto add_time_zone;
|
||||
}
|
||||
|
||||
/* NANOSECOND */
|
||||
if (sublen < 1) {
|
||||
goto string_too_short;
|
||||
}
|
||||
substr[0] = (char)((dts->ps / 100000) % 10 + '0');
|
||||
if (sublen < 2) {
|
||||
goto string_too_short;
|
||||
}
|
||||
substr[1] = (char)((dts->ps / 10000) % 10 + '0');
|
||||
if (sublen < 3) {
|
||||
goto string_too_short;
|
||||
}
|
||||
substr[2] = (char)((dts->ps / 1000) % 10 + '0');
|
||||
substr += 3;
|
||||
sublen -= 3;
|
||||
|
||||
/* Stop if the unit is nanoseconds */
|
||||
if (base == NPY_FR_ns) {
|
||||
goto add_time_zone;
|
||||
}
|
||||
|
||||
/* PICOSECOND */
|
||||
if (sublen < 1) {
|
||||
goto string_too_short;
|
||||
}
|
||||
substr[0] = (char)((dts->ps / 100) % 10 + '0');
|
||||
if (sublen < 2) {
|
||||
goto string_too_short;
|
||||
}
|
||||
substr[1] = (char)((dts->ps / 10) % 10 + '0');
|
||||
if (sublen < 3) {
|
||||
goto string_too_short;
|
||||
}
|
||||
substr[2] = (char)(dts->ps % 10 + '0');
|
||||
substr += 3;
|
||||
sublen -= 3;
|
||||
|
||||
/* Stop if the unit is picoseconds */
|
||||
if (base == NPY_FR_ps) {
|
||||
goto add_time_zone;
|
||||
}
|
||||
|
||||
/* FEMTOSECOND */
|
||||
if (sublen < 1) {
|
||||
goto string_too_short;
|
||||
}
|
||||
substr[0] = (char)((dts->as / 100000) % 10 + '0');
|
||||
if (sublen < 2) {
|
||||
goto string_too_short;
|
||||
}
|
||||
substr[1] = (char)((dts->as / 10000) % 10 + '0');
|
||||
if (sublen < 3) {
|
||||
goto string_too_short;
|
||||
}
|
||||
substr[2] = (char)((dts->as / 1000) % 10 + '0');
|
||||
substr += 3;
|
||||
sublen -= 3;
|
||||
|
||||
/* Stop if the unit is femtoseconds */
|
||||
if (base == NPY_FR_fs) {
|
||||
goto add_time_zone;
|
||||
}
|
||||
|
||||
/* ATTOSECOND */
|
||||
if (sublen < 1) {
|
||||
goto string_too_short;
|
||||
}
|
||||
substr[0] = (char)((dts->as / 100) % 10 + '0');
|
||||
if (sublen < 2) {
|
||||
goto string_too_short;
|
||||
}
|
||||
substr[1] = (char)((dts->as / 10) % 10 + '0');
|
||||
if (sublen < 3) {
|
||||
goto string_too_short;
|
||||
}
|
||||
substr[2] = (char)(dts->as % 10 + '0');
|
||||
substr += 3;
|
||||
sublen -= 3;
|
||||
|
||||
add_time_zone:
|
||||
/* UTC "Zulu" time */
|
||||
if (sublen < 1) {
|
||||
goto string_too_short;
|
||||
}
|
||||
substr[0] = 'Z';
|
||||
substr += 1;
|
||||
sublen -= 1;
|
||||
|
||||
/* Add a NULL terminator, and return */
|
||||
if (sublen > 0) {
|
||||
substr[0] = '\0';
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
string_too_short:
|
||||
PyErr_Format(PyExc_RuntimeError,
|
||||
"The string provided for NumPy ISO datetime formatting "
|
||||
"was too short, with length %d",
|
||||
outlen);
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
int make_iso_8601_timedelta(pandas_timedeltastruct *tds,
|
||||
char *outstr, size_t *outlen) {
|
||||
*outlen = 0;
|
||||
*outlen += snprintf(outstr, 60, // NOLINT
|
||||
"P%" NPY_INT64_FMT
|
||||
"DT%" NPY_INT32_FMT
|
||||
"H%" NPY_INT32_FMT
|
||||
"M%" NPY_INT32_FMT,
|
||||
tds->days, tds->hrs, tds->min, tds->sec);
|
||||
outstr += *outlen;
|
||||
|
||||
if (tds->ns != 0) {
|
||||
*outlen += snprintf(outstr, 12, // NOLINT
|
||||
".%03" NPY_INT32_FMT
|
||||
"%03" NPY_INT32_FMT
|
||||
"%03" NPY_INT32_FMT
|
||||
"S", tds->ms, tds->us, tds->ns);
|
||||
} else if (tds->us != 0) {
|
||||
*outlen += snprintf(outstr, 9, // NOLINT
|
||||
".%03" NPY_INT32_FMT
|
||||
"%03" NPY_INT32_FMT
|
||||
"S", tds->ms, tds->us);
|
||||
} else if (tds->ms != 0) {
|
||||
*outlen += snprintf(outstr, 6, // NOLINT
|
||||
".%03" NPY_INT32_FMT "S", tds->ms);
|
||||
} else {
|
||||
*outlen += snprintf(outstr, 2, // NOLINT
|
||||
"%s", "S");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2016, PyData Development Team
|
||||
All rights reserved.
|
||||
|
||||
Distributed under the terms of the BSD Simplified License.
|
||||
|
||||
The full license is in the LICENSE file, distributed with this software.
|
||||
|
||||
Written by Mark Wiebe (mwwiebe@gmail.com)
|
||||
Copyright (c) 2011 by Enthought, Inc.
|
||||
|
||||
Copyright (c) 2005-2011, NumPy Developers
|
||||
All rights reserved.
|
||||
|
||||
See NUMPY_LICENSE.txt for the license.
|
||||
|
||||
This file implements string parsing and creation for NumPy datetime.
|
||||
|
||||
*/
|
||||
|
||||
#ifndef PANDAS__LIBS_TSLIBS_SRC_DATETIME_NP_DATETIME_STRINGS_H_
|
||||
#define PANDAS__LIBS_TSLIBS_SRC_DATETIME_NP_DATETIME_STRINGS_H_
|
||||
|
||||
#ifndef NPY_NO_DEPRECATED_API
|
||||
#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
|
||||
#endif // NPY_NO_DEPRECATED_API
|
||||
|
||||
/*
|
||||
* Parses (almost) standard ISO 8601 date strings. The differences are:
|
||||
*
|
||||
* + The date "20100312" is parsed as the year 20100312, not as
|
||||
* equivalent to "2010-03-12". The '-' in the dates are not optional.
|
||||
* + Only seconds may have a decimal point, with up to 18 digits after it
|
||||
* (maximum attoseconds precision).
|
||||
* + Either a 'T' as in ISO 8601 or a ' ' may be used to separate
|
||||
* the date and the time. Both are treated equivalently.
|
||||
* + Doesn't (yet) handle the "YYYY-DDD" or "YYYY-Www" formats.
|
||||
* + Doesn't handle leap seconds (seconds value has 60 in these cases).
|
||||
* + Doesn't handle 24:00:00 as synonym for midnight (00:00:00) tomorrow
|
||||
* + Accepts special values "NaT" (not a time), "Today", (current
|
||||
* day according to local time) and "Now" (current time in UTC).
|
||||
*
|
||||
* 'str' must be a NULL-terminated string, and 'len' must be its length.
|
||||
*
|
||||
* 'out' gets filled with the parsed date-time.
|
||||
* 'out_local' gets whether returned value contains timezone. 0 for UTC, 1 for local time.
|
||||
* 'out_tzoffset' gets set to timezone offset by minutes
|
||||
* if the parsed time was in local time,
|
||||
* to 0 otherwise. The values 'now' and 'today' don't get counted
|
||||
* as local, and neither do UTC +/-#### timezone offsets, because
|
||||
* they aren't using the computer's local timezone offset.
|
||||
*
|
||||
* Returns 0 on success, -1 on failure.
|
||||
*/
|
||||
int
|
||||
parse_iso_8601_datetime(const char *str, int len, int want_exc,
|
||||
npy_datetimestruct *out,
|
||||
int *out_local,
|
||||
int *out_tzoffset);
|
||||
|
||||
/*
|
||||
* Provides a string length to use for converting datetime
|
||||
* objects with the given local and unit settings.
|
||||
*/
|
||||
int
|
||||
get_datetime_iso_8601_strlen(int local, NPY_DATETIMEUNIT base);
|
||||
|
||||
/*
|
||||
* Converts an npy_datetimestruct to an (almost) ISO 8601
|
||||
* NULL-terminated string using timezone Z (UTC).
|
||||
*
|
||||
* 'base' restricts the output to that unit. Set 'base' to
|
||||
* -1 to auto-detect a base after which all the values are zero.
|
||||
*
|
||||
* Returns 0 on success, -1 on failure (for example if the output
|
||||
* string was too short).
|
||||
*/
|
||||
int
|
||||
make_iso_8601_datetime(npy_datetimestruct *dts, char *outstr, int outlen,
|
||||
NPY_DATETIMEUNIT base);
|
||||
|
||||
/*
|
||||
* Converts an pandas_timedeltastruct to an ISO 8601 string.
|
||||
*
|
||||
* Mutates outlen to provide size of (non-NULL terminated) string.
|
||||
*
|
||||
* Currently has no error handling
|
||||
*/
|
||||
int make_iso_8601_timedelta(pandas_timedeltastruct *tds, char *outstr,
|
||||
size_t *outlen);
|
||||
#endif // PANDAS__LIBS_TSLIBS_SRC_DATETIME_NP_DATETIME_STRINGS_H_
|
Reference in New Issue
Block a user