strptime の実装

文字列から日付・時刻の構造体 (struct tm) への変換にはこれまで strptime() を用いていたのですが,この関数は Windows には存在しないようです.なので,strptime() 相当のものを独自に実装して Windowsコンパイルする際には代替関数を使うようにしてみました(かなり手を抜いていますが).実装に使用したものは,主に以下の 2 つ.

time_get

time_get は 入力ストリームから日付と時刻を解析するための C++ 標準ライブラリです.今回は,このクラスの get_weekday() メソッドと get_monthday() を利用して,曜日の名前 -> その曜日を表す数値(月の名前 -> 月)の変換を行いました.例えば,“Friday”を 5 に変換するサンプルコードは以下の通りです(曜日は,日曜日を 0 として 0--6 の値で表される).

#include <ctime>
#include <iostream>
#include <locale>
#include <sstream>
#include <string>

int main(int argc, char* argv[]) {
    const std::time_get<char>& tg = std::use_facet<std::time_get<char> >(std::locale());

    std::stringstream s;
    s << "Friday";
    std::istreambuf_iterator<char> from(s);
    std::istreambuf_iterator<char> to;
    
    std::ios_base::iostate err = std::ios::goodbit;
    struct tm date;
    tg.get_weekday(from, to, s, err, &date);
    
    std::cout << date.tm_wday << std::endl;
    
    return 0;
}

get_monthname() もインターフェースは同じ.週名/月名は完全な名前,省略形どちらも指定可能なようです.解析するためにいったんストリームを作成しなければならないのが面倒.time_get のテンプレート引数を見ると以下のようになっていたので,第 2 引数を std::string::iterator にでもすれば行けるのかなと思ったのですが,get_weekday() に入力イテレータの他にストリーム自体も渡さないといけないようで・・・無理なのかなぁ.

template <
    typename CharT,
    typename InputIterator = istreambuf_iterator<CharT>
>
class time_get : public locale::facet, public time_base;

tokenmap

正確には,tokenmap + format_separator.tokenmap は,tokenizer のマップコンテナ版です.format_separator は,scanf() のように書式をもとにして文字列を分割するための TokenizerFunc なのですが,これを少し拡張して,トークンの他にそのトークンを切り出した書式の識別子も返すようにしました.これによって,例えば識別子に指定できる文字を"ymdHMS"の6文字に拡張して,tok['y'], tok['m'] のようにその識別子で各トークンにアクセスできるように実装しています.サンプルコードは以下の通り.

#include <iostream>
#include <string>
#include <vector>
#include "clx/tokenmap.h"

typedef clx::basic_tokenmap<clx::format_separator<char> > date_map;

int main(int argc, char* argv[]) {
    std::string dateset = "ymdHMS";
    std::string infmt = "%04y/%02m/%02d %2H:%02M:%02S";
    std::string sdate = "2006/06/06 17:27:00";
    std::cout << "Input format: " << infmt << std::endl;
    std::cout << "Sample: " << sdate << std::endl;
    
    // analyze time string.
    clx::format_separator<char> f(infmt, dateset);
    date_map m(f);
    m.assign(sdate);
    
    // print time with output format.
    for (date_map::const_iterator p = m.begin(); p != m.end(); p++) {
        std::cout << "map[" << p->first << "] = " << p->second << std::endl;
    }
    
    return 0;
}

詳細は,tokenmaptokenizer_func を参照して下さい.

tokenmap は元々,文字列から日付・時刻を解析するために作成したクラスでした.strptime() の存在を知ってからは使用していなかったのですが,JSON パーサを書く際に便利でした.何でも再利用な形にしておくと,そのうち役に立ちますね(労力とのトレードオフでしょうが).

xstrptime

さて,上記 2 つのクラスを利用して実装した strptime が以下のようになります.

#include <ctime>
#include <locale>
#include <iostream>
#include <sstream>
#include <string>
#include "clx/replace.h"
#include "clx/tokenmap.h"
#include "clx/tokenizer_func.h"
#include "clx/lexical_cast.h"

/* ------------------------------------------------------------------------- */
/*
 *  get_from_xxx
 *
 *  一旦ストリームを作成する必要があるので,その部分の操作を関数化.
 */
/* ------------------------------------------------------------------------- */
void get_from_monthname(const std::basic_string<char>& name, struct tm* dest,
    const std::time_get<char>& tg) {
    std::basic_stringstream<char> ss;
    ss << name;
    std::istreambuf_iterator<char> from(ss);
    std::istreambuf_iterator<char> to;
    std::ios_base::iostate err = std::ios::goodbit;
    tg.get_monthname(from, to, ss, err, dest);
}

void get_from_weekname(const std::basic_string<char>& name, struct tm* dest,
    const std::time_get<char>& tg) {
    std::basic_stringstream<char> ss;
    ss << name;
    std::istreambuf_iterator<char> from(ss);
    std::istreambuf_iterator<char> to;
    std::ios_base::iostate err = std::ios::goodbit;
    tg.get_weekday(from, to, ss, err, dest);
}

/* ------------------------------------------------------------------------- */
//  xstrptime
/* ------------------------------------------------------------------------- */
void xstrptime(const char* src, const char* fmt, struct tm* dest) {
    std::basic_string<char> tmp_fmt(fmt);
    clx::replace(tmp_fmt, std::basic_string<char>("%F"), std::basic_string<char>("%Y-%m-%d"));
    clx::replace(tmp_fmt, std::basic_string<char>("%T"), std::basic_string<char>("%H:%M:%S"));
    
    std::basic_string<char> dateset = "aAbByYmdeHkMSjuw";
    clx::format_separator<char> sep(tmp_fmt, dateset);
    clx::basic_tokenmap<clx::format_separator<char>, char, std::basic_string<char> > date(sep);
    date.assign(src);
    
    // Consider the value of '69 or less as 20XX.
    if (date.exist('y')) {
        int y = clx::lexical_cast<int>(date['y']);
        if (y < 70) dest->tm_year = y;
        else dest->tm_year = y + 100;
    }
    if (date.exist('Y')) dest->tm_year = clx::lexical_cast<int>(date['Y']) - 1900;
    if (date.exist('m')) dest->tm_mon  = clx::lexical_cast<int>(date['m']) - 1;
    if (date.exist('d')) dest->tm_mday = clx::lexical_cast<int>(date['d']);
    if (date.exist('e')) dest->tm_mday = clx::lexical_cast<int>(date['e']);
    if (date.exist('H')) dest->tm_hour = clx::lexical_cast<int>(date['H']);
    if (date.exist('k')) dest->tm_hour = clx::lexical_cast<int>(date['k']);
    if (date.exist('M')) dest->tm_min  = clx::lexical_cast<int>(date['M']);
    if (date.exist('S')) dest->tm_sec  = clx::lexical_cast<int>(date['S']);
    if (date.exist('j')) dest->tm_yday = clx::lexical_cast<int>(date['j']);
    if (date.exist('u')) dest->tm_wday = clx::lexical_cast<int>(date['u']) - 1;
    if (date.exist('w')) dest->tm_wday = clx::lexical_cast<int>(date['w']);
    
    // resolve month name
    const std::time_get<char>& tg = std::use_facet<std::time_get<char> >(std::locale());
    if (date.exist('b')) get_from_monthname(date['b'], dest, tg);
    else if (date.exist('B')) get_from_monthname(date['B'], dest, tg);
    
    // resolve week name
    if (date.exist('a')) get_from_weekname(date['a'], dest, tg);
    else if (date.exist('A')) get_from_weekname(date['A'], dest, tg);
}
$ ./test
Input string: Sat 17 Mar 2007 19:56:19 JST
Input format: %a %d %b %Y %H:%M:%S JST

Result
        • -
year: 107 mon: 2 day: 17 wday: 6 yday: 0 hour: 19 min: 56 sec: 19