|
1 | 1 | import type { Clock } from '@bugsnag/core-performance' |
2 | | -import { millisecondsToNanoseconds, nanosecondsToMilliseconds } from '@bugsnag/core-performance' |
3 | 2 |
|
4 | 3 | interface Performance { |
5 | 4 | now: () => number |
@@ -27,22 +26,106 @@ const addStrings = (num1: string, num2: string): string => { |
27 | 26 | return result |
28 | 27 | } |
29 | 28 |
|
| 29 | +// Helper function to subtract two numbers represented as strings (num1 - num2) |
| 30 | +const subtractStrings = (num1: string, num2: string): string => { |
| 31 | + // Ensure num1 >= num2 for simplicity |
| 32 | + if (num1.length < num2.length || (num1.length === num2.length && num1 < num2)) { |
| 33 | + throw new Error('Cannot subtract larger number from smaller number') |
| 34 | + } |
| 35 | + |
| 36 | + let result = '' |
| 37 | + let borrow = 0 |
| 38 | + let i = num1.length - 1 |
| 39 | + let j = num2.length - 1 |
| 40 | + |
| 41 | + while (i >= 0) { |
| 42 | + const digit1 = parseInt(num1[i], 10) - borrow |
| 43 | + const digit2 = j >= 0 ? parseInt(num2[j], 10) : 0 |
| 44 | + |
| 45 | + if (digit1 >= digit2) { |
| 46 | + result = (digit1 - digit2) + result |
| 47 | + borrow = 0 |
| 48 | + } else { |
| 49 | + result = (digit1 + 10 - digit2) + result |
| 50 | + borrow = 1 |
| 51 | + } |
| 52 | + |
| 53 | + i-- |
| 54 | + j-- |
| 55 | + } |
| 56 | + |
| 57 | + // Remove leading zeros |
| 58 | + return result.replace(/^0+/, '') || '0' |
| 59 | +} |
| 60 | + |
| 61 | +// Helper function to handle decimal arithmetic with strings |
| 62 | +const subtractDecimalStrings = (num1Str: string, num2Str: string): string => { |
| 63 | + // Parse decimal numbers |
| 64 | + const [int1, frac1 = ''] = num1Str.split('.') |
| 65 | + const [int2, frac2 = ''] = num2Str.split('.') |
| 66 | + |
| 67 | + // Pad fractional parts to same length |
| 68 | + const maxFracLen = Math.max(frac1.length, frac2.length) |
| 69 | + const padded1 = frac1.padEnd(maxFracLen, '0') |
| 70 | + const padded2 = frac2.padEnd(maxFracLen, '0') |
| 71 | + |
| 72 | + // Combine integer and fractional parts |
| 73 | + const combined1 = int1 + padded1 |
| 74 | + const combined2 = int2 + padded2 |
| 75 | + |
| 76 | + // Subtract |
| 77 | + const resultCombined = subtractStrings(combined1, combined2) |
| 78 | + |
| 79 | + // Split back into integer and fractional parts |
| 80 | + if (maxFracLen === 0) { |
| 81 | + return resultCombined |
| 82 | + } |
| 83 | + |
| 84 | + const resultInt = resultCombined.slice(0, -maxFracLen) || '0' |
| 85 | + const resultFrac = resultCombined.slice(-maxFracLen).replace(/0+$/, '') |
| 86 | + |
| 87 | + return resultFrac ? `${resultInt}.${resultFrac}` : resultInt |
| 88 | +} |
| 89 | + |
| 90 | +const addDecimalStrings = (num1Str: string, num2Str: string): string => { |
| 91 | + // Parse decimal numbers |
| 92 | + const [int1, frac1 = ''] = num1Str.split('.') |
| 93 | + const [int2, frac2 = ''] = num2Str.split('.') |
| 94 | + |
| 95 | + // Pad fractional parts to same length |
| 96 | + const maxFracLen = Math.max(frac1.length, frac2.length) |
| 97 | + const padded1 = frac1.padEnd(maxFracLen, '0') |
| 98 | + const padded2 = frac2.padEnd(maxFracLen, '0') |
| 99 | + |
| 100 | + // Combine integer and fractional parts |
| 101 | + const combined1 = int1 + padded1 |
| 102 | + const combined2 = int2 + padded2 |
| 103 | + |
| 104 | + // Add |
| 105 | + const resultCombined = addStrings(combined1, combined2) |
| 106 | + |
| 107 | + // Split back into integer and fractional parts |
| 108 | + if (maxFracLen === 0) { |
| 109 | + return resultCombined |
| 110 | + } |
| 111 | + |
| 112 | + const resultInt = resultCombined.slice(0, -maxFracLen) || '0' |
| 113 | + const resultFrac = resultCombined.slice(-maxFracLen).replace(/0+$/, '') |
| 114 | + |
| 115 | + return resultFrac ? `${resultInt}.${resultFrac}` : resultInt |
| 116 | +} |
| 117 | + |
30 | 118 | const createClock = (performance: Performance): Clock => { |
31 | 119 | // Measurable "monotonic" time |
32 | 120 | // In React Native, `performance.now` often returns some very high values, but does not expose the `timeOrigin` it uses to calculate what "now" is. |
33 | 121 | // by storing the value of `performance.now` when the app starts, we can remove that value from any further `.now` calculations, and add it to the current "wall time" to get a useful timestamp. |
34 | 122 | const startPerfTime = performance.now() |
35 | 123 | const startWallTime = Date.now() |
36 | 124 |
|
37 | | - const toUnixNanoseconds = (time: number) => millisecondsToNanoseconds(time - startPerfTime + startWallTime) |
38 | | - |
39 | 125 | return { |
40 | 126 | now: () => performance.now(), |
41 | 127 | date: () => new Date(performance.now() - startPerfTime + startWallTime), |
42 | 128 | convert: (date: Date) => date.getTime() - startWallTime + startPerfTime, |
43 | | - toUnixNanoseconds, |
44 | | - // convert unix time in nanoseconds back to milliseconds since timeOrigin |
45 | | - fromUnixNanoseconds: (time: number) => nanosecondsToMilliseconds(time) - startWallTime + startPerfTime, |
46 | 129 | // convert milliseconds since timeOrigin to full timestamp |
47 | 130 | toUnixTimestampNanoseconds: (time: number) => { |
48 | 131 | // Calculate the unix timestamp in milliseconds with high precision |
@@ -85,6 +168,44 @@ const createClock = (performance: Performance): Clock => { |
85 | 168 | } |
86 | 169 |
|
87 | 170 | return result |
| 171 | + }, |
| 172 | + // convert unix timestamp in nanoseconds (string) back to milliseconds since timeOrigin |
| 173 | + fromUnixNanosecondsTimestamp: (nanosStr: string) => { |
| 174 | + // Convert nanoseconds string to milliseconds string with full precision |
| 175 | + let millisecondsStr: string |
| 176 | + |
| 177 | + if (nanosStr.length <= 6) { |
| 178 | + // Less than 1 millisecond - create fractional milliseconds |
| 179 | + const paddedNanos = nanosStr.padStart(6, '0') |
| 180 | + millisecondsStr = '0.' + paddedNanos |
| 181 | + } else { |
| 182 | + // Split nanoseconds into milliseconds and fractional nanoseconds |
| 183 | + const msLength = nanosStr.length - 6 |
| 184 | + const integerMs = nanosStr.substring(0, msLength) |
| 185 | + const fractionalNs = nanosStr.substring(msLength) |
| 186 | + |
| 187 | + if (fractionalNs === '000000') { |
| 188 | + millisecondsStr = integerMs |
| 189 | + } else { |
| 190 | + // Remove trailing zeros from fractional part |
| 191 | + const trimmedFractional = fractionalNs.replace(/0+$/, '') |
| 192 | + millisecondsStr = integerMs + '.' + trimmedFractional |
| 193 | + } |
| 194 | + } |
| 195 | + |
| 196 | + // Perform calculation using string arithmetic to maintain precision |
| 197 | + // unixMilliseconds - startWallTime + startPerfTime |
| 198 | + const startWallTimeStr = startWallTime.toString() |
| 199 | + const startPerfTimeStr = startPerfTime.toString() |
| 200 | + |
| 201 | + // Step 1: unixMilliseconds - startWallTime |
| 202 | + const afterSubtraction = subtractDecimalStrings(millisecondsStr, startWallTimeStr) |
| 203 | + |
| 204 | + // Step 2: result + startPerfTime |
| 205 | + const finalResult = addDecimalStrings(afterSubtraction, startPerfTimeStr) |
| 206 | + |
| 207 | + // Convert to number only at the very end |
| 208 | + return parseFloat(finalResult) |
88 | 209 | } |
89 | 210 | } |
90 | 211 | } |
|
0 commit comments