The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#include "Date.h"
#include "DateRel.h"

using panda::util::itoa;

#define TOSTR_START(maxlen)\
    dCheck();\
    int i;\
    static char ret[maxlen+1];\
    char* ptr = ret;\
    char* buf;\
    size_t len;
    
#define TOSTR_DEL(char) *(ptr++) = char;

#define TOSTR_VAL2(val)\
    buf = itoa(val);\
    len = strlen(buf);\
    if ((val) < 10) *(ptr++) = '0';\
    for (i = 0; i < len; i++) *(ptr++) = *(buf++);

#define TOSTR_YEAR\
    buf = itoa(_date.year);\
    len = strlen(buf);\
    if (_date.year >= 0 && _date.year <= 999) for (i = 0; i < 4 - len; i++) *(ptr++) = '0';\
    for (i = 0; i < len; i++) *(ptr++) = *(buf++);
    
#define TOSTR_MONTH TOSTR_VAL2(_date.mon+1)
#define TOSTR_DAY   TOSTR_VAL2(_date.mday)
#define TOSTR_HOUR  TOSTR_VAL2(_date.hour)
#define TOSTR_MIN   TOSTR_VAL2(_date.min)
#define TOSTR_SEC   TOSTR_VAL2(_date.sec)

#define TOSTR_AMPM\
    *(ptr++) = _date.hour < 12 ? 'A' : 'P';\
    *(ptr++) = 'M';

#define TOSTR_END\
    *(ptr++) = 0;\
    return ret;

namespace panda { namespace date {

void Date::eSync () { // w/o date normalization
    _hasEpoch = true;
    _epoch = itimeanyl(&_date, _zone);
}

void Date::dSync () {
    _normalized = true;
    if (_hasEpoch) { // no date -> calculate from epoch
        _hasDate = true;
        ianytime(_epoch, &_date, _zone);
    } else { // no epoch -> normalize from date (set epoch as a side effect as well)
        _hasEpoch = true;
        _epoch = itimeany(&_date, _zone);
    }
}

err_t Date::validateRange () {
    datetime old = _date;
    dSync();
    
    if (old.sec != _date.sec || old.min != _date.min || old.hour != _date.hour || old.mday != _date.mday ||
        old.mon != _date.mon || old.year != _date.year) {
        _error = E_RANGE;
        return E_RANGE;
    }
    
    return E_OK;
}

int Date::compare (Date* operand) {
    if (_zone != operand->_zone) return epoch_cmp(epoch(), operand->epoch());
    else if (_hasEpoch && operand->_hasEpoch) return epoch_cmp(_epoch, operand->_epoch);
    else return date_cmp(date(), operand->date());
}

Date* Date::add (const DateRel* operand) {
    dCheck();
    _date.sec  += operand->sec();
    _date.min  += operand->min();
    _date.hour += operand->hour();
    _date.mday += operand->day();
    _date.mon  += operand->month();
    _date.year += operand->year();
    dChgAuto();
    return this;
}

Date* Date::subtract (const DateRel* operand) {
    dCheck();
    _date.sec  -= operand->sec();
    _date.min  -= operand->min();
    _date.hour -= operand->hour();
    _date.mday -= operand->day();
    _date.mon  -= operand->month();
    _date.year -= operand->year();
    dChgAuto();
    return this;
}

char* Date::strftime (const char* format, char* buf, size_t maxsize) {
    dCheck();
    static char defbuf[1000];
    if (buf == NULL) {
        buf = defbuf;
        maxsize = 1000;
    }
    size_t reslen = panda::time::strftime(buf, maxsize, format, &_date);
    return reslen > 0 ? buf : NULL;
}

const char* Date::iso () {
    TOSTR_START(50);
    TOSTR_YEAR; TOSTR_DEL('-'); TOSTR_MONTH; TOSTR_DEL('-'); TOSTR_DAY; TOSTR_DEL(' ');
    TOSTR_HOUR; TOSTR_DEL(':'); TOSTR_MIN; TOSTR_DEL(':'); TOSTR_SEC;
    TOSTR_END;
}

const char* Date::mysql () {
    TOSTR_START(45);
    TOSTR_YEAR; TOSTR_MONTH; TOSTR_DAY; TOSTR_HOUR; TOSTR_MIN; TOSTR_SEC;
    TOSTR_END;
}

const char* Date::hms () {
    TOSTR_START(8); TOSTR_HOUR; TOSTR_DEL(':'); TOSTR_MIN; TOSTR_DEL(':'); TOSTR_SEC; TOSTR_END;
}

const char* Date::ymd () {
    TOSTR_START(41); TOSTR_YEAR; TOSTR_DEL('/'); TOSTR_MONTH; TOSTR_DEL('/'); TOSTR_DAY; TOSTR_END;
}

const char* Date::mdy () {
    TOSTR_START(41); TOSTR_MONTH; TOSTR_DEL('/'); TOSTR_DAY; TOSTR_DEL('/'); TOSTR_YEAR; TOSTR_END;
}

const char* Date::dmy () {
    TOSTR_START(41); TOSTR_DAY; TOSTR_DEL('/'); TOSTR_MONTH; TOSTR_DEL('/'); TOSTR_YEAR; TOSTR_END;
}

const char* Date::meridiam () {
    TOSTR_START(8);
    int hour = _date.hour % 12;
    if (hour == 0) hour = 12;
    TOSTR_VAL2(hour); TOSTR_DEL(':'); TOSTR_MIN; TOSTR_DEL(' '); TOSTR_AMPM;
    TOSTR_END;
}

const char* Date::ampm () {
    dCheck();
    return _date.hour < 12 ? "AM" : "PM";
}

const char* Date::errstr () const {
    switch (_error) {
        case E_OK:
            return NULL;
        case E_UNPARSABLE:
            return "can't parse date string";
        case E_RANGE:
            return "input date is out of range";
        default:
            return "unknown error";
    }
}

/////////   STATIC   ///////////////////////////////
char Date::_strfmt[] = "";
bool Date::_rangeCheck = false;

const char* Date::stringFormat () {
    if (_strfmt[0] == '\0') return NULL;
    return _strfmt;
}

void Date::stringFormat (const char* fmt) {
    if (fmt == NULL) _strfmt[0] = '\0';
    else strncpy(_strfmt, fmt, MAX_FMT);
}

};};