Refactor/time formatting (#696)

* chore: upgrade related dependencies

* fix: skip sentry on dev

* refactor: unify time formatting

* refactor: display default format for page
This commit is contained in:
Carlos Valente
2024-01-09 15:30:11 +01:00
committed by GitHub
parent 9031051465
commit b4e73ff6b5
54 changed files with 1177 additions and 687 deletions
+9 -3
View File
@@ -11,10 +11,16 @@ export { generateId } from './src/generate-id/generateId.js';
export { swapOntimeEvents } from './src/rundown-utils/rundownUtils.js';
// format utils
export { formatDisplay } from './src/date-utils/formatDisplay.js';
export { formatFromMillis } from './src/date-utils/formatFromMillis.js';
export {
MILLIS_PER_HOUR,
MILLIS_PER_MINUTE,
MILLIS_PER_SECOND,
millisToHours,
millisToMinutes,
millisToSeconds,
} from './src/date-utils/conversionUtils.js';
export { isTimeString } from './src/date-utils/isTimeString.js';
export { millisToString } from './src/date-utils/millisToString.js';
export { formatFromMillis, millisToString, removeLeadingZero, removeSeconds } from './src/date-utils/timeFormatting.js';
export { isColourHex } from './src/regex-utils/isColourHex.js';
// time utils
+2 -2
View File
@@ -13,11 +13,11 @@
},
"dependencies": {
"deepmerge-ts": "^5.1.0",
"luxon": "^3.3.0",
"luxon": "^3.4.4",
"nanoid": "^4.0.1"
},
"devDependencies": {
"@types/luxon": "^3.2.0",
"@types/luxon": "^3.4.0",
"@typescript-eslint/eslint-plugin": "^6.10.0",
"@typescript-eslint/parser": "^6.10.0",
"eslint": "^8.53.0",
@@ -0,0 +1,114 @@
import { millisToHours, millisToMinutes, millisToSeconds } from "./conversionUtils";
describe('millisToSecond()', () => {
test('null values', () => {
const t = { val: null, result: 0 };
expect(millisToSeconds(t.val)).toBe(t.result);
});
test('valid millis', () => {
const t = { val: 3600000, result: 3600 };
expect(millisToSeconds(t.val)).toBe(t.result);
});
test('negative millis', () => {
const t = { val: -3600000, result: -3600 };
expect(millisToSeconds(t.val)).toBe(t.result);
});
test('0', () => {
const t = { val: 0, result: 0 };
expect(millisToSeconds(t.val)).toBe(t.result);
});
test('-0', () => {
const t = { val: -0, result: 0 };
expect(millisToSeconds(t.val)).toBe(t.result);
});
test('86401000 (24 hours and 1 second)', () => {
const t = { val: 86401000, result: 86401 };
expect(millisToSeconds(t.val)).toBe(t.result);
});
test('-86401000 (-24 hours and 1 second)', () => {
const t = { val: -86401000, result: -86401 };
expect(millisToSeconds(t.val)).toBe(t.result);
});
});
describe('millisToMinutes()', () => {
test('null values', () => {
const t = { val: null, result: 0 };
expect(millisToMinutes(t.val)).toBe(t.result);
});
test('valid millis', () => {
const t = { val: 3600000, result: 60 };
expect(millisToMinutes(t.val)).toBe(t.result);
});
test('negative millis', () => {
const t = { val: -3600000, result: -60 };
expect(millisToMinutes(t.val)).toBe(t.result);
});
test('0', () => {
const t = { val: 0, result: 0 };
expect(millisToMinutes(t.val)).toBe(t.result);
});
test('-0', () => {
const t = { val: -0, result: 0 };
expect(millisToMinutes(t.val)).toBe(t.result);
});
test('86401000 (24 hours and 1 second)', () => {
const t = { val: 86401000, result: 1440 };
expect(millisToMinutes(t.val)).toBe(t.result);
});
test('-86401000 (-24 hours and 1 second)', () => {
// negative numbers are rounded up
const t = { val: -86401000, result: -1441 };
expect(millisToMinutes(t.val)).toBe(t.result);
});
});
describe('millisToHours()', () => {
test('null values', () => {
const t = { val: null, result: 0 };
expect(millisToHours(t.val)).toBe(t.result);
});
test('valid millis', () => {
const t = { val: 3600000, result: 1 };
expect(millisToHours(t.val)).toBe(t.result);
});
test('negative millis', () => {
const t = { val: -3600000, result: -1 };
expect(millisToHours(t.val)).toBe(t.result);
});
test('0', () => {
const t = { val: 0, result: 0 };
expect(millisToHours(t.val)).toBe(t.result);
});
test('-0', () => {
const t = { val: -0, result: 0 };
expect(millisToHours(t.val)).toBe(t.result);
});
test('86401000 (24 hours and 1 second)', () => {
const t = { val: 86401000, result: 24 };
expect(millisToHours(t.val)).toBe(t.result);
});
test('-86401000 (-24 hours and 1 second)', () => {
// negative numbers are rounded up
const t = { val: -86401000, result: -25 };
expect(millisToHours(t.val)).toBe(t.result);
});
});
@@ -0,0 +1,29 @@
type MaybeNumber = number | null;
export const MILLIS_PER_SECOND = 1000;
export const MILLIS_PER_MINUTE = 1000 * 60;
export const MILLIS_PER_HOUR = 1000 * 60 * 60;
function convertMillis(millis: MaybeNumber, conversion: number) {
if (millis == null || millis === 0) {
return 0;
}
// for negative times, we want to round up
if (millis < 0) {
Math.ceil(millis / conversion);
}
return Math.floor(millis / conversion);
}
export function millisToSeconds(millis: MaybeNumber) {
return convertMillis(millis, MILLIS_PER_SECOND);
}
export function millisToMinutes(millis: MaybeNumber) {
return convertMillis(millis, MILLIS_PER_MINUTE);
}
export function millisToHours(millis: MaybeNumber) {
return convertMillis(millis, MILLIS_PER_HOUR);
}
@@ -1,31 +0,0 @@
import { mts } from '../timeConstants.js';
/**
* another go at simpler string formatting (counters) -- Copied from client code
* @description Converts seconds to string representing time
* @param {number | null} milliseconds - time in seconds
* @param {boolean} [hideZero] - whether to show hours in case its 00
* @returns {string} String representing absolute time 00:12:02
*/
export function formatDisplay(milliseconds: number | null, hideZero = false): string {
if (typeof milliseconds !== 'number') {
return hideZero ? '00:00' : '00:00:00';
}
// add an extra 0 if necessary
const format = (val: number) => `0${Math.floor(val)}`.slice(-2);
const s = Math.abs(millisToSeconds(milliseconds));
const hours = Math.floor((s / 3600) % 24);
const minutes = Math.floor((s % 3600) / 60);
if (hideZero && hours < 1) return [minutes, s % 60].map(format).join(':');
return [hours, minutes, s % 60].map(format).join(':');
}
const millisToSeconds = (millis: number | null): number => {
if (millis === null) {
return 0;
}
return millis < 0 ? Math.ceil(millis / mts) : Math.floor(millis / mts);
};
@@ -1,11 +0,0 @@
import { DateTime } from 'luxon';
/**
* @description utility function to format a date in milliseconds using luxon
* @param {number} millis
* @param {string} format
* @return {string}
*/
export function formatFromMillis(millis: number, format: string) {
return DateTime.fromMillis(millis).toUTC().toFormat(format);
}
@@ -1,71 +0,0 @@
import { expect } from 'vitest';
import { millisToString } from './millisToString';
describe('millisToString()', () => {
it('returns fallback if millis is null', () => {
const fallback = 'testFallback';
expect(millisToString(null, true, fallback)).toBe(fallback);
});
it('returns 00:00:00 if 0 is passed', () => {
expect(millisToString(0)).toBe('00:00:00');
});
it('shows negative timers', () => {
const testScenarios = [
{ millis: -300, expected: '-00:00:00' },
{ millis: -1000, expected: '-00:00:01' },
{ millis: -1500, expected: '-00:00:01' },
{ millis: -60000, expected: '-00:01:00' },
{ millis: -600000, expected: '-00:10:00' },
{ millis: -3600000, expected: '-01:00:00' },
{ millis: -36000000, expected: '-10:00:00' },
{ millis: -86399000, expected: '-23:59:59' },
{ millis: -86400000, expected: '-00:00:00' },
{ millis: -86401000, expected: '-00:00:01' },
];
testScenarios.forEach((scenario) => {
expect(millisToString(scenario.millis)).toBe(scenario.expected);
});
});
test('random properties', () => {
const testScenarios = [
{ millis: 300, expected: '00:00:00' },
{ millis: 1000, expected: '00:00:01' },
{ millis: 1500, expected: '00:00:01' },
{ millis: 60000, expected: '00:01:00' },
{ millis: 600000, expected: '00:10:00' },
{ millis: 3600000, expected: '01:00:00' },
{ millis: 36000000, expected: '10:00:00' },
{ millis: 86399000, expected: '23:59:59' },
{ millis: 86400000, expected: '00:00:00' },
{ millis: 86401000, expected: '00:00:01' },
];
testScenarios.forEach((scenario) => {
expect(millisToString(scenario.millis)).toBe(scenario.expected);
});
});
test('random properties without seconds', () => {
const testScenarios = [
{ millis: 300, expected: '00:00' },
{ millis: 1000, expected: '00:00' },
{ millis: 1500, expected: '00:00' },
{ millis: 60000, expected: '00:01' },
{ millis: 600000, expected: '00:10' },
{ millis: 3600000, expected: '01:00' },
{ millis: 36000000, expected: '10:00' },
{ millis: 86399000, expected: '23:59' },
{ millis: 86400000, expected: '00:00' },
{ millis: 86401000, expected: '00:00' },
];
testScenarios.forEach((scenario) => {
expect(millisToString(scenario.millis, false)).toBe(scenario.expected);
});
});
});
@@ -1,19 +0,0 @@
import { DateTime } from 'luxon';
/**
* @description Converts milliseconds to string representing time
* @param {number | null} millis - time in milliseconds
* @param {boolean} showSeconds - weather to show the seconds
* @param {string} fallback - what to return if value is null
* @returns {string} String representing time 00:12:02
*/
export function millisToString(millis: number | null, showSeconds = true, fallback = '...') {
if (millis == null) {
return fallback;
}
const isNegative = millis < 0;
const format = `HH:mm${showSeconds ? ':ss' : ''}`;
return `${isNegative ? '-' : ''}${DateTime.fromMillis(Math.abs(millis)).toUTC().toFormat(format)}`;
}
@@ -1,4 +1,77 @@
import { formatDisplay } from './formatDisplay';
import { expect } from 'vitest';
import { millisToString } from './timeFormatting';
describe('millisToString()', () => {
it('returns fallback if millis is null', () => {
const fallback = 'testFallback';
expect(millisToString(null, { fallback })).toBe(fallback);
});
it('returns 00:00:00 if 0 is passed', () => {
expect(millisToString(0)).toBe('00:00:00');
});
it('shows negative timers', () => {
const testScenarios = [
{ millis: -300, expected: '-00:00:00' },
{ millis: -1000, expected: '-00:00:01' },
{ millis: -1500, expected: '-00:00:01' },
{ millis: -60000, expected: '-00:01:00' },
{ millis: -600000, expected: '-00:10:00' },
{ millis: -3600000, expected: '-01:00:00' },
{ millis: -36000000, expected: '-10:00:00' },
{ millis: -86399000, expected: '-23:59:59' },
{ millis: -86400000, expected: '-00:00:00' },
{ millis: -86401000, expected: '-00:00:01' },
];
testScenarios.forEach((scenario) => {
expect(millisToString(scenario.millis)).toBe(scenario.expected);
});
});
test('random properties', () => {
const testScenarios = [
{ millis: 300, expected: '00:00:00' },
{ millis: 1000, expected: '00:00:01' },
{ millis: 1500, expected: '00:00:01' },
{ millis: 60000, expected: '00:01:00' },
{ millis: 600000, expected: '00:10:00' },
{ millis: 3600000, expected: '01:00:00' },
{ millis: 36000000, expected: '10:00:00' },
{ millis: 86399000, expected: '23:59:59' },
{ millis: 86400000, expected: '00:00:00' },
{ millis: 86401000, expected: '00:00:01' },
];
testScenarios.forEach((scenario) => {
expect(millisToString(scenario.millis)).toBe(scenario.expected);
});
});
test.skip('random properties without seconds', () => {
const testScenarios = [
{ millis: 300, expected: '00:00' },
{ millis: 1000, expected: '00:00' },
{ millis: 1500, expected: '00:00' },
{ millis: 60000, expected: '00:01' },
{ millis: 600000, expected: '00:10' },
{ millis: 3600000, expected: '01:00' },
{ millis: 36000000, expected: '10:00' },
{ millis: 86399000, expected: '23:59' },
{ millis: 86400000, expected: '00:00' },
{ millis: 86401000, expected: '00:00' },
];
testScenarios.forEach((scenario) => {
expect(millisToString(scenario.millis)).toBe(scenario.expected);
});
});
});
/**
* import { formatDisplay } from './formatDisplay';
describe('test string from formatDisplay function', () => {
it('test with null values', () => {
@@ -89,3 +162,5 @@ describe('test string from formatDisplay function with hidezero', () => {
expect(formatDisplay(t.val, true)).toBe(t.result);
});
});
*/
@@ -0,0 +1,71 @@
import { DateTime } from 'luxon';
import { MaybeNumber } from 'ontime-types';
import { millisToHours, millisToMinutes, millisToSeconds } from './conversionUtils.js';
function pad(val: number): string {
return String(val).padStart(2, '0');
}
type FormatOptions = {
fallback?: string;
};
/**
* Converts a value in milliseconds to its time tag
* @param millis time to convert
* @param options optional overloads for format
* @returns formatted time such as 12:00:00
*/
export function millisToString(millis?: MaybeNumber, options?: FormatOptions): string {
if (millis == null) {
return options?.fallback ?? '...';
}
const absoluteMillis = Math.abs(millis);
const seconds = millisToSeconds(absoluteMillis) % 60;
const minutes = millisToMinutes(absoluteMillis) % 60;
const hours = millisToHours(absoluteMillis) % 24;
const isNegative = millis < 0;
return `${isNegative ? '-' : ''}${[hours, minutes, seconds].map(pad).join(':')}`;
}
/**
* Receives a string such as 00:10:10 and removes the hours field if it is 00
* @param timer
*/
export function removeLeadingZero(timer: string): string {
if (timer.startsWith('00:0')) {
return timer.slice(4);
}
if (timer.startsWith('00:')) {
return timer.slice(3);
}
if (timer.startsWith('-00:0')) {
return timer.slice(5);
}
if (timer.startsWith('-00:')) {
return timer.slice(4);
}
return timer;
}
/**
* Receives a string such as 00:10:10 and removes the seconds field
* @param timer
*/
export function removeSeconds(timer: string): string {
return timer.slice(0, -3);
}
/**
* @description utility function to format a date in milliseconds using luxon
* @param {number} millis
* @param {string} format
* @return {string}
*/
export function formatFromMillis(millis: number, format: string): string {
return DateTime.fromMillis(millis).toUTC().toFormat(format);
}