The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#pragma once
#include <panda/time/time.h>

namespace panda { namespace time {

const size_t 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 {
private:
    mutable size_t refcnt;
public:
    char      name[TZNAME_MAX+1];
    tztrans*  trans;
    uint32_t  trans_cnt;
    tztrans   ltrans;              // trans[trans_cnt-1]
    tzleap*   leaps;
    uint32_t  leaps_cnt;
    tzrule    future;
    mutable bool is_local; // if timezone is set as local at the moment

    tz () : refcnt(1) {}

    void retain () const { refcnt++; }

    void release () const {
        if (--refcnt <= 0) {
            clear();
            delete this;
        }
    }

    void clear () const {
        delete[] this->trans;
        if (this->leaps_cnt > 0) delete[] this->leaps;
    }
};

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

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

}}