Fix fallout from time/duration refactor.

- Fix broken tests.
- Fix bigint conversion in Duration.fromMillis().
- Remove dependency on globals in time.ts (fixes circular dependency).
- Remove stray newline.

Bug: 290833651
Change-Id: Ia2f95cb63bc87da6f628557774abd684c45bf51b
diff --git a/ui/src/common/time.ts b/ui/src/common/time.ts
index f62f9b7..ad598e5 100644
--- a/ui/src/common/time.ts
+++ b/ui/src/common/time.ts
@@ -15,7 +15,6 @@
 import {BigintMath} from '../base/bigint_math';
 import {assertTrue} from '../base/logging';
 import {Brand} from '../frontend/brand';
-import {globals} from '../frontend/globals';
 
 import {ColumnType} from './query_result';
 
@@ -146,7 +145,7 @@
   }
 
   static fromMillis(millis: number) {
-    return BigInt((millis / 1e3) * TIME_UNITS_PER_SEC);
+    return BigInt(Math.floor((millis / 1e3) * TIME_UNITS_PER_SEC));
   }
 
   // Throws if the value cannot be reasonably converted to a bigint.
@@ -266,26 +265,6 @@
   }
 }
 
-// Offset between t=0 and the configured time domain.
-export function timestampOffset(): time {
-  const fmt = timestampFormat();
-  switch (fmt) {
-    case TimestampFormat.Timecode:
-    case TimestampFormat.Seconds:
-      return globals.state.traceTime.start;
-    case TimestampFormat.Raw:
-    case TimestampFormat.RawLocale:
-      return Time.ZERO;
-    default:
-      const x: never = fmt;
-      throw new Error(`Unsupported format ${x}`);
-  }
-}
-
-// Convert absolute timestamp to domain time.
-export function toDomainTime(ts: time): time {
-  return Time.sub(ts, timestampOffset());
-}
 
 export function currentDateHourAndMinute(): string {
   const date = new Date();
diff --git a/ui/src/common/time_unittest.ts b/ui/src/common/time_unittest.ts
index 7dc9565..08d6c07 100644
--- a/ui/src/common/time_unittest.ts
+++ b/ui/src/common/time_unittest.ts
@@ -43,12 +43,12 @@
   expect(Duration.format(3_000n)).toEqual('3us');
   expect(Duration.format(1_000_001_000n)).toEqual('1s 1us');
   expect(Duration.format(200_000_000_030n)).toEqual('3m 20s 30ns');
-  expect(Duration.format(3_600_000_000_000n)).toEqual('60m');
-  expect(Duration.format(3_600_000_000_001n)).toEqual('60m 1ns');
-  expect(Duration.format(86_400_000_000_000n)).toEqual('1,440m');
-  expect(Duration.format(86_400_000_000_001n)).toEqual('1,440m 1ns');
-  expect(Duration.format(31_536_000_000_000_000n)).toEqual('525,600m');
-  expect(Duration.format(31_536_000_000_000_001n)).toEqual('525,600m 1ns');
+  expect(Duration.format(3_600_000_000_000n)).toEqual('1h');
+  expect(Duration.format(3_600_000_000_001n)).toEqual('1h 1ns');
+  expect(Duration.format(86_400_000_000_000n)).toEqual('24h');
+  expect(Duration.format(86_400_000_000_001n)).toEqual('24h 1ns');
+  expect(Duration.format(31_536_000_000_000_000n)).toEqual('8,760h');
+  expect(Duration.format(31_536_000_000_000_001n)).toEqual('8,760h 1ns');
 });
 
 test('Duration.humanise', () => {
@@ -72,6 +72,12 @@
   expect(Duration.humanise(31_536_000_000_000_000n)).toEqual('31536000s');
 });
 
+test('Duration.fromMillis', () => {
+  expect(Duration.fromMillis(123.456789)).toEqual(123456789n);
+  expect(Duration.fromMillis(123.4567895)).toEqual(123456789n);
+  expect(Duration.fromMillis(0.0000001)).toEqual(0n);
+});
+
 test('timecode', () => {
   expect(new Timecode(Time.fromRaw(0n)).toString(' '))
       .toEqual('00:00:00.000 000 000');
diff --git a/ui/src/controller/trace_controller.ts b/ui/src/controller/trace_controller.ts
index eb24289..4eb2310 100644
--- a/ui/src/controller/trace_controller.ts
+++ b/ui/src/controller/trace_controller.ts
@@ -48,7 +48,6 @@
   ProfileType,
 } from '../common/state';
 import {
-
   Duration,
   duration,
   Span,
diff --git a/ui/src/frontend/globals.ts b/ui/src/frontend/globals.ts
index c851a1b..48d13b0 100644
--- a/ui/src/frontend/globals.ts
+++ b/ui/src/frontend/globals.ts
@@ -40,8 +40,11 @@
 import {
   duration,
   Span,
+  Time,
   time,
   TimeSpan,
+  TimestampFormat,
+  timestampFormat,
 } from '../common/time';
 import {setPerfHooks} from '../core/perf';
 import {raf} from '../core/raf_scheduler';
@@ -714,6 +717,27 @@
   get commandManager(): CommandManager {
     return assertExists(this._cmdManager);
   }
+
+  // Offset between t=0 and the configured time domain.
+  timestampOffset(): time {
+    const fmt = timestampFormat();
+    switch (fmt) {
+      case TimestampFormat.Timecode:
+      case TimestampFormat.Seconds:
+        return globals.state.traceTime.start;
+      case TimestampFormat.Raw:
+      case TimestampFormat.RawLocale:
+        return Time.ZERO;
+      default:
+        const x: never = fmt;
+        throw new Error(`Unsupported format ${x}`);
+    }
+  }
+
+  // Convert absolute time to domain time.
+  toDomainTime(ts: time): time {
+    return Time.sub(ts, this.timestampOffset());
+  }
 }
 
 export const globals = new Globals();
diff --git a/ui/src/frontend/gridline_helper.ts b/ui/src/frontend/gridline_helper.ts
index 49e4af1..ea6639a 100644
--- a/ui/src/frontend/gridline_helper.ts
+++ b/ui/src/frontend/gridline_helper.ts
@@ -19,7 +19,6 @@
   Span,
   time,
   Time,
-  timestampOffset,
 } from '../common/time';
 
 import {TRACK_BORDER_COLOR, TRACK_SHELL_WIDTH} from './css_constants';
@@ -199,7 +198,7 @@
   if (width > TRACK_SHELL_WIDTH && span.duration > 0n) {
     const maxMajorTicks = getMaxMajorTicks(width - TRACK_SHELL_WIDTH);
     const map = timeScaleForVisibleWindow(TRACK_SHELL_WIDTH, width);
-    const offset = timestampOffset();
+    const offset = globals.timestampOffset();
     for (const {type, time} of new TickGenerator(span, maxMajorTicks, offset)) {
       const px = Math.floor(map.timeToPx(time));
       if (type === TickType.MAJOR) {
diff --git a/ui/src/frontend/notes_panel.ts b/ui/src/frontend/notes_panel.ts
index 106cacf..97cfa56 100644
--- a/ui/src/frontend/notes_panel.ts
+++ b/ui/src/frontend/notes_panel.ts
@@ -17,7 +17,7 @@
 import {Actions} from '../common/actions';
 import {randomColor} from '../common/colorizer';
 import {AreaNote, Note} from '../common/state';
-import {Time, timestampOffset} from '../common/time';
+import {Time} from '../common/time';
 import {raf} from '../core/raf_scheduler';
 
 import {
@@ -130,7 +130,7 @@
     if (size.width > TRACK_SHELL_WIDTH && span.duration > 0n) {
       const maxMajorTicks = getMaxMajorTicks(size.width - TRACK_SHELL_WIDTH);
       const map = timeScaleForVisibleWindow(TRACK_SHELL_WIDTH, size.width);
-      const offset = timestampOffset();
+      const offset = globals.timestampOffset();
       const tickGen = new TickGenerator(span, maxMajorTicks, offset);
       for (const {type, time} of tickGen) {
         const px = Math.floor(map.timeToPx(time));
diff --git a/ui/src/frontend/overview_timeline_panel.ts b/ui/src/frontend/overview_timeline_panel.ts
index 32eb53c..d0a011f 100644
--- a/ui/src/frontend/overview_timeline_panel.ts
+++ b/ui/src/frontend/overview_timeline_panel.ts
@@ -22,8 +22,6 @@
   time,
   TimestampFormat,
   timestampFormat,
-  timestampOffset,
-  toDomainTime,
 } from '../common/time';
 
 import {
@@ -100,7 +98,7 @@
 
     if (size.width > TRACK_SHELL_WIDTH && this.traceTime.duration > 0n) {
       const maxMajorTicks = getMaxMajorTicks(this.width - TRACK_SHELL_WIDTH);
-      const offset = timestampOffset();
+      const offset = globals.timestampOffset();
       const tickGen = new TickGenerator(this.traceTime, maxMajorTicks, offset);
 
       // Draw time labels
@@ -112,7 +110,7 @@
         if (xPos > this.width) break;
         if (type === TickType.MAJOR) {
           ctx.fillRect(xPos - 1, 0, 1, headerHeight - 5);
-          const domainTime = toDomainTime(time);
+          const domainTime = globals.toDomainTime(time);
           renderTimestamp(ctx, domainTime, xPos + 5, 18, MIN_PX_PER_STEP);
         } else if (type == TickType.MEDIUM) {
           ctx.fillRect(xPos - 1, 0, 1, 8);
diff --git a/ui/src/frontend/tickmark_panel.ts b/ui/src/frontend/tickmark_panel.ts
index 8234caf..51bfc6e 100644
--- a/ui/src/frontend/tickmark_panel.ts
+++ b/ui/src/frontend/tickmark_panel.ts
@@ -14,7 +14,7 @@
 
 import m from 'mithril';
 
-import {Time, timestampOffset} from '../common/time';
+import {Time} from '../common/time';
 
 import {TRACK_SHELL_WIDTH} from './css_constants';
 import {globals} from './globals';
@@ -48,7 +48,7 @@
       const maxMajorTicks = getMaxMajorTicks(size.width - TRACK_SHELL_WIDTH);
       const map = timeScaleForVisibleWindow(TRACK_SHELL_WIDTH, size.width);
 
-      const offset = timestampOffset();
+      const offset = globals.timestampOffset();
       const tickGen = new TickGenerator(visibleSpan, maxMajorTicks, offset);
       for (const {type, time} of tickGen) {
         const px = Math.floor(map.timeToPx(time));
diff --git a/ui/src/frontend/time_axis_panel.ts b/ui/src/frontend/time_axis_panel.ts
index c1c203a..e5ca40e 100644
--- a/ui/src/frontend/time_axis_panel.ts
+++ b/ui/src/frontend/time_axis_panel.ts
@@ -19,8 +19,6 @@
   time,
   TimestampFormat,
   timestampFormat,
-  timestampOffset,
-  toDomainTime,
 } from '../common/time';
 
 import {TRACK_SHELL_WIDTH} from './css_constants';
@@ -44,7 +42,7 @@
     ctx.textAlign = 'left';
     ctx.font = '11px Roboto Condensed';
 
-    const offset = timestampOffset();
+    const offset = globals.timestampOffset();
     // If our timecode domain has an offset, print this offset
     if (offset != 0n) {
       const width = renderTimestamp(ctx, offset, 6, 10, MIN_PX_PER_STEP);
@@ -62,13 +60,13 @@
       const maxMajorTicks = getMaxMajorTicks(size.width - TRACK_SHELL_WIDTH);
       const map = timeScaleForVisibleWindow(TRACK_SHELL_WIDTH, size.width);
 
-      const offset = timestampOffset();
+      const offset = globals.timestampOffset();
       const tickGen = new TickGenerator(span, maxMajorTicks, offset);
       for (const {type, time} of tickGen) {
         if (type === TickType.MAJOR) {
           const position = Math.floor(map.timeToPx(time));
           ctx.fillRect(position, 0, 1, size.height);
-          const domainTime = toDomainTime(time);
+          const domainTime = globals.toDomainTime(time);
           renderTimestamp(ctx, domainTime, position + 5, 10, MIN_PX_PER_STEP);
         }
       }
diff --git a/ui/src/frontend/time_selection_panel.ts b/ui/src/frontend/time_selection_panel.ts
index 3900252..ea4dacd 100644
--- a/ui/src/frontend/time_selection_panel.ts
+++ b/ui/src/frontend/time_selection_panel.ts
@@ -23,8 +23,6 @@
   TimeSpan,
   TimestampFormat,
   timestampFormat,
-  timestampOffset,
-  toDomainTime,
 } from '../common/time';
 
 import {
@@ -152,7 +150,7 @@
       const maxMajorTicks = getMaxMajorTicks(size.width - TRACK_SHELL_WIDTH);
       const map = timeScaleForVisibleWindow(TRACK_SHELL_WIDTH, size.width);
 
-      const offset = timestampOffset();
+      const offset = globals.timestampOffset();
       const tickGen = new TickGenerator(span, maxMajorTicks, offset);
       for (const {type, time} of tickGen) {
         const px = Math.floor(map.timeToPx(time));
@@ -195,7 +193,7 @@
   renderHover(ctx: CanvasRenderingContext2D, size: PanelSize, ts: time) {
     const {visibleTimeScale} = globals.frontendLocalState;
     const xPos = TRACK_SHELL_WIDTH + Math.floor(visibleTimeScale.timeToPx(ts));
-    const domainTime = toDomainTime(ts);
+    const domainTime = globals.toDomainTime(ts);
     const label = stringifyTimestamp(domainTime);
     drawIBar(ctx, xPos, this.bounds(size), label);
   }
diff --git a/ui/src/frontend/widgets/timestamp.ts b/ui/src/frontend/widgets/timestamp.ts
index 37e45ee..441c222 100644
--- a/ui/src/frontend/widgets/timestamp.ts
+++ b/ui/src/frontend/widgets/timestamp.ts
@@ -20,7 +20,6 @@
   Time,
   TimestampFormat,
   timestampFormat,
-  toDomainTime,
 } from '../../common/time';
 import {Anchor} from '../anchor';
 import {copyToClipboard} from '../clipboard';
@@ -74,7 +73,7 @@
 
 function renderTimestamp(time: time): m.Children {
   const fmt = timestampFormat();
-  const domainTime = toDomainTime(time);
+  const domainTime = globals.toDomainTime(time);
   switch (fmt) {
     case TimestampFormat.Timecode:
       return renderTimecode(domainTime);