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

#ifdef TEST_FULL

#ifdef TEST_INLINE
#define GMTIME(a,b)    igmtime(a,b)
#define TIMEGM(a)      itimegm(a)
#define TIMEGML(a)     itimegml(a)
#define LOCALTIME(a,b) ilocaltime(a,b)
#define TIMELOCAL(a)   itimelocal(a)
#define TIMELOCALL(a)  itimelocall(a)
#else
#define GMTIME(a,b)    gmtime(a,b)
#define TIMEGM(a)      timegm(a)
#define TIMEGML(a)     timegml(a)
#define LOCALTIME(a,b) localtime(a,b)
#define TIMELOCAL(a)   timelocal(a)
#define TIMELOCALL(a)  timelocall(a)
#endif

void
bench_my (const char* str)
PPCODE:
    XSRETURN_UNDEF;

HVR
test_my (const char* str)
CODE:
    RETVAL = newHV();
OUTPUT:
    RETVAL
    

bool
test_gmtime (ptime_t step, ptime_t from, ptime_t till)
ALIAS:
    test_localtime = 1
CODE:
    datetime date1;
    struct tm date2;
    
    const char* tzname = ix == 0 ? "GMT" : tzlocal()->name;
    char* hstr = getenv("HARNESS_ACTIVE");
    bool test = (hstr != NULL && strlen(hstr) > 0);
    
    static bool sranded = false;
    if (!sranded) { srand(time(NULL)); sranded = true; }

    bool isrand = false;
    ptime_t disperse, epoch;
    if (step == 0) {
        isrand = true; 
        disperse = from;
        step = 1;
        from = 0;
    }
    
    int cnt = 0;
    for (ptime_t i = from; i < till; i += step) {
        cnt++;
        bzero(&date1, sizeof(date1));
        bzero(&date2, sizeof(date2));
        
        if (isrand) epoch = rand() % (2*disperse) - disperse;
        else epoch = i;
        time_t sys_epoch = (time_t) epoch;
        
        if (ix == 0) {
            gmtime(epoch, &date1);
            gmtime_r(&sys_epoch, &date2);
        } else {
            localtime(epoch, &date1);
            localtime_r(&sys_epoch, &date2);
        }
        
        if (cnt % 100000 == 0 && !test) printf("TESTED #%d, last %04i-%02i-%02i %02i:%02i:%02i (off:%ld, dst=%d, zone=%s)\n", cnt, date2.tm_year+1900, date2.tm_mon+1, date2.tm_mday, date2.tm_hour, date2.tm_min, date2.tm_sec, date2.tm_gmtoff, date2.tm_isdst, date2.tm_zone);

        if (date1.year != (date2.tm_year + 1900) || date1.mon != date2.tm_mon || date1.mday != date2.tm_mday ||
            date1.hour != date2.tm_hour || date1.min != date2.tm_min || date1.sec != date2.tm_sec ||
            date1.isdst != date2.tm_isdst || date1.gmtoff != date2.tm_gmtoff || strcmp(date1.zone, date2.tm_zone) != 0) {
            warn(
                "zone=%s, epoch=%lli, got %d-%02lld-%02lld %02lld:%02lld:%02lld %d %d %d %d %s, should be %d-%02d-%02d %02d:%02d:%02d %d %d %d %ld %s",
                tzname, epoch,
                date1.year, date1.mon+1, date1.mday, date1.hour, date1.min, date1.sec, date1.wday, date1.yday, date1.isdst, date1.gmtoff, date1.zone,
                date2.tm_year+1900, date2.tm_mon+1, date2.tm_mday, date2.tm_hour, date2.tm_min, date2.tm_sec, date2.tm_wday, date2.tm_yday, date2.tm_isdst, date2.tm_gmtoff, date2.tm_zone
            );
            XSRETURN_UNDEF;
        }
    }
    
    if (!test) printf("TESTED %d TIMES\n", cnt);
    
    RETVAL = true;
OUTPUT:
    RETVAL


bool
test_timegm (ptime_t step, ptime_t from, ptime_t till)
ALIAS:
    test_timelocal = 1
CODE:
    datetime date1;
    struct tm date2;
    
    char* hstr = getenv("HARNESS_ACTIVE");
    bool test = (hstr != NULL && strlen(hstr) > 0);
    
    static bool sranded = false;
    if (!sranded) { srand(time(NULL)); sranded = true; }

    bool isrand = false;
    ptime_t disperce_years;
    if (step == 0) {
        isrand = true; 
        step = 1;
        disperce_years = from;
        if (disperce_years > 200) disperce_years = 200;
        from = 0;
    }
    
    int cnt = 0;
    for (ptime_t i = from; step > 0 ? (i < till) : (i > till); i += step) {
        cnt++;
        bzero(&date1, sizeof(date1));
        bzero(&date2, sizeof(date2));
        
        if (isrand) {
            if (ix == 0) {
                int rnum = rand();
                date1.sec = rnum % 10000 - 5000;
                rnum /= 1000;
                date1.min = rnum % 10000 - 5000;
                rnum /= 1000;
                date1.hour = rnum % 100 - 50;
                rnum = rand();
                date1.mday = rnum % 100 - 50;
                rnum /= 100;
                date1.mon = rnum % 100 - 50;
                rnum /= 100;
                date1.year = rnum % 200 + 1910;
            } else { // dont test denormalized values (LINUX has bugs with them + when using leap seconds zone, our normalization may differ with OS)
                int rnum = rand();
                date1.sec = rnum % 60;
                rnum /= 1000;
                date1.min = rnum % 60;
                rnum /= 1000;
                date1.hour = rnum % 24;
                rnum = rand();
                date1.mday = rnum % 31 + 1;
                rnum /= 100;
                date1.mon = rnum % 11;
                rnum /= 100;
                date1.year = rnum % disperce_years + 1910;
            }
        }
        else {
            if (ix == 0) gmtime(i, &date1);
            else localtime(i, &date1);
        }

        if (ix == 1) date1.isdst = -1;
        dt2tm(date2, date1);
        
        datetime copy1 = date1;
        struct tm copy2 = date2;
        
        ptime_t mytime;
        time_t truetime;
        if (ix == 0) {
            truetime = timegm(&date2);
            if (isrand) mytime = timegm(&date1); // need normalization
            else mytime = timegml(&date1);
        } else {
            mytime = timelocal(&date1);
            truetime = timelocal(&date2);
        }
        
        if (cnt % 100000 == 0 && !test) printf("TESTED #%d, last %04d-%02d-%02d %02d:%02d:%02d\n", cnt, date2.tm_year+1900, date2.tm_mon+1, date2.tm_mday, date2.tm_hour, date2.tm_min, date2.tm_sec);
        
        bool same_ymdhms = (date1.year != (date2.tm_year + 1900) || date1.mon != date2.tm_mon || date1.mday != date2.tm_mday || date1.hour != date2.tm_hour || date1.min != date2.tm_min || date1.sec != date2.tm_sec) ? false : true;
        bool same_zone = (date1.isdst != date2.tm_isdst || date1.gmtoff != date2.tm_gmtoff || strcmp(date1.zone, date2.tm_zone) != 0) ? false : true;
        bool same_date = same_ymdhms && same_zone;

        if (mytime != truetime || !same_date) {
            if (truetime == -1) continue; // OS cannot handle such dates
            
            if (same_ymdhms && ix == 1) { // if ambiguity, OS may return unpredicted results. Lets handle that.
                datetime tmpdate = date1;
                tmpdate.isdst = 1;
                mytime = timelocal(&tmpdate);
                if (mytime == truetime) continue;
            }

            warn(
                "MY: epoch=%lli (%04d/%02lld/%02lld %02lld:%02lld:%02lld %4s %d) from %04d/%02lld/%02lld %02lld:%02lld:%02lld DST=%d (%s)",
                mytime, date1.year, date1.mon+1, date1.mday, date1.hour, date1.min, date1.sec, date1.zone, date1.gmtoff,
                copy1.year, copy1.mon+1, copy1.mday, copy1.hour, copy1.min, copy1.sec, copy1.isdst, tzlocal()->name
            );
            warn(
                "OS: epoch=%li (%04d/%02d/%02d %02d:%02d:%02d %4s %ld) from %04d/%02d/%02d %02d:%02d:%02d DST=%d (%s)",
                truetime, date2.tm_year+1900, date2.tm_mon+1, date2.tm_mday, date2.tm_hour, date2.tm_min, date2.tm_sec, date2.tm_zone, date2.tm_gmtoff,
                copy2.tm_year+1900, copy2.tm_mon+1, copy2.tm_mday, copy2.tm_hour, copy2.tm_min, copy2.tm_sec, copy2.tm_isdst, tzlocal()->name
            );
            warn("diff is %lli", mytime - truetime);
            XSRETURN_UNDEF;
        }
    }
    
    if (!test) printf("TESTED %d TIMES\n", cnt);
    
    RETVAL = true;
OUTPUT:
    RETVAL


AVR
gmtime_bench (ptime_t epoch)
ALIAS:
    localtime_bench = 1
CODE:
    datetime date;
    ptime_t max_epoch = epoch + 10000;
    if      (ix == 0) while(epoch++ < max_epoch) GMTIME(epoch, &date);
    else if (ix == 1) while(epoch++ < max_epoch) LOCALTIME(epoch, &date);
    
    RETVAL = newAV();
    av_push(RETVAL, newSViv(date.sec));
    av_push(RETVAL, newSViv(date.min));
    av_push(RETVAL, newSViv(date.hour));
    av_push(RETVAL, newSViv(date.mday));
    av_push(RETVAL, newSViv(date.mon));
    av_push(RETVAL, newSViv(date.year));
    av_push(RETVAL, newSViv(date.wday));
    av_push(RETVAL, newSViv(date.yday));
    av_push(RETVAL, newSViv(date.isdst));
    av_push(RETVAL, newSViv(date.gmtoff));
    av_push(RETVAL, newSVpv(date.zone, 0));
OUTPUT:
    RETVAL


AVR
posix_gmtime_bench (time_t epoch)
ALIAS:
    posix_localtime_bench = 1
CODE:
    struct tm date;
    time_t max_epoch = epoch + 10000;
    if      (ix == 0) while(epoch++ < max_epoch) gmtime_r(&epoch, &date);
    else if (ix == 1) while(epoch++ < max_epoch) localtime_r(&epoch, &date);

    RETVAL = newAV();
    av_push(RETVAL, newSViv(date.tm_sec));
    av_push(RETVAL, newSViv(date.tm_min));
    av_push(RETVAL, newSViv(date.tm_hour));
    av_push(RETVAL, newSViv(date.tm_mday));
    av_push(RETVAL, newSViv(date.tm_mon));
    av_push(RETVAL, newSViv(date.tm_year));
    av_push(RETVAL, newSViv(date.tm_wday));
    av_push(RETVAL, newSViv(date.tm_yday));
    av_push(RETVAL, newSViv(date.tm_isdst));
    av_push(RETVAL, newSViv(date.tm_gmtoff));
    av_push(RETVAL, newSVpv(date.tm_zone, 0));
OUTPUT:
    RETVAL


ptime_t
timegm_bench (ptime_t sec, ptime_t min, ptime_t hour, ptime_t mday, ptime_t mon, ptime_t year)
ALIAS:
    timegml_bench = 1
    timelocal_bench = 2
    timelocall_bench = 3
CODE:
    datetime date;
    date.sec = sec;
    date.min = min;
    date.hour = hour;
    date.mday = mday;
    date.mon = mon;
    date.year = year;
    date.isdst = -1;
    
    int i = 0;
    int cnt = 10000;
    RETVAL = 0;
    
    if      (ix == 0) while (i++ < cnt) RETVAL += TIMEGM(&date);
    else if (ix == 1) while (i++ < cnt) RETVAL += TIMEGML(&date);
    else if (ix == 2) while (i++ < cnt) RETVAL += TIMELOCAL(&date);
    else if (ix == 3) while (i++ < cnt) RETVAL += TIMELOCALL(&date);
OUTPUT:
    RETVAL


time_t
posix_timegm_bench (int64_t sec, int64_t min, int64_t hour, int64_t mday, int64_t mon, int64_t year)
ALIAS:
    posix_timelocal_bench = 1
CODE:
    struct tm date;
    date.tm_sec = sec;
    date.tm_min = min;
    date.tm_hour = hour;
    date.tm_mday = mday;
    date.tm_mon = mon;
    date.tm_year = year-1900;
    date.tm_isdst = -1;
    
    int i = 0;
    int cnt = 10000;
    RETVAL = 0;
    
    if      (ix == 0) while (i++ < cnt) RETVAL += timegm(&date);
    else if (ix == 1) while (i++ < cnt) RETVAL += timelocal(&date);
OUTPUT:
    RETVAL

#endif