xref: /aosp_15_r20/development/tools/winscope/src/common/timestamp_converter.ts (revision 90c8c64db3049935a07c6143d7fd006e26f8ecca)
1*90c8c64dSAndroid Build Coastguard Worker/*
2*90c8c64dSAndroid Build Coastguard Worker * Copyright (C) 2024 The Android Open Source Project
3*90c8c64dSAndroid Build Coastguard Worker *
4*90c8c64dSAndroid Build Coastguard Worker * Licensed under the Apache License, Version 2.0 (the "License");
5*90c8c64dSAndroid Build Coastguard Worker * you may not use this file except in compliance with the License.
6*90c8c64dSAndroid Build Coastguard Worker * You may obtain a copy of the License at
7*90c8c64dSAndroid Build Coastguard Worker *
8*90c8c64dSAndroid Build Coastguard Worker *      http://www.apache.org/licenses/LICENSE-2.0
9*90c8c64dSAndroid Build Coastguard Worker *
10*90c8c64dSAndroid Build Coastguard Worker * Unless required by applicable law or agreed to in writing, software
11*90c8c64dSAndroid Build Coastguard Worker * distributed under the License is distributed on an "AS IS" BASIS,
12*90c8c64dSAndroid Build Coastguard Worker * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13*90c8c64dSAndroid Build Coastguard Worker * See the License for the specific language governing permissions and
14*90c8c64dSAndroid Build Coastguard Worker * limitations under the License.
15*90c8c64dSAndroid Build Coastguard Worker */
16*90c8c64dSAndroid Build Coastguard Worker
17*90c8c64dSAndroid Build Coastguard Workerimport {assertDefined, assertTrue} from './assert_utils';
18*90c8c64dSAndroid Build Coastguard Workerimport {BigintMath} from './bigint_math';
19*90c8c64dSAndroid Build Coastguard Workerimport {
20*90c8c64dSAndroid Build Coastguard Worker  INVALID_TIME_NS,
21*90c8c64dSAndroid Build Coastguard Worker  Timestamp,
22*90c8c64dSAndroid Build Coastguard Worker  TimestampFormatter,
23*90c8c64dSAndroid Build Coastguard Worker  TimestampFormatType,
24*90c8c64dSAndroid Build Coastguard Worker  TimezoneInfo,
25*90c8c64dSAndroid Build Coastguard Worker} from './time';
26*90c8c64dSAndroid Build Coastguard Workerimport {TimestampUtils} from './timestamp_utils';
27*90c8c64dSAndroid Build Coastguard Workerimport {TIME_UNITS, TIME_UNIT_TO_NANO} from './time_units';
28*90c8c64dSAndroid Build Coastguard Workerimport {UTCOffset} from './utc_offset';
29*90c8c64dSAndroid Build Coastguard Worker
30*90c8c64dSAndroid Build Coastguard Worker// Pre-T traces do not provide real-to-boottime or real-to-monotonic offsets,so
31*90c8c64dSAndroid Build Coastguard Worker// we group their timestamps under the "ELAPSED" umbrella term, and hope that
32*90c8c64dSAndroid Build Coastguard Worker// the CPU was not suspended before the tracing session, causing them to diverge.
33*90c8c64dSAndroid Build Coastguard Workerenum TimestampType {
34*90c8c64dSAndroid Build Coastguard Worker  ELAPSED,
35*90c8c64dSAndroid Build Coastguard Worker  REAL,
36*90c8c64dSAndroid Build Coastguard Worker}
37*90c8c64dSAndroid Build Coastguard Worker
38*90c8c64dSAndroid Build Coastguard Workerclass RealTimestampFormatter implements TimestampFormatter {
39*90c8c64dSAndroid Build Coastguard Worker  constructor(private utcOffset: UTCOffset) {}
40*90c8c64dSAndroid Build Coastguard Worker
41*90c8c64dSAndroid Build Coastguard Worker  setUTCOffset(value: UTCOffset) {
42*90c8c64dSAndroid Build Coastguard Worker    this.utcOffset = value;
43*90c8c64dSAndroid Build Coastguard Worker  }
44*90c8c64dSAndroid Build Coastguard Worker
45*90c8c64dSAndroid Build Coastguard Worker  format(timestamp: Timestamp, type: TimestampFormatType): string {
46*90c8c64dSAndroid Build Coastguard Worker    const timestampNanos =
47*90c8c64dSAndroid Build Coastguard Worker      timestamp.getValueNs() + (this.utcOffset.getValueNs() ?? 0n);
48*90c8c64dSAndroid Build Coastguard Worker    const ms = BigintMath.divideAndRound(
49*90c8c64dSAndroid Build Coastguard Worker      timestampNanos,
50*90c8c64dSAndroid Build Coastguard Worker      BigInt(TIME_UNIT_TO_NANO.ms),
51*90c8c64dSAndroid Build Coastguard Worker    );
52*90c8c64dSAndroid Build Coastguard Worker    const formattedTimestamp = new Date(Number(ms))
53*90c8c64dSAndroid Build Coastguard Worker      .toISOString()
54*90c8c64dSAndroid Build Coastguard Worker      .replace('Z', '')
55*90c8c64dSAndroid Build Coastguard Worker      .replace('T', ', ');
56*90c8c64dSAndroid Build Coastguard Worker    if (type === TimestampFormatType.DROP_DATE) {
57*90c8c64dSAndroid Build Coastguard Worker      return assertDefined(
58*90c8c64dSAndroid Build Coastguard Worker        TimestampUtils.extractTimeFromHumanTimestamp(formattedTimestamp),
59*90c8c64dSAndroid Build Coastguard Worker      );
60*90c8c64dSAndroid Build Coastguard Worker    }
61*90c8c64dSAndroid Build Coastguard Worker    return formattedTimestamp;
62*90c8c64dSAndroid Build Coastguard Worker  }
63*90c8c64dSAndroid Build Coastguard Worker}
64*90c8c64dSAndroid Build Coastguard Workerconst REAL_TIMESTAMP_FORMATTER_UTC = new RealTimestampFormatter(
65*90c8c64dSAndroid Build Coastguard Worker  new UTCOffset(),
66*90c8c64dSAndroid Build Coastguard Worker);
67*90c8c64dSAndroid Build Coastguard Worker
68*90c8c64dSAndroid Build Coastguard Workerclass ElapsedTimestampFormatter {
69*90c8c64dSAndroid Build Coastguard Worker  format(timestamp: Timestamp): string {
70*90c8c64dSAndroid Build Coastguard Worker    let leftNanos = timestamp.getValueNs();
71*90c8c64dSAndroid Build Coastguard Worker    const parts: Array<{value: bigint; unit: string}> = TIME_UNITS.slice()
72*90c8c64dSAndroid Build Coastguard Worker      .reverse()
73*90c8c64dSAndroid Build Coastguard Worker      .map(({nanosInUnit, unit}) => {
74*90c8c64dSAndroid Build Coastguard Worker        let amountOfUnit = BigInt(0);
75*90c8c64dSAndroid Build Coastguard Worker        if (leftNanos >= nanosInUnit) {
76*90c8c64dSAndroid Build Coastguard Worker          amountOfUnit = leftNanos / BigInt(nanosInUnit);
77*90c8c64dSAndroid Build Coastguard Worker        }
78*90c8c64dSAndroid Build Coastguard Worker        leftNanos = leftNanos % BigInt(nanosInUnit);
79*90c8c64dSAndroid Build Coastguard Worker        return {value: amountOfUnit, unit};
80*90c8c64dSAndroid Build Coastguard Worker      });
81*90c8c64dSAndroid Build Coastguard Worker
82*90c8c64dSAndroid Build Coastguard Worker    // Remove all 0ed units at start
83*90c8c64dSAndroid Build Coastguard Worker    while (parts.length > 1 && parts[0].value === 0n) {
84*90c8c64dSAndroid Build Coastguard Worker      parts.shift();
85*90c8c64dSAndroid Build Coastguard Worker    }
86*90c8c64dSAndroid Build Coastguard Worker
87*90c8c64dSAndroid Build Coastguard Worker    return parts.map((part) => `${part.value}${part.unit}`).join('');
88*90c8c64dSAndroid Build Coastguard Worker  }
89*90c8c64dSAndroid Build Coastguard Worker}
90*90c8c64dSAndroid Build Coastguard Workerconst ELAPSED_TIMESTAMP_FORMATTER = new ElapsedTimestampFormatter();
91*90c8c64dSAndroid Build Coastguard Worker
92*90c8c64dSAndroid Build Coastguard Workerexport interface ParserTimestampConverter {
93*90c8c64dSAndroid Build Coastguard Worker  makeTimestampFromRealNs(valueNs: bigint): Timestamp;
94*90c8c64dSAndroid Build Coastguard Worker  makeTimestampFromMonotonicNs(valueNs: bigint): Timestamp;
95*90c8c64dSAndroid Build Coastguard Worker  makeTimestampFromBootTimeNs(valueNs: bigint): Timestamp;
96*90c8c64dSAndroid Build Coastguard Worker  makeZeroTimestamp(): Timestamp;
97*90c8c64dSAndroid Build Coastguard Worker}
98*90c8c64dSAndroid Build Coastguard Worker
99*90c8c64dSAndroid Build Coastguard Workerexport interface ComponentTimestampConverter {
100*90c8c64dSAndroid Build Coastguard Worker  makeTimestampFromHuman(timestampHuman: string): Timestamp;
101*90c8c64dSAndroid Build Coastguard Worker  getUTCOffset(): string;
102*90c8c64dSAndroid Build Coastguard Worker  makeTimestampFromNs(valueNs: bigint): Timestamp;
103*90c8c64dSAndroid Build Coastguard Worker  validateHumanInput(timestampHuman: string): boolean;
104*90c8c64dSAndroid Build Coastguard Worker}
105*90c8c64dSAndroid Build Coastguard Worker
106*90c8c64dSAndroid Build Coastguard Workerexport interface RemoteToolTimestampConverter {
107*90c8c64dSAndroid Build Coastguard Worker  makeTimestampFromBootTimeNs(valueNs: bigint): Timestamp;
108*90c8c64dSAndroid Build Coastguard Worker  makeTimestampFromRealNs(valueNs: bigint): Timestamp;
109*90c8c64dSAndroid Build Coastguard Worker  tryGetBootTimeNs(timestamp: Timestamp): bigint | undefined;
110*90c8c64dSAndroid Build Coastguard Worker  tryGetRealTimeNs(timestamp: Timestamp): bigint | undefined;
111*90c8c64dSAndroid Build Coastguard Worker}
112*90c8c64dSAndroid Build Coastguard Worker
113*90c8c64dSAndroid Build Coastguard Workerexport class TimestampConverter
114*90c8c64dSAndroid Build Coastguard Worker  implements
115*90c8c64dSAndroid Build Coastguard Worker    ParserTimestampConverter,
116*90c8c64dSAndroid Build Coastguard Worker    ComponentTimestampConverter,
117*90c8c64dSAndroid Build Coastguard Worker    RemoteToolTimestampConverter
118*90c8c64dSAndroid Build Coastguard Worker{
119*90c8c64dSAndroid Build Coastguard Worker  private readonly utcOffset = new UTCOffset();
120*90c8c64dSAndroid Build Coastguard Worker  private readonly realTimestampFormatter = new RealTimestampFormatter(
121*90c8c64dSAndroid Build Coastguard Worker    this.utcOffset,
122*90c8c64dSAndroid Build Coastguard Worker  );
123*90c8c64dSAndroid Build Coastguard Worker  private createdTimestampType: TimestampType | undefined;
124*90c8c64dSAndroid Build Coastguard Worker
125*90c8c64dSAndroid Build Coastguard Worker  constructor(
126*90c8c64dSAndroid Build Coastguard Worker    private timezoneInfo: TimezoneInfo,
127*90c8c64dSAndroid Build Coastguard Worker    private realToMonotonicTimeOffsetNs?: bigint,
128*90c8c64dSAndroid Build Coastguard Worker    private realToBootTimeOffsetNs?: bigint,
129*90c8c64dSAndroid Build Coastguard Worker  ) {}
130*90c8c64dSAndroid Build Coastguard Worker
131*90c8c64dSAndroid Build Coastguard Worker  initializeUTCOffset(timestamp: Timestamp) {
132*90c8c64dSAndroid Build Coastguard Worker    if (
133*90c8c64dSAndroid Build Coastguard Worker      this.utcOffset.getValueNs() !== undefined ||
134*90c8c64dSAndroid Build Coastguard Worker      !this.canMakeRealTimestamps()
135*90c8c64dSAndroid Build Coastguard Worker    ) {
136*90c8c64dSAndroid Build Coastguard Worker      return;
137*90c8c64dSAndroid Build Coastguard Worker    }
138*90c8c64dSAndroid Build Coastguard Worker    const utcValueNs = timestamp.getValueNs();
139*90c8c64dSAndroid Build Coastguard Worker    const localNs =
140*90c8c64dSAndroid Build Coastguard Worker      this.timezoneInfo.timezone !== 'UTC'
141*90c8c64dSAndroid Build Coastguard Worker        ? this.addTimezoneOffset(this.timezoneInfo.timezone, utcValueNs)
142*90c8c64dSAndroid Build Coastguard Worker        : utcValueNs;
143*90c8c64dSAndroid Build Coastguard Worker    const utcOffsetNs = localNs - utcValueNs;
144*90c8c64dSAndroid Build Coastguard Worker    this.utcOffset.initialize(utcOffsetNs);
145*90c8c64dSAndroid Build Coastguard Worker  }
146*90c8c64dSAndroid Build Coastguard Worker
147*90c8c64dSAndroid Build Coastguard Worker  setRealToMonotonicTimeOffsetNs(ns: bigint) {
148*90c8c64dSAndroid Build Coastguard Worker    if (this.realToMonotonicTimeOffsetNs !== undefined) {
149*90c8c64dSAndroid Build Coastguard Worker      return;
150*90c8c64dSAndroid Build Coastguard Worker    }
151*90c8c64dSAndroid Build Coastguard Worker    this.realToMonotonicTimeOffsetNs = ns;
152*90c8c64dSAndroid Build Coastguard Worker  }
153*90c8c64dSAndroid Build Coastguard Worker
154*90c8c64dSAndroid Build Coastguard Worker  setRealToBootTimeOffsetNs(ns: bigint) {
155*90c8c64dSAndroid Build Coastguard Worker    if (this.realToBootTimeOffsetNs !== undefined) {
156*90c8c64dSAndroid Build Coastguard Worker      return;
157*90c8c64dSAndroid Build Coastguard Worker    }
158*90c8c64dSAndroid Build Coastguard Worker    this.realToBootTimeOffsetNs = ns;
159*90c8c64dSAndroid Build Coastguard Worker  }
160*90c8c64dSAndroid Build Coastguard Worker
161*90c8c64dSAndroid Build Coastguard Worker  getUTCOffset(): string {
162*90c8c64dSAndroid Build Coastguard Worker    return this.utcOffset.format();
163*90c8c64dSAndroid Build Coastguard Worker  }
164*90c8c64dSAndroid Build Coastguard Worker
165*90c8c64dSAndroid Build Coastguard Worker  makeTimestampFromMonotonicNs(valueNs: bigint): Timestamp {
166*90c8c64dSAndroid Build Coastguard Worker    if (this.realToMonotonicTimeOffsetNs !== undefined) {
167*90c8c64dSAndroid Build Coastguard Worker      return this.makeRealTimestamp(valueNs + this.realToMonotonicTimeOffsetNs);
168*90c8c64dSAndroid Build Coastguard Worker    }
169*90c8c64dSAndroid Build Coastguard Worker    return this.makeElapsedTimestamp(valueNs);
170*90c8c64dSAndroid Build Coastguard Worker  }
171*90c8c64dSAndroid Build Coastguard Worker
172*90c8c64dSAndroid Build Coastguard Worker  makeTimestampFromBootTimeNs(valueNs: bigint): Timestamp {
173*90c8c64dSAndroid Build Coastguard Worker    if (this.realToBootTimeOffsetNs !== undefined) {
174*90c8c64dSAndroid Build Coastguard Worker      return this.makeRealTimestamp(valueNs + this.realToBootTimeOffsetNs);
175*90c8c64dSAndroid Build Coastguard Worker    }
176*90c8c64dSAndroid Build Coastguard Worker    return this.makeElapsedTimestamp(valueNs);
177*90c8c64dSAndroid Build Coastguard Worker  }
178*90c8c64dSAndroid Build Coastguard Worker
179*90c8c64dSAndroid Build Coastguard Worker  makeTimestampFromRealNs(valueNs: bigint): Timestamp {
180*90c8c64dSAndroid Build Coastguard Worker    return this.makeRealTimestamp(valueNs);
181*90c8c64dSAndroid Build Coastguard Worker  }
182*90c8c64dSAndroid Build Coastguard Worker
183*90c8c64dSAndroid Build Coastguard Worker  makeTimestampFromHuman(timestampHuman: string): Timestamp {
184*90c8c64dSAndroid Build Coastguard Worker    if (TimestampUtils.isHumanElapsedTimeFormat(timestampHuman)) {
185*90c8c64dSAndroid Build Coastguard Worker      return this.makeTimestampfromHumanElapsed(timestampHuman);
186*90c8c64dSAndroid Build Coastguard Worker    }
187*90c8c64dSAndroid Build Coastguard Worker
188*90c8c64dSAndroid Build Coastguard Worker    if (
189*90c8c64dSAndroid Build Coastguard Worker      TimestampUtils.isISOFormat(timestampHuman) ||
190*90c8c64dSAndroid Build Coastguard Worker      TimestampUtils.isRealDateTimeFormat(timestampHuman)
191*90c8c64dSAndroid Build Coastguard Worker    ) {
192*90c8c64dSAndroid Build Coastguard Worker      return this.makeTimestampFromHumanReal(timestampHuman);
193*90c8c64dSAndroid Build Coastguard Worker    }
194*90c8c64dSAndroid Build Coastguard Worker
195*90c8c64dSAndroid Build Coastguard Worker    throw new Error('Invalid timestamp format');
196*90c8c64dSAndroid Build Coastguard Worker  }
197*90c8c64dSAndroid Build Coastguard Worker
198*90c8c64dSAndroid Build Coastguard Worker  makeTimestampFromNs(valueNs: bigint): Timestamp {
199*90c8c64dSAndroid Build Coastguard Worker    return new Timestamp(
200*90c8c64dSAndroid Build Coastguard Worker      valueNs,
201*90c8c64dSAndroid Build Coastguard Worker      this.canMakeRealTimestamps()
202*90c8c64dSAndroid Build Coastguard Worker        ? this.realTimestampFormatter
203*90c8c64dSAndroid Build Coastguard Worker        : ELAPSED_TIMESTAMP_FORMATTER,
204*90c8c64dSAndroid Build Coastguard Worker    );
205*90c8c64dSAndroid Build Coastguard Worker  }
206*90c8c64dSAndroid Build Coastguard Worker
207*90c8c64dSAndroid Build Coastguard Worker  makeZeroTimestamp(): Timestamp {
208*90c8c64dSAndroid Build Coastguard Worker    if (this.canMakeRealTimestamps()) {
209*90c8c64dSAndroid Build Coastguard Worker      return new Timestamp(INVALID_TIME_NS, REAL_TIMESTAMP_FORMATTER_UTC);
210*90c8c64dSAndroid Build Coastguard Worker    } else {
211*90c8c64dSAndroid Build Coastguard Worker      return new Timestamp(INVALID_TIME_NS, ELAPSED_TIMESTAMP_FORMATTER);
212*90c8c64dSAndroid Build Coastguard Worker    }
213*90c8c64dSAndroid Build Coastguard Worker  }
214*90c8c64dSAndroid Build Coastguard Worker
215*90c8c64dSAndroid Build Coastguard Worker  tryGetBootTimeNs(timestamp: Timestamp): bigint | undefined {
216*90c8c64dSAndroid Build Coastguard Worker    if (
217*90c8c64dSAndroid Build Coastguard Worker      this.createdTimestampType !== TimestampType.REAL ||
218*90c8c64dSAndroid Build Coastguard Worker      this.realToBootTimeOffsetNs === undefined
219*90c8c64dSAndroid Build Coastguard Worker    ) {
220*90c8c64dSAndroid Build Coastguard Worker      return undefined;
221*90c8c64dSAndroid Build Coastguard Worker    }
222*90c8c64dSAndroid Build Coastguard Worker    return timestamp.getValueNs() - this.realToBootTimeOffsetNs;
223*90c8c64dSAndroid Build Coastguard Worker  }
224*90c8c64dSAndroid Build Coastguard Worker
225*90c8c64dSAndroid Build Coastguard Worker  tryGetRealTimeNs(timestamp: Timestamp): bigint | undefined {
226*90c8c64dSAndroid Build Coastguard Worker    if (this.createdTimestampType !== TimestampType.REAL) {
227*90c8c64dSAndroid Build Coastguard Worker      return undefined;
228*90c8c64dSAndroid Build Coastguard Worker    }
229*90c8c64dSAndroid Build Coastguard Worker    return timestamp.getValueNs();
230*90c8c64dSAndroid Build Coastguard Worker  }
231*90c8c64dSAndroid Build Coastguard Worker
232*90c8c64dSAndroid Build Coastguard Worker  validateHumanInput(timestampHuman: string, context = this): boolean {
233*90c8c64dSAndroid Build Coastguard Worker    if (context.canMakeRealTimestamps()) {
234*90c8c64dSAndroid Build Coastguard Worker      return TimestampUtils.isHumanRealTimestampFormat(timestampHuman);
235*90c8c64dSAndroid Build Coastguard Worker    }
236*90c8c64dSAndroid Build Coastguard Worker    return TimestampUtils.isHumanElapsedTimeFormat(timestampHuman);
237*90c8c64dSAndroid Build Coastguard Worker  }
238*90c8c64dSAndroid Build Coastguard Worker
239*90c8c64dSAndroid Build Coastguard Worker  clear() {
240*90c8c64dSAndroid Build Coastguard Worker    this.createdTimestampType = undefined;
241*90c8c64dSAndroid Build Coastguard Worker    this.realToBootTimeOffsetNs = undefined;
242*90c8c64dSAndroid Build Coastguard Worker    this.realToMonotonicTimeOffsetNs = undefined;
243*90c8c64dSAndroid Build Coastguard Worker    this.utcOffset.clear();
244*90c8c64dSAndroid Build Coastguard Worker  }
245*90c8c64dSAndroid Build Coastguard Worker
246*90c8c64dSAndroid Build Coastguard Worker  private canMakeRealTimestamps(): boolean {
247*90c8c64dSAndroid Build Coastguard Worker    return this.createdTimestampType === TimestampType.REAL;
248*90c8c64dSAndroid Build Coastguard Worker  }
249*90c8c64dSAndroid Build Coastguard Worker
250*90c8c64dSAndroid Build Coastguard Worker  private makeRealTimestamp(valueNs: bigint): Timestamp {
251*90c8c64dSAndroid Build Coastguard Worker    assertTrue(
252*90c8c64dSAndroid Build Coastguard Worker      this.createdTimestampType === undefined ||
253*90c8c64dSAndroid Build Coastguard Worker        this.createdTimestampType === TimestampType.REAL,
254*90c8c64dSAndroid Build Coastguard Worker    );
255*90c8c64dSAndroid Build Coastguard Worker    this.createdTimestampType = TimestampType.REAL;
256*90c8c64dSAndroid Build Coastguard Worker    return new Timestamp(valueNs, this.realTimestampFormatter);
257*90c8c64dSAndroid Build Coastguard Worker  }
258*90c8c64dSAndroid Build Coastguard Worker
259*90c8c64dSAndroid Build Coastguard Worker  private makeElapsedTimestamp(valueNs: bigint): Timestamp {
260*90c8c64dSAndroid Build Coastguard Worker    assertTrue(
261*90c8c64dSAndroid Build Coastguard Worker      this.createdTimestampType === undefined ||
262*90c8c64dSAndroid Build Coastguard Worker        this.createdTimestampType === TimestampType.ELAPSED,
263*90c8c64dSAndroid Build Coastguard Worker    );
264*90c8c64dSAndroid Build Coastguard Worker    this.createdTimestampType = TimestampType.ELAPSED;
265*90c8c64dSAndroid Build Coastguard Worker    return new Timestamp(valueNs, ELAPSED_TIMESTAMP_FORMATTER);
266*90c8c64dSAndroid Build Coastguard Worker  }
267*90c8c64dSAndroid Build Coastguard Worker
268*90c8c64dSAndroid Build Coastguard Worker  private makeTimestampFromHumanReal(timestampHuman: string): Timestamp {
269*90c8c64dSAndroid Build Coastguard Worker    // Remove trailing Z if present
270*90c8c64dSAndroid Build Coastguard Worker    timestampHuman = timestampHuman.replace('Z', '');
271*90c8c64dSAndroid Build Coastguard Worker
272*90c8c64dSAndroid Build Coastguard Worker    // Convert to ISO format if required
273*90c8c64dSAndroid Build Coastguard Worker    if (TimestampUtils.isRealDateTimeFormat(timestampHuman)) {
274*90c8c64dSAndroid Build Coastguard Worker      timestampHuman = timestampHuman.replace(', ', 'T');
275*90c8c64dSAndroid Build Coastguard Worker    }
276*90c8c64dSAndroid Build Coastguard Worker
277*90c8c64dSAndroid Build Coastguard Worker    // Date.parse only considers up to millisecond precision,
278*90c8c64dSAndroid Build Coastguard Worker    // so only pass in YYYY-MM-DDThh:mm:ss
279*90c8c64dSAndroid Build Coastguard Worker    let nanos = 0n;
280*90c8c64dSAndroid Build Coastguard Worker    if (timestampHuman.includes('.')) {
281*90c8c64dSAndroid Build Coastguard Worker      const [datetime, ns] = timestampHuman.split('.');
282*90c8c64dSAndroid Build Coastguard Worker      nanos += BigInt(Math.floor(Number(ns.padEnd(9, '0'))));
283*90c8c64dSAndroid Build Coastguard Worker      timestampHuman = datetime;
284*90c8c64dSAndroid Build Coastguard Worker    }
285*90c8c64dSAndroid Build Coastguard Worker
286*90c8c64dSAndroid Build Coastguard Worker    timestampHuman += this.utcOffset.format().slice(3);
287*90c8c64dSAndroid Build Coastguard Worker
288*90c8c64dSAndroid Build Coastguard Worker    return this.makeTimestampFromRealNs(
289*90c8c64dSAndroid Build Coastguard Worker      BigInt(Date.parse(timestampHuman)) * BigInt(TIME_UNIT_TO_NANO['ms']) +
290*90c8c64dSAndroid Build Coastguard Worker        BigInt(nanos),
291*90c8c64dSAndroid Build Coastguard Worker    );
292*90c8c64dSAndroid Build Coastguard Worker  }
293*90c8c64dSAndroid Build Coastguard Worker
294*90c8c64dSAndroid Build Coastguard Worker  private makeTimestampfromHumanElapsed(timestampHuman: string): Timestamp {
295*90c8c64dSAndroid Build Coastguard Worker    const usedUnits = timestampHuman.split(/[0-9]+/).filter((it) => it !== '');
296*90c8c64dSAndroid Build Coastguard Worker    const usedValues = timestampHuman
297*90c8c64dSAndroid Build Coastguard Worker      .split(/[a-z]+/)
298*90c8c64dSAndroid Build Coastguard Worker      .filter((it) => it !== '')
299*90c8c64dSAndroid Build Coastguard Worker      .map((it) => Math.floor(Number(it)));
300*90c8c64dSAndroid Build Coastguard Worker
301*90c8c64dSAndroid Build Coastguard Worker    let ns = BigInt(0);
302*90c8c64dSAndroid Build Coastguard Worker
303*90c8c64dSAndroid Build Coastguard Worker    for (let i = 0; i < usedUnits.length; i++) {
304*90c8c64dSAndroid Build Coastguard Worker      const unit = usedUnits[i];
305*90c8c64dSAndroid Build Coastguard Worker      const value = usedValues[i];
306*90c8c64dSAndroid Build Coastguard Worker      const unitData = assertDefined(TIME_UNITS.find((it) => it.unit === unit));
307*90c8c64dSAndroid Build Coastguard Worker      ns += BigInt(unitData.nanosInUnit) * BigInt(value);
308*90c8c64dSAndroid Build Coastguard Worker    }
309*90c8c64dSAndroid Build Coastguard Worker
310*90c8c64dSAndroid Build Coastguard Worker    return this.makeElapsedTimestamp(ns);
311*90c8c64dSAndroid Build Coastguard Worker  }
312*90c8c64dSAndroid Build Coastguard Worker
313*90c8c64dSAndroid Build Coastguard Worker  private addTimezoneOffset(timezone: string, timestampNs: bigint): bigint {
314*90c8c64dSAndroid Build Coastguard Worker    const utcDate = new Date(Number(timestampNs / 1000000n));
315*90c8c64dSAndroid Build Coastguard Worker    const timezoneDateFormatted = utcDate.toLocaleString('en-US', {
316*90c8c64dSAndroid Build Coastguard Worker      timeZone: timezone,
317*90c8c64dSAndroid Build Coastguard Worker    });
318*90c8c64dSAndroid Build Coastguard Worker    const timezoneDate = new Date(timezoneDateFormatted);
319*90c8c64dSAndroid Build Coastguard Worker
320*90c8c64dSAndroid Build Coastguard Worker    let daysDiff = timezoneDate.getDay() - utcDate.getDay(); // day of the week
321*90c8c64dSAndroid Build Coastguard Worker    if (daysDiff > 1) {
322*90c8c64dSAndroid Build Coastguard Worker      // Saturday in timezone, Sunday in UTC
323*90c8c64dSAndroid Build Coastguard Worker      daysDiff = -1;
324*90c8c64dSAndroid Build Coastguard Worker    } else if (daysDiff < -1) {
325*90c8c64dSAndroid Build Coastguard Worker      // Sunday in timezone, Saturday in UTC
326*90c8c64dSAndroid Build Coastguard Worker      daysDiff = 1;
327*90c8c64dSAndroid Build Coastguard Worker    }
328*90c8c64dSAndroid Build Coastguard Worker
329*90c8c64dSAndroid Build Coastguard Worker    const hoursDiff =
330*90c8c64dSAndroid Build Coastguard Worker      timezoneDate.getHours() - utcDate.getHours() + daysDiff * 24;
331*90c8c64dSAndroid Build Coastguard Worker    const minutesDiff = timezoneDate.getMinutes() - utcDate.getMinutes();
332*90c8c64dSAndroid Build Coastguard Worker    const localTimezoneOffsetMinutes = utcDate.getTimezoneOffset();
333*90c8c64dSAndroid Build Coastguard Worker
334*90c8c64dSAndroid Build Coastguard Worker    return (
335*90c8c64dSAndroid Build Coastguard Worker      timestampNs +
336*90c8c64dSAndroid Build Coastguard Worker      BigInt(hoursDiff * 3.6e12) +
337*90c8c64dSAndroid Build Coastguard Worker      BigInt(minutesDiff * 6e10) -
338*90c8c64dSAndroid Build Coastguard Worker      BigInt(localTimezoneOffsetMinutes * 6e10)
339*90c8c64dSAndroid Build Coastguard Worker    );
340*90c8c64dSAndroid Build Coastguard Worker  }
341*90c8c64dSAndroid Build Coastguard Worker}
342*90c8c64dSAndroid Build Coastguard Worker
343*90c8c64dSAndroid Build Coastguard Workerexport const UTC_TIMEZONE_INFO = {
344*90c8c64dSAndroid Build Coastguard Worker  timezone: 'UTC',
345*90c8c64dSAndroid Build Coastguard Worker  locale: 'en-US',
346*90c8c64dSAndroid Build Coastguard Worker};
347