The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#pragma once

namespace panda { namespace time {

const int TZNAME_MAX = 255; // max length of timezone name or POSIX rule (Europe/Moscow, ...)

enum tzswitch_t { TZSWITCH_DATE, TZSWITCH_JDAY, TZSWITCH_DAY };

struct tztrans {
    ptime_t start;        // time of transition
    ptime_t local_start;  // local time of transition (epoch+offset).
    ptime_t local_end;    // local time of transition's end (next transition epoch + MY offset).
    ptime_t local_lower;  // local_start or prev transition's local_end
    ptime_t local_upper;  // local_start or prev transition's local_end
    int32_t offset;       // offset from non-leap GMT
    int32_t gmt_offset;   // offset from leap GMT
    int32_t delta;        // offset minus previous transition's offset
    int32_t isdst;        // is DST in effect after this transition
    int32_t leap_corr;    // summary leap seconds correction at the moment
    int32_t leap_delta;   // delta leap seconds correction (0 if it's just a transition, != 0 if it's a leap correction)
    ptime_t leap_end;     // end of leap period (not including last second) = start + leap_delta
    ptime_t leap_lend;    // local_start + 2*leap_delta
    union {
        char    abbrev[ZONE_ABBR_MAX+1]; // transition (zone) abbreviation
        int64_t n_abbrev;                // abbrev as int64_t
    };
};

// rule for future (beyond transition list) dates and for abstract timezones
// http://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html
// --------------------------------------------------------------------------------------------
// 1 Jan   OUTER ZONE   OUTER END        INNER ZONE        INNER END     OUTER ZONE      31 Dec                
// --------------------------------------------------------------------------------------------
struct tzrulezone {
    union {
        char    abbrev[ZONE_ABBR_MAX+1]; // zone abbreviation
        int64_t n_abbrev;                // abbrev as int64_t
    };
    int32_t    offset;                     // offset from non-leap GMT
    int32_t    gmt_offset;                 // offset from leap GMT
    int32_t    isdst;                      // true if zone represents DST time
    tzswitch_t type;                       // type of 'end' field
    dt         end;                        // dynamic date when this zone ends (only if hasdst=1)
};

struct tzrule {
    uint32_t   hasdst;       // does this rule have DST switching
    tzrulezone outer;        // always present
    tzrulezone inner;        // only present if hasdst=1
    int32_t    max_offset;   // max(outer.offset, inner.offset)
    int32_t    delta;        // inner.offset - outer.offset
};

struct tzleap {
    ptime_t  time;
    uint32_t correction;
};

struct tz {
    size_t   refcnt;
    char     name[TZNAME_MAX+1];
    tztrans* trans;
    int32_t  trans_cnt;
    tztrans  ltrans;              // trans[trans_cnt-1]
    tzleap*  leaps;
    int32_t  leaps_cnt;
    tzrule   future;
    bool     is_local;
};

void tzset   (const char* zonename = NULL);
tz*  tzget   (const char*);
tz*  tzlocal ();

const char* tzdir    ();
bool        tzdir    (const char*);
const char* tzsysdir ();

inline void tzcapture (tz* zone) {
    zone->refcnt++;
}

inline void tzfree (tz* zone) {
    if (--zone->refcnt <= 0) {
        delete[] zone->trans;
        if (zone->leaps_cnt > 0) delete[] zone->leaps;
        delete zone;
    }
}

};};