The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
MODULE = Panda::Date                PACKAGE = Panda::Date
PROTOTYPES: DISABLE

#///////////////////////////// STATIC FUNCTIONS ///////////////////////////////////

Date* now () {
    static SV* CLASS = newSVpv_share(DATE_CLASS, 0);
    RETVAL = Date::now();
}

Date* today () {
    static SV* CLASS = newSVpv_share(DATE_CLASS, 0);
    RETVAL = Date::today();
}

ptime_t today_epoch () {
    datetime date;
    localtime(time(NULL), &date);
    date.sec = 0;
    date.min = 0;
    date.hour = 0;
    RETVAL = timelocall(&date);
}

Date* date (SV* date = NULL, SV* zone = NULL) {
    static SV* CLASS = newSVpv_share(DATE_CLASS, 0);
    if (date) RETVAL = date_new(aTHX_ date, tzget_optional(aTHX_ zone));
    else      RETVAL = new Date();
}

const char* string_format (SV* newval = NULL) {
    if (newval) {
        if (SvOK(newval) && SvTRUE(newval)) Date::string_format(SvPV_nolen(newval));
        else Date::string_format(NULL);
    }
    RETVAL = Date::string_format();
}    

bool range_check (SV* newval = NULL) {
    if (newval) Date::range_check(SvTRUE(newval));
    RETVAL = Date::range_check();
}

#///////////////////////////// OBJECT METHODS ///////////////////////////////////

Date* Date::new (SV* date = NULL, SV* zone = NULL) {
    if (date) RETVAL = date_new(aTHX_ date, tzget_optional(aTHX_ zone));
    else      RETVAL = new Date();
}

void Date::set (SV* arg, SV* zone = NULL) {
    date_set(aTHX_ arg, tzget_optional(aTHX_ zone), THIS);
}

ptime_t Date::epoch (SV* newval = NULL) {
    if (newval) THIS->epoch(SvMIV(newval));
    RETVAL = THIS->epoch();
}    
    
int32_t Date::year (SV* newval = NULL) {
    if (newval) THIS->year(SvMIV(newval));
    RETVAL = THIS->year();
}    

int32_t Date::_year (SV* newval = NULL) {
    if (newval) THIS->_year(SvMIV(newval));
    RETVAL = THIS->_year();
}

int8_t Date::yr (SV* newval = NULL) {
    if (newval) THIS->yr(SvMIV(newval));
    RETVAL = THIS->yr();
}

uint8_t Date::month (SV* newval = NULL) : ALIAS(mon=1) {
    if (newval) THIS->month(SvMIV(newval));
    RETVAL = THIS->month();
    PERL_UNUSED_VAR(ix);
}

uint8_t Date::_month (SV* newval = NULL) : ALIAS(_mon=1) {
    if (newval) THIS->_month(SvMIV(newval));
    RETVAL = THIS->_month();
    PERL_UNUSED_VAR(ix);
}    

uint8_t Date::day (SV* newval = NULL) : ALIAS(mday=1, day_of_month=2) {
    if (newval) THIS->day(SvMIV(newval));
    RETVAL = THIS->day();
    PERL_UNUSED_VAR(ix);
}

uint8_t Date::hour (SV* newval = NULL) {
    if (newval) THIS->hour(SvMIV(newval));
    RETVAL = THIS->hour();
}

uint8_t Date::min (SV* newval = NULL) : ALIAS(minute=1) {
    if (newval) THIS->min(SvMIV(newval));
    RETVAL = THIS->min();
    PERL_UNUSED_VAR(ix);
}

uint8_t Date::sec (SV* newval = NULL) : ALIAS(second=1) {
    if (newval) THIS->sec(SvMIV(newval));
    RETVAL = THIS->sec();
    PERL_UNUSED_VAR(ix);
}

uint8_t Date::wday (SV* newval = NULL) : ALIAS(day_of_week=1) {
    if (newval) THIS->wday(SvMUV(newval));
    RETVAL = THIS->wday();
    PERL_UNUSED_VAR(ix);
}    

uint8_t Date::_wday (SV* newval = NULL) {
    if (newval) THIS->_wday(SvMUV(newval));
    RETVAL = THIS->_wday();
}

uint8_t Date::ewday (SV* newval = NULL) {
    if (newval) THIS->ewday(SvMUV(newval));
    RETVAL = THIS->ewday();
}

uint16_t Date::yday (SV* newval = NULL) : ALIAS(day_of_year=1) {
    if (newval) THIS->yday(SvMUV(newval));
    RETVAL = THIS->yday();
    PERL_UNUSED_VAR(ix);
}

uint16_t Date::_yday (SV* newval = NULL) {
    if (newval) THIS->_yday(SvMUV(newval));
    RETVAL = THIS->_yday();
}

bool Date::isdst () : ALIAS(daylight_savings=1) {
    RETVAL = THIS->isdst();
    PERL_UNUSED_VAR(ix);
}

const char* Date::to_string (...) : ALIAS(as_string=1, string=2) {
    RETVAL = THIS->toString();
    PERL_UNUSED_VAR(ix);
}    

bool Date::to_bool (...) {
    RETVAL = THIS->error() == E_OK ? true : false;
}

ptime_t Date::to_number (...) {
    RETVAL = THIS->error() == E_OK ? THIS->epoch() : 0;
}

const char* Date::strftime (const char* format) {
    RETVAL = THIS->strftime(format, NULL, 0);
}

const char* Date::monthname () : ALIAS(monname=1) {
    RETVAL = THIS->strftime("%B", NULL, 0);
    PERL_UNUSED_VAR(ix);
}

const char* Date::wdayname () : ALIAS(day_of_weekname=1) {
    RETVAL = THIS->strftime("%A", NULL, 0);
    PERL_UNUSED_VAR(ix);
}

const char* Date::iso () : ALIAS(sql=1) {
    RETVAL = THIS->iso();
    PERL_UNUSED_VAR(ix);
}

const char* Date::mysql ()

const char* Date::hms ()

const char* Date::ymd ()

const char* Date::mdy ()

const char* Date::dmy ()

const char* Date::ampm ()

const char* Date::meridiam ()

int Date::gmtoff ()

const char* Date::tzabbr ()

string Date::tzname () {
    RETVAL = THIS->timezone()->name;
}

bool Date::tzlocal () {
    RETVAL = THIS->timezone()->is_local;
}

HV* Date::tz (SV* newzone = NULL) : ALIAS(timezone=1, zone=2) {
    if (newzone) {
        THIS->timezone(tzget_required(aTHX_ newzone));
        XSRETURN_UNDEF;
    }
    RETVAL = export_timezone(aTHX_ THIS->timezone());
    PERL_UNUSED_VAR(ix);
}

void Date::to_tz (SV* newzone) : ALIAS(to_timezone=1, to_zone=2) {
    THIS->to_timezone(tzget_required(aTHX_ newzone));
    PERL_UNUSED_VAR(ix);
}

void Date::array () {
    EXTEND(SP, 6);
    mPUSHi(THIS->year());
    mPUSHu(THIS->month());
    mPUSHu(THIS->day());
    mPUSHu(THIS->hour());
    mPUSHu(THIS->min());
    mPUSHu(THIS->sec());
    XSRETURN(6);
}

AV* Date::aref () {
    RETVAL = newAV();
    av_extend(RETVAL, 5);
    av_store(RETVAL, 0, newSViv(THIS->year()));
    av_store(RETVAL, 1, newSVuv(THIS->month()));
    av_store(RETVAL, 2, newSVuv(THIS->day()));
    av_store(RETVAL, 3, newSVuv(THIS->hour()));
    av_store(RETVAL, 4, newSVuv(THIS->min()));
    av_store(RETVAL, 5, newSVuv(THIS->sec()));
}

void Date::struct () {
    EXTEND(SP, 9);
    mPUSHu(THIS->sec());
    mPUSHu(THIS->min());
    mPUSHu(THIS->hour());
    mPUSHu(THIS->day());
    mPUSHu(THIS->_month());
    mPUSHi(THIS->_year());
    mPUSHu(THIS->_wday());
    mPUSHu(THIS->_yday());
    mPUSHu(THIS->isdst() ? 1 : 0);
    XSRETURN(9);
}

AV* Date::sref () {
    RETVAL = newAV();
    av_extend(RETVAL, 8);
    av_store(RETVAL, 0, newSVuv(THIS->sec()));
    av_store(RETVAL, 1, newSVuv(THIS->min()));
    av_store(RETVAL, 2, newSVuv(THIS->hour()));
    av_store(RETVAL, 3, newSVuv(THIS->day()));
    av_store(RETVAL, 4, newSVuv(THIS->_month()));
    av_store(RETVAL, 5, newSViv(THIS->_year()));
    av_store(RETVAL, 6, newSVuv(THIS->_wday()));
    av_store(RETVAL, 7, newSVuv(THIS->_yday()));
    av_store(RETVAL, 8, newSVuv(THIS->isdst() ? 1 : 0));
}

void Date::hash () {
    EXTEND(SP, 12);
    mPUSHp("year", 4);
    mPUSHi(THIS->year());
    mPUSHp("month", 5);
    mPUSHu(THIS->month());
    mPUSHp("day", 3);
    mPUSHu(THIS->day());
    mPUSHp("hour", 4);
    mPUSHu(THIS->hour());
    mPUSHp("min", 3);
    mPUSHu(THIS->min());
    mPUSHp("sec", 3);
    mPUSHu(THIS->sec());
    XSRETURN(12);
}

HV* Date::href () {
    RETVAL = newHV();
    hv_store(RETVAL, "year",  4, newSViv(THIS->year()), 0);
    hv_store(RETVAL, "month", 5, newSVuv(THIS->month()), 0);
    hv_store(RETVAL, "day",   3, newSVuv(THIS->day()), 0);
    hv_store(RETVAL, "hour",  4, newSVuv(THIS->hour()), 0);
    hv_store(RETVAL, "min",   3, newSVuv(THIS->min()), 0);
    hv_store(RETVAL, "sec",   3, newSVuv(THIS->sec()), 0);
}

Date* Date::clone (SV* diff = NULL, SV* zoneSV = NULL) {
    HV* CLASS = SvSTASH(SvRV(ST(0)));
    if (diff) {
        const Timezone* zone = tzget_optional(aTHX_ zoneSV);
        if (SvROK(diff)) RETVAL = date_clone(aTHX_ diff, zone, THIS);
        else RETVAL = THIS->clone(zone);
    }
    else RETVAL = THIS->clone();
}

SV* Date::month_begin () {
    THIS->month_begin();
    XSRETURN(1);
}

Date* Date::month_begin_new () {
    HV* CLASS = SvSTASH(SvRV(ST(0)));
    RETVAL = THIS->month_begin_new();
}

SV* Date::month_end () {
    THIS->month_end();
    XSRETURN(1);
}

Date* Date::month_end_new () {
    HV* CLASS = SvSTASH(SvRV(ST(0)));
    RETVAL = THIS->month_end_new();
}

int Date::days_in_month () {
    RETVAL = THIS->days_in_month();
}

uint8_t Date::error () {
    RETVAL = (uint8_t) THIS->error();
}

const char* Date::errstr ()

SV* Date::truncate () {
    THIS->truncate();
    XSRETURN(1);
}    

Date* Date::truncate_new () {
    HV* CLASS = SvSTASH(SvRV(ST(0)));
    RETVAL = THIS->truncate_new();
}

int Date::compare (SV* arg, bool reverse = false) {
    if (sv_isobject(arg)) {
        if (sv_isa(arg, DATE_CLASS)) RETVAL = THIS->compare(typemap_incast<Date*>(arg));
        else croak("Panda::Date: cannot '<=>' or 'cmp' - object isn't a Panda::Date object");
    }
    else {
        Date tmp((ptime_t) 0);
        date_set(aTHX_ arg, THIS->timezone(), &tmp);
        RETVAL = THIS->compare(tmp);
    }
    
    if (reverse) RETVAL = -RETVAL;
}

Date* Date::add_new (SV* arg, ...) {
    HV* CLASS = SvSTASH(SvRV(ST(0)));
    if (sv_isobject(arg)) {
        if (sv_isa(arg, DATEREL_CLASS)) RETVAL = THIS->add_new(typemap_incast<DateRel*>(arg));
        else croak("Panda::Date: cannot '+' - object isn't a Panda::Date::Rel object");
    }
    else {
        DateRel tmp;
        daterel_set(aTHX_ arg, &tmp);
        RETVAL = THIS->add_new(&tmp);
    }
}

SV* Date::add (SV* arg, ...) {
    if (sv_isobject(arg)) {
        if (sv_isa(arg, DATEREL_CLASS)) THIS->add(typemap_incast<DateRel*>(arg));
        else croak("Panda::Date: cannot '+=' - object isn't a Panda::Date::Rel object");
    }
    else {
        DateRel tmp;
        daterel_set(aTHX_ arg, &tmp);
        THIS->add(&tmp);
    }
    XSRETURN(1);
}

SV* Date::subtract_new (SV* arg, bool reverse = false) {
    if (sv_isobject(arg)) { // reverse is impossible here
        if (sv_isa(arg, DATEREL_CLASS)) {
            RETVAL = typemap_outcast<Date*, HV* CLASS>(THIS->subtract_new(typemap_incast<DateRel*>(arg)), SvSTASH(SvRV(ST(0))));
        }
        else if (sv_isa(arg, DATE_CLASS)) {
            RETVAL = typemap_outcast<DateInt*, const char* CLASS>(new DateInt(typemap_incast<Date*>(arg), THIS), DATEINT_CLASS);
        }
        else croak("Panda::Date: cannot '-' unsupported object type");
    }
    else if (reverse) { // only date supported for reverse
        Date tmp((ptime_t) 0);
        date_set(aTHX_ arg, THIS->timezone(), &tmp);
        RETVAL = typemap_outcast<DateInt*, const char* CLASS>(new DateInt(THIS, &tmp), DATEINT_CLASS);
    }
    else if (looks_like_number(arg)) {
        Date* ret = THIS->clone();
        RETVAL = typemap_outcast<Date*, HV* CLASS>(ret, SvSTASH(SvRV(ST(0))));
        ret->epoch(THIS->epoch() - SvMIV(arg));
    }
    else { // date or rdate scalar
        const char* argstr = SvPV_nolen(arg);
        if (looks_like_relative(argstr)) { // not a date -> reldate
            DateRel tmp;
            daterel_set(aTHX_ arg, &tmp);
            RETVAL = typemap_outcast<Date*, HV* CLASS>(THIS->subtract_new(&tmp), SvSTASH(SvRV(ST(0))));
        } else { // date
            Date tmp((ptime_t) 0);
            date_set(aTHX_ arg, THIS->timezone(), &tmp);
            RETVAL = typemap_outcast<DateInt*, const char* CLASS>(new DateInt(&tmp, THIS), DATEINT_CLASS);
        }
    }
}

SV* Date::subtract (SV* arg, ...) {
    if (sv_isobject(arg)) {
        if (sv_isa(arg, DATEREL_CLASS)) THIS->subtract(typemap_incast<DateRel*>(arg));
        else croak("Panda::Date: cannot '-=' unsupported object type");
    }
    else {
        DateRel tmp;
        daterel_set(aTHX_ arg, &tmp);
        THIS->subtract(&tmp);
    }
    XSRETURN(1);
}

void Date::DESTROY ()