88 EDIT: This has been turned into a general-purpose time formatting library.
99*/
1010
11- type Resolution = "seconds" | "minutes" | "automatic" ;
11+ type Resolution = "seconds" | "minutes" | "automatic" ;
1212
1313
14- const leftPad = ( num : number ) => ( num < 10 ? `0${ num } ` : num ) ;
14+ const leftPad = ( num : number ) => {
15+ num = Math . abs ( num ) ;
16+ return ( num < 10 ? `0${ num } ` : num ) ;
17+ } ;
18+
19+
20+ /** Rounds away from zero. */
21+ const roundUp = ( num : number ) => num > 0 ? Math . ceil ( num ) : Math . floor ( num ) ;
1522
1623/** Returns "minutes" if seconds>=1hr, otherwise minutes. */
1724function automaticRes ( seconds : number ) {
18- if ( seconds >= 3600 ) return "minutes"
19- return "seconds"
25+ if ( seconds >= 3600 ) return "minutes"
26+ return "seconds"
2027}
2128
2229
@@ -28,48 +35,60 @@ export function formatFromResolution(seconds: number, resolution: Resolution): s
2835}
2936
3037function joinWithColons ( h : number , m : number , s : number ) : string {
31- if ( h > 0 ) return `${ h } :${ leftPad ( m ) } :${ leftPad ( s ) } ` ;
32- if ( m > 0 ) return `${ m } :${ leftPad ( s ) } ` ;
38+ if ( h ) return `${ h } :${ leftPad ( m ) } :${ leftPad ( s ) } ` ;
39+ if ( m ) return `${ m } :${ leftPad ( s ) } ` ;
3340 return "" + s ;
3441}
3542const hmsTime = ( d : number ) => {
36- const h = Math . floor ( d / 3600 ) ;
37- const m = Math . floor ( ( d % 3600 ) / 60 ) ;
38- const s = Math . floor ( ( d % 3600 ) % 60 ) ;
43+ const h = Math . trunc ( d / 3600 ) ;
44+ const m = Math . trunc ( ( d % 3600 ) / 60 ) ;
45+ const s = Math . trunc ( ( d % 3600 ) % 60 ) ;
3946 return joinWithColons ( h , m , s ) ;
4047}
4148const hmTime = ( d : number ) => {
42- const h = Math . floor ( d / 3600 ) ;
49+ const h = Math . trunc ( d / 3600 ) ;
4350 const m = Math . ceil ( ( d % 3600 ) / 60 ) ; // Round up the minutes (#86)
4451 return joinWithColons ( 0 , h , m ) ;
4552}
4653
54+ /** Like Math.truncate(), but truncates `-.5` to `'-0'` */
55+ function truncateWithSign ( d : number ) : string {
56+ const result = Math . trunc ( d ) ;
57+ if ( d < 0 && result === 0 ) return `-${ - result } ` ;
58+ return result . toString ( ) ;
59+ }
60+
4761export default function formatTime ( d : number , format : string ) {
4862 if ( format == 'hms' ) return hmsTime ( d )
4963 if ( format == 'hm' ) return hmTime ( d )
50- if ( format == 'd' ) return '' + Math . ceil ( d / 24 / 3600 )
51- if ( format == 'h' ) return '' + Math . ceil ( d / 3600 )
52- if ( format == 'm' ) return '' + Math . ceil ( d / 60 )
53- if ( format == 's' ) return '' + Math . ceil ( d )
5464
65+ // When rendering a single component, always round up to count consistent whole units.
66+ if ( format == 'd' ) return '' + Math . ceil ( d / 24 / 3600 )
67+ if ( format == 'h' ) return '' + Math . ceil ( d / 3600 )
68+ if ( format == 'm' ) return '' + Math . ceil ( d / 60 )
69+ if ( format == 's' ) return '' + Math . ceil ( d )
70+
71+
72+ // When rendering multiple components, round towards zero because the fraction should
73+ // be represented by the next unit.
5574 return format . replace ( / % ( \w + ) / g, ( match , S ) => {
5675 const sl = S . toLowerCase ( )
5776 if ( sl . startsWith ( 'hms' ) ) return hmsTime ( d ) + S . substring ( 3 )
5877 if ( sl . startsWith ( 'hm' ) ) return hmTime ( d ) + S . substring ( 2 )
59- // 1 lowercase letter: ceil
78+ // 1 lowercase letter: round up
6079 if ( S . startsWith ( 'd' ) ) return Math . ceil ( d / 24 / 3600 ) + S . substring ( 1 )
6180 if ( S . startsWith ( 'h' ) ) return Math . ceil ( d / 3600 ) + S . substring ( 1 )
6281 if ( S . startsWith ( 'm' ) ) return Math . ceil ( d / 60 ) + S . substring ( 1 )
6382 if ( S . startsWith ( 's' ) ) return Math . ceil ( d ) + S . substring ( 1 )
64- // 2 capital letter: pad + floor
65- if ( S . startsWith ( 'HH' ) ) return leftPad ( Math . floor ( ( d % ( 3600 * 24 ) ) / 3600 ) ) + S . substring ( 2 )
66- if ( S . startsWith ( 'MM' ) ) return leftPad ( Math . floor ( ( d % 3600 ) / 60 ) ) + S . substring ( 2 )
67- if ( S . startsWith ( 'SS' ) ) return leftPad ( Math . floor ( d % 60 ) ) + S . substring ( 2 )
68- // 1 capital letter: floor
69- if ( S . startsWith ( 'D' ) ) return Math . floor ( d / 24 / 3600 ) + S . substring ( 1 )
70- if ( S . startsWith ( 'H' ) ) return Math . floor ( ( d % ( 3600 * 24 ) ) / 3600 ) + S . substring ( 1 )
71- if ( S . startsWith ( 'M' ) ) return Math . floor ( ( d % 3600 ) / 60 ) + S . substring ( 1 )
72- if ( S . startsWith ( 'S' ) ) return Math . floor ( d % 60 ) + S . substring ( 1 )
83+ // 2 capital letter: pad + round down
84+ if ( S . startsWith ( 'HH' ) ) return leftPad ( Math . trunc ( ( d % ( 3600 * 24 ) ) / 3600 ) ) + S . substring ( 2 )
85+ if ( S . startsWith ( 'MM' ) ) return leftPad ( Math . trunc ( ( d % 3600 ) / 60 ) ) + S . substring ( 2 )
86+ if ( S . startsWith ( 'SS' ) ) return leftPad ( Math . trunc ( d % 60 ) ) + S . substring ( 2 )
87+ // 1 capital letter: round down, always include sign (for next component)
88+ if ( S . startsWith ( 'D' ) ) return truncateWithSign ( d / 24 / 3600 ) + S . substring ( 1 )
89+ if ( S . startsWith ( 'H' ) ) return truncateWithSign ( ( d % ( 3600 * 24 ) ) / 3600 ) + S . substring ( 1 )
90+ if ( S . startsWith ( 'M' ) ) return truncateWithSign ( ( d % 3600 ) / 60 ) + S . substring ( 1 )
91+ if ( S . startsWith ( 'S' ) ) return truncateWithSign ( d % 60 ) + S . substring ( 1 )
7392 return match
7493 } )
7594}
0 commit comments