DateTimeライブラリを書きました

Boost.DateTimeが使いにくかったので、日時計算を目的とした簡単なDateTimeライブラリを書きました。
SHAND_DATE_TIME_CUSTOM_NOW_TIMEをdefineすることで、現在日時を返す関数を書き換えることができるようになるので、Testableです。


フォーマット指定は、strftimeを参考に、よく使うものだけを採用しました。


shand/date_time.hpp

#ifndef SHAND_DATE_TIME_INCLUDE
#define SHAND_DATE_TIME_INCLUDE

#include <cstddef>
#include <ctime>
#include <map>
#include <boost/xpressive/xpressive_static.hpp>
#include <boost/xpressive/regex_actions.hpp>
#include <boost/assign/list_of.hpp>
#include <boost/format.hpp>
#include <boost/lambda/lambda.hpp>

namespace shand {

template <int>
class primitive {
    std::size_t value_;
public:
    primitive() : value_(0) {}
    primitive(std::size_t value) : value_(value) {}
    std::size_t value() const { return value_; }
};

typedef primitive<0> year;
typedef primitive<1> month;
typedef primitive<2> day;
typedef primitive<3> hour;
typedef primitive<4> minute;
typedef primitive<5> second;

#ifdef SHAND_DATE_TIME_CUSTOM_NOW_TIME
    std::time_t now_time_t();
#else
    inline std::time_t now_time_t()
    {
        std::time_t t;
        std::time(&t);
        return t;
    }
#endif

class date_time {
    std::time_t time_;

    template <class F>
    date_time& assign_time(F f)
    {
        std::time_t tmp = time_;
        std::tm* st = std::localtime(&tmp);

        f(st);

        time_ = std::mktime(st);
        return *this;
    }

    template <class F>
    static date_time calc_time(const date_time& d, F f)
    {
        std::time_t tmp = d.time_;
        std::tm* st = std::localtime(&tmp);

        f(st);

        return date_time(std::mktime(st));
    }

public:
    date_time() {}
    explicit date_time(std::time_t t) : time_(t) {}

    std::string format(const std::string& fmt) const
    {
        std::tm* st = std::localtime(&time_);

        const std::map<std::string, std::string> rep = boost::assign::list_of
            (std::make_pair("%Y", (boost::format("%04d") % (1900 + st->tm_year)).str()))
            (std::make_pair("%m", (boost::format("%02d") % (1 + st->tm_mon)).str()))
            (std::make_pair("%d", (boost::format("%02d") % st->tm_mday).str()))
            (std::make_pair("%H", (boost::format("%02d") % st->tm_hour).str()))
            (std::make_pair("%M", (boost::format("%02d") % st->tm_min).str()))
            (std::make_pair("%S", (boost::format("%02d") % st->tm_sec).str()))
        ;

        using namespace boost::xpressive;

        local<std::string const *> pstr;
        const sregex rx = (a1 = rep)[pstr = &a1];

        return regex_replace(fmt, rx, *pstr);
    }

    static date_time now()
    {
        return date_time(now_time_t());
    }

    time_t to_time_t() const { return time_; }

    // assign
    date_time& operator=(const year& x)
        { return assign_time(boost::lambda::_1 ->* &std::tm::tm_year = x.value() - 1900); }

    date_time& operator=(const month& x)
        { return assign_time(boost::lambda::_1 ->* &std::tm::tm_mon = x.value() - 1); }

    date_time& operator=(const day& x)
        { return assign_time(boost::lambda::_1 ->* &std::tm::tm_mday = x.value()); }

    date_time& operator=(const hour& x)
        { return assign_time(boost::lambda::_1 ->* &std::tm::tm_hour = x.value()); }

    date_time& operator=(const minute& x)
        { return assign_time(boost::lambda::_1 ->* &std::tm::tm_min = x.value()); }

    date_time& operator=(const second& x)
        { return assign_time(boost::lambda::_1 ->* &std::tm::tm_sec = x.value()); }

    // add
    friend date_time operator+(const date_time& d, const year& x)
        { return date_time::calc_time(d, boost::lambda::_1 ->* &std::tm::tm_year += x.value()); }

    friend date_time operator+(const date_time& d, const month& x)
        { return date_time::calc_time(d, boost::lambda::_1 ->* &std::tm::tm_mon += x.value()); }

    friend date_time operator+(const date_time& d, const day& x)
        { return date_time::calc_time(d, boost::lambda::_1 ->* &std::tm::tm_mday += x.value()); }

    friend date_time operator+(const date_time& d, const hour& x)
        { return date_time::calc_time(d, boost::lambda::_1 ->* &std::tm::tm_hour += x.value()); }

    friend date_time operator+(const date_time& d, const minute& x)
        { return date_time::calc_time(d, boost::lambda::_1 ->* &std::tm::tm_min += x.value()); }

    friend date_time operator+(const date_time& d, const second& x)
        { return date_time::calc_time(d, boost::lambda::_1 ->* &std::tm::tm_sec += x.value()); }

    // substract
    friend date_time operator-(const date_time& d, const year& x)
        { return date_time::calc_time(d, boost::lambda::_1 ->* &std::tm::tm_year -= x.value()); }

    friend date_time operator-(const date_time& d, const month& x)
        { return date_time::calc_time(d, boost::lambda::_1 ->* &std::tm::tm_mon -= x.value()); }

    friend date_time operator-(const date_time& d, const day& x)
        { return date_time::calc_time(d, boost::lambda::_1 ->* &std::tm::tm_mday -= x.value()); }

    friend date_time operator-(const date_time& d, const hour& x)
        { return date_time::calc_time(d, boost::lambda::_1 ->* &std::tm::tm_hour -= x.value()); }

    friend date_time operator-(const date_time& d, const minute& x)
        { return date_time::calc_time(d, boost::lambda::_1 ->* &std::tm::tm_min -= x.value()); }

    friend date_time operator-(const date_time& d, const second& x)
        { return date_time::calc_time(d, boost::lambda::_1 ->* &std::tm::tm_sec -= x.value()); }
};


inline day diff_day(const date_time& a, const date_time& b)
{
    const std::time_t& at = (std::max)(a.to_time_t(), b.to_time_t());
    const std::time_t& bt = (std::min)(a.to_time_t(), b.to_time_t());
    return day(static_cast<std::size_t>(std::difftime(at, bt) / (60 * 60 * 24)));
}

inline hour diff_hour(const date_time& a, const date_time& b)
{
    const std::time_t& at = (std::max)(a.to_time_t(), b.to_time_t());
    const std::time_t& bt = (std::min)(a.to_time_t(), b.to_time_t());
    return hour(static_cast<std::size_t>(std::difftime(at, bt) / (60 * 60)));
}

inline minute diff_minute(const date_time& a, const date_time& b)
{
    const std::time_t& at = (std::max)(a.to_time_t(), b.to_time_t());
    const std::time_t& bt = (std::min)(a.to_time_t(), b.to_time_t());
    return minute(static_cast<std::size_t>(std::difftime(at, bt) / 60));
}

inline second diff_second(const date_time& a, const date_time& b)
{
    const std::time_t& at = (std::max)(a.to_time_t(), b.to_time_t());
    const std::time_t& bt = (std::min)(a.to_time_t(), b.to_time_t());
    return second(static_cast<std::size_t>(std::difftime(at, bt)));
}

} // namespace shand

#endif // SHAND_DATE_TIME_INCLUDE


テスト

#include <boost/detail/lightweight_test.hpp>

#define SHAND_DATE_TIME_CUSTOM_NOW_TIME // 現在時間を返す関数を書き換える
#include <shand/date_time.hpp>

namespace shand {
    std::time_t now_time_t()
    {
        std::tm t;
        t.tm_year   = 2010 - 1900;
        t.tm_mon    = 12 - 1;
        t.tm_yday   = 353;
        t.tm_mday   = 20;
        t.tm_wday   = 1;
        t.tm_hour   = 15;
        t.tm_min    = 30;
        t.tm_sec    = 45;
        t.tm_isdst  = 0;

        return std::mktime(&t);
    }
}

int main()
{
    using namespace shand;

    // 現在日時をフォーマット指定して出力
    BOOST_TEST(date_time::now().format("%Y/%m/%d %H:%M:%S") == "2010/12/20 15:30:45");

    // 1年加算
    {
        const date_time tm = date_time::now();
        BOOST_TEST((tm + year(1)).format("%Y/%m/%d %H:%M:%S") == "2011/12/20 15:30:45");
    }

    // 今月の日数を求める(次の月の1日から1日引く)
    {
        date_time tm = date_time::now();
        BOOST_TEST((((tm + month(1)) = day(1)) - day(1)).format("%d") == "31");
    }

    // 日数の差を計算
    {
        date_time a = date_time::now();
        ((a = year(2010)) = month(3)) = day(1);

        date_time b = date_time::now();
        ((b = year(2010)) = month(3)) = day(5);

        BOOST_TEST(diff_day(a, b).value() == 4);
    }

    return boost::report_errors();
}