日時のフォーマッター

日時を文字列化するプログラムを書きました。datetime_string("%Y/%m/%d")のような記法だとローカライズで困るので、名前付き引数で順不同に要素を指定させるようにしました。

#include <cstdint>
#include <string>
#include <boost/format.hpp>
#include <boost/optional.hpp>
#include <boost/parameter/name.hpp>
 
namespace param {
    BOOST_PARAMETER_NAME(years)
    BOOST_PARAMETER_NAME(months)
    BOOST_PARAMETER_NAME(days)
    BOOST_PARAMETER_NAME(hours)
    BOOST_PARAMETER_NAME(minutes)
    BOOST_PARAMETER_NAME(seconds)
}
 
class default_formatter {
public:
    std::string date(std::uint32_t years,
                     std::uint32_t months,
                     std::uint32_t days) const
    {
        return (boost::format("%1%/%2%/%3%") % years % months % days).str();
    }
 
    std::string hour_minute(std::uint32_t hours,
                            std::uint32_t minutes) const
    {
        return (boost::format("%1%:%2%") % hours % minutes).str();
    }
 
    std::string hour_minute_second(std::uint32_t hours,
                                   std::uint32_t minutes,
                                   std::uint32_t seconds) const
    {
        return (boost::format("%1%:%2%:%3%") % hours % minutes % seconds).str();
    }
 
    std::string datetime(std::uint32_t years,
                         std::uint32_t months,
                         std::uint32_t days,
                         std::uint32_t hours,
                         std::uint32_t minutes) const
    {
        return (boost::format("%1%/%2%/%3% %4%:%5%")
                    % years % months % days
                    % hours % minutes
                ).str();
    }
 
    std::string full_datetime(std::uint32_t years,
                              std::uint32_t months,
                              std::uint32_t days,
                              std::uint32_t hours,
                              std::uint32_t minutes,
                              std::uint32_t seconds) const
    {
        return (boost::format("%1%/%2%/%3% %4%:%5%:%6%")
                % years % months % days
                % hours % minutes % seconds
            ).str();
    }
};
 
class datetime_string_maker {
public:
    using value_type = std::uint32_t;
 
    template <class Formatter, class ArgumentPack>
    std::string make(const Formatter& formatter, const ArgumentPack& args) const
    {
        using opt_value = boost::optional<value_type>;
        const opt_value none_opt = opt_value();
 
        opt_value years   = args[param::_years   | none_opt];
        opt_value months  = args[param::_months  | none_opt];
        opt_value days    = args[param::_days    | none_opt];
        opt_value hours   = args[param::_hours   | none_opt];
        opt_value minutes = args[param::_minutes | none_opt];
        opt_value seconds = args[param::_seconds | none_opt];
 
        const bool has_date = years && months && days;
        const bool has_time = hours && minutes;
        const bool has_fulltime = has_time && seconds;
 
        if (!has_date && !has_time)
            throw std::invalid_argument("invalid argument");
 
        if (has_date && !has_time)
            return formatter.date(years.get(), months.get(), days.get());
 
        if (has_time && !has_fulltime && !has_date)
            return formatter.hour_minute(hours.get(), minutes.get());
 
        if (has_fulltime && !has_date)
            return formatter.hour_minute_second(hours.get(), minutes.get(), seconds.get());
 
        if (has_date && has_time && !has_fulltime)
            return formatter.datetime(
                        years.get(), months.get(), days.get(),
                        hours.get(), minutes.get());
 
        if (has_date && has_fulltime)
            return formatter.full_datetime(
                        years.get(), months.get(), days.get(),
                        hours.get(), minutes.get(), seconds.get());
 
        throw std::runtime_error("non reachable path");
    }
};
 
template <class ArgumentPack>
std::string datetime_string(const ArgumentPack& args)
{
    return datetime_string_maker().make(default_formatter(), args);
}
 
#include <iostream>
int main()
{
    using namespace param;
    std::cout << datetime_string((_years = 1985, _months = 3, _days = 1)) << std::endl;
    std::cout << datetime_string((_hours = 15, _minutes = 30)) << std::endl;
    std::cout << datetime_string((_years = 1985, _months = 3, _days = 1,
                                    _hours = 15, _minutes = 30)) << std::endl;
}

出力:

1985/3/1
15:30
1985/3/1 15:30

設計として、名前付き引数の解析とフォーマットを分けています。ローカライズの際には、フォーマット設定の部分だけ条件分岐しましょう。