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.

154 lines
4.1 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: 'short',
  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. year: PropTypes.number.isRequired,
  59. };
  60. state = {
  61. now: this.props.intl.now(),
  62. };
  63. static defaultProps = {
  64. year: (new Date()).getFullYear(),
  65. };
  66. shouldComponentUpdate (nextProps, nextState) {
  67. // As of right now the locale doesn't change without a new page load,
  68. // but we might as well check in case that ever changes.
  69. return this.props.timestamp !== nextProps.timestamp ||
  70. this.props.intl.locale !== nextProps.intl.locale ||
  71. this.state.now !== nextState.now;
  72. }
  73. componentWillReceiveProps (nextProps) {
  74. if (this.props.timestamp !== nextProps.timestamp) {
  75. this.setState({ now: this.props.intl.now() });
  76. }
  77. }
  78. componentDidMount () {
  79. this._scheduleNextUpdate(this.props, this.state);
  80. }
  81. componentWillUpdate (nextProps, nextState) {
  82. this._scheduleNextUpdate(nextProps, nextState);
  83. }
  84. componentWillUnmount () {
  85. clearTimeout(this._timer);
  86. }
  87. _scheduleNextUpdate (props, state) {
  88. clearTimeout(this._timer);
  89. const { timestamp } = props;
  90. const delta = (new Date(timestamp)).getTime() - state.now;
  91. const unitDelay = getUnitDelay(selectUnits(delta));
  92. const unitRemainder = Math.abs(delta % unitDelay);
  93. const updateInterval = 1000 * 10;
  94. const delay = delta < 0 ? Math.max(updateInterval, unitDelay - unitRemainder) : Math.max(updateInterval, unitRemainder);
  95. this._timer = setTimeout(() => {
  96. this.setState({ now: this.props.intl.now() });
  97. }, delay);
  98. }
  99. render () {
  100. const { timestamp, intl, year } = this.props;
  101. const date = new Date(timestamp);
  102. const delta = this.state.now - date.getTime();
  103. let relativeTime;
  104. if (delta < 10 * SECOND) {
  105. relativeTime = intl.formatMessage(messages.just_now);
  106. } else if (delta < 7 * DAY) {
  107. if (delta < MINUTE) {
  108. relativeTime = intl.formatMessage(messages.seconds, { number: Math.floor(delta / SECOND) });
  109. } else if (delta < HOUR) {
  110. relativeTime = intl.formatMessage(messages.minutes, { number: Math.floor(delta / MINUTE) });
  111. } else if (delta < DAY) {
  112. relativeTime = intl.formatMessage(messages.hours, { number: Math.floor(delta / HOUR) });
  113. } else {
  114. relativeTime = intl.formatMessage(messages.days, { number: Math.floor(delta / DAY) });
  115. }
  116. } else if (date.getFullYear() === year) {
  117. relativeTime = intl.formatDate(date, shortDateFormatOptions);
  118. } else {
  119. relativeTime = intl.formatDate(date, { ...shortDateFormatOptions, year: 'numeric' });
  120. }
  121. return (
  122. <time dateTime={timestamp} title={intl.formatDate(date, dateFormatOptions)}>
  123. {relativeTime}
  124. </time>
  125. );
  126. }
  127. }