Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Continue porting period_helper; fix leftover asfreq bug #19834

Merged
merged 5 commits into from Feb 23, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion doc/source/whatsnew/v0.23.0.txt
Expand Up @@ -763,7 +763,7 @@ Timedelta
- Bug in :class:`TimedeltaIndex` where division by a ``Series`` would return a ``TimedeltaIndex`` instead of a ``Series`` (:issue:`19042`)
- Bug in :func:`Timedelta.__add__`, :func:`Timedelta.__sub__` where adding or subtracting a ``np.timedelta64`` object would return another ``np.timedelta64`` instead of a ``Timedelta`` (:issue:`19738`)
- Bug in :func:`Timedelta.__floordiv__`, :func:`Timedelta.__rfloordiv__` where operating with a ``Tick`` object would raise a ``TypeError`` instead of returning a numeric value (:issue:`19738`)
- Bug in :func:`Period.asfreq` where periods near ``datetime(1, 1, 1)`` could be converted incorrectly (:issue:`19643`)
- Bug in :func:`Period.asfreq` where periods near ``datetime(1, 1, 1)`` could be converted incorrectly (:issue:`19643`, :issue:`19834`)
- Bug in :func:`Timedelta.total_seconds()` causing precision errors i.e. ``Timedelta('30S').total_seconds()==30.000000000000004`` (:issue:`19458`)
-

Expand Down
120 changes: 56 additions & 64 deletions pandas/_libs/src/period_helper.c
Expand Up @@ -42,10 +42,10 @@ static int floordiv(int x, int divisor) {
static int monthToQuarter(int month) { return ((month - 1) / 3) + 1; }


/* Find the absdate (days elapsed since datetime(1, 1, 1)
/* Find the unix_date (days elapsed since datetime(1970, 1, 1)
* for the given year/month/day.
* Assumes GREGORIAN_CALENDAR */
npy_int64 absdate_from_ymd(int year, int month, int day) {
npy_int64 unix_date_from_ymd(int year, int month, int day) {
/* Calculate the absolute date */
pandas_datetimestruct dts;
npy_int64 unix_date;
Expand All @@ -55,16 +55,16 @@ npy_int64 absdate_from_ymd(int year, int month, int day) {
dts.month = month;
dts.day = day;
unix_date = pandas_datetimestruct_to_datetime(PANDAS_FR_D, &dts);
return ORD_OFFSET + unix_date;
return unix_date;
}

/* Sets the date part of the date_info struct
Assumes GREGORIAN_CALENDAR */
static int dInfoCalc_SetFromAbsDate(register struct date_info *dinfo,
npy_int64 absdate) {
npy_int64 unix_date) {
pandas_datetimestruct dts;

pandas_datetime_to_datetimestruct(absdate - ORD_OFFSET, PANDAS_FR_D, &dts);
pandas_datetime_to_datetimestruct(unix_date, PANDAS_FR_D, &dts);
dinfo->year = dts.year;
dinfo->month = dts.month;
dinfo->day = dts.day;
Expand Down Expand Up @@ -137,26 +137,26 @@ PANDAS_INLINE npy_int64 transform_via_day(npy_int64 ordinal,
return result;
}

static npy_int64 DtoB_weekday(npy_int64 absdate) {
return floordiv(absdate, 7) * 5 + mod_compat(absdate, 7) - BDAY_OFFSET;
static npy_int64 DtoB_weekday(npy_int64 unix_date) {
return floordiv(unix_date + 4, 7) * 5 + mod_compat(unix_date + 4, 7) - 4;
}

static npy_int64 DtoB(struct date_info *dinfo,
int roll_back, npy_int64 absdate) {
int roll_back, npy_int64 unix_date) {
int day_of_week = dayofweek(dinfo->year, dinfo->month, dinfo->day);

if (roll_back == 1) {
if (day_of_week > 4) {
// change to friday before weekend
absdate -= (day_of_week - 4);
unix_date -= (day_of_week - 4);
}
} else {
if (day_of_week > 4) {
// change to Monday after weekend
absdate += (7 - day_of_week);
unix_date += (7 - day_of_week);
}
}
return DtoB_weekday(absdate);
return DtoB_weekday(unix_date);
}


Expand All @@ -165,18 +165,19 @@ static npy_int64 DtoB(struct date_info *dinfo,
static npy_int64 asfreq_DTtoA(npy_int64 ordinal, asfreq_info *af_info) {
struct date_info dinfo;
ordinal = downsample_daytime(ordinal, af_info);
dInfoCalc_SetFromAbsDate(&dinfo, ordinal + ORD_OFFSET);
dInfoCalc_SetFromAbsDate(&dinfo, ordinal);
if (dinfo.month > af_info->to_a_year_end) {
return (npy_int64)(dinfo.year + 1 - BASE_YEAR);
return (npy_int64)(dinfo.year + 1 - 1970);
} else {
return (npy_int64)(dinfo.year - BASE_YEAR);
return (npy_int64)(dinfo.year - 1970);
}
}

static npy_int64 DtoQ_yq(npy_int64 ordinal, asfreq_info *af_info, int *year,
int *quarter) {
static int DtoQ_yq(npy_int64 ordinal, asfreq_info *af_info, int *year) {
struct date_info dinfo;
dInfoCalc_SetFromAbsDate(&dinfo, ordinal + ORD_OFFSET);
int quarter;

dInfoCalc_SetFromAbsDate(&dinfo, ordinal);
if (af_info->to_q_year_end != 12) {
dinfo.month -= af_info->to_q_year_end;
if (dinfo.month <= 0) {
Expand All @@ -187,47 +188,43 @@ static npy_int64 DtoQ_yq(npy_int64 ordinal, asfreq_info *af_info, int *year,
}

*year = dinfo.year;
*quarter = monthToQuarter(dinfo.month);

return 0;
quarter = monthToQuarter(dinfo.month);
return quarter;
}

static npy_int64 asfreq_DTtoQ(npy_int64 ordinal, asfreq_info *af_info) {
int year, quarter;

ordinal = downsample_daytime(ordinal, af_info);

DtoQ_yq(ordinal, af_info, &year, &quarter);
return (npy_int64)((year - BASE_YEAR) * 4 + quarter - 1);
quarter = DtoQ_yq(ordinal, af_info, &year);
return (npy_int64)((year - 1970) * 4 + quarter - 1);
}

static npy_int64 asfreq_DTtoM(npy_int64 ordinal, asfreq_info *af_info) {
struct date_info dinfo;

ordinal = downsample_daytime(ordinal, af_info);

dInfoCalc_SetFromAbsDate(&dinfo, ordinal + ORD_OFFSET);
return (npy_int64)((dinfo.year - BASE_YEAR) * 12 + dinfo.month - 1);
dInfoCalc_SetFromAbsDate(&dinfo, ordinal);
return (npy_int64)((dinfo.year - 1970) * 12 + dinfo.month - 1);
}

static npy_int64 asfreq_DTtoW(npy_int64 ordinal, asfreq_info *af_info) {
ordinal = downsample_daytime(ordinal, af_info);
return (ordinal + ORD_OFFSET - (1 + af_info->to_week_end)) / 7 + 1 -
WEEK_OFFSET;
return floordiv(ordinal + 3 - af_info->to_week_end, 7) + 1;
}

static npy_int64 asfreq_DTtoB(npy_int64 ordinal, asfreq_info *af_info) {
struct date_info dinfo;
npy_int64 absdate;
int roll_back;

ordinal = downsample_daytime(ordinal, af_info);
absdate = ordinal + ORD_OFFSET;
dInfoCalc_SetFromAbsDate(&dinfo, absdate);
dInfoCalc_SetFromAbsDate(&dinfo, ordinal);

// This usage defines roll_back the opposite way from the others
roll_back = 1 - af_info->is_end;
return DtoB(&dinfo, roll_back, absdate);
return DtoB(&dinfo, roll_back, ordinal);
}

// all intra day calculations are now done within one function
Expand All @@ -243,10 +240,7 @@ static npy_int64 asfreq_UpsampleWithinDay(npy_int64 ordinal,
//************ FROM BUSINESS ***************

static npy_int64 asfreq_BtoDT(npy_int64 ordinal, asfreq_info *af_info) {
ordinal += BDAY_OFFSET;
ordinal =
(floordiv(ordinal - 1, 5) * 7 + mod_compat(ordinal - 1, 5) + 1 -
ORD_OFFSET);
ordinal = floordiv(ordinal + 3, 5) * 7 + mod_compat(ordinal + 3, 5) - 3;

return upsample_daytime(ordinal, af_info);
}
Expand All @@ -270,8 +264,7 @@ static npy_int64 asfreq_BtoW(npy_int64 ordinal, asfreq_info *af_info) {
//************ FROM WEEKLY ***************

static npy_int64 asfreq_WtoDT(npy_int64 ordinal, asfreq_info *af_info) {
ordinal = (ordinal + WEEK_OFFSET) * 7 +
af_info->from_week_end - ORD_OFFSET +
ordinal = ordinal * 7 + af_info->from_week_end - 4 +
(7 - 1) * (af_info->is_end - 1);
return upsample_daytime(ordinal, af_info);
}
Expand All @@ -294,30 +287,29 @@ static npy_int64 asfreq_WtoW(npy_int64 ordinal, asfreq_info *af_info) {

static npy_int64 asfreq_WtoB(npy_int64 ordinal, asfreq_info *af_info) {
struct date_info dinfo;
npy_int64 absdate = asfreq_WtoDT(ordinal, af_info) + ORD_OFFSET;
npy_int64 unix_date = asfreq_WtoDT(ordinal, af_info);
int roll_back = af_info->is_end;
dInfoCalc_SetFromAbsDate(&dinfo, absdate);
dInfoCalc_SetFromAbsDate(&dinfo, unix_date);

return DtoB(&dinfo, roll_back, absdate);
return DtoB(&dinfo, roll_back, unix_date);
}

//************ FROM MONTHLY ***************
static void MtoD_ym(npy_int64 ordinal, int *y, int *m) {
*y = floordiv(ordinal, 12) + BASE_YEAR;
*y = floordiv(ordinal, 12) + 1970;
*m = mod_compat(ordinal, 12) + 1;
}

static npy_int64 asfreq_MtoDT(npy_int64 ordinal, asfreq_info *af_info) {
npy_int64 absdate;
npy_int64 unix_date;
int y, m;

ordinal += af_info->is_end;
MtoD_ym(ordinal, &y, &m);
absdate = absdate_from_ymd(y, m, 1);
ordinal = absdate - ORD_OFFSET;
unix_date = unix_date_from_ymd(y, m, 1);

ordinal -= af_info->is_end;
return upsample_daytime(ordinal, af_info);
unix_date -= af_info->is_end;
return upsample_daytime(unix_date, af_info);
}

static npy_int64 asfreq_MtoA(npy_int64 ordinal, asfreq_info *af_info) {
Expand All @@ -334,18 +326,18 @@ static npy_int64 asfreq_MtoW(npy_int64 ordinal, asfreq_info *af_info) {

static npy_int64 asfreq_MtoB(npy_int64 ordinal, asfreq_info *af_info) {
struct date_info dinfo;
npy_int64 absdate = asfreq_MtoDT(ordinal, af_info) + ORD_OFFSET;
npy_int64 unix_date = asfreq_MtoDT(ordinal, af_info);
int roll_back = af_info->is_end;

dInfoCalc_SetFromAbsDate(&dinfo, absdate);
dInfoCalc_SetFromAbsDate(&dinfo, unix_date);

return DtoB(&dinfo, roll_back, absdate);
return DtoB(&dinfo, roll_back, unix_date);
}

//************ FROM QUARTERLY ***************

static void QtoD_ym(npy_int64 ordinal, int *y, int *m, asfreq_info *af_info) {
*y = floordiv(ordinal, 4) + BASE_YEAR;
*y = floordiv(ordinal, 4) + 1970;
*m = mod_compat(ordinal, 4) * 3 + 1;

if (af_info->from_q_year_end != 12) {
Expand All @@ -359,16 +351,16 @@ static void QtoD_ym(npy_int64 ordinal, int *y, int *m, asfreq_info *af_info) {
}

static npy_int64 asfreq_QtoDT(npy_int64 ordinal, asfreq_info *af_info) {
npy_int64 absdate;
npy_int64 unix_date;
int y, m;

ordinal += af_info->is_end;
QtoD_ym(ordinal, &y, &m, af_info);

absdate = absdate_from_ymd(y, m, 1);
unix_date = unix_date_from_ymd(y, m, 1);

absdate -= af_info->is_end;
return upsample_daytime(absdate - ORD_OFFSET, af_info);
unix_date -= af_info->is_end;
return upsample_daytime(unix_date, af_info);
}

static npy_int64 asfreq_QtoQ(npy_int64 ordinal, asfreq_info *af_info) {
Expand All @@ -389,32 +381,32 @@ static npy_int64 asfreq_QtoW(npy_int64 ordinal, asfreq_info *af_info) {

static npy_int64 asfreq_QtoB(npy_int64 ordinal, asfreq_info *af_info) {
struct date_info dinfo;
npy_int64 absdate = asfreq_QtoDT(ordinal, af_info) + ORD_OFFSET;
npy_int64 unix_date = asfreq_QtoDT(ordinal, af_info);
int roll_back = af_info->is_end;

dInfoCalc_SetFromAbsDate(&dinfo, absdate);
dInfoCalc_SetFromAbsDate(&dinfo, unix_date);

return DtoB(&dinfo, roll_back, absdate);
return DtoB(&dinfo, roll_back, unix_date);
}

//************ FROM ANNUAL ***************

static npy_int64 asfreq_AtoDT(npy_int64 ordinal, asfreq_info *af_info) {
npy_int64 absdate;
npy_int64 unix_date;

// start from 1970
npy_int64 year = ordinal + BASE_YEAR;
npy_int64 year = ordinal + 1970;

int month = (af_info->from_a_year_end % 12) + 1;
if (af_info->from_a_year_end != 12) {
year -= 1;
}

year += af_info->is_end;
absdate = absdate_from_ymd(year, month, 1);
unix_date = unix_date_from_ymd(year, month, 1);

absdate -= af_info->is_end;
return upsample_daytime(absdate - ORD_OFFSET, af_info);
unix_date -= af_info->is_end;
return upsample_daytime(unix_date, af_info);
}

static npy_int64 asfreq_AtoA(npy_int64 ordinal, asfreq_info *af_info) {
Expand All @@ -435,11 +427,11 @@ static npy_int64 asfreq_AtoW(npy_int64 ordinal, asfreq_info *af_info) {

static npy_int64 asfreq_AtoB(npy_int64 ordinal, asfreq_info *af_info) {
struct date_info dinfo;
npy_int64 absdate = asfreq_AtoDT(ordinal, af_info) + ORD_OFFSET;
npy_int64 unix_date = asfreq_AtoDT(ordinal, af_info);
int roll_back = af_info->is_end;
dInfoCalc_SetFromAbsDate(&dinfo, absdate);
dInfoCalc_SetFromAbsDate(&dinfo, unix_date);

return DtoB(&dinfo, roll_back, absdate);
return DtoB(&dinfo, roll_back, unix_date);
}

static npy_int64 nofunc(npy_int64 ordinal, asfreq_info *af_info) {
Expand Down
24 changes: 0 additions & 24 deletions pandas/_libs/src/period_helper.h
Expand Up @@ -20,32 +20,8 @@ frequency conversion routines.
#include "limits.h"
#include "numpy/ndarraytypes.h"

/*
* declarations from period here
*/

#define Py_Error(errortype, errorstr) \
{ \
PyErr_SetString(errortype, errorstr); \
goto onError; \
}

/*** FREQUENCY CONSTANTS ***/

// HIGHFREQ_ORIG is the datetime ordinal from which to begin the second
// frequency ordinal sequence

// #define HIGHFREQ_ORIG 62135683200LL
#define BASE_YEAR 1970
#define ORD_OFFSET 719163LL // days until 1970-01-01
#define BDAY_OFFSET 513689LL // days until 1970-01-01
#define WEEK_OFFSET 102737LL
#define BASE_WEEK_TO_DAY_OFFSET \
1 // difference between day 0 and end of week in days
#define DAYS_PER_WEEK 7
#define BUSINESS_DAYS_PER_WEEK 5
#define HIGHFREQ_ORIG 0 // ORD_OFFSET * 86400LL // days until 1970-01-01

#define FR_ANN 1000 /* Annual */
#define FR_ANNDEC FR_ANN /* Annual - December year end*/
#define FR_ANNJAN 1001 /* Annual - January year end*/
Expand Down