Shand.DateTime v1.1

昨日のDateTimeライブラリを少し拡張して、複数のprimitiveをoperator&で繋ぎ、まとめて代入できるようにしました。
可変引数にしなかったのは、Boost.PPを使いたくなかったからです。可変引数として扱いたい場合は簡単に後付けすることができます。


なお、つなげたprimitiveはFusion Sequenceとなるようにしたので、ユーザーコードがかなり柔軟になっています。


shand/date_time.hpp

#ifndef SHAND_DATE_TIME_INCLUDE
#define SHAND_DATE_TIME_INCLUDE

#include <cstddef>
#include <ctime>
#include <map>
#include <boost/assert.hpp>
#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>
#include <boost/fusion/include/make_vector.hpp>
#include <boost/fusion/include/push_back.hpp>
#include <boost/fusion/include/is_sequence.hpp>
#include <boost/fusion/include/for_each.hpp>
#include <boost/utility/enable_if.hpp>

namespace shand {

template <int>
class primitive {
    std::size_t value_;
public:
    primitive() : value_(0) {}
    explicit 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

template <int I, int J>
inline boost::fusion::vector<primitive<I>, primitive<J> >
    operator&(const primitive<I>& a, const primitive<J>& b)
{
    return boost::fusion::make_vector(a, b);
}

template <class Seq, int I>
inline typename boost::enable_if<boost::fusion::traits::is_sequence<Seq>,
        typename boost::fusion::result_of::push_back<const Seq, primitive<I> >::type >::type
    operator&(const Seq& a, const primitive<I>& b)
{
    return boost::fusion::push_back(a, b);
}

namespace detail {

class primitive_assigner {
    std::tm* st_;
public:
    primitive_assigner(std::tm* st) : st_(st) {}

    void operator()(const year& x) const    { st_->tm_year = x.value() - 1900; }
    void operator()(const month& x) const   { st_->tm_mon  = x.value() - 1; }
    void operator()(const day& x) const     { st_->tm_mday = x.value(); }
    void operator()(const hour& x) const    { st_->tm_hour = x.value(); }
    void operator()(const minute& x) const  { st_->tm_min  = x.value(); }
    void operator()(const second& x) const  { st_->tm_sec  = x.value(); }
};

} // namespace detail

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) {}

    template <class Seq>
    date_time(const Seq& seq,
        typename boost::enable_if<boost::fusion::traits::is_sequence<Seq> >::type* = 0)
    {
        std::time_t tmp = now_time_t();
        std::tm* st = std::localtime(&tmp);

        boost::fusion::for_each(seq, detail::primitive_assigner(st));

        time_ = std::mktime(st);
    }

    std::string format(const std::string& fmt) const
    {
        BOOST_ASSERT(time_ >= 0);
        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
    template <class Seq>
    typename boost::enable_if<boost::fusion::traits::is_sequence<Seq>, date_time&>::type
        operator=(const Seq& seq)
    {
        std::time_t tmp = time_;
        std::tm* st = std::localtime(&tmp);

        boost::fusion::for_each(seq, detail::primitive_assigner(st));

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

    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 calc_time(d, boost::lambda::_1 ->* &std::tm::tm_year += x.value()); }

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

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

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

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

    friend date_time operator+(const date_time& d, const second& x)
        { return 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>
#include <boost/fusion/include/adapt_struct.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);
    }
}

namespace fusion = boost::fusion;

struct UserDate {
    shand::year year;
    shand::month month;
    shand::day day;

    UserDate(const shand::year& year_, const shand::month& month_, const shand::day& day_)
        : year(year_), month(month_), day(day_) {}
};

BOOST_FUSION_ADAPT_STRUCT(
    UserDate,
    (shand::year,  year)
    (shand::month, month)
    (shand::day,   day)
)

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");
    }

    // 月を書き換え
    {
        date_time tm = date_time::now();
        tm = month(3);
        BOOST_TEST(tm.format("%Y/%m/%d %H:%M:%S") == "2010/03/20 15:30:45");
    }

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

    // &で繋げてまとめて設定(mktimeが1回で済む)
    {
        const date_time tm = year(2010) & month(3) & day(1) & hour(15) & minute(30) & second(12);
        BOOST_TEST(tm.format("%Y/%m/%d %H:%M:%S") == "2010/03/01 15:30:12");
    }
    {
        date_time tm = date_time::now();
        tm = year(2010) & month(3) & day(1) & hour(15) & minute(30) & second(12);
        BOOST_TEST(tm.format("%Y/%m/%d %H:%M:%S") == "2010/03/01 15:30:12");
    }

    // Fusion Sequenceにアダプトされたユーザー定義型を、shand::date_timeとして扱う
    {
        const date_time tm = UserDate(year(2010), month(3), day(1));
        BOOST_TEST(tm.format("%Y/%m/%d") == "2010/03/01");
    }

    // 日数の差を計算
    {
        const date_time a = year(2010) & month(3) & day(1);
        const date_time b = year(2010) & month(3) & day(5);

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

    return boost::report_errors();
}