timeSince.tsx 4.2 KB

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