/* A little program to test the limits of your system's time functions */

#include "time64_config.h"
#include <time.h>
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>

struct tm Test_TM;

time_t Time_Max;
time_t Time_Min;

time_t Time_Zero = 0;


char *dump_date(const struct tm *date) {
    char *dump = malloc(80 * sizeof(char));
    sprintf(
        dump,
        "{ %d, %d, %d, %d, %d, %d }",
        date->tm_sec, date->tm_min, date->tm_hour, date->tm_mday, date->tm_mon, date->tm_year
    );

    return dump;
}


/* Visual C++ 2008's difftime() can't do negative times */
double my_difftime(time_t left, time_t right) {
    double diff = (double)left - (double)right;
    return diff;
}

time_t check_date_max( struct tm * (*date_func)(const time_t *), const char *func_name ) {
    struct tm *date;
    time_t time        = Time_Max;
    time_t good_time   = 0;
    time_t time_change = Time_Max;

    /* Binary search for the exact failure point */
    do {
        printf("# Trying %s(%.0f) max...", func_name, my_difftime(time, Time_Zero));
        date = (*date_func)(&time);

        time_change /= 2;

        /* date_func() broke or tm_year overflowed or time_t overflowed */
        if(date == NULL || date->tm_year < 69 || time < good_time) {
            printf(" failed\n");
            time -= time_change;
        }
        else {
            printf(" success\n");
            good_time = time;
            time += time_change;
        }
    } while(time_change > 0 && good_time < Time_Max);

    return(good_time);
}


time_t check_date_min( struct tm * (*date_func)(const time_t *), const char *func_name ) {
    struct tm *date;
    time_t time        = Time_Min;
    time_t good_time   = 0;
    time_t time_change = Time_Min;

    /* Binary search for the exact failure point */
    do {
        printf("# Trying %s(%.0f) min...", func_name, my_difftime(time, Time_Zero));
        date = (*date_func)(&time);

        time_change /= 2;

        /* date_func() broke or tm_year overflowed or time_t overflowed */
        if(date == NULL || date->tm_year > 70 || time > good_time) {
            printf(" failed\n");
            time -= time_change;
        }
        else {
            printf(" success\n");
            good_time = time;
            time += time_change;
        }
    } while((time_change < 0) && (good_time > Time_Min));

    return(good_time);
}


struct tm * check_to_time_max( time_t (*to_time)(struct tm *), const char *func_name,
                          struct tm * (*to_date)(const time_t *) )
{
    time_t round_trip;
    time_t time        = Time_Max;
    time_t good_time   = 0;
    struct tm *date;
    struct tm *good_date = malloc(sizeof(struct tm));
    time_t time_change = Time_Max;

    /* Binary search for the exact failure point */
    do {
        printf("# Trying %s(%.0f) max...", func_name, my_difftime(time, Time_Zero));
        date = (*to_date)(&time);
        round_trip = (*to_time)(date);

        time_change /= 2;

        /* date_func() broke or tm_year overflowed or time_t overflowed */
        if(time != round_trip) {
            printf(" failed\n");
            time -= time_change;
        }
        else {
            printf(" success\n");
            good_time = time;
            memcpy(good_date, date, sizeof(struct tm));
            time += time_change;
        }
    } while(time_change > 0 && good_time < Time_Max);

    return(good_date);
}


struct tm * check_to_time_min( time_t (*to_time)(struct tm *), const char *func_name,
                          struct tm * (*to_date)(const time_t *) )
{
    time_t round_trip;
    time_t time        = Time_Min;
    time_t good_time   = 0;
    struct tm *date;
    struct tm *good_date = malloc(sizeof(struct tm));
    time_t time_change = Time_Min;

    /* Binary search for the exact failure point */
    do {
        printf("# Trying %s(%.0f) min...", func_name, my_difftime(time, Time_Zero));
        date = (*to_date)(&time);
        round_trip = (*to_time)(date);

        time_change /= 2;

        /* date_func() broke or tm_year overflowed or time_t overflowed */
        if(time != round_trip) {
            printf(" failed\n");
            time -= time_change;
        }
        else {
            printf(" success\n");
            good_time = time;
            memcpy(good_date, date, sizeof(struct tm));
            time += time_change;
        }
    } while((time_change < 0) && (good_time > Time_Min));

    return(good_date);
}


void guess_time_limits_from_types(void) {
    if( sizeof(time_t) == 4 ) {
        /* y2038 bug, out to 2**31-1 */
        Time_Max =  2147483647;
        Time_Min = -2147483647 - 1;     /* "C90 doesn't have negative constants, only
                                           positive ones that have been negated." */
    }
    else if( sizeof(time_t) >= 8 ) {
        /* The compiler might warn about overflowing in the assignments
           below.  Don't worry, these won't get run in that case */
        if( sizeof(Test_TM.tm_year) == 4 ) {
            /* y2**31-1 bug */
            Time_Max =  67768036160140799LL;
            Time_Min = -67768036191676800LL;
        }
        else {
            /* All the way out to 2**63-1 */
            Time_Max =  9223372036854775807LL;
            Time_Min = -9223372036854775807LL;
        }
    }
    else {
        printf("Weird sizeof(time_t): %ld\n", sizeof(time_t));
    }
}


/* Dump a tm struct as a json fragment */
char * tm_as_json(const struct tm* date) {
    char *date_json = malloc(sizeof(char) * 512);
#ifdef HAS_TM_TM_ZONE
    char zone_json[32];
#endif
#ifdef HAS_TM_TM_GMTOFF
    char gmtoff_json[32];
#endif

    sprintf(date_json,
            "\"tm_sec\": %d, \"tm_min\": %d, \"tm_hour\": %d, \"tm_mday\": %d, \"tm_mon\": %d, \"tm_year\": %d, \"tm_wday\": %d, \"tm_yday\": %d, \"tm_isdst\": %d",
            date->tm_sec, date->tm_min, date->tm_hour, date->tm_mday,
            date->tm_mon, date->tm_year, date->tm_wday, date->tm_yday, date->tm_isdst
    );

#ifdef HAS_TM_TM_ZONE
    sprintf(zone_json, ", \"tm_zone\": \"%s\"", date->tm_zone);
    strcat(date_json, zone_json);
#endif
#ifdef HAS_TM_TM_GMTOFF
    sprintf(gmtoff_json, ", \"tm_gmtoff\": %ld", date->tm_gmtoff);
    strcat(date_json, gmtoff_json);
#endif

    return date_json;
}


int main(void) {
    time_t gmtime_max;
    time_t gmtime_min;
    time_t localtime_max;
    time_t localtime_min;
#ifdef HAS_TIMEGM
    struct tm* timegm_max;
    struct tm* timegm_min;
#endif
    struct tm* mktime_max;
    struct tm* mktime_min;

    guess_time_limits_from_types();

    gmtime_max = check_date_max(gmtime, "gmtime");
    gmtime_min = check_date_min(gmtime, "gmtime");

    localtime_max = check_date_max(localtime, "localtime");
    localtime_min = check_date_min(localtime, "localtime");

#ifdef HAS_TIMEGM
    Time_Max = gmtime_max;
    Time_Min = gmtime_min;
    timegm_max = check_to_time_max(timegm, "timegm", gmtime);
    timegm_min = check_to_time_min(timegm, "timegm", gmtime);
#endif

    Time_Max = localtime_max;
    Time_Min = localtime_min;
    mktime_max = check_to_time_max(mktime, "mktime", localtime);
    mktime_min = check_to_time_min(mktime, "mktime", localtime);

    printf("# system time.h limits, as JSON\n");
    printf("{\n");

    printf("    \"gmtime\": { \"max\": %.0f, \"min\": %0.f },\n",
           my_difftime(gmtime_max, Time_Zero),
           my_difftime(gmtime_min, Time_Zero)
    );

    printf("    \"localtime\": { \"max\": %.0f, \"min\": %0.f },\n",
           my_difftime(localtime_max, Time_Zero),
           my_difftime(localtime_min, Time_Zero)
    );

    printf("    \"mktime\": {\n");
    printf("        \"max\": { %s },\n", tm_as_json(mktime_max));
    printf("        \"min\": { %s }\n", tm_as_json(mktime_min));
    printf("    }\n");

#ifdef HAS_TIMEGM
    printf("    ,\n");
    printf("    \"timegm\": {\n");
    printf("        \"max\": { %s },\n", tm_as_json(timegm_max));
    printf("        \"min\": { %s }\n", tm_as_json(timegm_min));
    printf("    }\n");
#endif

    printf("}\n");

    return 0;
}
