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