xref: /aosp_15_r20/external/perfetto/ui/src/base/time.ts (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
1*6dbdd20aSAndroid Build Coastguard Worker// Copyright (C) 2018 The Android Open Source Project
2*6dbdd20aSAndroid Build Coastguard Worker//
3*6dbdd20aSAndroid Build Coastguard Worker// Licensed under the Apache License, Version 2.0 (the "License");
4*6dbdd20aSAndroid Build Coastguard Worker// you may not use this file except in compliance with the License.
5*6dbdd20aSAndroid Build Coastguard Worker// You may obtain a copy of the License at
6*6dbdd20aSAndroid Build Coastguard Worker//
7*6dbdd20aSAndroid Build Coastguard Worker//      http://www.apache.org/licenses/LICENSE-2.0
8*6dbdd20aSAndroid Build Coastguard Worker//
9*6dbdd20aSAndroid Build Coastguard Worker// Unless required by applicable law or agreed to in writing, software
10*6dbdd20aSAndroid Build Coastguard Worker// distributed under the License is distributed on an "AS IS" BASIS,
11*6dbdd20aSAndroid Build Coastguard Worker// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*6dbdd20aSAndroid Build Coastguard Worker// See the License for the specific language governing permissions and
13*6dbdd20aSAndroid Build Coastguard Worker// limitations under the License.
14*6dbdd20aSAndroid Build Coastguard Worker
15*6dbdd20aSAndroid Build Coastguard Workerimport {BigintMath} from './bigint_math';
16*6dbdd20aSAndroid Build Coastguard Workerimport {Brand} from './brand';
17*6dbdd20aSAndroid Build Coastguard Workerimport {assertTrue} from './logging';
18*6dbdd20aSAndroid Build Coastguard Worker
19*6dbdd20aSAndroid Build Coastguard Worker// The |time| type represents trace time in the same units and domain as trace
20*6dbdd20aSAndroid Build Coastguard Worker// processor (i.e. typically boot time in nanoseconds, but most of the UI should
21*6dbdd20aSAndroid Build Coastguard Worker// be completely agnostic to this).
22*6dbdd20aSAndroid Build Coastguard Workerexport type time = Brand<bigint, 'time'>;
23*6dbdd20aSAndroid Build Coastguard Worker
24*6dbdd20aSAndroid Build Coastguard Worker// The |duration| type is used to represent the duration of time between two
25*6dbdd20aSAndroid Build Coastguard Worker// |time|s. The domain is irrelevant because a duration is relative.
26*6dbdd20aSAndroid Build Coastguard Workerexport type duration = bigint;
27*6dbdd20aSAndroid Build Coastguard Worker
28*6dbdd20aSAndroid Build Coastguard Worker// The conversion factor for converting between different time units.
29*6dbdd20aSAndroid Build Coastguard Workerconst TIME_UNITS_PER_SEC = 1e9;
30*6dbdd20aSAndroid Build Coastguard Workerconst TIME_UNITS_PER_MILLISEC = 1e6;
31*6dbdd20aSAndroid Build Coastguard Workerconst TIME_UNITS_PER_MICROSEC = 1e3;
32*6dbdd20aSAndroid Build Coastguard Worker
33*6dbdd20aSAndroid Build Coastguard Workerexport class Time {
34*6dbdd20aSAndroid Build Coastguard Worker  // Negative time is never found in a trace - so -1 is commonly used as a flag
35*6dbdd20aSAndroid Build Coastguard Worker  // to represent a value is undefined or unset, without having to use a
36*6dbdd20aSAndroid Build Coastguard Worker  // nullable or union type.
37*6dbdd20aSAndroid Build Coastguard Worker  static readonly INVALID = Time.fromRaw(-1n);
38*6dbdd20aSAndroid Build Coastguard Worker
39*6dbdd20aSAndroid Build Coastguard Worker  // The min and max possible values, considering times cannot be negative.
40*6dbdd20aSAndroid Build Coastguard Worker  static readonly MIN = Time.fromRaw(0n);
41*6dbdd20aSAndroid Build Coastguard Worker  static readonly MAX = Time.fromRaw(BigintMath.INT64_MAX);
42*6dbdd20aSAndroid Build Coastguard Worker
43*6dbdd20aSAndroid Build Coastguard Worker  static readonly ZERO = Time.fromRaw(0n);
44*6dbdd20aSAndroid Build Coastguard Worker
45*6dbdd20aSAndroid Build Coastguard Worker  // Cast a bigint to a |time|. Supports potentially |undefined| values.
46*6dbdd20aSAndroid Build Coastguard Worker  // I.e. it performs the following conversions:
47*6dbdd20aSAndroid Build Coastguard Worker  // - `bigint` -> `time`
48*6dbdd20aSAndroid Build Coastguard Worker  // - `bigint|undefined` -> `time|undefined`
49*6dbdd20aSAndroid Build Coastguard Worker  //
50*6dbdd20aSAndroid Build Coastguard Worker  // Use this function with caution. The function is effectively a no-op in JS,
51*6dbdd20aSAndroid Build Coastguard Worker  // but using it tells TypeScript that "this value is a time value". It's up to
52*6dbdd20aSAndroid Build Coastguard Worker  // the caller to ensure the value is in the correct units and time domain.
53*6dbdd20aSAndroid Build Coastguard Worker  //
54*6dbdd20aSAndroid Build Coastguard Worker  // If you're reaching for this function after doing some maths on a |time|
55*6dbdd20aSAndroid Build Coastguard Worker  // value and it's decayed to a |bigint| consider using the static math methods
56*6dbdd20aSAndroid Build Coastguard Worker  // in |Time| instead, as they will do the appropriate casting for you.
57*6dbdd20aSAndroid Build Coastguard Worker  static fromRaw(v: bigint): time;
58*6dbdd20aSAndroid Build Coastguard Worker  static fromRaw(v?: bigint): time | undefined;
59*6dbdd20aSAndroid Build Coastguard Worker  static fromRaw(v?: bigint): time | undefined {
60*6dbdd20aSAndroid Build Coastguard Worker    return v as time | undefined;
61*6dbdd20aSAndroid Build Coastguard Worker  }
62*6dbdd20aSAndroid Build Coastguard Worker
63*6dbdd20aSAndroid Build Coastguard Worker  // Convert seconds (number) to a time value.
64*6dbdd20aSAndroid Build Coastguard Worker  // Note: number -> BigInt conversion is relatively slow.
65*6dbdd20aSAndroid Build Coastguard Worker  static fromSeconds(seconds: number): time {
66*6dbdd20aSAndroid Build Coastguard Worker    return Time.fromRaw(BigInt(Math.floor(seconds * TIME_UNITS_PER_SEC)));
67*6dbdd20aSAndroid Build Coastguard Worker  }
68*6dbdd20aSAndroid Build Coastguard Worker
69*6dbdd20aSAndroid Build Coastguard Worker  // Convert time value to seconds and return as a number (i.e. float).
70*6dbdd20aSAndroid Build Coastguard Worker  // Warning: This function is lossy, i.e. precision is lost when converting
71*6dbdd20aSAndroid Build Coastguard Worker  // BigInt -> number.
72*6dbdd20aSAndroid Build Coastguard Worker  // Note: BigInt -> number conversion is relatively slow.
73*6dbdd20aSAndroid Build Coastguard Worker  static toSeconds(t: time): number {
74*6dbdd20aSAndroid Build Coastguard Worker    return Number(t) / TIME_UNITS_PER_SEC;
75*6dbdd20aSAndroid Build Coastguard Worker  }
76*6dbdd20aSAndroid Build Coastguard Worker
77*6dbdd20aSAndroid Build Coastguard Worker  // Convert milliseconds (number) to a time value.
78*6dbdd20aSAndroid Build Coastguard Worker  // Note: number -> BigInt conversion is relatively slow.
79*6dbdd20aSAndroid Build Coastguard Worker  static fromMillis(millis: number): time {
80*6dbdd20aSAndroid Build Coastguard Worker    return Time.fromRaw(BigInt(Math.floor(millis * TIME_UNITS_PER_MILLISEC)));
81*6dbdd20aSAndroid Build Coastguard Worker  }
82*6dbdd20aSAndroid Build Coastguard Worker
83*6dbdd20aSAndroid Build Coastguard Worker  // Convert time value to milliseconds and return as a number (i.e. float).
84*6dbdd20aSAndroid Build Coastguard Worker  // Warning: This function is lossy, i.e. precision is lost when converting
85*6dbdd20aSAndroid Build Coastguard Worker  // BigInt -> number.
86*6dbdd20aSAndroid Build Coastguard Worker  // Note: BigInt -> number conversion is relatively slow.
87*6dbdd20aSAndroid Build Coastguard Worker  static toMillis(t: time): number {
88*6dbdd20aSAndroid Build Coastguard Worker    return Number(t) / TIME_UNITS_PER_MILLISEC;
89*6dbdd20aSAndroid Build Coastguard Worker  }
90*6dbdd20aSAndroid Build Coastguard Worker
91*6dbdd20aSAndroid Build Coastguard Worker  // Convert microseconds (number) to a time value.
92*6dbdd20aSAndroid Build Coastguard Worker  // Note: number -> BigInt conversion is relatively slow.
93*6dbdd20aSAndroid Build Coastguard Worker  static fromMicros(millis: number): time {
94*6dbdd20aSAndroid Build Coastguard Worker    return Time.fromRaw(BigInt(Math.floor(millis * TIME_UNITS_PER_MICROSEC)));
95*6dbdd20aSAndroid Build Coastguard Worker  }
96*6dbdd20aSAndroid Build Coastguard Worker
97*6dbdd20aSAndroid Build Coastguard Worker  // Convert time value to microseconds and return as a number (i.e. float).
98*6dbdd20aSAndroid Build Coastguard Worker  // Warning: This function is lossy, i.e. precision is lost when converting
99*6dbdd20aSAndroid Build Coastguard Worker  // BigInt -> number.
100*6dbdd20aSAndroid Build Coastguard Worker  // Note: BigInt -> number conversion is relatively slow.
101*6dbdd20aSAndroid Build Coastguard Worker  static toMicros(t: time): number {
102*6dbdd20aSAndroid Build Coastguard Worker    return Number(t) / TIME_UNITS_PER_MICROSEC;
103*6dbdd20aSAndroid Build Coastguard Worker  }
104*6dbdd20aSAndroid Build Coastguard Worker
105*6dbdd20aSAndroid Build Coastguard Worker  // Convert a Date object to a time value, given an offset from the unix epoch.
106*6dbdd20aSAndroid Build Coastguard Worker  // Note: number -> BigInt conversion is relatively slow.
107*6dbdd20aSAndroid Build Coastguard Worker  static fromDate(d: Date, offset: duration): time {
108*6dbdd20aSAndroid Build Coastguard Worker    const millis = d.getTime();
109*6dbdd20aSAndroid Build Coastguard Worker    const t = Time.fromMillis(millis);
110*6dbdd20aSAndroid Build Coastguard Worker    return Time.add(t, offset);
111*6dbdd20aSAndroid Build Coastguard Worker  }
112*6dbdd20aSAndroid Build Coastguard Worker
113*6dbdd20aSAndroid Build Coastguard Worker  // Convert time value to a Date object, given an offset from the unix epoch.
114*6dbdd20aSAndroid Build Coastguard Worker  // Warning: This function is lossy, i.e. precision is lost when converting
115*6dbdd20aSAndroid Build Coastguard Worker  // BigInt -> number.
116*6dbdd20aSAndroid Build Coastguard Worker  // Note: BigInt -> number conversion is relatively slow.
117*6dbdd20aSAndroid Build Coastguard Worker  static toDate(t: time, offset: duration): Date {
118*6dbdd20aSAndroid Build Coastguard Worker    const timeSinceEpoch = Time.sub(t, offset);
119*6dbdd20aSAndroid Build Coastguard Worker    const millis = Time.toMillis(timeSinceEpoch);
120*6dbdd20aSAndroid Build Coastguard Worker    return new Date(millis);
121*6dbdd20aSAndroid Build Coastguard Worker  }
122*6dbdd20aSAndroid Build Coastguard Worker
123*6dbdd20aSAndroid Build Coastguard Worker  // Find the closest previous midnight for a given time value.
124*6dbdd20aSAndroid Build Coastguard Worker  static getLatestMidnight(time: time, offset: duration): time {
125*6dbdd20aSAndroid Build Coastguard Worker    const date = Time.toDate(time, offset);
126*6dbdd20aSAndroid Build Coastguard Worker    const floorDay = new Date(
127*6dbdd20aSAndroid Build Coastguard Worker      Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()),
128*6dbdd20aSAndroid Build Coastguard Worker    );
129*6dbdd20aSAndroid Build Coastguard Worker
130*6dbdd20aSAndroid Build Coastguard Worker    return Time.fromDate(floorDay, offset);
131*6dbdd20aSAndroid Build Coastguard Worker  }
132*6dbdd20aSAndroid Build Coastguard Worker
133*6dbdd20aSAndroid Build Coastguard Worker  static add(t: time, d: duration): time {
134*6dbdd20aSAndroid Build Coastguard Worker    return Time.fromRaw(t + d);
135*6dbdd20aSAndroid Build Coastguard Worker  }
136*6dbdd20aSAndroid Build Coastguard Worker
137*6dbdd20aSAndroid Build Coastguard Worker  static sub(t: time, d: duration): time {
138*6dbdd20aSAndroid Build Coastguard Worker    return Time.fromRaw(t - d);
139*6dbdd20aSAndroid Build Coastguard Worker  }
140*6dbdd20aSAndroid Build Coastguard Worker
141*6dbdd20aSAndroid Build Coastguard Worker  static diff(a: time, b: time): duration {
142*6dbdd20aSAndroid Build Coastguard Worker    return a - b;
143*6dbdd20aSAndroid Build Coastguard Worker  }
144*6dbdd20aSAndroid Build Coastguard Worker
145*6dbdd20aSAndroid Build Coastguard Worker  static min(a: time, b: time): time {
146*6dbdd20aSAndroid Build Coastguard Worker    return Time.fromRaw(BigintMath.min(a, b));
147*6dbdd20aSAndroid Build Coastguard Worker  }
148*6dbdd20aSAndroid Build Coastguard Worker
149*6dbdd20aSAndroid Build Coastguard Worker  static max(a: time, b: time): time {
150*6dbdd20aSAndroid Build Coastguard Worker    return Time.fromRaw(BigintMath.max(a, b));
151*6dbdd20aSAndroid Build Coastguard Worker  }
152*6dbdd20aSAndroid Build Coastguard Worker
153*6dbdd20aSAndroid Build Coastguard Worker  static quantFloor(a: time, b: duration): time {
154*6dbdd20aSAndroid Build Coastguard Worker    return Time.fromRaw(BigintMath.quantFloor(a, b));
155*6dbdd20aSAndroid Build Coastguard Worker  }
156*6dbdd20aSAndroid Build Coastguard Worker
157*6dbdd20aSAndroid Build Coastguard Worker  static quantCeil(a: time, b: duration): time {
158*6dbdd20aSAndroid Build Coastguard Worker    return Time.fromRaw(BigintMath.quantCeil(a, b));
159*6dbdd20aSAndroid Build Coastguard Worker  }
160*6dbdd20aSAndroid Build Coastguard Worker
161*6dbdd20aSAndroid Build Coastguard Worker  static quant(a: time, b: duration): time {
162*6dbdd20aSAndroid Build Coastguard Worker    return Time.fromRaw(BigintMath.quant(a, b));
163*6dbdd20aSAndroid Build Coastguard Worker  }
164*6dbdd20aSAndroid Build Coastguard Worker
165*6dbdd20aSAndroid Build Coastguard Worker  static formatSeconds(time: time): string {
166*6dbdd20aSAndroid Build Coastguard Worker    return Time.toSeconds(time).toString() + ' s';
167*6dbdd20aSAndroid Build Coastguard Worker  }
168*6dbdd20aSAndroid Build Coastguard Worker
169*6dbdd20aSAndroid Build Coastguard Worker  static formatMilliseconds(time: time): string {
170*6dbdd20aSAndroid Build Coastguard Worker    return Time.toMillis(time).toString() + ' ms';
171*6dbdd20aSAndroid Build Coastguard Worker  }
172*6dbdd20aSAndroid Build Coastguard Worker
173*6dbdd20aSAndroid Build Coastguard Worker  static formatMicroseconds(time: time): string {
174*6dbdd20aSAndroid Build Coastguard Worker    return Time.toMicros(time).toString() + ' us';
175*6dbdd20aSAndroid Build Coastguard Worker  }
176*6dbdd20aSAndroid Build Coastguard Worker
177*6dbdd20aSAndroid Build Coastguard Worker  static toTimecode(time: time): Timecode {
178*6dbdd20aSAndroid Build Coastguard Worker    return new Timecode(time);
179*6dbdd20aSAndroid Build Coastguard Worker  }
180*6dbdd20aSAndroid Build Coastguard Worker}
181*6dbdd20aSAndroid Build Coastguard Worker
182*6dbdd20aSAndroid Build Coastguard Workerexport class Duration {
183*6dbdd20aSAndroid Build Coastguard Worker  // The min and max possible duration values - durations can be negative.
184*6dbdd20aSAndroid Build Coastguard Worker  static MIN = BigintMath.INT64_MIN;
185*6dbdd20aSAndroid Build Coastguard Worker  static MAX = BigintMath.INT64_MAX;
186*6dbdd20aSAndroid Build Coastguard Worker  static ZERO = 0n;
187*6dbdd20aSAndroid Build Coastguard Worker
188*6dbdd20aSAndroid Build Coastguard Worker  // Cast a bigint to a |duration|. Supports potentially |undefined| values.
189*6dbdd20aSAndroid Build Coastguard Worker  // I.e. it performs the following conversions:
190*6dbdd20aSAndroid Build Coastguard Worker  // - `bigint` -> `duration`
191*6dbdd20aSAndroid Build Coastguard Worker  // - `bigint|undefined` -> `duration|undefined`
192*6dbdd20aSAndroid Build Coastguard Worker  //
193*6dbdd20aSAndroid Build Coastguard Worker  // Use this function with caution. The function is effectively a no-op in JS,
194*6dbdd20aSAndroid Build Coastguard Worker  // but using it tells TypeScript that "this value is a duration value". It's
195*6dbdd20aSAndroid Build Coastguard Worker  // up to the caller to ensure the value is in the correct units.
196*6dbdd20aSAndroid Build Coastguard Worker  //
197*6dbdd20aSAndroid Build Coastguard Worker  // If you're reaching for this function after doing some maths on a |duration|
198*6dbdd20aSAndroid Build Coastguard Worker  // value and it's decayed to a |bigint| consider using the static math methods
199*6dbdd20aSAndroid Build Coastguard Worker  // in |duration| instead, as they will do the appropriate casting for you.
200*6dbdd20aSAndroid Build Coastguard Worker  static fromRaw(v: bigint): duration;
201*6dbdd20aSAndroid Build Coastguard Worker  static fromRaw(v?: bigint): duration | undefined;
202*6dbdd20aSAndroid Build Coastguard Worker  static fromRaw(v?: bigint): duration | undefined {
203*6dbdd20aSAndroid Build Coastguard Worker    return v as duration | undefined;
204*6dbdd20aSAndroid Build Coastguard Worker  }
205*6dbdd20aSAndroid Build Coastguard Worker
206*6dbdd20aSAndroid Build Coastguard Worker  static min(a: duration, b: duration): duration {
207*6dbdd20aSAndroid Build Coastguard Worker    return BigintMath.min(a, b);
208*6dbdd20aSAndroid Build Coastguard Worker  }
209*6dbdd20aSAndroid Build Coastguard Worker
210*6dbdd20aSAndroid Build Coastguard Worker  static max(a: duration, b: duration): duration {
211*6dbdd20aSAndroid Build Coastguard Worker    return BigintMath.max(a, b);
212*6dbdd20aSAndroid Build Coastguard Worker  }
213*6dbdd20aSAndroid Build Coastguard Worker
214*6dbdd20aSAndroid Build Coastguard Worker  static fromMillis(millis: number) {
215*6dbdd20aSAndroid Build Coastguard Worker    return BigInt(Math.floor((millis / 1e3) * TIME_UNITS_PER_SEC));
216*6dbdd20aSAndroid Build Coastguard Worker  }
217*6dbdd20aSAndroid Build Coastguard Worker
218*6dbdd20aSAndroid Build Coastguard Worker  // Convert time to seconds as a number.
219*6dbdd20aSAndroid Build Coastguard Worker  // Use this function with caution. It loses precision and is slow.
220*6dbdd20aSAndroid Build Coastguard Worker  static toSeconds(d: duration) {
221*6dbdd20aSAndroid Build Coastguard Worker    return Number(d) / TIME_UNITS_PER_SEC;
222*6dbdd20aSAndroid Build Coastguard Worker  }
223*6dbdd20aSAndroid Build Coastguard Worker
224*6dbdd20aSAndroid Build Coastguard Worker  // Convert time to seconds as a number.
225*6dbdd20aSAndroid Build Coastguard Worker  // Use this function with caution. It loses precision and is slow.
226*6dbdd20aSAndroid Build Coastguard Worker  static toMilliseconds(d: duration) {
227*6dbdd20aSAndroid Build Coastguard Worker    return Number(d) / TIME_UNITS_PER_MILLISEC;
228*6dbdd20aSAndroid Build Coastguard Worker  }
229*6dbdd20aSAndroid Build Coastguard Worker
230*6dbdd20aSAndroid Build Coastguard Worker  // Convert time to seconds as a number.
231*6dbdd20aSAndroid Build Coastguard Worker  // Use this function with caution. It loses precision and is slow.
232*6dbdd20aSAndroid Build Coastguard Worker  static toMicroSeconds(d: duration) {
233*6dbdd20aSAndroid Build Coastguard Worker    return Number(d) / TIME_UNITS_PER_MICROSEC;
234*6dbdd20aSAndroid Build Coastguard Worker  }
235*6dbdd20aSAndroid Build Coastguard Worker
236*6dbdd20aSAndroid Build Coastguard Worker  // Print duration as as human readable string - i.e. to only a handful of
237*6dbdd20aSAndroid Build Coastguard Worker  // significant figues.
238*6dbdd20aSAndroid Build Coastguard Worker  // Use this when readability is more desireable than precision.
239*6dbdd20aSAndroid Build Coastguard Worker  // Examples: 1234 -> 1.23ns
240*6dbdd20aSAndroid Build Coastguard Worker  //           123456789 -> 123ms
241*6dbdd20aSAndroid Build Coastguard Worker  //           123,123,123,123,123 -> 34h 12m
242*6dbdd20aSAndroid Build Coastguard Worker  //           1,000,000,023 -> 1 s
243*6dbdd20aSAndroid Build Coastguard Worker  //           1,230,000,023 -> 1.2 s
244*6dbdd20aSAndroid Build Coastguard Worker  static humanise(dur: duration): string {
245*6dbdd20aSAndroid Build Coastguard Worker    const sec = Duration.toSeconds(dur);
246*6dbdd20aSAndroid Build Coastguard Worker    const units = ['s', 'ms', 'us', 'ns'];
247*6dbdd20aSAndroid Build Coastguard Worker    const sign = Math.sign(sec);
248*6dbdd20aSAndroid Build Coastguard Worker    let n = Math.abs(sec);
249*6dbdd20aSAndroid Build Coastguard Worker    let u = 0;
250*6dbdd20aSAndroid Build Coastguard Worker    while (n < 1 && n !== 0 && u < units.length - 1) {
251*6dbdd20aSAndroid Build Coastguard Worker      n *= 1000;
252*6dbdd20aSAndroid Build Coastguard Worker      u++;
253*6dbdd20aSAndroid Build Coastguard Worker    }
254*6dbdd20aSAndroid Build Coastguard Worker    return `${sign < 0 ? '-' : ''}${Math.round(n * 10) / 10}${units[u]}`;
255*6dbdd20aSAndroid Build Coastguard Worker  }
256*6dbdd20aSAndroid Build Coastguard Worker
257*6dbdd20aSAndroid Build Coastguard Worker  // Print duration with absolute precision.
258*6dbdd20aSAndroid Build Coastguard Worker  static format(duration: duration): string {
259*6dbdd20aSAndroid Build Coastguard Worker    let result = '';
260*6dbdd20aSAndroid Build Coastguard Worker    if (duration < 1) return '0s';
261*6dbdd20aSAndroid Build Coastguard Worker    const unitAndValue: [string, bigint][] = [
262*6dbdd20aSAndroid Build Coastguard Worker      ['h', 3_600_000_000_000n],
263*6dbdd20aSAndroid Build Coastguard Worker      ['m', 60_000_000_000n],
264*6dbdd20aSAndroid Build Coastguard Worker      ['s', 1_000_000_000n],
265*6dbdd20aSAndroid Build Coastguard Worker      ['ms', 1_000_000n],
266*6dbdd20aSAndroid Build Coastguard Worker      ['us', 1_000n],
267*6dbdd20aSAndroid Build Coastguard Worker      ['ns', 1n],
268*6dbdd20aSAndroid Build Coastguard Worker    ];
269*6dbdd20aSAndroid Build Coastguard Worker    unitAndValue.forEach(([unit, unitSize]) => {
270*6dbdd20aSAndroid Build Coastguard Worker      if (duration >= unitSize) {
271*6dbdd20aSAndroid Build Coastguard Worker        const unitCount = duration / unitSize;
272*6dbdd20aSAndroid Build Coastguard Worker        result += unitCount.toLocaleString() + unit + ' ';
273*6dbdd20aSAndroid Build Coastguard Worker        duration = duration % unitSize;
274*6dbdd20aSAndroid Build Coastguard Worker      }
275*6dbdd20aSAndroid Build Coastguard Worker    });
276*6dbdd20aSAndroid Build Coastguard Worker    return result.slice(0, -1);
277*6dbdd20aSAndroid Build Coastguard Worker  }
278*6dbdd20aSAndroid Build Coastguard Worker
279*6dbdd20aSAndroid Build Coastguard Worker  static formatSeconds(dur: duration): string {
280*6dbdd20aSAndroid Build Coastguard Worker    return Duration.toSeconds(dur).toString() + ' s';
281*6dbdd20aSAndroid Build Coastguard Worker  }
282*6dbdd20aSAndroid Build Coastguard Worker
283*6dbdd20aSAndroid Build Coastguard Worker  static formatMilliseconds(dur: duration): string {
284*6dbdd20aSAndroid Build Coastguard Worker    return Duration.toMilliseconds(dur).toString() + ' ms';
285*6dbdd20aSAndroid Build Coastguard Worker  }
286*6dbdd20aSAndroid Build Coastguard Worker
287*6dbdd20aSAndroid Build Coastguard Worker  static formatMicroseconds(dur: duration): string {
288*6dbdd20aSAndroid Build Coastguard Worker    return Duration.toMicroSeconds(dur).toString() + ' us';
289*6dbdd20aSAndroid Build Coastguard Worker  }
290*6dbdd20aSAndroid Build Coastguard Worker}
291*6dbdd20aSAndroid Build Coastguard Worker
292*6dbdd20aSAndroid Build Coastguard Worker// This class takes a time and converts it to a set of strings representing a
293*6dbdd20aSAndroid Build Coastguard Worker// time code where each string represents a group of time units formatted with
294*6dbdd20aSAndroid Build Coastguard Worker// an appropriate number of leading zeros.
295*6dbdd20aSAndroid Build Coastguard Workerexport class Timecode {
296*6dbdd20aSAndroid Build Coastguard Worker  public readonly sign: string;
297*6dbdd20aSAndroid Build Coastguard Worker  public readonly days: string;
298*6dbdd20aSAndroid Build Coastguard Worker  public readonly hours: string;
299*6dbdd20aSAndroid Build Coastguard Worker  public readonly minutes: string;
300*6dbdd20aSAndroid Build Coastguard Worker  public readonly seconds: string;
301*6dbdd20aSAndroid Build Coastguard Worker  public readonly millis: string;
302*6dbdd20aSAndroid Build Coastguard Worker  public readonly micros: string;
303*6dbdd20aSAndroid Build Coastguard Worker  public readonly nanos: string;
304*6dbdd20aSAndroid Build Coastguard Worker
305*6dbdd20aSAndroid Build Coastguard Worker  constructor(time: time) {
306*6dbdd20aSAndroid Build Coastguard Worker    this.sign = time < 0 ? '-' : '';
307*6dbdd20aSAndroid Build Coastguard Worker
308*6dbdd20aSAndroid Build Coastguard Worker    const absTime = BigintMath.abs(time);
309*6dbdd20aSAndroid Build Coastguard Worker
310*6dbdd20aSAndroid Build Coastguard Worker    const days = absTime / 86_400_000_000_000n;
311*6dbdd20aSAndroid Build Coastguard Worker    const hours = (absTime / 3_600_000_000_000n) % 24n;
312*6dbdd20aSAndroid Build Coastguard Worker    const minutes = (absTime / 60_000_000_000n) % 60n;
313*6dbdd20aSAndroid Build Coastguard Worker    const seconds = (absTime / 1_000_000_000n) % 60n;
314*6dbdd20aSAndroid Build Coastguard Worker    const millis = (absTime / 1_000_000n) % 1_000n;
315*6dbdd20aSAndroid Build Coastguard Worker    const micros = (absTime / 1_000n) % 1_000n;
316*6dbdd20aSAndroid Build Coastguard Worker    const nanos = absTime % 1_000n;
317*6dbdd20aSAndroid Build Coastguard Worker
318*6dbdd20aSAndroid Build Coastguard Worker    this.days = days.toString();
319*6dbdd20aSAndroid Build Coastguard Worker    this.hours = hours.toString().padStart(2, '0');
320*6dbdd20aSAndroid Build Coastguard Worker    this.minutes = minutes.toString().padStart(2, '0');
321*6dbdd20aSAndroid Build Coastguard Worker    this.seconds = seconds.toString().padStart(2, '0');
322*6dbdd20aSAndroid Build Coastguard Worker    this.millis = millis.toString().padStart(3, '0');
323*6dbdd20aSAndroid Build Coastguard Worker    this.micros = micros.toString().padStart(3, '0');
324*6dbdd20aSAndroid Build Coastguard Worker    this.nanos = nanos.toString().padStart(3, '0');
325*6dbdd20aSAndroid Build Coastguard Worker  }
326*6dbdd20aSAndroid Build Coastguard Worker
327*6dbdd20aSAndroid Build Coastguard Worker  // Get the upper part of the timecode formatted as: [-]DdHH:MM:SS.
328*6dbdd20aSAndroid Build Coastguard Worker  get dhhmmss(): string {
329*6dbdd20aSAndroid Build Coastguard Worker    const days = this.days === '0' ? '' : `${this.days}d`;
330*6dbdd20aSAndroid Build Coastguard Worker    return `${this.sign}${days}${this.hours}:${this.minutes}:${this.seconds}`;
331*6dbdd20aSAndroid Build Coastguard Worker  }
332*6dbdd20aSAndroid Build Coastguard Worker
333*6dbdd20aSAndroid Build Coastguard Worker  // Get the subsecond part of the timecode formatted as: mmm uuu nnn.
334*6dbdd20aSAndroid Build Coastguard Worker  // The "space" char is configurable but defaults to a normal space.
335*6dbdd20aSAndroid Build Coastguard Worker  subsec(spaceChar: string = ' '): string {
336*6dbdd20aSAndroid Build Coastguard Worker    return `${this.millis}${spaceChar}${this.micros}${spaceChar}${this.nanos}`;
337*6dbdd20aSAndroid Build Coastguard Worker  }
338*6dbdd20aSAndroid Build Coastguard Worker
339*6dbdd20aSAndroid Build Coastguard Worker  // Formats the entire timecode to a string.
340*6dbdd20aSAndroid Build Coastguard Worker  toString(spaceChar: string = ' '): string {
341*6dbdd20aSAndroid Build Coastguard Worker    return `${this.dhhmmss}.${this.subsec(spaceChar)}`;
342*6dbdd20aSAndroid Build Coastguard Worker  }
343*6dbdd20aSAndroid Build Coastguard Worker}
344*6dbdd20aSAndroid Build Coastguard Worker
345*6dbdd20aSAndroid Build Coastguard Workerexport function currentDateHourAndMinute(): string {
346*6dbdd20aSAndroid Build Coastguard Worker  const date = new Date();
347*6dbdd20aSAndroid Build Coastguard Worker  return `${date
348*6dbdd20aSAndroid Build Coastguard Worker    .toISOString()
349*6dbdd20aSAndroid Build Coastguard Worker    .substr(0, 10)}-${date.getHours()}-${date.getMinutes()}`;
350*6dbdd20aSAndroid Build Coastguard Worker}
351*6dbdd20aSAndroid Build Coastguard Worker
352*6dbdd20aSAndroid Build Coastguard Workerexport class TimeSpan {
353*6dbdd20aSAndroid Build Coastguard Worker  static readonly ZERO = new TimeSpan(Time.ZERO, Time.ZERO);
354*6dbdd20aSAndroid Build Coastguard Worker
355*6dbdd20aSAndroid Build Coastguard Worker  readonly start: time;
356*6dbdd20aSAndroid Build Coastguard Worker  readonly end: time;
357*6dbdd20aSAndroid Build Coastguard Worker
358*6dbdd20aSAndroid Build Coastguard Worker  constructor(start: time, end: time) {
359*6dbdd20aSAndroid Build Coastguard Worker    assertTrue(
360*6dbdd20aSAndroid Build Coastguard Worker      start <= end,
361*6dbdd20aSAndroid Build Coastguard Worker      `Span start [${start}] cannot be greater than end [${end}]`,
362*6dbdd20aSAndroid Build Coastguard Worker    );
363*6dbdd20aSAndroid Build Coastguard Worker    this.start = start;
364*6dbdd20aSAndroid Build Coastguard Worker    this.end = end;
365*6dbdd20aSAndroid Build Coastguard Worker  }
366*6dbdd20aSAndroid Build Coastguard Worker
367*6dbdd20aSAndroid Build Coastguard Worker  static fromTimeAndDuration(start: time, duration: duration): TimeSpan {
368*6dbdd20aSAndroid Build Coastguard Worker    return new TimeSpan(start, Time.add(start, duration));
369*6dbdd20aSAndroid Build Coastguard Worker  }
370*6dbdd20aSAndroid Build Coastguard Worker
371*6dbdd20aSAndroid Build Coastguard Worker  get duration(): duration {
372*6dbdd20aSAndroid Build Coastguard Worker    return this.end - this.start;
373*6dbdd20aSAndroid Build Coastguard Worker  }
374*6dbdd20aSAndroid Build Coastguard Worker
375*6dbdd20aSAndroid Build Coastguard Worker  get midpoint(): time {
376*6dbdd20aSAndroid Build Coastguard Worker    return Time.fromRaw((this.start + this.end) / 2n);
377*6dbdd20aSAndroid Build Coastguard Worker  }
378*6dbdd20aSAndroid Build Coastguard Worker
379*6dbdd20aSAndroid Build Coastguard Worker  contains(t: time): boolean {
380*6dbdd20aSAndroid Build Coastguard Worker    return this.start <= t && t < this.end;
381*6dbdd20aSAndroid Build Coastguard Worker  }
382*6dbdd20aSAndroid Build Coastguard Worker
383*6dbdd20aSAndroid Build Coastguard Worker  containsSpan(start: time, end: time): boolean {
384*6dbdd20aSAndroid Build Coastguard Worker    return this.start <= start && end <= this.end;
385*6dbdd20aSAndroid Build Coastguard Worker  }
386*6dbdd20aSAndroid Build Coastguard Worker
387*6dbdd20aSAndroid Build Coastguard Worker  overlaps(start: time, end: time): boolean {
388*6dbdd20aSAndroid Build Coastguard Worker    return !(end <= this.start || start >= this.end);
389*6dbdd20aSAndroid Build Coastguard Worker  }
390*6dbdd20aSAndroid Build Coastguard Worker
391*6dbdd20aSAndroid Build Coastguard Worker  equals(span: TimeSpan): boolean {
392*6dbdd20aSAndroid Build Coastguard Worker    return this.start === span.start && this.end === span.end;
393*6dbdd20aSAndroid Build Coastguard Worker  }
394*6dbdd20aSAndroid Build Coastguard Worker
395*6dbdd20aSAndroid Build Coastguard Worker  translate(x: duration): TimeSpan {
396*6dbdd20aSAndroid Build Coastguard Worker    return new TimeSpan(Time.add(this.start, x), Time.add(this.end, x));
397*6dbdd20aSAndroid Build Coastguard Worker  }
398*6dbdd20aSAndroid Build Coastguard Worker
399*6dbdd20aSAndroid Build Coastguard Worker  pad(padding: duration): TimeSpan {
400*6dbdd20aSAndroid Build Coastguard Worker    return new TimeSpan(
401*6dbdd20aSAndroid Build Coastguard Worker      Time.sub(this.start, padding),
402*6dbdd20aSAndroid Build Coastguard Worker      Time.add(this.end, padding),
403*6dbdd20aSAndroid Build Coastguard Worker    );
404*6dbdd20aSAndroid Build Coastguard Worker  }
405*6dbdd20aSAndroid Build Coastguard Worker}
406*6dbdd20aSAndroid Build Coastguard Worker
407*6dbdd20aSAndroid Build Coastguard Worker// Print the date only for a given date in ISO format.
408*6dbdd20aSAndroid Build Coastguard Workerexport function toISODateOnly(date: Date) {
409*6dbdd20aSAndroid Build Coastguard Worker  const year = date.getUTCFullYear();
410*6dbdd20aSAndroid Build Coastguard Worker  const month = String(date.getUTCMonth() + 1).padStart(2, '0');
411*6dbdd20aSAndroid Build Coastguard Worker  const day = String(date.getUTCDate()).padStart(2, '0');
412*6dbdd20aSAndroid Build Coastguard Worker
413*6dbdd20aSAndroid Build Coastguard Worker  return `${year}-${month}-${day}`;
414*6dbdd20aSAndroid Build Coastguard Worker}
415