timeSince.tsx 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. import {Fragment, useCallback, useEffect, useRef, useState} from 'react';
  2. import isNumber from 'lodash/isNumber';
  3. import isString from 'lodash/isString';
  4. import moment from 'moment-timezone';
  5. import {t} from 'sentry/locale';
  6. import ConfigStore from 'sentry/stores/configStore';
  7. import {getDuration} from 'sentry/utils/formatters';
  8. import getDynamicText from 'sentry/utils/getDynamicText';
  9. import {ColorOrAlias} from 'sentry/utils/theme';
  10. import Tooltip from './tooltip';
  11. const ONE_MINUTE_IN_MS = 60000;
  12. function getDateObj(date: RelaxedDateType): Date {
  13. return isString(date) || isNumber(date) ? new Date(date) : date;
  14. }
  15. type RelaxedDateType = string | number | Date;
  16. interface Props extends React.TimeHTMLAttributes<HTMLTimeElement> {
  17. /**
  18. * The date value, can be string, number (e.g. timestamp), or instance of Date
  19. */
  20. date: RelaxedDateType;
  21. /**
  22. * By default we show tooltip with absolute date on hover, this prop disables
  23. * that
  24. */
  25. disabledAbsoluteTooltip?: boolean;
  26. /**
  27. * Shortens the shortened relative time
  28. * min to m, hr to h
  29. */
  30. extraShort?: boolean;
  31. /**
  32. * For relative time shortens minutes to min, hour to hr etc.
  33. */
  34. shorten?: boolean;
  35. /**
  36. * Suffix after elapsed time e.g. "ago" in "5 minutes ago"
  37. */
  38. suffix?: string;
  39. tooltipShowSeconds?: boolean;
  40. tooltipTitle?: React.ReactNode;
  41. tooltipUnderlineColor?: ColorOrAlias;
  42. }
  43. function TimeSince({
  44. date,
  45. suffix = t('ago'),
  46. disabledAbsoluteTooltip,
  47. tooltipShowSeconds,
  48. tooltipTitle,
  49. tooltipUnderlineColor,
  50. shorten,
  51. extraShort,
  52. ...props
  53. }: Props) {
  54. const tickerRef = useRef<number | undefined>();
  55. const computeRelativeDate = useCallback(
  56. () => getRelativeDate(date, suffix, shorten, extraShort),
  57. [date, suffix, shorten, extraShort]
  58. );
  59. const [relative, setRelative] = useState<string>(computeRelativeDate());
  60. useEffect(() => {
  61. // Immediately update if props change
  62. setRelative(computeRelativeDate());
  63. // Start a ticker to update the relative time
  64. tickerRef.current = window.setTimeout(
  65. () => setRelative(computeRelativeDate()),
  66. ONE_MINUTE_IN_MS
  67. );
  68. return () => window.clearTimeout(tickerRef.current);
  69. }, [computeRelativeDate]);
  70. const dateObj = getDateObj(date);
  71. const user = ConfigStore.get('user');
  72. const options = user ? user.options : null;
  73. // Use short months when showing seconds, because "September" causes the tooltip to overflow.
  74. const tooltipFormat = tooltipShowSeconds
  75. ? 'MMM D, YYYY h:mm:ss A z'
  76. : 'MMMM D, YYYY h:mm A z';
  77. const format = options?.clock24Hours ? 'MMMM D, YYYY HH:mm z' : tooltipFormat;
  78. const tooltip = getDynamicText({
  79. fixed: options?.clock24Hours
  80. ? 'November 3, 2020 08:57 UTC'
  81. : 'November 3, 2020 8:58 AM UTC',
  82. value: moment.tz(dateObj, options?.timezone ?? '').format(format),
  83. });
  84. return (
  85. <Tooltip
  86. disabled={disabledAbsoluteTooltip}
  87. underlineColor={tooltipUnderlineColor}
  88. showUnderline
  89. title={
  90. <Fragment>
  91. {tooltipTitle && <div>{tooltipTitle}</div>}
  92. {tooltip}
  93. </Fragment>
  94. }
  95. >
  96. <time dateTime={dateObj?.toISOString()} {...props}>
  97. {relative}
  98. </time>
  99. </Tooltip>
  100. );
  101. }
  102. export default TimeSince;
  103. export function getRelativeDate(
  104. currentDateTime: RelaxedDateType,
  105. suffix?: string,
  106. shorten?: boolean,
  107. extraShort?: boolean
  108. ): string {
  109. const date = getDateObj(currentDateTime);
  110. if ((shorten || extraShort) && suffix) {
  111. return t('%(time)s %(suffix)s', {
  112. time: getDuration(moment().diff(moment(date), 'seconds'), 0, shorten, extraShort),
  113. suffix,
  114. });
  115. }
  116. if ((shorten || extraShort) && !suffix) {
  117. return getDuration(moment().diff(moment(date), 'seconds'), 0, shorten, extraShort);
  118. }
  119. if (!suffix) {
  120. return moment(date).fromNow(true);
  121. }
  122. if (suffix === 'ago') {
  123. return moment(date).fromNow();
  124. }
  125. return t('%(time)s %(suffix)s', {time: moment(date).fromNow(true), suffix});
  126. }