dateUtils.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571
  1. /**
  2. * Date utility functions to replace moment.js with native JavaScript Date
  3. */
  4. /**
  5. * Format a date to YYYY-MM-DD HH:mm format
  6. * @param {Date|string} date - Date to format
  7. * @returns {string} Formatted date string
  8. */
  9. export function formatDateTime(date) {
  10. const d = new Date(date);
  11. if (isNaN(d.getTime())) return '';
  12. const year = d.getFullYear();
  13. const month = String(d.getMonth() + 1).padStart(2, '0');
  14. const day = String(d.getDate()).padStart(2, '0');
  15. const hours = String(d.getHours()).padStart(2, '0');
  16. const minutes = String(d.getMinutes()).padStart(2, '0');
  17. return `${year}-${month}-${day} ${hours}:${minutes}`;
  18. }
  19. /**
  20. * Format a date to YYYY-MM-DD format
  21. * @param {Date|string} date - Date to format
  22. * @returns {string} Formatted date string
  23. */
  24. export function formatDate(date) {
  25. const d = new Date(date);
  26. if (isNaN(d.getTime())) return '';
  27. const year = d.getFullYear();
  28. const month = String(d.getMonth() + 1).padStart(2, '0');
  29. const day = String(d.getDate()).padStart(2, '0');
  30. return `${year}-${month}-${day}`;
  31. }
  32. /**
  33. * Format a date according to user's preferred format
  34. * @param {Date|string} date - Date to format
  35. * @param {string} format - Format string (YYYY-MM-DD, DD-MM-YYYY, MM-DD-YYYY)
  36. * @param {boolean} includeTime - Whether to include time (HH:MM)
  37. * @returns {string} Formatted date string
  38. */
  39. export function formatDateByUserPreference(date, format = 'YYYY-MM-DD', includeTime = true) {
  40. const d = new Date(date);
  41. if (isNaN(d.getTime())) return '';
  42. const year = d.getFullYear();
  43. const month = String(d.getMonth() + 1).padStart(2, '0');
  44. const day = String(d.getDate()).padStart(2, '0');
  45. const hours = String(d.getHours()).padStart(2, '0');
  46. const minutes = String(d.getMinutes()).padStart(2, '0');
  47. let dateString;
  48. switch (format) {
  49. case 'DD-MM-YYYY':
  50. dateString = `${day}-${month}-${year}`;
  51. break;
  52. case 'MM-DD-YYYY':
  53. dateString = `${month}-${day}-${year}`;
  54. break;
  55. case 'YYYY-MM-DD':
  56. default:
  57. dateString = `${year}-${month}-${day}`;
  58. break;
  59. }
  60. if (includeTime) {
  61. return `${dateString} ${hours}:${minutes}`;
  62. }
  63. return dateString;
  64. }
  65. /**
  66. * Format a time to HH:mm format
  67. * @param {Date|string} date - Date to format
  68. * @returns {string} Formatted time string
  69. */
  70. export function formatTime(date) {
  71. const d = new Date(date);
  72. if (isNaN(d.getTime())) return '';
  73. const hours = String(d.getHours()).padStart(2, '0');
  74. const minutes = String(d.getMinutes()).padStart(2, '0');
  75. return `${hours}:${minutes}`;
  76. }
  77. /**
  78. * Get ISO week number (ISO 8601)
  79. * @param {Date|string} date - Date to get week number for
  80. * @returns {number} ISO week number
  81. */
  82. export function getISOWeek(date) {
  83. const d = new Date(date);
  84. if (isNaN(d.getTime())) return 0;
  85. // Set to nearest Thursday: current date + 4 - current day number
  86. // Make Sunday's day number 7
  87. const target = new Date(d);
  88. const dayNr = (d.getDay() + 6) % 7;
  89. target.setDate(target.getDate() - dayNr + 3);
  90. // ISO week date weeks start on monday, so correct the day number
  91. const firstThursday = target.valueOf();
  92. target.setMonth(0, 1);
  93. if (target.getDay() !== 4) {
  94. target.setMonth(0, 1 + ((4 - target.getDay()) + 7) % 7);
  95. }
  96. return 1 + Math.ceil((firstThursday - target) / 604800000); // 604800000 = 7 * 24 * 3600 * 1000
  97. }
  98. /**
  99. * Check if a date is valid
  100. * @param {Date|string} date - Date to check
  101. * @returns {boolean} True if date is valid
  102. */
  103. export function isValidDate(date) {
  104. const d = new Date(date);
  105. return !isNaN(d.getTime());
  106. }
  107. /**
  108. * Check if a date is before another date
  109. * @param {Date|string} date1 - First date
  110. * @param {Date|string} date2 - Second date
  111. * @param {string} unit - Unit of comparison ('minute', 'hour', 'day', etc.)
  112. * @returns {boolean} True if date1 is before date2
  113. */
  114. export function isBefore(date1, date2, unit = 'millisecond') {
  115. const d1 = new Date(date1);
  116. const d2 = new Date(date2);
  117. if (isNaN(d1.getTime()) || isNaN(d2.getTime())) return false;
  118. switch (unit) {
  119. case 'year':
  120. return d1.getFullYear() < d2.getFullYear();
  121. case 'month':
  122. return d1.getFullYear() < d2.getFullYear() ||
  123. (d1.getFullYear() === d2.getFullYear() && d1.getMonth() < d2.getMonth());
  124. case 'day':
  125. return d1.getFullYear() < d2.getFullYear() ||
  126. (d1.getFullYear() === d2.getFullYear() && d1.getMonth() < d2.getMonth()) ||
  127. (d1.getFullYear() === d2.getFullYear() && d1.getMonth() === d2.getMonth() && d1.getDate() < d2.getDate());
  128. case 'hour':
  129. return d1.getTime() < d2.getTime() && Math.floor(d1.getTime() / (1000 * 60 * 60)) < Math.floor(d2.getTime() / (1000 * 60 * 60));
  130. case 'minute':
  131. return d1.getTime() < d2.getTime() && Math.floor(d1.getTime() / (1000 * 60)) < Math.floor(d2.getTime() / (1000 * 60));
  132. default:
  133. return d1.getTime() < d2.getTime();
  134. }
  135. }
  136. /**
  137. * Check if a date is after another date
  138. * @param {Date|string} date1 - First date
  139. * @param {Date|string} date2 - Second date
  140. * @param {string} unit - Unit of comparison ('minute', 'hour', 'day', etc.)
  141. * @returns {boolean} True if date1 is after date2
  142. */
  143. export function isAfter(date1, date2, unit = 'millisecond') {
  144. return isBefore(date2, date1, unit);
  145. }
  146. /**
  147. * Check if a date is the same as another date
  148. * @param {Date|string} date1 - First date
  149. * @param {Date|string} date2 - Second date
  150. * @param {string} unit - Unit of comparison ('minute', 'hour', 'day', etc.)
  151. * @returns {boolean} True if dates are the same
  152. */
  153. export function isSame(date1, date2, unit = 'millisecond') {
  154. const d1 = new Date(date1);
  155. const d2 = new Date(date2);
  156. if (isNaN(d1.getTime()) || isNaN(d2.getTime())) return false;
  157. switch (unit) {
  158. case 'year':
  159. return d1.getFullYear() === d2.getFullYear();
  160. case 'month':
  161. return d1.getFullYear() === d2.getFullYear() && d1.getMonth() === d2.getMonth();
  162. case 'day':
  163. return d1.getFullYear() === d2.getFullYear() && d1.getMonth() === d2.getMonth() && d1.getDate() === d2.getDate();
  164. case 'hour':
  165. return Math.floor(d1.getTime() / (1000 * 60 * 60)) === Math.floor(d2.getTime() / (1000 * 60 * 60));
  166. case 'minute':
  167. return Math.floor(d1.getTime() / (1000 * 60)) === Math.floor(d2.getTime() / (1000 * 60));
  168. default:
  169. return d1.getTime() === d2.getTime();
  170. }
  171. }
  172. /**
  173. * Add time to a date
  174. * @param {Date|string} date - Base date
  175. * @param {number} amount - Amount to add
  176. * @param {string} unit - Unit ('years', 'months', 'days', 'hours', 'minutes', 'seconds')
  177. * @returns {Date} New date
  178. */
  179. export function add(date, amount, unit) {
  180. const d = new Date(date);
  181. if (isNaN(d.getTime())) return new Date();
  182. switch (unit) {
  183. case 'years':
  184. d.setFullYear(d.getFullYear() + amount);
  185. break;
  186. case 'months':
  187. d.setMonth(d.getMonth() + amount);
  188. break;
  189. case 'days':
  190. d.setDate(d.getDate() + amount);
  191. break;
  192. case 'hours':
  193. d.setHours(d.getHours() + amount);
  194. break;
  195. case 'minutes':
  196. d.setMinutes(d.getMinutes() + amount);
  197. break;
  198. case 'seconds':
  199. d.setSeconds(d.getSeconds() + amount);
  200. break;
  201. default:
  202. d.setTime(d.getTime() + amount);
  203. }
  204. return d;
  205. }
  206. /**
  207. * Subtract time from a date
  208. * @param {Date|string} date - Base date
  209. * @param {number} amount - Amount to subtract
  210. * @param {string} unit - Unit ('years', 'months', 'days', 'hours', 'minutes', 'seconds')
  211. * @returns {Date} New date
  212. */
  213. export function subtract(date, amount, unit) {
  214. return add(date, -amount, unit);
  215. }
  216. /**
  217. * Get start of a time unit
  218. * @param {Date|string} date - Base date
  219. * @param {string} unit - Unit ('year', 'month', 'day', 'hour', 'minute', 'second')
  220. * @returns {Date} Start of unit
  221. */
  222. export function startOf(date, unit) {
  223. const d = new Date(date);
  224. if (isNaN(d.getTime())) return new Date();
  225. switch (unit) {
  226. case 'year':
  227. d.setMonth(0, 1);
  228. d.setHours(0, 0, 0, 0);
  229. break;
  230. case 'month':
  231. d.setDate(1);
  232. d.setHours(0, 0, 0, 0);
  233. break;
  234. case 'day':
  235. d.setHours(0, 0, 0, 0);
  236. break;
  237. case 'hour':
  238. d.setMinutes(0, 0, 0);
  239. break;
  240. case 'minute':
  241. d.setSeconds(0, 0);
  242. break;
  243. case 'second':
  244. d.setMilliseconds(0);
  245. break;
  246. }
  247. return d;
  248. }
  249. /**
  250. * Get end of a time unit
  251. * @param {Date|string} date - Base date
  252. * @param {string} unit - Unit ('year', 'month', 'day', 'hour', 'minute', 'second')
  253. * @returns {Date} End of unit
  254. */
  255. export function endOf(date, unit) {
  256. const d = new Date(date);
  257. if (isNaN(d.getTime())) return new Date();
  258. switch (unit) {
  259. case 'year':
  260. d.setMonth(11, 31);
  261. d.setHours(23, 59, 59, 999);
  262. break;
  263. case 'month':
  264. d.setMonth(d.getMonth() + 1, 0);
  265. d.setHours(23, 59, 59, 999);
  266. break;
  267. case 'day':
  268. d.setHours(23, 59, 59, 999);
  269. break;
  270. case 'hour':
  271. d.setMinutes(59, 59, 999);
  272. break;
  273. case 'minute':
  274. d.setSeconds(59, 999);
  275. break;
  276. case 'second':
  277. d.setMilliseconds(999);
  278. break;
  279. }
  280. return d;
  281. }
  282. /**
  283. * Format date for display with locale
  284. * @param {Date|string} date - Date to format
  285. * @param {string} format - Format string (simplified)
  286. * @returns {string} Formatted date string
  287. */
  288. export function format(date, format = 'L') {
  289. const d = new Date(date);
  290. if (isNaN(d.getTime())) return '';
  291. const year = d.getFullYear();
  292. const month = String(d.getMonth() + 1).padStart(2, '0');
  293. const day = String(d.getDate()).padStart(2, '0');
  294. const hours = String(d.getHours()).padStart(2, '0');
  295. const minutes = String(d.getMinutes()).padStart(2, '0');
  296. const seconds = String(d.getSeconds()).padStart(2, '0');
  297. switch (format) {
  298. case 'L':
  299. return `${month}/${day}/${year}`;
  300. case 'LL':
  301. return d.toLocaleDateString();
  302. case 'LLL':
  303. return d.toLocaleString();
  304. case 'llll':
  305. return d.toLocaleString();
  306. case 'LLLL':
  307. return d.toLocaleString();
  308. case 'l LT':
  309. return `${month}/${day}/${year} ${hours}:${minutes}`;
  310. case 'YYYY-MM-DD':
  311. return `${year}-${month}-${day}`;
  312. case 'YYYY-MM-DD HH:mm':
  313. return `${year}-${month}-${day} ${hours}:${minutes}`;
  314. case 'HH:mm':
  315. return `${hours}:${minutes}`;
  316. default:
  317. return d.toLocaleString();
  318. }
  319. }
  320. /**
  321. * Parse a date string with multiple formats
  322. * @param {string} dateString - Date string to parse
  323. * @param {string[]} formats - Array of format strings to try
  324. * @param {boolean} strict - Whether to use strict parsing
  325. * @returns {Date|null} Parsed date or null if invalid
  326. */
  327. export function parseDate(dateString, formats = [], strict = true) {
  328. if (!dateString) return null;
  329. // Try native Date parsing first
  330. const nativeDate = new Date(dateString);
  331. if (!isNaN(nativeDate.getTime())) {
  332. return nativeDate;
  333. }
  334. // Try common formats
  335. const commonFormats = [
  336. 'YYYY-MM-DD HH:mm',
  337. 'YYYY-MM-DD',
  338. 'MM/DD/YYYY HH:mm',
  339. 'MM/DD/YYYY',
  340. 'DD.MM.YYYY HH:mm',
  341. 'DD.MM.YYYY',
  342. 'DD/MM/YYYY HH:mm',
  343. 'DD/MM/YYYY',
  344. 'DD-MM-YYYY HH:mm',
  345. 'DD-MM-YYYY'
  346. ];
  347. const allFormats = [...formats, ...commonFormats];
  348. for (const format of allFormats) {
  349. const parsed = parseWithFormat(dateString, format);
  350. if (parsed && isValidDate(parsed)) {
  351. return parsed;
  352. }
  353. }
  354. return null;
  355. }
  356. /**
  357. * Parse date with specific format
  358. * @param {string} dateString - Date string to parse
  359. * @param {string} format - Format string
  360. * @returns {Date|null} Parsed date or null
  361. */
  362. function parseWithFormat(dateString, format) {
  363. // Simple format parsing - can be extended as needed
  364. const formatMap = {
  365. 'YYYY': '\\d{4}',
  366. 'MM': '\\d{2}',
  367. 'DD': '\\d{2}',
  368. 'HH': '\\d{2}',
  369. 'mm': '\\d{2}',
  370. 'ss': '\\d{2}'
  371. };
  372. let regex = format;
  373. for (const [key, value] of Object.entries(formatMap)) {
  374. regex = regex.replace(new RegExp(key, 'g'), `(${value})`);
  375. }
  376. const match = dateString.match(new RegExp(regex));
  377. if (!match) return null;
  378. const groups = match.slice(1);
  379. let year, month, day, hour = 0, minute = 0, second = 0;
  380. let groupIndex = 0;
  381. for (let i = 0; i < format.length; i++) {
  382. if (format[i] === 'Y' && format[i + 1] === 'Y' && format[i + 2] === 'Y' && format[i + 3] === 'Y') {
  383. year = parseInt(groups[groupIndex++]);
  384. i += 3;
  385. } else if (format[i] === 'M' && format[i + 1] === 'M') {
  386. month = parseInt(groups[groupIndex++]) - 1;
  387. i += 1;
  388. } else if (format[i] === 'D' && format[i + 1] === 'D') {
  389. day = parseInt(groups[groupIndex++]);
  390. i += 1;
  391. } else if (format[i] === 'H' && format[i + 1] === 'H') {
  392. hour = parseInt(groups[groupIndex++]);
  393. i += 1;
  394. } else if (format[i] === 'm' && format[i + 1] === 'm') {
  395. minute = parseInt(groups[groupIndex++]);
  396. i += 1;
  397. } else if (format[i] === 's' && format[i + 1] === 's') {
  398. second = parseInt(groups[groupIndex++]);
  399. i += 1;
  400. }
  401. }
  402. if (year === undefined || month === undefined || day === undefined) {
  403. return null;
  404. }
  405. return new Date(year, month, day, hour, minute, second);
  406. }
  407. /**
  408. * Get current date and time
  409. * @returns {Date} Current date
  410. */
  411. export function now() {
  412. return new Date();
  413. }
  414. /**
  415. * Create a date from components
  416. * @param {number} year - Year
  417. * @param {number} month - Month (0-based)
  418. * @param {number} day - Day
  419. * @param {number} hour - Hour (optional)
  420. * @param {number} minute - Minute (optional)
  421. * @param {number} second - Second (optional)
  422. * @returns {Date} Created date
  423. */
  424. export function createDate(year, month, day, hour = 0, minute = 0, second = 0) {
  425. return new Date(year, month, day, hour, minute, second);
  426. }
  427. /**
  428. * Get relative time string (e.g., "2 hours ago")
  429. * @param {Date|string} date - Date to compare
  430. * @param {Date|string} now - Current date (optional)
  431. * @returns {string} Relative time string
  432. */
  433. export function fromNow(date, now = new Date()) {
  434. const d = new Date(date);
  435. const n = new Date(now);
  436. if (isNaN(d.getTime()) || isNaN(n.getTime())) return '';
  437. const diffMs = n.getTime() - d.getTime();
  438. const diffSeconds = Math.floor(diffMs / 1000);
  439. const diffMinutes = Math.floor(diffSeconds / 60);
  440. const diffHours = Math.floor(diffMinutes / 60);
  441. const diffDays = Math.floor(diffHours / 24);
  442. const diffWeeks = Math.floor(diffDays / 7);
  443. const diffMonths = Math.floor(diffDays / 30);
  444. const diffYears = Math.floor(diffDays / 365);
  445. if (diffSeconds < 60) return 'a few seconds ago';
  446. if (diffMinutes < 60) return `${diffMinutes} minute${diffMinutes !== 1 ? 's' : ''} ago`;
  447. if (diffHours < 24) return `${diffHours} hour${diffHours !== 1 ? 's' : ''} ago`;
  448. if (diffDays < 7) return `${diffDays} day${diffDays !== 1 ? 's' : ''} ago`;
  449. if (diffWeeks < 4) return `${diffWeeks} week${diffWeeks !== 1 ? 's' : ''} ago`;
  450. if (diffMonths < 12) return `${diffMonths} month${diffMonths !== 1 ? 's' : ''} ago`;
  451. return `${diffYears} year${diffYears !== 1 ? 's' : ''} ago`;
  452. }
  453. /**
  454. * Get calendar format (e.g., "Today", "Yesterday", "Tomorrow")
  455. * @param {Date|string} date - Date to format
  456. * @param {Date|string} now - Current date (optional)
  457. * @returns {string} Calendar format string
  458. */
  459. export function calendar(date, now = new Date()) {
  460. const d = new Date(date);
  461. const n = new Date(now);
  462. if (isNaN(d.getTime()) || isNaN(n.getTime())) return format(d);
  463. const diffMs = d.getTime() - n.getTime();
  464. const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
  465. if (diffDays === 0) return 'Today';
  466. if (diffDays === 1) return 'Tomorrow';
  467. if (diffDays === -1) return 'Yesterday';
  468. if (diffDays > 1 && diffDays < 7) return `In ${diffDays} days`;
  469. if (diffDays < -1 && diffDays > -7) return `${Math.abs(diffDays)} days ago`;
  470. return format(d, 'L');
  471. }
  472. /**
  473. * Calculate the difference between two dates in the specified unit
  474. * @param {Date|string} date1 - First date
  475. * @param {Date|string} date2 - Second date
  476. * @param {string} unit - Unit of measurement ('millisecond', 'second', 'minute', 'hour', 'day', 'week', 'month', 'year')
  477. * @returns {number} Difference in the specified unit
  478. */
  479. export function diff(date1, date2, unit = 'millisecond') {
  480. const d1 = new Date(date1);
  481. const d2 = new Date(date2);
  482. if (isNaN(d1.getTime()) || isNaN(d2.getTime())) return 0;
  483. const diffMs = d1.getTime() - d2.getTime();
  484. switch (unit) {
  485. case 'millisecond':
  486. return diffMs;
  487. case 'second':
  488. return Math.floor(diffMs / 1000);
  489. case 'minute':
  490. return Math.floor(diffMs / (1000 * 60));
  491. case 'hour':
  492. return Math.floor(diffMs / (1000 * 60 * 60));
  493. case 'day':
  494. return Math.floor(diffMs / (1000 * 60 * 60 * 24));
  495. case 'week':
  496. return Math.floor(diffMs / (1000 * 60 * 60 * 24 * 7));
  497. case 'month':
  498. return Math.floor(diffMs / (1000 * 60 * 60 * 24 * 30));
  499. case 'year':
  500. return Math.floor(diffMs / (1000 * 60 * 60 * 24 * 365));
  501. default:
  502. return diffMs;
  503. }
  504. }