C++ で今日は何の日か調べる

(1/1)
C++で今日は何の日か調べる
Wikipedia および 一般社団法人 日本記念日協会にアクセスし、カレンダーで指摘した日付で起きた出来事や記念日を表示したり、ファイルに保存するプログラムを作る。「PHPで今日は何の日か調べる」「PHPで記念日を表示する」で作ったPHPプログラムを合体し、C++に移植したものである。画面上で並べ替えることもできる。

(2026年5月24日)getDayToday() 「年」取得パターン修正, 使用ライブラリ更新
(2026年2月21日)使用ライブラリ更新
(2025年10月25日)getDayToday() キャッシュシステムを利用するよう変更,使用ライブラリ更新
(2025年9月11日)getDayToday() 「できごと」取得パターン変更
(2025年7月17日)不具合修正, 使用ライブラリ更新

目次

サンプル・プログラム

圧縮ファイルの内容
daytodaywin.msiインストーラ
bin/daytodaywin.exe実行プログラム本体
bin/libcurl-x64.dll 実行時に必要になるDLL
bin/etc/help.chmヘルプ・ファイル
sour/daytodaywin.cppソース・プログラム
sour/resource.hリソース・ヘッダ
sour/resource.rcリソース・ファイル
sour/application.icoアプリケーション・アイコン
sour/mystrings.cpp汎用文字列処理関数など(ソース)
sour/mystrings.h汎用文字列処理関数など(ヘッダ)
sour/pahooCache.cppキャッシュ処理に関わるクラス(ソース)
sour/pahooCache.hppキャッシュ処理に関わるクラス(ヘッダ)
sour/makefileビルド
daytodaywin.cpp 更新履歴
バージョン 更新日 内容
1.5.2 2026/05/24 getDayToday() 「年」取得パターン修正, 使用ライブラリ更新
1.5.1 2026/02/21 使用ライブラリ更新
1.5.0 2025/09/11 getDayToday() キャッシュシステムを利用するよう変更,使用ライブラリ更新
1.4.6 2025/09/11 getDayToday() 「できごと」取得パターン変更
1.4.5 2025/07/17 使用ライブラリ更新, readWebContents引数不足修正

使用ライブラリ

Wikipedia日本記念日協会にアクセスするために、オープンソースのライブラリ Boost C++ライブラリcURL (カール)  および OpenSSL が必要になる。導入方法等については、「C++ 開発環境の準備」をご覧いただきたい。

リソースの準備

Eclipse を起動し、新規プロジェクト daytodaywin を用意する。
ResEdit を起動し、resource.rc を用意する。
Eclipse に戻り、ソース・プログラム "daytodaywin.cpp" を追加する。
リンカー・フラグを -mwindows -static -lstdc++ -lgcc -lwinpthread -lcurl -lssl -llzma -lz -lws2_32 "C:\pleiades\eclipse\mingw\x86_64-w64-mingw32\bin\libcurl-x64.dll" に設定する。

MSYS2 コマンドラインからビルドするのであれば、"makefile" を利用してほしい。

解説:ヘッダファイル等

daytodaywin.cpp

  35: using namespace std;
  36: using namespace boost;
  37: using namespace boost::property_tree;
  38: 
  39: #define MAKER       "pahoo.org"             // 作成者
  40: #define APPNAME     "daytodaywin"           // アプリケーション名
  41: #define APPNAMEJP   "今日は何の日"          // アプリケーション名(日本語)
  42: #define APPVERSION  "1.5.2"                 // バージョン
  43: #define APPYEAR     "2020-25"               // 作成年・更新日
  44: #define REFERENCE   "https://www.pahoo.org/e-soul/webtech/cpp01/cpp01-11-01.shtm"   // 参考サイト
  45: 
  46: // char*バッファサイズ
  47: #define SIZE_BUFF2      5120
  48: 
  49: // ListViewItemの最大文字長:変更不可
  50: #define MAX_LISTVIEWITEM    255
  51: 
  52: // リクエストURL(日本記念日協会)(※変更不可)
  53: #define KINENBI_URL     "https://www.kinenbi.gr.jp/"
  54: 
  55: // 記念日識別値(※変更不可)
  56: #define KINENBI_YEAR    (-99999)
  57: 
  58: // キャッシュ・ディレクトリ
  59: #define DIR_CACHE_WIKIPEDIA     "pcache\\"
  60: 
  61: // キャッシュ保持時間(デフォルト;分)(0:キャッシュしない)
  62: #define LIFE_CACHE_WIKIPEDIA    (7 * 24 * 60)
  63: 
  64: // 現在のインターフェイス
  65: static HINSTANCE hInst;
  66: 
  67: // アプリケーション・ウィンドウ
  68: HWND hParent;
  69: 
  70: // アプリケーション・ウィンドウ位置
  71: unsigned hParent_X, hParent_Y;
  72: 
  73: // エラー・メッセージ格納用
  74: string ErrorMessage;
  75: 
  76: // ヘルプ・ファイル
  77: #define HELPFILE    ".\\etc\\help.chm"
  78: 
  79: // デフォルト保存ファイル名
  80: #define SAVEFILE    "daytodaywin.csv"
  81: 
  82: // UserAgent
  83: string UserAgent;

Windowsの ListViewクラスは、1つのカラムに格納できるデータ長制限がある。これを定数 MAX_LISTVIEWITEM で定義しておき、ListViewに代入するときにsubstrメソッドを使ってこの範囲に収まるようにする。

その他、とくに注意記載が無い限り、定数は自由に変更できる。

解説:データ構造

daytodaywin.cpp

  85: // 出来事格納用クラス
  86: #define SIZE_EVENTS     500     // 格納上限
  87: class _Events {
  88: public:
  89:     wstring category = L"";     // 区分
  90:     int year = 0;               // 西暦年
  91:     wstring era = L"";          // 元号
  92:     wstring event = L"";        // 出来事
  93:     wstring plus = L"";         // 追加情報
  94: };
  95: unique_ptr<_Events> Events[SIZE_EVENTS] = {};
  96: 
  97: // ソート用構造体
  98: struct _stEvents {
  99:     wchar_t* category = NULL;   // 区分
 100:     int *year = NULL;           // 西暦年
 101:     wchar_t* event = NULL;      // 出来事
 102:     size_t id = 0;              // Eventsの添字
 103: };
 104: vector<_stEvents> Vevents;

取得したHTMLコンテンツをPHPの連想配列のようにして使うために、C++スマートポインタ unique_ptr を導入する。
コンテンツの必要な部分をクラス _Events に収め、このクラスの実体であるオブジェクトへのポインタに unique_p 配列tr を利用する。出来事の数が未知なので、オブジェクトは必要に応じて動的に確保する(つまり、配列の要素数は不定)。

データ要素としては、日本語テキストを格納する要素にはワイド文字列型(wstring型)を割り当てた。西暦年は、intg型で格納しておく。

クラス _Events のメンバのうち、ソート可能な項目(文字列)へのポインタを vectorクラス へコピーする背景については、「C++ で Googleニュース検索」で述べたとおりである。

解説:キャッシュ・システム

後述する Wikipedia からの「今日は何の日」を取得する処理では、毎回、Wikipedia にアクセスすることは、サイトへ負荷を掛けることになる。そこで、PHPプログラムの場合と同様、一定時間、ローカルドライブにXMLファイルを保持しておくキャッシュ・システムを導入した。
このキャッシュ・システムの仕組みについては、「解説:pahooCacheクラスとデータ構造 - C++ で週間天気予報を表示する」をご覧いただきたい。

daytodaywin.cpp

  58: // キャッシュ・ディレクトリ
  59: #define DIR_CACHE_WIKIPEDIA     "pcache\\"
  60: 
  61: // キャッシュ保持時間(デフォルト;分)(0:キャッシュしない)
  62: #define LIFE_CACHE_WIKIPEDIA    (7 * 24 * 60)

キャッシュ保持時間、キャッシュ・ディレクトリともに、ローカル環境に応じて変更してほしい。配布ファイルは、キャッシュ・ディレクトリは "[%USERPROFILE%\AppData\Roaming\pahoo.org\daytodaywin\pcache\:vbblue]" に、キャッシュ保持時間は7日間を設定している。

解説:Wikipedia URL取得

daytodaywin.cpp

 327: /**
 328:  * Wikipedia URL取得
 329:  * @param   int month, day  月,日
 330:  * @param   char* url       URLを格納
 331:  * @param   size_t sz       urlの最大長
 332:  * @return  なし
 333: */
 334: void getURL_Wikipedia(int month, int day, char *url, size_t sz) {
 335:     const string wiki = "https://ja.wikipedia.org/wiki/";
 336:     static char buff1[SIZE_BUFF2 + 1], buff2[SIZE_BUFF2 + 1];
 337: 
 338:     snprintf(buff1, SIZE_BUFF2, "%d月%d日", month, day);
 339:     string s1 = sjis_utf8(buff1);
 340: 
 341:     CURL *curl = curl_easy_init();
 342:     strncpy(buff2, curl_easy_escape(curl, (const char *)s1.c_str(), strlen(s1.c_str())), SIZE_BUFF2);
 343: 
 344: 
 345:     string ss = wiki + (string)buff2;
 346:     strncpy(url, ss.c_str(), SIZE_BUFF2);
 347: 
 348:     curl_easy_cleanup(curl);
 349: }

Wikipedia には「×月×日」という見出しがあり、ここから「今日は何の日」を取得する。
見出しURLを作成するユーザー関数が getURL_Wikipedia である。

解説:今日は何の日 取得

daytodaywin.cpp

 361: /**
 362:  * 今日は何の日 取得
 363:  * @param   int  month, day 月,日
 364:  * @param   bool lfn        脚注を残す(省略時=FALSE)
 365:  * @return  int 情報件数
 366: */
 367: int getDayToday(int month, int day, bool lfn=FALSE) {
 368:     // 初期化
 369:     for (int i = 0i < SIZE_EVENTSi++) {
 370:         Events[i].reset();
 371:         Events[i] = NULL;
 372:     }
 373: 
 374:     // キャッシュシステム準備
 375:     pahooCache* pCC;
 376:     pCC = new pahooCache(LIFE_CACHE_WIKIPEDIA, getMyPath(APPNAME+ DIR_CACHE_WIKIPEDIA, UserAgent);
 377: 
 378:     // Wikipediaコンテンツ取得
 379:     char url[SIZE_BUFF2];
 380:     string contents = "";
 381:     getURL_Wikipedia(month, day, (char *)url, SIZE_BUFF2);
 382:     if (pCC->load(url, &contents) == FALSE) {
 383:         ErrorMessage = "Wikipediaにアクセスできません:" + _WS(pCC->getError());
 384:         delete pCC;
 385:         return FALSE;
 386:     }
 387: 
 388:     // コンテンツの解釈
 389:     setlocale(LC_ALL, "Japanese");
 390:     int cnt = -1;
 391:     stringstream ss;
 392:     string ss0;
 393:     wstring mode = L"";
 394:     wstring ws, ws2;
 395:     wstringstream wss;
 396:     static wsmatch mt1, mt2, mt3;
 397:     static smatch mt4;
 398:     wregex re1(_SW("<h2[\\s\\S]+id=\"(できごと|出来事)\""));
 399:     wregex re2(_SW("<h2[\\s\\S]+id=\"誕生日\""));
 400:     wregex re3(_SW("<h2[\\s\\S]+id=\"忌日\""));
 401: //  wregex re5(_SW("<li>[^\\>]+([0-9]+(年|世紀).+)<\\/li>"));
 402:     wregex re5(_SW("<li[^\\>]*>[^\\>]+([0-9]+(年|世紀).+)<\\/li>"));
 403:     wregex re6(_SW("([^0-9]*)([0-9]+)年(\\s*[\((].[^\\))]+[)\\)].)?[ \\-]+(.+)"));   //  - 出来事
 404:     wregex re7(_SW("([^0-9]*)([0-9]+)年(\\s*[\\((].[^\\))]+[)\\)].)?[ \\-]+([^、]+)、?([^(\\(]+)*([^0-9]*)([0-9]*)"));      //  - 名前、職業(生没年)
 405:     wregex re8(_SW("<span[\\s\\S]+id=\"フィクションのできごと\">"));
 406:     wregex re9(_SW("^\\([\\*\\+]."));          //  - 名前、職業(生没年)
 407:     wregex re11(_SW("紀元前"));
 408:     wregex re99(_SW("<span[\\s\\S]+class=\"vector-menu-heading-label\">他言語版"));     // 1行読み込み打ち切り
 409: 
 410:     ss << contents;
 411:     while(ss && getline(ss, ss0)) {
 412:         // 1行をwstring変換
 413:         ws = _UW(ss0);
 414:         // 4000文字以上なら処理しない
 415:         if (ws.length() > 4000) {
 416:             continue;
 417:         }
 418:         // マッチング処理
 419:         if (regex_search(ws, mt1, re99)) {
 420:             break;
 421:         } else if (regex_search(ws, mt1, re1)) {
 422:             mode = _SW("出来事");
 423:         } else if (regex_search(ws, mt1, re8)) {
 424:             mode = _SW("架空");
 425:         } else if (regex_search(ws, mt1, re2)) {
 426:             mode = _SW("誕生");
 427:         } else if (regex_search(ws, mt1, re3)) {
 428:             mode = _SW("死亡");
 429:         } else if ((mode.length() !0&& (regex_search(ws, mt1, re5) == TRUE)) {
 430:             // HTMLタグ除去
 431:             ws = wstrip_tags(ws);
 432:             // 注釈番号除去
 433:             ws = wstripAnnotations(ws);
 434:             // 出来事
 435:             if ((mode == _SW("出来事")) && (regex_search(ws, mt2, re6) == TRUE)) {
 436:                 cnt++;
 437:                 Events[cnt] = make_unique<_Events>();
 438:                 Events[cnt]->category = mode;
 439:                 if (mt2[1].str().length() == 0) {
 440:                     Events[cnt]->year = atoi(_WS(mt2[2].str()).c_str());
 441:                 } else {
 442:                     Events[cnt]->year = 0 - atoi(_WS(mt2[2].str()).c_str());
 443:                 }
 444:                 Events[cnt]->era   = mt2[3].str();
 445:                 Events[cnt]->event = mt2[4].str();
 446: 
 447:             // 誕生・死亡
 448:             } else if (regex_search(ws, mt2, re7)) {
 449:                 cnt++;
 450:                 Events[cnt] = make_unique<_Events>();
 451:                 Events[cnt]->category = mode;
 452:                 if (mt2[1].str().length() == 0) {
 453:                     Events[cnt]->year = atoi(_WS(mt2[2].str()).c_str());
 454:                 } else {
 455:                     Events[cnt]->year = 0 - atoi(_WS(mt2[2].str()).c_str());
 456:                 }
 457:                 Events[cnt]->era = mt2[3].str();
 458:                 ws2 = mt2[6].str();
 459:                 if (regex_search(ws2, mt3, re11) == TRUE) {
 460:                     Events[cnt]->plus = L"-" + mt2[7].str();
 461:                 } else if (regex_search(ws2, mt3, re9) == TRUE) {
 462:                     Events[cnt]->plus = mt2[7].str();
 463:                 } else {
 464:                     Events[cnt]->plus = L"";
 465:                 }
 466:                 wss << mt2[4].str() << _SW("("<< mt2[5].str();
 467:                 if (mode == _SW("誕生")) {
 468:                     wss << _SW(")誕生");
 469:                     if (Events[cnt]->plus.length() !0) {
 470:                         wss << _SW("(〜"<< Events[cnt]->plus << _SW("年)");
 471:                     }
 472:                 } else {
 473:                     wss << _SW(")死去");
 474:                     if (Events[cnt]->plus.length() !0) {
 475:                         wss << _SW("("<< Events[cnt]->plus << _SW("年〜")<< Events[cnt]->year << _SW("年)");
 476:                     }
 477:                 }
 478:                 Events[cnt]->event = wss.str();
 479:                 wss.str(L"");
 480:             }
 481:         }
 482:     }
 483:     // インスタンス解放
 484:     delete pCC;
 485: 
 486:     return cnt;
 487: }

取得するURLが決まったら、cURL 関数群を使って全コンテンツを変数 chunk に代入する。
次に、この内容をスクレイピングしていくのだが、今回は、ワイド文字列に対する正規表現を使うことにした。ソースはSJISで書いているので、ユーザーマクロ関数 _SW を使ってワイド文字列に変換し、これを使って正規表現によるパターンマッチングを行う。ちなみに、C++に正規表現が正式導入されたのはC++11からで、Boost C++ライブラリをベースにしている。
スクレイピング用パターン
パターン名内容
re1できごと
re2誕生日
re3忌日
re5西暦年
re6西暦年(出来事)
re7年 - 名前、職業(生没年)
re8フィクションのできごと
re9年 - 名前、職業(生没年)
re11紀元前の識別
re991行読み込み打ち切りパターン

解説:記念日を取得

daytodaywin.cpp

 489: /**
 490:  * 指定月日の記念日を取得する
 491:  * @param   int month, day 月日
 492:  * @param   int cnt 情報を追加する配列の最初の番号
 493:  * @return  int 情報の総数
 494: */
 495: int getAnniversary(int month, int day, int cnt=0) {
 496:     // コンテンツ読み込み
 497:     static char post[SIZE_BUFF2 + 1];
 498:     snprintf(post, SIZE_BUFF2, "MD=%d&M=%d&D=%d", month, month, day);
 499:     string contents = "";
 500:     int httpStatus;
 501:     bool res = readWebContents(KINENBI_URL, UserAgent, &contents, &httpStatus, post);
 502: 
 503:     if (res == FALSE) {
 504:         ErrorMessage = "日本記念日協会のサイトにアクセスできません.";
 505:         return (-1);
 506:     }
 507: 
 508:     // スクレイピング
 509:     wregex re1(_SW("<a\\s+class=\"winDetail\"\\s+href=\"([^\"]+)\"><font[^>]+>([^<]+)</font>"), wregex::icase);
 510:     static wsmatch mt1;
 511: 
 512:     stringstream ss;
 513:     wstring mode = _SW("記念日");
 514:     string ss0;
 515:     wstring ws;
 516:     ss << contents;
 517:     while(ss && getline(ss, ss0)) {
 518:         // 1行をwstring変換
 519:         ws = _UW(ss0);
 520:         if (regex_search(ws, mt1, re1)) {
 521:             Events[cnt] = make_unique<_Events>();
 522:             Events[cnt]->category = mode;
 523:             Events[cnt]->event = mt1[2].str();
 524:             Events[cnt]->year  = KINENBI_YEAR;
 525:             cnt++;
 526:         }
 527:     }
 528: 
 529:     return cnt;
 530: }

日本記念日協会にアクセスし、指定した月日の記念日を取り出す。
PHPで記念日を表示する」で紹介した関数 scrapingAnniversarygetAnniversary を合体させた。
前述の getDayToday で取り込んだ情報配列 Events に記念日を追加していく。
その他の関数、ヘルプファイルやインストーラー作成方法については、これまでの連載で説明してきたとおりである。

参考サイト

(この項おわり)
header