formatDuration.tsx 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. import {Timespan, Unit} from 'sentry/utils/duration/types';
  2. import {formatSecondsToClock} from 'sentry/utils/formatters';
  3. type Format =
  4. // example: `3,600`
  5. | 'count-locale'
  6. // example: `86400`
  7. | 'count'
  8. // example: `1:00:00.000`
  9. | 'h:mm:ss.sss'
  10. // example: `1:00:00`
  11. | 'h:mm:ss'
  12. // example: `01:00:00.000
  13. | 'hh:mm:ss.sss'
  14. // example: `01:00:00`
  15. | 'hh:mm:ss';
  16. type Args = {
  17. /**
  18. * The precision of the output.
  19. *
  20. * If the output precision is more granular than the input precision you might
  21. * find the output value is rounded down, because least-significant digits are
  22. * simply chopped off.
  23. * Alternativly, because of IEEE 754, converting from a granular precision to
  24. * something less granular might, in some cases, change the least-significant
  25. * digits of the final value.
  26. */
  27. precision: Unit;
  28. /**
  29. * The output style to use
  30. *
  31. * ie: 120 seconds formatted as "h:mm" results in "2:00"
  32. * ie: 10500 formatted as "count" + "sec" results in "10.5"
  33. */
  34. style: Format;
  35. /**
  36. * The timespan/duration to be displayed
  37. * ie: "1000 miliseconds" would have the same output as "1 second"
  38. *
  39. * If it's coming from javascript `new Date` then 'ms'
  40. * If it's from an SDK event, probably 'sec'
  41. */
  42. timespan: Timespan;
  43. };
  44. const PRECISION_FACTORS: Record<Unit, number> = {
  45. ms: 1,
  46. sec: 1000,
  47. min: 1000 * 60,
  48. hour: 1000 * 60 * 60,
  49. day: 1000 * 60 * 60 * 24,
  50. week: 1000 * 60 * 60 * 24 * 7,
  51. };
  52. /**
  53. * Format a timespan (aka duration) into a formatted string.
  54. *
  55. * A timespan is expressed a `number` and a `unit` pair -> [value, unit]
  56. */
  57. export default function formatDuration({
  58. precision,
  59. style,
  60. timespan: [value, unit],
  61. }: Args): string {
  62. const ms = normalizeTimespanToMs(value, unit);
  63. const valueInUnit = msToPrecision(ms, precision);
  64. switch (style) {
  65. case 'count-locale':
  66. return valueInUnit.toLocaleString();
  67. case 'count':
  68. return String(valueInUnit);
  69. case 'h:mm:ss': // fall-through
  70. case 'hh:mm:ss': // fall-through
  71. case 'h:mm:ss.sss': // fall-through
  72. case 'hh:mm:ss.sss':
  73. const includeMs = style.endsWith('.sss');
  74. const valueInSec = msToPrecision(ms, 'sec');
  75. const str = formatSecondsToClock(valueInSec, {
  76. padAll: style.startsWith('hh:mm:ss'),
  77. });
  78. const [head, tail] = str.split('.');
  79. return includeMs ? [head, tail ?? '000'].join('.') : String(head);
  80. default:
  81. throw new Error('Invalid style');
  82. }
  83. }
  84. function normalizeTimespanToMs(value: number, unit: Unit): number {
  85. const factor = PRECISION_FACTORS[unit];
  86. return value * factor;
  87. }
  88. function msToPrecision(value: number, unit: Unit): number {
  89. if (value === 0) {
  90. return 0;
  91. }
  92. const factor = PRECISION_FACTORS[unit];
  93. return value / factor;
  94. }