You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

147 lines
3.9 KiB

  1. import React from 'react';
  2. import { injectIntl, defineMessages } from 'react-intl';
  3. import PropTypes from 'prop-types';
  4. const messages = defineMessages({
  5. just_now: { id: 'relative_time.just_now', defaultMessage: 'now' },
  6. seconds: { id: 'relative_time.seconds', defaultMessage: '{number}s' },
  7. minutes: { id: 'relative_time.minutes', defaultMessage: '{number}m' },
  8. hours: { id: 'relative_time.hours', defaultMessage: '{number}h' },
  9. days: { id: 'relative_time.days', defaultMessage: '{number}d' },
  10. });
  11. const dateFormatOptions = {
  12. hour12: false,
  13. year: 'numeric',
  14. month: 'short',
  15. day: '2-digit',
  16. hour: '2-digit',
  17. minute: '2-digit',
  18. };
  19. const shortDateFormatOptions = {
  20. month: 'numeric',
  21. day: 'numeric',
  22. };
  23. const SECOND = 1000;
  24. const MINUTE = 1000 * 60;
  25. const HOUR = 1000 * 60 * 60;
  26. const DAY = 1000 * 60 * 60 * 24;
  27. const MAX_DELAY = 2147483647;
  28. const selectUnits = delta => {
  29. const absDelta = Math.abs(delta);
  30. if (absDelta < MINUTE) {
  31. return 'second';
  32. } else if (absDelta < HOUR) {
  33. return 'minute';
  34. } else if (absDelta < DAY) {
  35. return 'hour';
  36. }
  37. return 'day';
  38. };
  39. const getUnitDelay = units => {
  40. switch (units) {
  41. case 'second':
  42. return SECOND;
  43. case 'minute':
  44. return MINUTE;
  45. case 'hour':
  46. return HOUR;
  47. case 'day':
  48. return DAY;
  49. default:
  50. return MAX_DELAY;
  51. }
  52. };
  53. @injectIntl
  54. export default class RelativeTimestamp extends React.Component {
  55. static propTypes = {
  56. intl: PropTypes.object.isRequired,
  57. timestamp: PropTypes.string.isRequired,
  58. };
  59. state = {
  60. now: this.props.intl.now(),
  61. };
  62. shouldComponentUpdate (nextProps, nextState) {
  63. // As of right now the locale doesn't change without a new page load,
  64. // but we might as well check in case that ever changes.
  65. return this.props.timestamp !== nextProps.timestamp ||
  66. this.props.intl.locale !== nextProps.intl.locale ||
  67. this.state.now !== nextState.now;
  68. }
  69. componentWillReceiveProps (nextProps) {
  70. if (this.props.timestamp !== nextProps.timestamp) {
  71. this.setState({ now: this.props.intl.now() });
  72. }
  73. }
  74. componentDidMount () {
  75. this._scheduleNextUpdate(this.props, this.state);
  76. }
  77. componentWillUpdate (nextProps, nextState) {
  78. this._scheduleNextUpdate(nextProps, nextState);
  79. }
  80. componentWillUnmount () {
  81. clearTimeout(this._timer);
  82. }
  83. _scheduleNextUpdate (props, state) {
  84. clearTimeout(this._timer);
  85. const { timestamp } = props;
  86. const delta = (new Date(timestamp)).getTime() - state.now;
  87. const unitDelay = getUnitDelay(selectUnits(delta));
  88. const unitRemainder = Math.abs(delta % unitDelay);
  89. const updateInterval = 1000 * 10;
  90. const delay = delta < 0 ? Math.max(updateInterval, unitDelay - unitRemainder) : Math.max(updateInterval, unitRemainder);
  91. this._timer = setTimeout(() => {
  92. this.setState({ now: this.props.intl.now() });
  93. }, delay);
  94. }
  95. render () {
  96. const { timestamp, intl } = this.props;
  97. const date = new Date(timestamp);
  98. const delta = this.state.now - date.getTime();
  99. let relativeTime;
  100. if (delta < 10 * SECOND) {
  101. relativeTime = intl.formatMessage(messages.just_now);
  102. } else if (delta < 3 * DAY) {
  103. if (delta < MINUTE) {
  104. relativeTime = intl.formatMessage(messages.seconds, { number: Math.floor(delta / SECOND) });
  105. } else if (delta < HOUR) {
  106. relativeTime = intl.formatMessage(messages.minutes, { number: Math.floor(delta / MINUTE) });
  107. } else if (delta < DAY) {
  108. relativeTime = intl.formatMessage(messages.hours, { number: Math.floor(delta / HOUR) });
  109. } else {
  110. relativeTime = intl.formatMessage(messages.days, { number: Math.floor(delta / DAY) });
  111. }
  112. } else {
  113. relativeTime = intl.formatDate(date, shortDateFormatOptions);
  114. }
  115. return (
  116. <time dateTime={timestamp} title={intl.formatDate(date, dateFormatOptions)}>
  117. {relativeTime}
  118. </time>
  119. );
  120. }
  121. }