文字列から日付・時刻の構造体 (struct tm) への変換にはこれまで strptime() を用いていたのですが,この関数は Windows には存在しないようです.なので,strptime() 相当のものを独自に実装して Windows でコンパイルする際には代替関数を使うようにしてみました(かなり手を抜いていますが).実装に使用したものは,主に以下の 2 つ.
- std::time_get<char>
- CLX C++ Libraries - tokenmap
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; }
詳細は,tokenmap,tokenizer_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
-
-
-
- -
-
-