db.cpp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780
  1. #include "db.h"
  2. #include "utils.h"
  3. #include <SQLiteCpp/VariadicBind.h>
  4. #include <chrono>
  5. #include <iomanip>
  6. #include <iostream>
  7. #include <sstream>
  8. #include <thread>
  9. /*
  10. database is locked
  11. This happens when more then one node plays the game.
  12. The database access is slow.
  13. So, make sure you set it up so that you do your writes right
  14. before you collect user input. That way, the user won't see
  15. the lags.
  16. This might be an issue on rPI systems!
  17. Change the strategy so we only update when the game ends.
  18. */
  19. DBData::DBData(void)
  20. : db("space-data.db", SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE) {
  21. locked_retries = 50;
  22. create_tables();
  23. }
  24. DBData::~DBData() {}
  25. void DBData::retry_wait(void) {
  26. std::this_thread::sleep_for(std::chrono::milliseconds(100));
  27. }
  28. #define DBLOCK "database is locked"
  29. /**
  30. * @brief create tables if they don't exist.
  31. */
  32. void DBData::create_tables(void) {
  33. int tries = 0;
  34. retry:
  35. try {
  36. db.exec("CREATE TABLE IF NOT EXISTS settings(username TEXT, setting TEXT, "
  37. "value TEXT, PRIMARY KEY(username, setting));");
  38. db.exec(
  39. "CREATE TABLE IF NOT EXISTS scores ( \"username\" TEXT, \"when\" "
  40. "INTEGER, \"date\" INTEGER, \"hand\" INTEGER, \"won\" INTEGER, "
  41. "\"score\" INTEGER, PRIMARY KEY(\"username\", \"date\", \"hand\"));");
  42. db.exec("CREATE TABLE IF NOT EXISTS \"monthly\" ( \"month\" INTEGER, "
  43. "\"username\" TEXT, \"days\" INTEGER, \"hands_won\" INTEGER, "
  44. "\"score\" INTEGER, PRIMARY KEY(\"month\",\"username\") );");
  45. } catch (std::exception &e) {
  46. if (get_logger) {
  47. get_logger() << "create_tables():" << std::endl;
  48. get_logger() << "SQLite exception: " << e.what() << std::endl;
  49. }
  50. if (strcmp(e.what(), DBLOCK) == 0) {
  51. ++tries;
  52. if (tries < locked_retries) {
  53. retry_wait();
  54. goto retry;
  55. }
  56. if (get_logger)
  57. get_logger() << "giving up! " << tries << " retries." << std::endl;
  58. }
  59. }
  60. if (tries > 0) {
  61. if (get_logger)
  62. get_logger() << "success after " << tries << std::endl;
  63. }
  64. }
  65. /**
  66. * @brief get setting from the settings table
  67. *
  68. * We use user and setting.
  69. * Return isMissing if not found.
  70. *
  71. * @param setting
  72. * @param ifMissing
  73. * @return std::string
  74. */
  75. std::string DBData::getSetting(const std::string &setting,
  76. std::string ifMissing) {
  77. SQLite::Statement stmt_getSet = SQLite::Statement(
  78. db, "SELECT value FROM settings WHERE username=? AND setting=?");
  79. int tries = 0;
  80. retry:
  81. try {
  82. stmt_getSet.bind(1, user);
  83. stmt_getSet.bind(2, setting);
  84. if (stmt_getSet.executeStep()) {
  85. std::string value = stmt_getSet.getColumn(0);
  86. return value;
  87. };
  88. return ifMissing;
  89. } catch (std::exception &e) {
  90. if (get_logger) {
  91. get_logger() << "getSettings( " << setting << "," << ifMissing
  92. << " ): " << user << std::endl;
  93. get_logger() << "SQLite exception: " << e.what() << std::endl;
  94. }
  95. if (strcmp(e.what(), DBLOCK) == 0) {
  96. ++tries;
  97. if (tries < locked_retries) {
  98. retry_wait();
  99. goto retry;
  100. }
  101. if (get_logger)
  102. get_logger() << "giving up! " << tries << " retries." << std::endl;
  103. }
  104. }
  105. if (tries > 0) {
  106. if (get_logger)
  107. get_logger() << "success after " << tries << std::endl;
  108. }
  109. return ifMissing;
  110. }
  111. /**
  112. * @brief save setting in the settings table
  113. *
  114. * We save user setting in the settings table.
  115. * We use SQLite's REPLACE INTO so it does an update if it exists, or an insert
  116. * if it is missing.
  117. * @param setting
  118. * @param value
  119. */
  120. void DBData::setSetting(const std::string &setting, const std::string &value) {
  121. SQLite::Statement stmt_setSet = SQLite::Statement(
  122. db, "REPLACE INTO settings(username, setting, value) VALUES(?,?,?);");
  123. int tries = 0;
  124. retry:
  125. try {
  126. stmt_setSet.bind(1, user);
  127. stmt_setSet.bind(2, setting);
  128. stmt_setSet.bind(3, value);
  129. stmt_setSet.exec();
  130. } catch (std::exception &e) {
  131. if (get_logger) {
  132. get_logger() << "setSettings( " << setting << "," << value
  133. << " ): " << user << std::endl;
  134. get_logger() << "SQLite exception: " << e.what() << std::endl;
  135. }
  136. if (strcmp(e.what(), DBLOCK) == 0) {
  137. ++tries;
  138. if (tries < locked_retries) {
  139. retry_wait();
  140. goto retry;
  141. }
  142. if (get_logger)
  143. get_logger() << "giving up! " << tries << " retries." << std::endl;
  144. }
  145. }
  146. if (tries > 0)
  147. if (get_logger)
  148. get_logger() << "success after " << tries << std::endl;
  149. }
  150. /**
  151. * @brief save the user's score
  152. *
  153. * @param when now()
  154. * @param date what day they played
  155. * @param hand which hand they played
  156. * @param won did they win? 1/0
  157. * @param score
  158. */
  159. void DBData::saveScore(time_t when, time_t date, int hand, int won, int score) {
  160. int tries = 0;
  161. retry:
  162. try {
  163. SQLite::Statement stmt(
  164. db, "INSERT INTO scores( \"username\", \"when\", "
  165. "\"date\", \"hand\", \"won\", \"score\") VALUES(?,?,?,?,?,?);");
  166. stmt.bind(1, user);
  167. stmt.bind(2, when);
  168. stmt.bind(3, date);
  169. stmt.bind(4, hand);
  170. stmt.bind(5, won);
  171. stmt.bind(6, score);
  172. stmt.exec();
  173. } catch (std::exception &e) {
  174. if (get_logger) {
  175. get_logger() << "saveScore( " << when << "," << date << "," << hand << ","
  176. << won << "," << score << " ): " << user << std::endl;
  177. get_logger() << "SQLite exception: " << e.what() << std::endl;
  178. }
  179. if (strcmp(e.what(), DBLOCK) == 0) {
  180. ++tries;
  181. if (tries < locked_retries) {
  182. retry_wait();
  183. goto retry;
  184. }
  185. if (get_logger)
  186. get_logger() << "giving up! " << tries << " retries." << std::endl;
  187. }
  188. }
  189. if (tries > 0)
  190. if (get_logger)
  191. get_logger() << "success after " << tries << std::endl;
  192. }
  193. /**
  194. * @brief Returns number of hands played on given day
  195. *
  196. * returns number of hands they played, or 0
  197. * @param day
  198. * @return int
  199. */
  200. int DBData::handsPlayedOnDay(time_t day) {
  201. int tries = 0;
  202. retry:
  203. try {
  204. SQLite::Statement stmt(
  205. db, "SELECT COUNT(*) FROM scores WHERE \"username\"=? AND \"DATE\"=?;");
  206. stmt.bind(1, user);
  207. stmt.bind(2, day);
  208. int count = 0;
  209. if (stmt.executeStep()) {
  210. count = stmt.getColumn(0);
  211. };
  212. return count;
  213. } catch (std::exception &e) {
  214. if (get_logger) {
  215. get_logger() << "handsPlayedOnDay( " << day << " ): " << user
  216. << std::endl;
  217. get_logger() << "SQLite exception: " << e.what() << std::endl;
  218. }
  219. if (strcmp(e.what(), DBLOCK) == 0) {
  220. ++tries;
  221. if (tries < locked_retries) {
  222. retry_wait();
  223. goto retry;
  224. }
  225. if (get_logger)
  226. get_logger() << "giving up! " << tries << " retries." << std::endl;
  227. }
  228. }
  229. if (tries > 0)
  230. if (get_logger)
  231. get_logger() << "success after " << tries << std::endl;
  232. return 0;
  233. }
  234. /*
  235. * If you're looking at scores, you're not really looking for all the details.
  236. * I think using the group/SUM would be better, and it sorts scores from highest
  237. * to lowest. Let SQL do the work for me.
  238. */
  239. // select date,username,SUM(score),SUM(won) FROM scores group by date,username
  240. // ORDER BY SUM(score) DESC;
  241. std::vector<scores_details> DBData::getScoresOnDay(time_t date) {
  242. std::vector<scores_details> scores;
  243. int tries = 0;
  244. retry:
  245. try {
  246. // \"when\",
  247. SQLite::Statement stmt(db, "SELECT \"username\", \"date\", \"hand\", "
  248. "\"won\", \"score\" FROM SCORES WHERE "
  249. "\"date\"=? ORDER BY \"username\", \"hand\";");
  250. stmt.bind(1, date);
  251. while (stmt.executeStep()) {
  252. scores_details sd;
  253. sd.user = (const char *)stmt.getColumn(0);
  254. sd.date = stmt.getColumn(1);
  255. sd.hand = stmt.getColumn(2);
  256. sd.won = stmt.getColumn(3);
  257. sd.score = stmt.getColumn(4);
  258. scores.push_back(sd);
  259. }
  260. } catch (std::exception &e) {
  261. if (get_logger) {
  262. get_logger() << "getScoresOnDay( " << date << " ): " << std::endl;
  263. get_logger() << "SQLite exception: " << e.what() << std::endl;
  264. }
  265. scores.clear();
  266. if (strcmp(e.what(), DBLOCK) == 0) {
  267. ++tries;
  268. if (tries < locked_retries) {
  269. retry_wait();
  270. goto retry;
  271. }
  272. if (get_logger)
  273. get_logger() << "giving up! " << tries << " retries." << std::endl;
  274. }
  275. }
  276. if (tries > 0)
  277. if (get_logger)
  278. get_logger() << "success after " << tries << std::endl;
  279. return scores;
  280. }
  281. /**
  282. * @brief Gets scores, time_t is day, vector has scores sorted highest
  283. * to lowest.
  284. *
  285. * @return std::map<time_t, std::vector<scores_data>>
  286. */
  287. std::vector<scores_data> DBData::getScores(int limit) {
  288. std::vector<scores_data> scores;
  289. int tries = 0;
  290. retry:
  291. try {
  292. SQLite::Statement stmt(
  293. db, "SELECT `date`,username,SUM(score),SUM(won) FROM scores "
  294. "GROUP BY `date`,username ORDER BY SUM(score) DESC LIMIT ?;");
  295. stmt.bind(1, limit);
  296. // db, "SELECT `date`,username,SUM(score),SUM(won) FROM scores "
  297. // "GROUP BY `date`,username ORDER BY `date`,SUM(score) DESC;");
  298. while (stmt.executeStep()) {
  299. scores_data sd;
  300. sd.date = (long)stmt.getColumn(0);
  301. sd.user = (const char *)stmt.getColumn(1);
  302. sd.score = stmt.getColumn(2);
  303. sd.won = stmt.getColumn(3);
  304. scores.push_back(sd);
  305. }
  306. } catch (std::exception &e) {
  307. if (get_logger) {
  308. get_logger() << "getScores(): " << std::endl;
  309. get_logger() << "SQLite exception: " << e.what() << std::endl;
  310. }
  311. scores.clear();
  312. if (strcmp(e.what(), DBLOCK) == 0) {
  313. ++tries;
  314. if (tries < locked_retries) {
  315. retry_wait();
  316. goto retry;
  317. }
  318. if (get_logger)
  319. get_logger() << "giving up! " << locked_retries << " retries."
  320. << std::endl;
  321. }
  322. }
  323. if (tries > 0)
  324. if (get_logger)
  325. get_logger() << "success after " << tries << std::endl;
  326. return scores;
  327. }
  328. /**
  329. * @brief Gets scores, time_t is day, vector has user and scores sorted highest
  330. * to lowest.
  331. *
  332. * ( \"month\", \"username\" , \"days\", hands_won, "\"score\"
  333. *
  334. * @return std::map<time_t, std::vector<scores_data>>
  335. */
  336. std::vector<monthly_data> DBData::getMonthlyScores(int limit) {
  337. std::vector<monthly_data> scores;
  338. scores.reserve(limit);
  339. int tries = 0;
  340. retry:
  341. try {
  342. SQLite::Statement stmt(
  343. db, "SELECT month, username, days, hands_won, score FROM monthly "
  344. "ORDER BY score DESC LIMIT ?;");
  345. stmt.bind(1, limit);
  346. while (stmt.executeStep()) {
  347. monthly_data data;
  348. data.date = (long)stmt.getColumn(0);
  349. data.user = (const char *)stmt.getColumn(1);
  350. data.days = stmt.getColumn(2);
  351. data.hands_won = stmt.getColumn(3);
  352. data.score = stmt.getColumn(4);
  353. scores.push_back(data);
  354. }
  355. } catch (std::exception &e) {
  356. if (get_logger) {
  357. get_logger() << "getMonthlyScores( " << limit << "): " << std::endl;
  358. get_logger() << "SQLite exception: " << e.what() << std::endl;
  359. }
  360. scores.clear();
  361. if (strcmp(e.what(), DBLOCK) == 0) {
  362. ++tries;
  363. if (tries < locked_retries) {
  364. retry_wait();
  365. goto retry;
  366. }
  367. if (get_logger)
  368. get_logger() << "giving up! " << locked_retries << " retries."
  369. << std::endl;
  370. }
  371. }
  372. if (tries > 0)
  373. if (get_logger)
  374. get_logger() << "success after " << tries << std::endl;
  375. return scores;
  376. }
  377. /**
  378. * @brief Get hands played per day
  379. *
  380. * Uses the user value.
  381. *
  382. * @return std::map<time_t, int>
  383. */
  384. std::map<time_t, int> DBData::getPlayed(void) {
  385. std::map<time_t, int> hands;
  386. int tries = 0;
  387. retry:
  388. try {
  389. SQLite::Statement stmt(
  390. // select date, count(hand) from scores where username='grinder' group
  391. // by date;
  392. db, "SELECT `date`,COUNT(hand) FROM scores "
  393. "WHERE username=? GROUP BY `date`;");
  394. stmt.bind(1, user);
  395. while (stmt.executeStep()) {
  396. time_t the_date = stmt.getColumn(0);
  397. hands[the_date] = stmt.getColumn(1);
  398. }
  399. } catch (std::exception &e) {
  400. if (get_logger) {
  401. get_logger() << "getPlayed(): " << user << std::endl;
  402. get_logger() << "SQLite exception: " << e.what() << std::endl;
  403. }
  404. hands.clear();
  405. if (strcmp(e.what(), DBLOCK) == 0) {
  406. ++tries;
  407. if (tries < locked_retries) {
  408. retry_wait();
  409. goto retry;
  410. }
  411. if (get_logger)
  412. get_logger() << "giving up! " << tries << " retries." << std::endl;
  413. }
  414. }
  415. if (tries > 0)
  416. if (get_logger)
  417. get_logger() << "success after " << tries << std::endl;
  418. return hands;
  419. }
  420. /**
  421. * @brief When has the user played?
  422. *
  423. * This returns a map of date (time_t), and number of hands played on that
  424. * date.
  425. *
  426. * @return std::map<time_t, long>
  427. */
  428. std::map<time_t, int> DBData::whenPlayed(void) {
  429. // select "date", count(hand) from scores where username='?' group by
  430. // "date";
  431. std::map<time_t, int> plays;
  432. int tries = 0;
  433. retry:
  434. try {
  435. SQLite::Statement stmt(db, "SELECT `date`, COUNT(hand) FROM scores WHERE "
  436. "username=? GROUP BY `date`;");
  437. stmt.bind(1, user);
  438. while (stmt.executeStep()) {
  439. time_t d = (long)stmt.getColumn(0);
  440. plays[d] = stmt.getColumn(1);
  441. }
  442. } catch (std::exception &e) {
  443. if (get_logger) {
  444. get_logger() << "whenPlayed(): " << std::endl;
  445. get_logger() << "SQLite exception: " << e.what() << std::endl;
  446. }
  447. plays.clear();
  448. if (strcmp(e.what(), DBLOCK) == 0) {
  449. ++tries;
  450. if (tries < locked_retries) {
  451. retry_wait();
  452. goto retry;
  453. }
  454. if (get_logger)
  455. get_logger() << "giving up! " << tries << " retries." << std::endl;
  456. }
  457. }
  458. if (tries > 0)
  459. if (get_logger)
  460. get_logger() << "success after " << tries << std::endl;
  461. return plays;
  462. }
  463. struct month_user {
  464. time_t date;
  465. std::string username;
  466. // friend bool operator<(const month_user &l, const month_user &r);
  467. };
  468. bool operator<(const month_user &l, const month_user &r) {
  469. if (l.date < r.date)
  470. return true;
  471. if (l.date > r.date)
  472. return false;
  473. // Otherwise a are equal
  474. if (l.username < r.username)
  475. return true;
  476. if (l.username > r.username)
  477. return false;
  478. // Otherwise both are equal
  479. return false;
  480. }
  481. struct month_stats {
  482. int days;
  483. int hands_won;
  484. int score;
  485. };
  486. /**
  487. * @brief This will expire out old scores
  488. *
  489. * Merges scores into monthly table.
  490. *
  491. * @param month_first_t
  492. *
  493. */
  494. bool DBData::expireScores(time_t month_first_t) {
  495. // step 1: aquire lock
  496. std::ofstream lockfile;
  497. lockfile.open("db.lock", std::ofstream::out | std::ofstream::app);
  498. long pos = lockfile.tellp();
  499. if (pos == 0) {
  500. lockfile << "OK.";
  501. lockfile.flush();
  502. lockfile.close();
  503. } else {
  504. if (get_logger()) {
  505. get_logger() << "db.lock file exists. Skipping maint." << std::endl;
  506. }
  507. lockfile.close();
  508. return false;
  509. }
  510. std::map<month_user, month_stats> monthly;
  511. // Ok, do maint things here
  512. SQLite::Statement stmt(
  513. db, "SELECT `date`,username,SUM(score),SUM(won) FROM scores "
  514. "WHERE `date` < ? GROUP BY `date`,username ORDER BY "
  515. "`date`,SUM(score) DESC;");
  516. try {
  517. stmt.bind(1, (long)month_first_t);
  518. while (stmt.executeStep()) {
  519. // get time_t, conver to time_point, find first of month, convert back
  520. // to time_t
  521. std::chrono::_V2::system_clock::time_point date;
  522. date = std::chrono::system_clock::from_time_t((long)stmt.getColumn(0));
  523. firstOfMonthDate(date);
  524. time_t date_t = std::chrono::system_clock::to_time_t(date);
  525. month_user mu;
  526. mu.date = date_t;
  527. mu.username = (const char *)stmt.getColumn(1);
  528. auto map_iter = monthly.find(mu);
  529. if (map_iter == monthly.end()) {
  530. // not found
  531. month_stats ms;
  532. ms.days = 1;
  533. ms.score = stmt.getColumn(2);
  534. ms.hands_won = stmt.getColumn(3);
  535. monthly[mu] = ms;
  536. } else {
  537. // was found
  538. month_stats &ms = monthly[mu];
  539. ms.days++;
  540. ms.score += (int)stmt.getColumn(2);
  541. ms.hands_won += (int)stmt.getColumn(3);
  542. }
  543. }
  544. } catch (std::exception &e) {
  545. if (get_logger) {
  546. get_logger() << "expireScores() SELECT failed" << std::endl;
  547. get_logger() << "SQLite exception: " << e.what() << std::endl;
  548. }
  549. }
  550. // Ok! I have the data that I need!
  551. if (monthly.empty()) {
  552. std::remove("db.lock");
  553. return false;
  554. }
  555. try {
  556. SQLite::Transaction transaction(db);
  557. // If we fail for any reason within here -- we should be safe.
  558. SQLite::Statement stmt_delete(db, "DELETE FROM scores WHERE `date` < ?;");
  559. stmt_delete.bind(1, (long)month_first_t);
  560. stmt_delete.exec();
  561. /*
  562. if (get_logger) {
  563. get_logger() << "Ok, deleted records < " << month_first_t <<
  564. std::endl;
  565. }
  566. */
  567. SQLite::Statement stmt_insert(db,
  568. "INSERT INTO monthly(month, username, days, "
  569. "hands_won, score) VALUES(?,?,?,?,?);");
  570. for (auto key : monthly) {
  571. /*
  572. if (get_logger) {
  573. get_logger() << key.first.date << ":" << key.first.username << "
  574. "
  575. << key.second.score << std::endl;
  576. }
  577. */
  578. stmt_insert.bind(1, (long)key.first.date);
  579. stmt_insert.bind(2, key.first.username.c_str());
  580. stmt_insert.bind(3, key.second.days);
  581. stmt_insert.bind(4, key.second.hands_won);
  582. stmt_insert.bind(5, key.second.score);
  583. stmt_insert.exec();
  584. stmt_insert.reset();
  585. }
  586. transaction.commit();
  587. } catch (std::exception &e) {
  588. if (get_logger) {
  589. get_logger() << "expireScores() DELETE/INSERTs failed" << std::endl;
  590. get_logger() << "SQLite exception: " << e.what() << std::endl;
  591. }
  592. }
  593. if (get_logger) {
  594. for (auto key : monthly) {
  595. get_logger() << key.first.date << ":" << key.first.username << " "
  596. << key.second.days << ", " << key.second.hands_won << ", "
  597. << key.second.score << ::std::endl;
  598. }
  599. }
  600. // clean up
  601. std::remove("db.lock");
  602. return true;
  603. }
  604. /**
  605. * @brief Format date to string.
  606. *
  607. * We use default "%0m/%0d/%Y", but can be configured by SysOp via
  608. * config["date_score"] setting. "%Y/%0m/%0d" for non-US?
  609. *
  610. * @param tt
  611. * @return std::string
  612. */
  613. std::string convertDateToDateScoreFormat(time_t tt) {
  614. std::stringstream ss;
  615. if (config["date_score"]) {
  616. std::string custom_format = config["date_score"].as<std::string>();
  617. ss << std::put_time(std::localtime(&tt), custom_format.c_str());
  618. } else {
  619. ss << std::put_time(std::localtime(&tt), "%0m/%0d/%Y");
  620. }
  621. std::string date = ss.str();
  622. return date;
  623. }
  624. /**
  625. * @brief Format date to string.
  626. *
  627. * We use default "%0m/%0d/%Y", but can be configured by SysOp via
  628. * config["date_score"] setting. "%Y/%0m/%0d" for non-US?
  629. * https://en.cppreference.com/w/cpp/io/manip/put_time
  630. *
  631. * @param tt
  632. * @return std::string
  633. */
  634. std::string convertDateToMonthlyFormat(time_t tt) {
  635. std::stringstream ss;
  636. if (config["date_monthly"]) {
  637. std::string custom_format = config["date_monthly"].as<std::string>();
  638. ss << std::put_time(std::localtime(&tt), custom_format.c_str());
  639. } else {
  640. ss << std::put_time(std::localtime(&tt), "%B %Y");
  641. }
  642. std::string date = ss.str();
  643. return date;
  644. }
  645. /**
  646. * @brief Format date to string.
  647. *
  648. * We use default "%B %0d".
  649. * https://en.cppreference.com/w/cpp/io/manip/put_time
  650. *
  651. * @param tt
  652. * @return std::string
  653. */
  654. std::string convertDateToMonthDayFormat(time_t tt) {
  655. std::stringstream ss;
  656. if (config["date_monthday"]) {
  657. std::string custom_format = config["date_monthday"].as<std::string>();
  658. ss << std::put_time(std::localtime(&tt), custom_format.c_str());
  659. } else {
  660. ss << std::put_time(std::localtime(&tt), "%b %Od");
  661. }
  662. std::string date = ss.str();
  663. return date;
  664. }
  665. void normalizeDate(std::chrono::_V2::system_clock::time_point &date) {
  666. time_t date_t = std::chrono::system_clock::to_time_t(date);
  667. normalizeDate(date_t);
  668. date = std::chrono::system_clock::from_time_t(date_t);
  669. }
  670. /**
  671. * @brief change datetime to have consistent time
  672. *
  673. * This converts the time part to hour:00
  674. *
  675. * @param tt
  676. * @param hour
  677. */
  678. void normalizeDate(time_t &tt, int hour) {
  679. std::tm *local_tm = localtime(&tt);
  680. // adjust date to 2:00:00 AM
  681. tt -= (local_tm->tm_min * 60) + local_tm->tm_sec;
  682. while (local_tm->tm_hour < hour) {
  683. ++local_tm->tm_hour;
  684. tt += 60 * 60;
  685. }
  686. if (local_tm->tm_hour > hour) {
  687. tt -= (60 * 60) * (local_tm->tm_hour - hour);
  688. }
  689. /* // possible DST adjustment. LMTATSM
  690. if (local_tm->tm_isdst) {
  691. // DST in effect
  692. tt -= (60*60);
  693. }
  694. */
  695. }
  696. void firstOfMonthDate(std::chrono::_V2::system_clock::time_point &date) {
  697. using namespace std::literals;
  698. time_t date_t = std::chrono::system_clock::to_time_t(date);
  699. // adjust to first day of the month
  700. std::tm date_tm;
  701. localtime_r(&date_t, &date_tm);
  702. if (date_tm.tm_mday > 1) {
  703. date -= 24h * (date_tm.tm_mday - 1);
  704. }
  705. normalizeDate(date);
  706. }