muduo 库解析之四:TimeZone

源码

TimeZone.h

#pragma once
#include <time.h>
#include <memory>

#include "Copyable.h"

namespace muduo
{
    class TimeZone : public Copyable
    {
    public:
        TimeZone() = default;
        explicit TimeZone(const char *zone_file);
        TimeZone(int east_of_utc, const char *tzname);

        bool valid() const
        {
            return static_cast<bool>(data_);
        }

        struct tm to_local_time(time_t seconds_since_epoch) const;
        time_t from_local_time(const struct tm &) const;

        static struct tm to_utc_time(time_t seconds_since_epoch, bool yday = false);
        static time_t from_utc_time(const struct tm &);
        static time_t from_utc_time(int year, int month, int day, int hour, int minute, int seconds);

        struct Data;

    private:
        std::shared_ptr<Data> data_;
    };
}

TimeZone.cc

#include "TimeZone.h"

#include <vector>
#include <string>
#include <stdexcept>
#include <algorithm>
#include <stdio.h>
#include <endian.h>
#include <assert.h>

#include "NonCopyable.h"
#include "Types.h"
#include "Date.h"

namespace muduo
{
    namespace detail
    {
        struct Transition
        {
            time_t gmt_time;
            time_t local_time;
            int local_time_index;

            Transition(time_t t, time_t l, int local_index) : gmt_time(t), local_time(l), local_time_index(local_index)
            {
            }
        };

        struct Comp
        {
            bool compare_gmt;

            Comp(bool gmt) : compare_gmt(gmt)
            {
            }

            bool operator()(const Transition &lhs, const Transition &rhs) const
            {
                if (compare_gmt)
                    return lhs.gmt_time < rhs.gmt_time;
                else
                    return lhs.local_time < rhs.local_time;
            }

            bool equal(const Transition &lhs, const Transition &rhs) const
            {
                if (compare_gmt)
                    return lhs.gmt_time == rhs.gmt_time;
                else
                    return lhs.local_time == rhs.local_time;
            }
        };

        struct LocalTime
        {
            time_t gmt_offset;
            bool is_dst;
            int arrb_index;

            LocalTime(time_t offset, bool dst, int arrb) : gmt_offset(offset), is_dst(dst), arrb_index(arrb)
            {
            }
        };

        inline void fill_hms(unsigned seconds, struct tm *utc)
        {
            utc->tm_sec = seconds % 60;
            unsigned minutes = seconds / 60;
            utc->tm_min = minutes;
            utc->tm_hour = minutes / 60;
        }
    }

    const int kSecondsPerDay = 24 * 60 * 60;

    struct TimeZone::Data
    {
        std::vector<detail::Transition> transitions;
        std::vector<detail::LocalTime> local_times;
        std::vector<std::string> names;
        std::string abbreviation;
    };

    namespace detail
    {
        class File : public NonCopyable
        {
        public:
            File(const char *file) : fp_(::fopen(file, "rb"))
            {
            }

            ~File()
            {
                if (fp_)
                {
                    ::fclose(fp_);
                }
            }

            bool valid() const { return fp_; }

            std::string read_bytes(int n)
            {
                char buf[n];
                ssize_t nr = ::fread(buf, 1, n, fp_);
                if (nr != n)
                    throw std::logic_error("no enough data");
                return std::string(buf, n);
            }

            int32_t read_int32()
            {
                int32_t x = 0;
                ssize_t nr = ::fread(&x, 1, sizeof(int32_t), fp_);
                if (nr != sizeof(int32_t))
                    throw std::logic_error("bad int32_t data");
                return be32toh(x); //@ convert byte order
            }

            uint8_t read_uint8()
            {
                uint8_t x = 0;
                ssize_t nr = ::fread(&x, 1, sizeof(uint8_t), fp_);
                if (nr != sizeof(uint8_t))
                    throw std::logic_error("bad uint8_t data");
                return x;
            }

        private:
            FILE *fp_;
        };

        bool read_time_zone_file(const char *zone_file, struct TimeZone::Data *data)
        {
            File f(zone_file);
            if (f.valid())
            {
                try
                {
                    std::string header = f.read_bytes(4);
                    if (header != "TZif")
                        throw std::logic_error("bad header");
                    std::string version = f.read_bytes(1);
                    f.read_bytes(15);

                    int32_t is_gmt_cnt = f.read_int32();
                    int32_t is_std_cnt = f.read_int32();
                    int32_t leap_cnt = f.read_int32();
                    int32_t time_cnt = f.read_int32();
                    int32_t type_cnt = f.read_int32();
                    int32_t char_cnt = f.read_int32();

                    std::vector<int32_t> trans;
                    std::vector<int> local_times;
                    trans.reserve(time_cnt);
                    for (int i = 0; i < time_cnt; ++i)
                    {
                        trans.push_back(f.read_int32());
                    }

                    for (int i = 0; i < time_cnt; ++i)
                    {
                        uint8_t local = f.read_uint8();
                        local_times.push_back(local);
                    }

                    for (int i = 0; i < type_cnt; ++i)
                    {
                        int32_t gmt_off = f.read_int32();
                        uint8_t is_dst = f.read_uint8();
                        uint8_t abbrind = f.read_uint8();
                        data->local_times.push_back(LocalTime(gmt_off, is_dst, abbrind));
                    }

                    for (int i = 0; i < time_cnt; ++i)
                    {
                        int local_idx = local_times[i];
                        time_t localtime = trans[i] + data->local_times[local_idx].gmt_offset;
                        data->transitions.push_back(Transition(trans[i], localtime, local_idx));
                    }

                    data->abbreviation = f.read_bytes(char_cnt);

                    for (int i = 0; i < leap_cnt; ++i)
                    {
                        // int32_t leaptime = f.read_int32();
                        // int32_t cumleap = f.read_int32();
                    }

                    (void)is_std_cnt;
                    (void)is_gmt_cnt;
                }
                catch (const std::logic_error &e)
                {
                    fprintf(stderr, "%s
", e.what());
                }
            }
            return true;
        }

        const LocalTime *find_local_time(const TimeZone::Data &data, Transition sentry, Comp comp)
        {
            const LocalTime *local = NULL;

            if (data.transitions.empty() || comp(sentry, data.transitions.front()))
            {
                local = &data.local_times.front();
            }
            else
            {
                std::vector<Transition>::const_iterator trans_i = std::lower_bound(data.transitions.begin(), data.transitions.end(), sentry, comp);
                if (trans_i != data.transitions.end())
                {
                    if (!comp.equal(sentry, *trans_i))
                    {
                        assert(trans_i != data.transitions.begin());
                        --trans_i;
                    }

                    local = &data.local_times[trans_i->local_time_index];
                }
                else
                {
                    local = &data.local_times[data.transitions.back().local_time_index];
                }
            }
            return local;
        }

    } //@ namespace detail

    TimeZone::TimeZone(const char *zone_file) : data_(new TimeZone::Data)
    {
        if (!detail::read_time_zone_file(zone_file, data_.get()))
            data_.reset();
    }

    TimeZone::TimeZone(int east_of_utc, const char *tzname) : data_(new TimeZone::Data)
    {
        data_->local_times.push_back(detail::LocalTime(east_of_utc, false, 0));
        data_->abbreviation = tzname;
    }

    struct tm TimeZone::to_local_time(time_t seconds) const
    {
        struct tm local_time;
        mem_zero(&local_time, sizeof(local_time));
        assert(data_ != NULL);
        const Data &data(*data_);

        detail::Transition sentry(seconds, 0, 0);
        const detail::LocalTime *local = find_local_time(data, sentry, detail::Comp(true));
        if (local)
        {
            time_t local_seconds = seconds + local->gmt_offset;
            ::gmtime_r(&local_seconds, &local_time);
            local_time.tm_isdst = local->is_dst;
            local_time.tm_gmtoff = local->gmt_offset;
            local_time.tm_zone = &data.abbreviation[local->arrb_index];
        }
        return local_time;
    }

    time_t TimeZone::from_local_time(const struct tm &local) const
    {
        assert(data_ != NULL);
        const Data &data(*data_);
        struct tm tmp = local;
        time_t seconds = ::timegm(&tmp);
        detail::Transition sentry(0, seconds, 0);
        const detail::LocalTime *local_time = find_local_time(data, sentry, detail::Comp(false));
        if (local.tm_isdst)
        {
            struct tm try_tm = to_local_time(seconds - local_time->gmt_offset);
            if (!try_tm.tm_isdst && try_tm.tm_hour == local.tm_hour && try_tm.tm_min == local.tm_min)
            {
                seconds -= 3600;
            }
        }
        return seconds - local_time->gmt_offset;
    }

    struct tm TimeZone::to_utc_time(time_t seconds_since_epoch, bool yday)
    {
        struct tm utc;
        mem_zero(&utc, sizeof(utc));
        utc.tm_zone = "GMT";
        int seconds = static_cast<int>(seconds_since_epoch % kSecondsPerDay);
        int days = static_cast<int>(seconds_since_epoch / kSecondsPerDay);
        if (seconds < 0)
        {
            seconds += kSecondsPerDay;
            --days;
        }
        detail::fill_hms(seconds, &utc);
        Date date(days + Date::kJulianDayOf1970_01_01);
        Date::YearMonthDay ymd = date.year_month_day();
        utc.tm_year = ymd.year - 1900;
        utc.tm_mon = ymd.month - 1;
        utc.tm_mday = ymd.day;
        utc.tm_wday = date.week_day();

        if (yday)
        {
            Date start_of_year(ymd.year, 1, 1);
            utc.tm_yday = date.julian_day_number() - start_of_year.julian_day_number();
        }
        return utc;
    }

    time_t TimeZone::from_utc_time(const struct tm &utc)
    {
        return from_utc_time(utc.tm_year + 1900, utc.tm_mon + 1, utc.tm_mday, utc.tm_hour, utc.tm_mon, utc.tm_sec);
    }

    time_t TimeZone::from_utc_time(int year, int month, int day, int hour, int minute, int seconds)
    {
        Date date(year, month, day);
        int seconds_in_day = hour * 3600 + minute * 60 + seconds;
        time_t days = date.julian_day_number() - Date::kJulianDayOf1970_01_01;
        return days * kSecondsPerDay + seconds_in_day;
    }
}
原文地址:https://www.cnblogs.com/xiaojianliu/p/14698251.html