timeSince.tsx 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. import {PureComponent} 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. type RelaxedDateType = string | number | Date;
  13. interface DefaultProps {
  14. /**
  15. * Suffix after elapsed time
  16. * e.g. "ago" in "5 minutes ago"
  17. */
  18. suffix: string;
  19. }
  20. interface Props extends DefaultProps, React.TimeHTMLAttributes<HTMLTimeElement> {
  21. /**
  22. * The date value, can be string, number (e.g. timestamp), or instance of Date
  23. */
  24. date: RelaxedDateType;
  25. className?: string;
  26. /**
  27. * By default we show tooltip with absolute date on hover, this prop disables that
  28. */
  29. disabledAbsoluteTooltip?: boolean;
  30. /**
  31. * Shortens the shortened relative time
  32. * min to m, hr to h
  33. */
  34. extraShort?: boolean;
  35. /**
  36. * For relative time shortens minutes to min, hour to hr etc.
  37. */
  38. shorten?: boolean;
  39. tooltipTitle?: React.ReactNode;
  40. tooltipUnderlineColor?: ColorOrAlias;
  41. }
  42. type State = {
  43. relative: string;
  44. };
  45. class TimeSince extends PureComponent<Props, State> {
  46. static defaultProps: DefaultProps = {
  47. suffix: 'ago',
  48. };
  49. state: State = {
  50. relative: '',
  51. };
  52. // TODO(ts) TODO(emotion): defining the props type breaks emotion's typings
  53. // See: https://github.com/emotion-js/emotion/pull/1514
  54. static getDerivedStateFromProps(props) {
  55. return {
  56. relative: getRelativeDate(
  57. props.date,
  58. props.suffix,
  59. props.shorten,
  60. props.extraShort
  61. ),
  62. };
  63. }
  64. componentDidMount() {
  65. this.setRelativeDateTicker();
  66. }
  67. componentWillUnmount() {
  68. window.clearTimeout(this.tickerTimeout);
  69. }
  70. tickerTimeout: number | undefined = undefined;
  71. setRelativeDateTicker = () => {
  72. window.clearTimeout(this.tickerTimeout);
  73. this.tickerTimeout = window.setTimeout(() => {
  74. this.setState({
  75. relative: getRelativeDate(
  76. this.props.date,
  77. this.props.suffix,
  78. this.props.shorten,
  79. this.props.extraShort
  80. ),
  81. });
  82. this.setRelativeDateTicker();
  83. }, ONE_MINUTE_IN_MS);
  84. };
  85. render() {
  86. const {
  87. date,
  88. suffix: _suffix,
  89. disabledAbsoluteTooltip,
  90. className,
  91. tooltipTitle,
  92. tooltipUnderlineColor,
  93. shorten: _shorten,
  94. extraShort: _extraShort,
  95. ...props
  96. } = this.props;
  97. const dateObj = getDateObj(date);
  98. const user = ConfigStore.get('user');
  99. const options = user ? user.options : null;
  100. const format = options?.clock24Hours ? 'MMMM D, YYYY HH:mm z' : 'LLL z';
  101. const tooltip = getDynamicText({
  102. fixed: options?.clock24Hours
  103. ? 'November 3, 2020 08:57 UTC'
  104. : 'November 3, 2020 8:58 AM UTC',
  105. value: moment.tz(dateObj, options?.timezone ?? '').format(format),
  106. });
  107. return (
  108. <Tooltip
  109. disabled={disabledAbsoluteTooltip}
  110. underlineColor={tooltipUnderlineColor}
  111. showUnderline
  112. title={
  113. <div>
  114. <div>{tooltipTitle}</div>
  115. {tooltip}
  116. </div>
  117. }
  118. >
  119. <time dateTime={dateObj?.toISOString()} className={className} {...props}>
  120. {this.state.relative}
  121. </time>
  122. </Tooltip>
  123. );
  124. }
  125. }
  126. export default TimeSince;
  127. function getDateObj(date: RelaxedDateType): Date {
  128. if (isString(date) || isNumber(date)) {
  129. date = new Date(date);
  130. }
  131. return date;
  132. }
  133. export function getRelativeDate(
  134. currentDateTime: RelaxedDateType,
  135. suffix?: string,
  136. shorten?: boolean,
  137. extraShort?: boolean
  138. ): string {
  139. const date = getDateObj(currentDateTime);
  140. if ((shorten || extraShort) && suffix) {
  141. return t('%(time)s %(suffix)s', {
  142. time: getDuration(moment().diff(moment(date), 'seconds'), 0, shorten, extraShort),
  143. suffix,
  144. });
  145. }
  146. if ((shorten || extraShort) && !suffix) {
  147. return getDuration(moment().diff(moment(date), 'seconds'), 0, shorten, extraShort);
  148. }
  149. if (!suffix) {
  150. return moment(date).fromNow(true);
  151. }
  152. if (suffix === 'ago') {
  153. return moment(date).fromNow();
  154. }
  155. return t('%(time)s %(suffix)s', {time: moment(date).fromNow(true), suffix});
  156. }