timeSince.tsx 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  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. tooltipTitle?: React.ReactNode;
  40. tooltipUnderlineColor?: ColorOrAlias;
  41. }
  42. function TimeSince({
  43. date,
  44. suffix = t('ago'),
  45. disabledAbsoluteTooltip,
  46. tooltipTitle,
  47. tooltipUnderlineColor,
  48. shorten,
  49. extraShort,
  50. ...props
  51. }: Props) {
  52. const tickerRef = useRef<number | undefined>();
  53. const computeRelativeDate = useCallback(
  54. () => getRelativeDate(date, suffix, shorten, extraShort),
  55. [date, suffix, shorten, extraShort]
  56. );
  57. const [relative, setRelative] = useState<string>(computeRelativeDate());
  58. useEffect(() => {
  59. // Immediately update if props change
  60. setRelative(computeRelativeDate());
  61. // Start a ticker to update the relative time
  62. tickerRef.current = window.setTimeout(
  63. () => setRelative(computeRelativeDate()),
  64. ONE_MINUTE_IN_MS
  65. );
  66. return () => window.clearTimeout(tickerRef.current);
  67. }, [computeRelativeDate]);
  68. const dateObj = getDateObj(date);
  69. const user = ConfigStore.get('user');
  70. const options = user ? user.options : null;
  71. const format = options?.clock24Hours ? 'MMMM D, YYYY HH:mm z' : 'LLL z';
  72. const tooltip = getDynamicText({
  73. fixed: options?.clock24Hours
  74. ? 'November 3, 2020 08:57 UTC'
  75. : 'November 3, 2020 8:58 AM UTC',
  76. value: moment.tz(dateObj, options?.timezone ?? '').format(format),
  77. });
  78. return (
  79. <Tooltip
  80. disabled={disabledAbsoluteTooltip}
  81. underlineColor={tooltipUnderlineColor}
  82. showUnderline
  83. title={
  84. <Fragment>
  85. {tooltipTitle && <div>{tooltipTitle}</div>}
  86. {tooltip}
  87. </Fragment>
  88. }
  89. >
  90. <time dateTime={dateObj?.toISOString()} {...props}>
  91. {relative}
  92. </time>
  93. </Tooltip>
  94. );
  95. }
  96. export default TimeSince;
  97. export function getRelativeDate(
  98. currentDateTime: RelaxedDateType,
  99. suffix?: string,
  100. shorten?: boolean,
  101. extraShort?: boolean
  102. ): string {
  103. const date = getDateObj(currentDateTime);
  104. if ((shorten || extraShort) && suffix) {
  105. return t('%(time)s %(suffix)s', {
  106. time: getDuration(moment().diff(moment(date), 'seconds'), 0, shorten, extraShort),
  107. suffix,
  108. });
  109. }
  110. if ((shorten || extraShort) && !suffix) {
  111. return getDuration(moment().diff(moment(date), 'seconds'), 0, shorten, extraShort);
  112. }
  113. if (!suffix) {
  114. return moment(date).fromNow(true);
  115. }
  116. if (suffix === 'ago') {
  117. return moment(date).fromNow();
  118. }
  119. return t('%(time)s %(suffix)s', {time: moment(date).fromNow(true), suffix});
  120. }