timeSince.tsx 4.1 KB

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