DateTime.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562
  1. /*
  2. * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <AK/CharacterTypes.h>
  7. #include <AK/DateConstants.h>
  8. #include <AK/String.h>
  9. #include <AK/StringBuilder.h>
  10. #include <AK/Time.h>
  11. #include <LibCore/DateTime.h>
  12. #include <errno.h>
  13. #include <time.h>
  14. namespace Core {
  15. DateTime DateTime::now()
  16. {
  17. return from_timestamp(time(nullptr));
  18. }
  19. DateTime DateTime::create(int year, int month, int day, int hour, int minute, int second)
  20. {
  21. DateTime dt;
  22. dt.set_time(year, month, day, hour, minute, second);
  23. return dt;
  24. }
  25. DateTime DateTime::from_timestamp(time_t timestamp)
  26. {
  27. struct tm tm;
  28. localtime_r(&timestamp, &tm);
  29. DateTime dt;
  30. dt.m_year = tm.tm_year + 1900;
  31. dt.m_month = tm.tm_mon + 1;
  32. dt.m_day = tm.tm_mday;
  33. dt.m_hour = tm.tm_hour;
  34. dt.m_minute = tm.tm_min;
  35. dt.m_second = tm.tm_sec;
  36. dt.m_timestamp = timestamp;
  37. return dt;
  38. }
  39. unsigned DateTime::weekday() const
  40. {
  41. return ::day_of_week(m_year, m_month, m_day);
  42. }
  43. unsigned DateTime::days_in_month() const
  44. {
  45. return ::days_in_month(m_year, m_month);
  46. }
  47. unsigned DateTime::day_of_year() const
  48. {
  49. return ::day_of_year(m_year, m_month, m_day);
  50. }
  51. bool DateTime::is_leap_year() const
  52. {
  53. return ::is_leap_year(m_year);
  54. }
  55. void DateTime::set_time(int year, int month, int day, int hour, int minute, int second)
  56. {
  57. struct tm tm = {};
  58. tm.tm_sec = second;
  59. tm.tm_min = minute;
  60. tm.tm_hour = hour;
  61. tm.tm_mday = day;
  62. tm.tm_mon = month - 1;
  63. tm.tm_year = year - 1900;
  64. tm.tm_isdst = -1;
  65. // mktime() doesn't read tm.tm_wday and tm.tm_yday, no need to fill them in.
  66. m_timestamp = mktime(&tm);
  67. // mktime() normalizes the components to the right ranges (Jan 32 -> Feb 1 etc), so read fields back out from tm.
  68. m_year = tm.tm_year + 1900;
  69. m_month = tm.tm_mon + 1;
  70. m_day = tm.tm_mday;
  71. m_hour = tm.tm_hour;
  72. m_minute = tm.tm_min;
  73. m_second = tm.tm_sec;
  74. }
  75. ErrorOr<String> DateTime::to_string(StringView format) const
  76. {
  77. struct tm tm;
  78. localtime_r(&m_timestamp, &tm);
  79. StringBuilder builder;
  80. int const format_len = format.length();
  81. auto format_time_zone_offset = [&](bool with_separator) -> ErrorOr<void> {
  82. struct tm gmt_tm;
  83. gmtime_r(&m_timestamp, &gmt_tm);
  84. gmt_tm.tm_isdst = -1;
  85. auto gmt_timestamp = mktime(&gmt_tm);
  86. auto offset_seconds = static_cast<time_t>(difftime(m_timestamp, gmt_timestamp));
  87. StringView offset_sign;
  88. if (offset_seconds >= 0) {
  89. offset_sign = "+"sv;
  90. } else {
  91. offset_sign = "-"sv;
  92. offset_seconds *= -1;
  93. }
  94. auto offset_hours = offset_seconds / 3600;
  95. auto offset_minutes = (offset_seconds % 3600) / 60;
  96. auto separator = with_separator ? ":"sv : ""sv;
  97. TRY(builder.try_appendff("{}{:02}{}{:02}", offset_sign, offset_hours, separator, offset_minutes));
  98. return {};
  99. };
  100. for (int i = 0; i < format_len; ++i) {
  101. if (format[i] != '%') {
  102. TRY(builder.try_append(format[i]));
  103. } else {
  104. if (++i == format_len)
  105. return String {};
  106. switch (format[i]) {
  107. case 'a':
  108. TRY(builder.try_append(short_day_names[tm.tm_wday]));
  109. break;
  110. case 'A':
  111. TRY(builder.try_append(long_day_names[tm.tm_wday]));
  112. break;
  113. case 'b':
  114. TRY(builder.try_append(short_month_names[tm.tm_mon]));
  115. break;
  116. case 'B':
  117. TRY(builder.try_append(long_month_names[tm.tm_mon]));
  118. break;
  119. case 'C':
  120. TRY(builder.try_appendff("{:02}", (tm.tm_year + 1900) / 100));
  121. break;
  122. case 'd':
  123. TRY(builder.try_appendff("{:02}", tm.tm_mday));
  124. break;
  125. case 'D':
  126. TRY(builder.try_appendff("{:02}/{:02}/{:02}", tm.tm_mon + 1, tm.tm_mday, (tm.tm_year + 1900) % 100));
  127. break;
  128. case 'e':
  129. TRY(builder.try_appendff("{:2}", tm.tm_mday));
  130. break;
  131. case 'h':
  132. TRY(builder.try_append(short_month_names[tm.tm_mon]));
  133. break;
  134. case 'H':
  135. TRY(builder.try_appendff("{:02}", tm.tm_hour));
  136. break;
  137. case 'I': {
  138. int display_hour = tm.tm_hour % 12;
  139. if (display_hour == 0)
  140. display_hour = 12;
  141. TRY(builder.try_appendff("{:02}", display_hour));
  142. break;
  143. }
  144. case 'j':
  145. TRY(builder.try_appendff("{:03}", tm.tm_yday + 1));
  146. break;
  147. case 'l': {
  148. int display_hour = tm.tm_hour % 12;
  149. if (display_hour == 0)
  150. display_hour = 12;
  151. TRY(builder.try_appendff("{:2}", display_hour));
  152. break;
  153. }
  154. case 'm':
  155. TRY(builder.try_appendff("{:02}", tm.tm_mon + 1));
  156. break;
  157. case 'M':
  158. TRY(builder.try_appendff("{:02}", tm.tm_min));
  159. break;
  160. case 'n':
  161. TRY(builder.try_append('\n'));
  162. break;
  163. case 'p':
  164. TRY(builder.try_append(tm.tm_hour < 12 ? "AM"sv : "PM"sv));
  165. break;
  166. case 'r': {
  167. int display_hour = tm.tm_hour % 12;
  168. if (display_hour == 0)
  169. display_hour = 12;
  170. TRY(builder.try_appendff("{:02}:{:02}:{:02} {}", display_hour, tm.tm_min, tm.tm_sec, tm.tm_hour < 12 ? "AM" : "PM"));
  171. break;
  172. }
  173. case 'R':
  174. TRY(builder.try_appendff("{:02}:{:02}", tm.tm_hour, tm.tm_min));
  175. break;
  176. case 'S':
  177. TRY(builder.try_appendff("{:02}", tm.tm_sec));
  178. break;
  179. case 't':
  180. TRY(builder.try_append('\t'));
  181. break;
  182. case 'T':
  183. TRY(builder.try_appendff("{:02}:{:02}:{:02}", tm.tm_hour, tm.tm_min, tm.tm_sec));
  184. break;
  185. case 'u':
  186. TRY(builder.try_appendff("{}", tm.tm_wday ? tm.tm_wday : 7));
  187. break;
  188. case 'U': {
  189. int const wday_of_year_beginning = (tm.tm_wday + 6 * tm.tm_yday) % 7;
  190. int const week_number = (tm.tm_yday + wday_of_year_beginning) / 7;
  191. TRY(builder.try_appendff("{:02}", week_number));
  192. break;
  193. }
  194. case 'V': {
  195. int const wday_of_year_beginning = (tm.tm_wday + 6 + 6 * tm.tm_yday) % 7;
  196. int week_number = (tm.tm_yday + wday_of_year_beginning) / 7 + 1;
  197. if (wday_of_year_beginning > 3) {
  198. if (tm.tm_yday >= 7 - wday_of_year_beginning)
  199. --week_number;
  200. else {
  201. int const days_of_last_year = days_in_year(tm.tm_year + 1900 - 1);
  202. int const wday_of_last_year_beginning = (wday_of_year_beginning + 6 * days_of_last_year) % 7;
  203. week_number = (days_of_last_year + wday_of_last_year_beginning) / 7 + 1;
  204. if (wday_of_last_year_beginning > 3)
  205. --week_number;
  206. }
  207. }
  208. TRY(builder.try_appendff("{:02}", week_number));
  209. break;
  210. }
  211. case 'w':
  212. TRY(builder.try_appendff("{}", tm.tm_wday));
  213. break;
  214. case 'W': {
  215. int const wday_of_year_beginning = (tm.tm_wday + 6 + 6 * tm.tm_yday) % 7;
  216. int const week_number = (tm.tm_yday + wday_of_year_beginning) / 7;
  217. TRY(builder.try_appendff("{:02}", week_number));
  218. break;
  219. }
  220. case 'y':
  221. TRY(builder.try_appendff("{:02}", (tm.tm_year + 1900) % 100));
  222. break;
  223. case 'Y':
  224. TRY(builder.try_appendff("{}", tm.tm_year + 1900));
  225. break;
  226. case 'z':
  227. TRY(format_time_zone_offset(false));
  228. break;
  229. case ':':
  230. if (++i == format_len) {
  231. TRY(builder.try_append("%:"sv));
  232. break;
  233. }
  234. if (format[i] != 'z') {
  235. TRY(builder.try_append("%:"sv));
  236. TRY(builder.try_append(format[i]));
  237. break;
  238. }
  239. TRY(format_time_zone_offset(true));
  240. break;
  241. case 'Z': {
  242. auto const* timezone_name = tzname[tm.tm_isdst == 0 ? 0 : 1];
  243. TRY(builder.try_append({ timezone_name, strlen(timezone_name) }));
  244. break;
  245. }
  246. case '%':
  247. TRY(builder.try_append('%'));
  248. break;
  249. default:
  250. TRY(builder.try_append('%'));
  251. TRY(builder.try_append(format[i]));
  252. break;
  253. }
  254. }
  255. }
  256. return builder.to_string();
  257. }
  258. DeprecatedString DateTime::to_deprecated_string(StringView format) const
  259. {
  260. return MUST(to_string(format)).to_deprecated_string();
  261. }
  262. Optional<DateTime> DateTime::parse(StringView format, DeprecatedString const& string)
  263. {
  264. unsigned format_pos = 0;
  265. unsigned string_pos = 0;
  266. struct tm tm = {};
  267. tm.tm_isdst = -1;
  268. auto parsing_failed = false;
  269. auto tm_represents_utc_time = false;
  270. auto parse_number = [&] {
  271. if (string_pos >= string.length()) {
  272. parsing_failed = true;
  273. return 0;
  274. }
  275. char* end_ptr = nullptr;
  276. errno = 0;
  277. int number = strtol(string.characters() + string_pos, &end_ptr, 10);
  278. auto chars_parsed = end_ptr - (string.characters() + string_pos);
  279. if (chars_parsed == 0 || errno != 0)
  280. parsing_failed = true;
  281. else
  282. string_pos += chars_parsed;
  283. return number;
  284. };
  285. auto consume = [&](char x) {
  286. if (string_pos >= string.length()) {
  287. parsing_failed = true;
  288. return;
  289. }
  290. if (string[string_pos] != x)
  291. parsing_failed = true;
  292. else
  293. string_pos++;
  294. };
  295. while (format_pos < format.length() && string_pos < string.length()) {
  296. if (format[format_pos] != '%') {
  297. if (format[format_pos] != string[string_pos]) {
  298. return {};
  299. }
  300. format_pos++;
  301. string_pos++;
  302. continue;
  303. }
  304. format_pos++;
  305. if (format_pos == format.length()) {
  306. return {};
  307. }
  308. switch (format[format_pos]) {
  309. case 'a': {
  310. auto wday = 0;
  311. for (auto name : short_day_names) {
  312. if (string.substring_view(string_pos).starts_with(name, AK::CaseSensitivity::CaseInsensitive)) {
  313. string_pos += name.length();
  314. tm.tm_wday = wday;
  315. break;
  316. }
  317. ++wday;
  318. }
  319. if (wday == 7)
  320. return {};
  321. break;
  322. }
  323. case 'A': {
  324. auto wday = 0;
  325. for (auto name : long_day_names) {
  326. if (string.substring_view(string_pos).starts_with(name, AK::CaseSensitivity::CaseInsensitive)) {
  327. string_pos += name.length();
  328. tm.tm_wday = wday;
  329. break;
  330. }
  331. ++wday;
  332. }
  333. if (wday == 7)
  334. return {};
  335. break;
  336. }
  337. case 'h':
  338. case 'b': {
  339. auto mon = 0;
  340. for (auto name : short_month_names) {
  341. if (string.substring_view(string_pos).starts_with(name, AK::CaseSensitivity::CaseInsensitive)) {
  342. string_pos += name.length();
  343. tm.tm_mon = mon;
  344. break;
  345. }
  346. ++mon;
  347. }
  348. if (mon == 12)
  349. return {};
  350. break;
  351. }
  352. case 'B': {
  353. auto mon = 0;
  354. for (auto name : long_month_names) {
  355. if (string.substring_view(string_pos).starts_with(name, AK::CaseSensitivity::CaseInsensitive)) {
  356. string_pos += name.length();
  357. tm.tm_mon = mon;
  358. break;
  359. }
  360. ++mon;
  361. }
  362. if (mon == 12)
  363. return {};
  364. break;
  365. }
  366. case 'C': {
  367. int num = parse_number();
  368. tm.tm_year = (num - 19) * 100;
  369. break;
  370. }
  371. case 'd': {
  372. tm.tm_mday = parse_number();
  373. break;
  374. }
  375. case 'D': {
  376. int mon = parse_number();
  377. consume('/');
  378. int day = parse_number();
  379. consume('/');
  380. int year = parse_number();
  381. tm.tm_mon = mon + 1;
  382. tm.tm_mday = day;
  383. tm.tm_year = (year + 1900) % 100;
  384. break;
  385. }
  386. case 'e': {
  387. tm.tm_mday = parse_number();
  388. break;
  389. }
  390. case 'H': {
  391. tm.tm_hour = parse_number();
  392. break;
  393. }
  394. case 'I': {
  395. int num = parse_number();
  396. tm.tm_hour = num % 12;
  397. break;
  398. }
  399. case 'j': {
  400. // a little trickery here... we can get mktime() to figure out mon and mday using out of range values.
  401. // yday is not used so setting it is pointless.
  402. tm.tm_mday = parse_number();
  403. tm.tm_mon = 0;
  404. mktime(&tm);
  405. break;
  406. }
  407. case 'm': {
  408. int num = parse_number();
  409. tm.tm_mon = num - 1;
  410. break;
  411. }
  412. case 'M': {
  413. tm.tm_min = parse_number();
  414. break;
  415. }
  416. case 'n':
  417. case 't':
  418. while (is_ascii_blank(string[string_pos])) {
  419. string_pos++;
  420. }
  421. break;
  422. case 'p': {
  423. auto ampm = string.substring_view(string_pos, 2);
  424. if (ampm == "PM" && tm.tm_hour < 12) {
  425. tm.tm_hour += 12;
  426. }
  427. string_pos += 2;
  428. break;
  429. }
  430. case 'r': {
  431. auto ampm = string.substring_view(string_pos, 2);
  432. if (ampm == "PM" && tm.tm_hour < 12) {
  433. tm.tm_hour += 12;
  434. }
  435. string_pos += 2;
  436. break;
  437. }
  438. case 'R': {
  439. tm.tm_hour = parse_number();
  440. consume(':');
  441. tm.tm_min = parse_number();
  442. break;
  443. }
  444. case 'S':
  445. tm.tm_sec = parse_number();
  446. break;
  447. case 'T':
  448. tm.tm_hour = parse_number();
  449. consume(':');
  450. tm.tm_min = parse_number();
  451. consume(':');
  452. tm.tm_sec = parse_number();
  453. break;
  454. case 'w':
  455. tm.tm_wday = parse_number();
  456. break;
  457. case 'y': {
  458. int year = parse_number();
  459. tm.tm_year = year <= 99 && year > 69 ? 1900 + year : 2000 + year;
  460. break;
  461. }
  462. case 'Y': {
  463. int year = parse_number();
  464. tm.tm_year = year - 1900;
  465. break;
  466. }
  467. case 'z': {
  468. tm_represents_utc_time = true;
  469. if (string[string_pos] == 'Z') {
  470. // UTC time
  471. string_pos++;
  472. break;
  473. }
  474. int sign;
  475. if (string[string_pos] == '+')
  476. sign = -1;
  477. else if (string[string_pos] == '-')
  478. sign = +1;
  479. else
  480. return {};
  481. string_pos++;
  482. auto hours = parse_number();
  483. int minutes;
  484. if (string_pos < string.length() && string[string_pos] == ':') {
  485. string_pos++;
  486. minutes = parse_number();
  487. } else {
  488. minutes = hours % 100;
  489. hours = hours / 100;
  490. }
  491. tm.tm_hour += sign * hours;
  492. tm.tm_min += sign * minutes;
  493. break;
  494. }
  495. case '%':
  496. if (string[string_pos] != '%') {
  497. return {};
  498. }
  499. string_pos += 1;
  500. break;
  501. default:
  502. parsing_failed = true;
  503. break;
  504. }
  505. if (parsing_failed) {
  506. return {};
  507. }
  508. format_pos++;
  509. }
  510. if (string_pos != string.length() || format_pos != format.length()) {
  511. return {};
  512. }
  513. // If an explicit timezone was present, the time in tm was shifted to UTC.
  514. // Convert it to local time, since that is what `mktime` expects.
  515. if (tm_represents_utc_time) {
  516. auto utc_time = timegm(&tm);
  517. localtime_r(&utc_time, &tm);
  518. }
  519. return DateTime::from_timestamp(mktime(&tm));
  520. }
  521. }