dates.ts 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. // Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. import type { Translator } from './translator.ts'
  3. const formatNumber = (num: number, digits: number): string => {
  4. let result = num.toString()
  5. while (result.length < digits) {
  6. result = `0${result}`
  7. }
  8. return result
  9. }
  10. const parseDate = (dateTimeString: string): Date => {
  11. let date = new Date(dateTimeString)
  12. // On firefox the Date constructor does not recongise date format that
  13. // ends with UTC, instead it returns a NaN (Invalid Date Format) this
  14. // block serves as polyfill to support time format that ends UTC in firefox
  15. if (Number.isNaN(date.getDate())) {
  16. // works for only time string with this format: 2021-02-08 09:13:20 UTC
  17. const timeArray = dateTimeString.match(/\d+/g) || []
  18. const [y, m, d, H, M] = timeArray.map((value) => {
  19. return parseInt(value, 10)
  20. })
  21. date = new Date(Date.UTC(y, m - 1, d, H, M))
  22. }
  23. return date
  24. }
  25. export const absoluteDateTime = (
  26. dateTimeString: string,
  27. template: string,
  28. ): string => {
  29. const date = parseDate(dateTimeString)
  30. const d = date.getDate()
  31. const m = date.getMonth() + 1
  32. const H = date.getHours()
  33. const yfull = date.getFullYear().toString()
  34. const yshort = yfull.substring(yfull.length - 2)
  35. const lnum = ((H + 11) % 12) + 1
  36. const l = lnum < 10 ? ` ${lnum}` : lnum
  37. return template
  38. .replace('dd', formatNumber(d, 2))
  39. .replace('d', d.toString())
  40. .replace('mm', formatNumber(m, 2))
  41. .replace('m', m.toString())
  42. .replace('yyyy', yfull)
  43. .replace('yy', yshort)
  44. .replace('SS', formatNumber(date.getSeconds(), 2))
  45. .replace('MM', formatNumber(date.getMinutes(), 2))
  46. .replace('HH', formatNumber(H, 2))
  47. .replace('l', l.toString())
  48. .replace('P', H >= 12 ? 'pm' : 'am')
  49. }
  50. export const getISODatetime = (dateTimeString: string): string =>
  51. parseDate(dateTimeString).toISOString()
  52. const durationMinute = 60
  53. const durationHour = 60 * durationMinute
  54. const durationDay = 24 * durationHour
  55. const durationWeek = 7 * durationDay
  56. const durationMonth = 30 * durationDay
  57. const durationYear = 356 * durationDay
  58. enum Direction {
  59. Past,
  60. Future,
  61. }
  62. type DurationMessages = {
  63. duration: number
  64. pastSingular: string
  65. pastPlural: string
  66. futureSingular: string
  67. futurePlural: string
  68. }
  69. const durations: DurationMessages[] = [
  70. {
  71. duration: durationYear,
  72. pastSingular: __('1 year ago'),
  73. pastPlural: __('%s years ago'),
  74. futureSingular: __('in 1 year'),
  75. futurePlural: __('in %s years'),
  76. },
  77. {
  78. duration: durationMonth,
  79. pastSingular: __('1 month ago'),
  80. pastPlural: __('%s months ago'),
  81. futureSingular: __('in 1 month'),
  82. futurePlural: __('in %s months'),
  83. },
  84. {
  85. duration: durationWeek,
  86. pastSingular: __('1 week ago'),
  87. pastPlural: __('%s weeks ago'),
  88. futureSingular: __('in 1 week'),
  89. futurePlural: __('in %s weeks'),
  90. },
  91. {
  92. duration: durationDay,
  93. pastSingular: __('1 day ago'),
  94. pastPlural: __('%s days ago'),
  95. futureSingular: __('in 1 day'),
  96. futurePlural: __('in %s days'),
  97. },
  98. {
  99. duration: durationHour,
  100. pastSingular: __('1 hour ago'),
  101. pastPlural: __('%s hours ago'),
  102. futureSingular: __('in 1 hour'),
  103. futurePlural: __('in %s hours'),
  104. },
  105. {
  106. duration: durationMinute,
  107. pastSingular: __('1 minute ago'),
  108. pastPlural: __('%s minutes ago'),
  109. futureSingular: __('in 1 minute'),
  110. futurePlural: __('in %s minutes'),
  111. },
  112. ]
  113. export const relativeDateTime = (
  114. dateTimeString: string,
  115. baseDate: Date,
  116. translator: Translator,
  117. ): string => {
  118. const date = new Date(dateTimeString)
  119. let diffSeconds = (baseDate.getTime() - date.getTime()) / 1000
  120. const direction: Direction =
  121. diffSeconds > -1 ? Direction.Past : Direction.Future
  122. diffSeconds = Math.abs(diffSeconds)
  123. for (const duration of durations) {
  124. if (diffSeconds >= duration.duration) {
  125. const count = Math.floor(diffSeconds / duration.duration)
  126. if (direction === Direction.Past) {
  127. return count === 1
  128. ? translator.translate(duration.pastSingular)
  129. : translator.translate(duration.pastPlural, count)
  130. }
  131. return count === 1
  132. ? translator.translate(duration.futureSingular)
  133. : translator.translate(duration.futurePlural, count)
  134. }
  135. }
  136. return translator.translate('just now')
  137. }
  138. export const getDateFormat = (translator: Translator) => {
  139. return translator.lookup('FORMAT_DATE') || 'yyyy-mm-dd'
  140. }
  141. export const getDateTimeFormat = (translator: Translator) => {
  142. return translator.lookup('FORMAT_DATETIME') || 'yyyy-mm-dd HH:MM'
  143. }