#define PERL_POLLUTE #include #include #include static UV dim[14] = { 31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 31, 28 }; static IV tweak[12] = { 1, 2, 4, 5, 7, 8, 9, 11, 12, 14, 15, 16 }; static IV cum_days[12] = { -1, 30, 58, 89, 119, 150, 180, 211, 242, 272, 303, 333 }; static bool is_leap_year (IV y) { return (y % 4 == 0) && ((y % 100 != 0) || (y % 400 == 0)); } static IV days_in_month (IV month, IV year) { IV ret = dim [ month - 1 ]; if (ret == 0) ret = is_leap_year (year) ? 29 : 28; return ret; } /* Compute the number of days since 1970. */ static bool ymd_to_days (IV y, IV m, IV d, IV* days) { IV x; IV nonleap_days; IV leap_days_4; IV leap_holes_100; IV leap_days_400; if (m < 1 || m > 12 || d < 1 || (d > 28 && d > days_in_month (m, y))) return FALSE; x = (m <= 2 ? y - 1 : y); nonleap_days = d + cum_days [m - 1] + 365 * (y - 1970); leap_days_4 = (x - 1968) >> 2; if (x >= 1900) leap_holes_100 = (x - 1900) / 100; else leap_holes_100 = - (1999 - x) / 100; if (x >= 1600) leap_days_400 = (x - 1600) / 400; else leap_days_400 = - (1999 - x) / 400; *days = nonleap_days + leap_days_4 - leap_holes_100 + leap_days_400; return TRUE; } /* Compute year, month, and day given days_since_1970. */ static void days_to_ymd (IV days, IV ymd[3]) { IV year; IV month, day, quot; /* Shift frame of reference from 1 Jan 1970 to (the imaginary) 1 Mar 0AD. */ days += 719468; /* Do the math. */ quot = days / 146097; days -= 146097 * quot; year = 400 * quot; if (days == 146096) { /* Handle 29 Feb 2000, 2400, ... */ year += 400; month = 2; day = 29; } else { quot = days / 36524; days -= 36524 * quot; year += 100 * quot; quot = days / 1461; days -= 1461 * quot; year += 4 * quot; if (days == 1460) { year += 4; month = 2; day = 29; } else { quot = days / 365; days -= 365 * quot; year += quot; quot = days / 32; days -= 32 * quot; month = quot; day = days + tweak [month]; days = dim [month + 2]; if (day > days) { day -= days; month += 1; } if (month > 9) { month -= 9; year += 1; } else month += 3; } } ymd[0] = year; ymd[1] = month; ymd[2] = day; } static bool d8_to_days (SV* d8, IV* days) { char buf[5]; STRLEN len; char* p; p = SvPV(d8, len); if (len == 8) { while (len > 0) { if (!isDIGIT(p[len - 1])) break; len--; } if (len != 0) return FALSE; } else return FALSE; return ymd_to_days(10*(10*(10*(p[0]-'0')+p[1]-'0')+p[2]-'0')+p[3]-'0', 10*(p[4]-'0')+p[5]-'0', 10*(p[6]-'0')+p[7]-'0', days); } static SV* days_to_date (IV days, SV* pkg) { char* pack=0; if (SvROK (pkg)) { HV* stash; stash=SvSTASH(SvRV(pkg)); return sv_bless( newRV_noinc (newSViv (days)), stash ); } else if (SvTRUE(pkg)) { pack=SvPV_nolen(pkg); } return sv_bless( newRV_noinc (newSViv (days)), gv_stashpv (pack == 0 ? "Date::Simple" : pack, 1)); } static int is_object (SV* sv) { return (SvROK (sv) && SvTYPE (SvRV (sv)) == SVt_PVMG); } static SV* new_for_cmp (SV* left, SV* right, int croak_on_fail) { dSP; SV* ret; /* Comparing date with non-date. Try to convert the right side to a date. */ EXTEND (sp, 2); PUSHMARK(sp); PUSHs (left); PUSHs (right); PUTBACK; perl_call_method (croak_on_fail ? "new" : "_new", G_SCALAR); SPAGAIN; ret = POPs; if (croak_on_fail && ! is_object (ret)) { PUSHMARK(sp); PUSHs (left); PUSHs (right); PUTBACK; perl_call_pv ("Date::Simple::_inval", G_VOID); SPAGAIN; } return ret; } MODULE = Date::Simple PACKAGE = Date::Simple SV* _ymd(obj_or_class, y, m, d) SV* obj_or_class IV y IV m IV d CODE: { IV days; if (ymd_to_days (y, m, d, &days)) RETVAL = days_to_date (days, obj_or_class); else XSRETURN_UNDEF; } OUTPUT: RETVAL SV* _d8(obj_or_class, d8) SV* obj_or_class SV* d8 CODE: { IV days; if (d8_to_days (d8, &days)) RETVAL = days_to_date (days, obj_or_class); else XSRETURN_UNDEF; } OUTPUT: RETVAL bool leap_year(y) IV y CODE: { RETVAL = is_leap_year (y); } OUTPUT: RETVAL IV days_in_month(y, m) IV y IV m CODE: { if (m < 1 || m > 12) croak ("days_in_month: month out of range (%d)", (int) m); RETVAL = days_in_month (m, y); } OUTPUT: RETVAL IV validate(ysv, m, d) SV* ysv IV m IV d CODE: { IV y; y = SvIV (ysv); if ((IV) SvNV (ysv) != y) RETVAL = 0; else if (m < 1 || m > 12) RETVAL = 0; else if (d < 1 || d > days_in_month (m, y)) RETVAL = 0; else RETVAL = 1; } OUTPUT: RETVAL void ymd_to_days(y, m, d) IV y IV m IV d CODE: { IV days; if (! ymd_to_days (y, m, d, &days)) XSRETURN_UNDEF; else XSRETURN_IV (days); } SV* days_since_1970(date) SV* date CODE: { if (SvROK(date)) RETVAL = SvREFCNT_inc (SvRV(date)); else XSRETURN_UNDEF; } OUTPUT: RETVAL void days_to_ymd(days) IV days PPCODE: { IV ymd[3]; days_to_ymd (days, ymd); EXTEND (sp, 3); PUSHs (sv_2mortal (newSViv (ymd[0]))); PUSHs (sv_2mortal (newSViv (ymd[1]))); PUSHs (sv_2mortal (newSViv (ymd[2]))); } IV year(date) SV* date CODE: { IV ymd[3]; if (! SvROK (date)) XSRETURN_UNDEF; days_to_ymd (SvIV (SvRV (date)), ymd); RETVAL = ymd[0]; } OUTPUT: RETVAL IV month(date) SV* date CODE: { IV ymd[3]; if (! SvROK (date)) XSRETURN_UNDEF; days_to_ymd (SvIV (SvRV (date)), ymd); RETVAL = ymd[1]; } OUTPUT: RETVAL IV day(date) SV* date CODE: { IV ymd[3]; if (! SvROK (date)) XSRETURN_UNDEF; days_to_ymd (SvIV (SvRV (date)), ymd); RETVAL = ymd[2]; } OUTPUT: RETVAL SV* as_iso(date, ...) SV* date CODE: { IV ymd[3]; if (! SvROK (date)) XSRETURN_UNDEF; days_to_ymd (SvIV (SvRV (date)), ymd); RETVAL = newSVpvf ("%04d-%02d-%02d", ymd[0] % 10000, ymd[1], ymd[2]); } OUTPUT: RETVAL SV* as_d8(date, ...) SV* date CODE: { IV ymd[3]; if (! SvROK (date)) XSRETURN_UNDEF; days_to_ymd (SvIV (SvRV (date)), ymd); RETVAL = newSVpvf ("%04d%02d%02d", ymd[0] % 10000, ymd[1], ymd[2]); } OUTPUT: RETVAL void as_ymd(date) SV* date PPCODE: { IV ymd[3]; if (! SvROK (date)) XSRETURN_EMPTY; days_to_ymd (SvIV (SvRV (date)), ymd); EXTEND (sp, 3); PUSHs (sv_2mortal (newSViv (ymd[0]))); PUSHs (sv_2mortal (newSViv (ymd[1]))); PUSHs (sv_2mortal (newSViv (ymd[2]))); } SV* _add(date, diff, ...) SV* date IV diff CODE: { IV days; if (! is_object (date)) XSRETURN_UNDEF; days = SvIV (SvRV (date)) + diff; RETVAL = sv_bless (newRV_noinc (newSViv (days)), SvSTASH (SvRV (date))); } OUTPUT: RETVAL SV* _subtract(left, right, reverse) SV* left SV* right SV* reverse CODE: { if (! is_object (left)) XSRETURN_UNDEF; if (SvTRUE (reverse)) croak ("Can't subtract a date from a non-date"); if (SvROK (right)) { IV diff = SvIV (SvRV (left)) - SvIV (SvRV (right)); RETVAL = newSViv (diff); } else { IV days = SvIV (SvRV (left)) - SvIV (right); RETVAL = sv_bless (newRV_noinc (newSViv (days)), SvSTASH (SvRV (left))); } } OUTPUT: RETVAL IV _compare(left, right, reverse) SV* left SV* right bool reverse CODE: { IV diff; if (! is_object (left)) XSRETURN_UNDEF; if (! is_object (right)) right = new_for_cmp (left, right, 1); diff = SvIV (SvRV (left)) - SvIV (SvRV (right)); RETVAL = diff > 0 ? 1 : (diff < 0 ? -1 : 0); if (reverse) RETVAL = -RETVAL; } OUTPUT: RETVAL SV* _eq(left, right, reverse) SV* left SV* right bool reverse CODE: { if (! is_object (left)) XSRETURN_UNDEF; if (! is_object (right)) right = new_for_cmp (left, right, 0); if (! is_object (right)) XSRETURN_NO; if (SvIV (SvRV (left)) == SvIV (SvRV (right))) XSRETURN_YES; else XSRETURN_NO; } OUTPUT: RETVAL SV* _ne(left, right, reverse) SV* left SV* right bool reverse CODE: { if (! is_object (left)) XSRETURN_UNDEF; if (! is_object (right)) right = new_for_cmp (left, right, 0); if (! is_object (right)) XSRETURN_YES; if (SvIV (SvRV (left)) == SvIV (SvRV (right))) XSRETURN_NO; else XSRETURN_YES; } OUTPUT: RETVAL IV day_of_week(date) SV* date CODE: { IV days; if (! SvROK (date)) XSRETURN_UNDEF; RETVAL = (SvIV (SvRV (date)) + 4) % 7; if (RETVAL < 0) RETVAL += 7; } OUTPUT: RETVAL