Time & Duration refactor.

- Rename `TPTime` type to `time`.
- Rename `TPDuration` type to `duration`.
- Remove `TPTimestamp` type (in favour of `time`).
- Brand `time` type.
- Add `Time` and `Duration` static classes for functions that operate on
  time and duration types.
- Remove free functions like `toNs` and `fromNs`.

Change-Id: I7651b97e44ded76d23fe2f566da10690e80c47be
diff --git a/ui/src/controller/aggregation/counter_aggregation_controller.ts b/ui/src/controller/aggregation/counter_aggregation_controller.ts
index e632f8e..41a47fe 100644
--- a/ui/src/controller/aggregation/counter_aggregation_controller.ts
+++ b/ui/src/controller/aggregation/counter_aggregation_controller.ts
@@ -15,7 +15,7 @@
 import {ColumnDef} from '../../common/aggregation_data';
 import {Engine} from '../../common/engine';
 import {Area, Sorting} from '../../common/state';
-import {tpDurationToSeconds} from '../../common/time';
+import {Duration} from '../../common/time';
 import {globals} from '../../frontend/globals';
 import {Config, COUNTER_TRACK_KIND} from '../../tracks/counter';
 
@@ -39,7 +39,7 @@
     }
     if (ids.length === 0) return false;
     const duration = area.end - area.start;
-    const durationSec = tpDurationToSeconds(duration);
+    const durationSec = Duration.toSeconds(duration);
 
     const query = `create view ${this.kind} as select
     name,
diff --git a/ui/src/controller/area_selection_handler_unittest.ts b/ui/src/controller/area_selection_handler_unittest.ts
index 2f533f7..6a26bd7 100644
--- a/ui/src/controller/area_selection_handler_unittest.ts
+++ b/ui/src/controller/area_selection_handler_unittest.ts
@@ -14,6 +14,7 @@
 
 import {createEmptyState} from '../common/empty_state';
 import {AreaById} from '../common/state';
+import {Time} from '../common/time';
 import {globals} from '../frontend/globals';
 
 import {AreaSelectionHandler} from './area_selection_handler';
@@ -25,7 +26,8 @@
 
 test('validAreaAfterUndefinedArea', () => {
   const areaId = '0';
-  const latestArea: AreaById = {start: 0n, end: 1n, tracks: [], id: areaId};
+  const latestArea: AreaById =
+      {start: Time.fromRaw(0n), end: Time.fromRaw(1n), tracks: [], id: areaId};
   globals.store.edit((draft) => {
     draft.currentSelection = {kind: 'AREA', areaId: areaId};
     draft.areas[areaId] = latestArea;
@@ -40,8 +42,12 @@
 
 test('UndefinedAreaAfterValidArea', () => {
   const previousAreaId = '0';
-  const previous:
-      AreaById = {start: 0n, end: 1n, tracks: [], id: previousAreaId};
+  const previous: AreaById = {
+    start: Time.fromRaw(0n),
+    end: Time.fromRaw(1n),
+    tracks: [],
+    id: previousAreaId,
+  };
   globals.store.edit((draft) => {
     draft.currentSelection = {
       kind: 'AREA',
@@ -83,8 +89,12 @@
 
 test('validAreaAfterValidArea', () => {
   const previousAreaId = '0';
-  const previous:
-      AreaById = {start: 0n, end: 1n, tracks: [], id: previousAreaId};
+  const previous: AreaById = {
+    start: Time.fromRaw(0n),
+    end: Time.fromRaw(1n),
+    tracks: [],
+    id: previousAreaId,
+  };
   globals.store.edit((draft) => {
     draft.currentSelection = {
       kind: 'AREA',
@@ -96,7 +106,12 @@
   areaSelectionHandler.getAreaChange();
 
   const currentAreaId = '1';
-  const current: AreaById = {start: 1n, end: 2n, tracks: [], id: currentAreaId};
+  const current: AreaById = {
+    start: Time.fromRaw(1n),
+    end: Time.fromRaw(2n),
+    tracks: [],
+    id: currentAreaId,
+  };
   globals.store.edit((draft) => {
     draft.currentSelection = {
       kind: 'AREA',
@@ -112,8 +127,12 @@
 
 test('sameAreaSelected', () => {
   const previousAreaId = '0';
-  const previous:
-      AreaById = {start: 0n, end: 1n, tracks: [], id: previousAreaId};
+  const previous: AreaById = {
+    start: Time.fromRaw(0n),
+    end: Time.fromRaw(1n),
+    tracks: [],
+    id: previousAreaId,
+  };
   globals.store.edit((draft) => {
     draft.currentSelection = {
       kind: 'AREA',
@@ -125,7 +144,12 @@
   areaSelectionHandler.getAreaChange();
 
   const currentAreaId = '0';
-  const current: AreaById = {start: 0n, end: 1n, tracks: [], id: currentAreaId};
+  const current: AreaById = {
+    start: Time.fromRaw(0n),
+    end: Time.fromRaw(1n),
+    tracks: [],
+    id: currentAreaId,
+  };
   globals.store.edit((draft) => {
     draft.currentSelection = {
       kind: 'AREA',
@@ -147,7 +171,12 @@
   areaSelectionHandler.getAreaChange();
 
   globals.store.edit((draft) => {
-    draft.currentSelection = {kind: 'COUNTER', leftTs: 0n, rightTs: 0n, id: 1};
+    draft.currentSelection = {
+      kind: 'COUNTER',
+      leftTs: Time.fromRaw(0n),
+      rightTs: Time.fromRaw(0n),
+      id: 1,
+    };
   });
   const [hasAreaChanged, selectedArea] = areaSelectionHandler.getAreaChange();
 
diff --git a/ui/src/controller/flamegraph_controller.ts b/ui/src/controller/flamegraph_controller.ts
index 31a74c3..b7289a5 100644
--- a/ui/src/controller/flamegraph_controller.ts
+++ b/ui/src/controller/flamegraph_controller.ts
@@ -27,7 +27,7 @@
 } from '../common/flamegraph_util';
 import {NUM, STR} from '../common/query_result';
 import {CallsiteInfo, FlamegraphState, ProfileType} from '../common/state';
-import {tpDurationToSeconds, TPTime} from '../common/time';
+import {Duration, time} from '../common/time';
 import {FlamegraphDetails, globals} from '../frontend/globals';
 import {publishFlamegraphDetails} from '../frontend/publish';
 import {
@@ -267,7 +267,7 @@
   }
 
   async getFlamegraphData(
-      baseKey: string, viewingOption: string, start: TPTime, end: TPTime,
+      baseKey: string, viewingOption: string, start: time, end: time,
       upids: number[], type: ProfileType,
       focusRegex: string): Promise<CallsiteInfo[]> {
     let currentData: CallsiteInfo[];
@@ -413,7 +413,7 @@
   }
 
   private async prepareViewsAndTables(
-      start: TPTime, end: TPTime, upids: number[], type: ProfileType,
+      start: time, end: time, upids: number[], type: ProfileType,
       focusRegex: string): Promise<string> {
     // Creating unique names for views so we can reuse and not delete them
     // for each marker.
@@ -456,7 +456,8 @@
       number {
     const timeState = globals.state.frontendLocalState.visibleState;
     const dur = globals.stateVisibleTime().duration;
-    let width = tpDurationToSeconds(dur / timeState.resolution);
+    // TODO(stevegolton): Does this actually do what we want???
+    let width = Duration.toSeconds(dur / timeState.resolution);
     // TODO(168048193): Remove screen size hack:
     width = Math.max(width, 800);
     if (rootSize === undefined) {
@@ -466,7 +467,7 @@
   }
 
   async getFlamegraphMetadata(
-      type: ProfileType, start: TPTime, end: TPTime,
+      type: ProfileType, start: time, end: time,
       upids: number[]): Promise<FlamegraphDetails|undefined> {
     // Don't do anything if selection of the marker stayed the same.
     if ((this.lastSelectedFlamegraphState !== undefined &&
diff --git a/ui/src/controller/flow_events_controller.ts b/ui/src/controller/flow_events_controller.ts
index 51822c9..dd94d55 100644
--- a/ui/src/controller/flow_events_controller.ts
+++ b/ui/src/controller/flow_events_controller.ts
@@ -16,9 +16,10 @@
 import {featureFlags} from '../common/feature_flags';
 import {LONG, NUM, STR_NULL} from '../common/query_result';
 import {Area} from '../common/state';
+import {Time} from '../common/time';
 import {Flow, globals} from '../frontend/globals';
 import {publishConnectedFlows, publishSelectedFlows} from '../frontend/publish';
-import {asSliceSqlId, asTPTimestamp} from '../frontend/sql_types';
+import {asSliceSqlId} from '../frontend/sql_types';
 import {
   ACTUAL_FRAMES_SLICE_TRACK_KIND,
   Config as ActualConfig,
@@ -129,8 +130,8 @@
         sliceName: nullToStr(it.beginSliceName),
         sliceChromeCustomName: nullToUndefined(it.beginSliceChromeCustomName),
         sliceCategory: nullToStr(it.beginSliceCategory),
-        sliceStartTs: asTPTimestamp(it.beginSliceStartTs),
-        sliceEndTs: asTPTimestamp(it.beginSliceEndTs),
+        sliceStartTs: Time.fromRaw(it.beginSliceStartTs),
+        sliceEndTs: Time.fromRaw(it.beginSliceEndTs),
         depth: it.beginDepth,
         threadName: nullToStr(it.beginThreadName),
         processName: nullToStr(it.beginProcessName),
@@ -142,8 +143,8 @@
         sliceName: nullToStr(it.endSliceName),
         sliceChromeCustomName: nullToUndefined(it.endSliceChromeCustomName),
         sliceCategory: nullToStr(it.endSliceCategory),
-        sliceStartTs: asTPTimestamp(it.endSliceStartTs),
-        sliceEndTs: asTPTimestamp(it.endSliceEndTs),
+        sliceStartTs: Time.fromRaw(it.endSliceStartTs),
+        sliceEndTs: Time.fromRaw(it.endSliceEndTs),
         depth: it.endDepth,
         threadName: nullToStr(it.endThreadName),
         processName: nullToStr(it.endProcessName),
diff --git a/ui/src/controller/ftrace_controller.ts b/ui/src/controller/ftrace_controller.ts
index 7f4ecd9..eee568f 100644
--- a/ui/src/controller/ftrace_controller.ts
+++ b/ui/src/controller/ftrace_controller.ts
@@ -19,7 +19,7 @@
 } from '../common/high_precision_time';
 import {LONG, NUM, STR, STR_NULL} from '../common/query_result';
 import {FtraceFilterState, Pagination} from '../common/state';
-import {Span} from '../common/time';
+import {Span, Time} from '../common/time';
 import {FtraceEvent, globals} from '../frontend/globals';
 import {publishFtracePanelData} from '../frontend/publish';
 import {ratelimit} from '../frontend/rate_limiters';
@@ -142,7 +142,7 @@
     for (let row = 0; it.valid(); it.next(), row++) {
       events.push({
         id: it.id,
-        ts: it.ts,
+        ts: Time.fromRaw(it.ts),
         name: it.name,
         cpu: it.cpu,
         thread: it.thread,
diff --git a/ui/src/controller/logs_controller.ts b/ui/src/controller/logs_controller.ts
index c931ce0..e9a44c3 100644
--- a/ui/src/controller/logs_controller.ts
+++ b/ui/src/controller/logs_controller.ts
@@ -12,7 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import {BigintMath} from '../base/bigint_math';
 import {Engine} from '../common/engine';
 import {
   LogBounds,
@@ -24,10 +23,12 @@
 import {LONG, LONG_NULL, NUM, STR} from '../common/query_result';
 import {escapeGlob, escapeQuery} from '../common/query_utils';
 import {LogFilteringCriteria} from '../common/state';
-import {Span} from '../common/time';
 import {
-  TPTime,
-  TPTimeSpan,
+  duration,
+  Span,
+  time,
+  Time,
+  TimeSpan,
 } from '../common/time';
 import {globals} from '../frontend/globals';
 import {publishTrackData} from '../frontend/publish';
@@ -35,7 +36,7 @@
 import {Controller} from './controller';
 
 async function updateLogBounds(
-    engine: Engine, span: Span<TPTime>): Promise<LogBounds> {
+    engine: Engine, span: Span<time, duration>): Promise<LogBounds> {
   const vizStartNs = span.start;
   const vizEndNs = span.end;
 
@@ -57,14 +58,14 @@
     countTs: NUM,
   });
 
-  const firstLogTs = data.minTs ?? 0n;
-  const lastLogTs = data.maxTs ?? BigintMath.INT64_MAX;
+  const firstLogTs = Time.fromRaw(data.minTs ?? 0n);
+  const lastLogTs = Time.fromRaw(data.maxTs ?? Time.MAX);
 
   const bounds: LogBounds = {
     firstLogTs,
     lastLogTs,
-    firstVisibleLogTs: data.minVizTs ?? firstLogTs,
-    lastVisibleLogTs: data.maxVizTs ?? lastLogTs,
+    firstVisibleLogTs: Time.fromRaw(data.minVizTs ?? firstLogTs),
+    lastVisibleLogTs: Time.fromRaw(data.maxVizTs ?? lastLogTs),
     totalVisibleLogs: data.countTs,
   };
 
@@ -72,7 +73,7 @@
 }
 
 async function updateLogEntries(
-    engine: Engine, span: Span<TPTime>, pagination: Pagination):
+    engine: Engine, span: Span<time, duration>, pagination: Pagination):
     Promise<LogEntries> {
   const vizStartNs = span.start;
   const vizEndNs = span.end;
@@ -93,7 +94,7 @@
         limit ${pagination.start}, ${pagination.count}
     `);
 
-  const timestamps = [];
+  const timestamps: time[] = [];
   const priorities = [];
   const tags = [];
   const messages = [];
@@ -110,7 +111,7 @@
     processName: STR,
   });
   for (; it.valid(); it.next()) {
-    timestamps.push(it.ts);
+    timestamps.push(Time.fromRaw(it.ts));
     priorities.push(it.prio);
     tags.push(it.tag);
     messages.push(it.msg);
@@ -179,7 +180,7 @@
  */
 export class LogsController extends Controller<'main'> {
   private engine: Engine;
-  private span: Span<TPTime>;
+  private span: Span<time, duration>;
   private pagination: Pagination;
   private hasLogs = false;
   private logFilteringCriteria?: LogFilteringCriteria;
@@ -189,7 +190,7 @@
   constructor(args: LogsControllerArgs) {
     super('main');
     this.engine = args.engine;
-    this.span = new TPTimeSpan(0n, BigInt(10e9));
+    this.span = new TimeSpan(Time.ZERO, Time.fromSeconds(10));
     this.pagination = new Pagination(0, 0);
     this.hasAnyLogs().then((exists) => {
       this.hasLogs = exists;
diff --git a/ui/src/controller/search_controller.ts b/ui/src/controller/search_controller.ts
index b2c7e04..b403705 100644
--- a/ui/src/controller/search_controller.ts
+++ b/ui/src/controller/search_controller.ts
@@ -12,17 +12,18 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import {BigintMath} from '../base/bigint_math';
 import {sqliteString} from '../base/string_utils';
 import {Engine} from '../common/engine';
 import {LONG, NUM, STR} from '../common/query_result';
 import {escapeSearchQuery} from '../common/query_utils';
 import {CurrentSearchResults, SearchSummary} from '../common/search_data';
-import {Span} from '../common/time';
 import {
-  TPDuration,
-  TPTime,
-  TPTimeSpan,
+  Duration,
+  duration,
+  Span,
+  time,
+  Time,
+  TimeSpan,
 } from '../common/time';
 import {globals} from '../frontend/globals';
 import {publishSearch, publishSearchResult} from '../frontend/publish';
@@ -35,8 +36,8 @@
 
 export class SearchController extends Controller<'main'> {
   private engine: Engine;
-  private previousSpan: Span<TPTime>;
-  private previousResolution: TPDuration;
+  private previousSpan: Span<time, duration>;
+  private previousResolution: duration;
   private previousSearch: string;
   private updateInProgress: boolean;
   private setupInProgress: boolean;
@@ -44,7 +45,7 @@
   constructor(args: SearchControllerArgs) {
     super('main');
     this.engine = args.engine;
-    this.previousSpan = new TPTimeSpan(0n, 1n);
+    this.previousSpan = new TimeSpan(Time.fromRaw(0n), Time.fromRaw(1n));
     this.previousSearch = '';
     this.updateInProgress = false;
     this.setupInProgress = true;
@@ -89,7 +90,7 @@
     // that is not easily available here.
     // N.B. Timestamps can be negative.
     const {start, end} = newSpan.pad(newSpan.duration);
-    this.previousSpan = new TPTimeSpan(start, end);
+    this.previousSpan = new TimeSpan(start, end);
     this.previousResolution = newResolution;
     this.previousSearch = newSearch;
     if (newSearch === '' || newSearch.length < 4) {
@@ -131,18 +132,18 @@
   onDestroy() {}
 
   private async update(
-      search: string, startNs: TPTime, endNs: TPTime,
-      resolution: TPDuration): Promise<SearchSummary> {
+      search: string, start: time, end: time,
+      resolution: duration): Promise<SearchSummary> {
     const searchLiteral = escapeSearchQuery(search);
 
-    const quantumNs = resolution * 10n;
-    startNs = BigintMath.quantFloor(startNs, quantumNs);
+    const quantum = resolution * 10n;
+    start = Time.quantFloor(start, quantum);
 
-    const windowDur = BigintMath.max(endNs - startNs, 1n);
+    const windowDur = Duration.max(Time.diff(end, start), 1n);
     await this.query(`update search_summary_window set
-      window_start=${startNs},
+      window_start=${start},
       window_dur=${windowDur},
-      quantum=${quantumNs}
+      quantum=${quantum}
       where rowid = 0;`);
 
     const utidRes = await this.query(`select utid from thread join process
@@ -159,8 +160,8 @@
 
     const res = await this.query(`
         select
-          (quantum_ts * ${quantumNs} + ${startNs}) as tsStart,
-          ((quantum_ts+1) * ${quantumNs} + ${startNs}) as tsEnd,
+          (quantum_ts * ${quantum} + ${start}) as tsStart,
+          ((quantum_ts+1) * ${quantum} + ${start}) as tsEnd,
           min(count(*), 255) as count
           from (
               select
diff --git a/ui/src/controller/selection_controller.ts b/ui/src/controller/selection_controller.ts
index c3ccd8a..be6a207 100644
--- a/ui/src/controller/selection_controller.ts
+++ b/ui/src/controller/selection_controller.ts
@@ -23,17 +23,13 @@
   STR_NULL,
 } from '../common/query_result';
 import {ChromeSliceSelection} from '../common/state';
-import {
-  tpDurationFromSql,
-  TPTime,
-  tpTimeFromSql,
-} from '../common/time';
+import {Duration, Time, time} from '../common/time';
 import {
   CounterDetails,
+  globals,
   SliceDetails,
   ThreadStateDetails,
 } from '../frontend/globals';
-import {globals} from '../frontend/globals';
 import {
   publishCounterDetails,
   publishSliceDetails,
@@ -184,10 +180,10 @@
         case 'id':
           break;
         case 'ts':
-          ts = tpTimeFromSql(v);
+          ts = Time.fromSql(v);
           break;
         case 'thread_ts':
-          threadTs = tpTimeFromSql(v);
+          threadTs = Time.fromSql(v);
           break;
         case 'absTime':
           if (v) absTime = `${v}`;
@@ -196,10 +192,10 @@
           name = `${v}`;
           break;
         case 'dur':
-          dur = tpDurationFromSql(v);
+          dur = Duration.fromSql(v);
           break;
         case 'thread_dur':
-          threadDur = tpDurationFromSql(v);
+          threadDur = Duration.fromSql(v);
           break;
         case 'category':
         case 'cat':
@@ -339,7 +335,7 @@
         dur: LONG,
       });
       const selected: ThreadStateDetails = {
-        ts: row.ts,
+        ts: Time.fromRaw(row.ts),
         dur: row.dur,
       };
       publishThreadStateDetails(selected);
@@ -370,7 +366,7 @@
         cpu: NUM,
         threadStateId: NUM_NULL,
       });
-      const ts = row.ts;
+      const ts = Time.fromRaw(row.ts);
       const dur = row.dur;
       const priority = row.priority;
       const endState = row.endState;
@@ -399,7 +395,7 @@
     }
   }
 
-  async counterDetails(ts: TPTime, rightTs: TPTime, id: number):
+  async counterDetails(ts: time, rightTs: time, id: number):
       Promise<CounterDetails> {
     const counter = await this.args.engine.query(
         `SELECT value, track_id as trackId FROM counter WHERE id = ${id}`);
@@ -424,7 +420,7 @@
     return {startTime: ts, value, delta, duration, name};
   }
 
-  async schedulingDetails(ts: TPTime, utid: number) {
+  async schedulingDetails(ts: time, utid: number) {
     // Find the ts of the first wakeup before the current slice.
     const wakeResult = await this.args.engine.query(`
       select ts, waker_utid as wakerUtid
diff --git a/ui/src/controller/trace_controller.ts b/ui/src/controller/trace_controller.ts
index cc8b161..8b1abee 100644
--- a/ui/src/controller/trace_controller.ts
+++ b/ui/src/controller/trace_controller.ts
@@ -47,7 +47,15 @@
   PendingDeeplinkState,
   ProfileType,
 } from '../common/state';
-import {Span, TPTime, TPTimeSpan} from '../common/time';
+import {
+
+  Duration,
+  duration,
+  Span,
+  time,
+  Time,
+  TimeSpan,
+} from '../common/time';
 import {resetEngineWorker, WasmEngineProxy} from '../common/wasm_engine_proxy';
 import {BottomTabList} from '../frontend/bottom_tab';
 import {
@@ -479,10 +487,10 @@
         traceTime.start, traceTime.end, isJsonTrace, this.engine);
     // We don't know the resolution at this point. However this will be
     // replaced in 50ms so a guess is fine.
-    const resolution = visibleTimeSpan.duration.divide(1000).toTPTime();
+    const resolution = visibleTimeSpan.duration.divide(1000).toTime();
     actions.push(Actions.setVisibleTraceTime({
-      start: visibleTimeSpan.start.toTPTime(),
-      end: visibleTimeSpan.end.toTPTime(),
+      start: visibleTimeSpan.start.toTime(),
+      end: visibleTimeSpan.end.toTime(),
       lastUpdate: Date.now() / 1000,
       resolution: BigintMath.max(resolution, 1n),
     }));
@@ -596,7 +604,7 @@
     const profile = await assertExists(this.engine).query(query);
     if (profile.numRows() !== 1) return;
     const row = profile.firstRow({ts: LONG, type: STR, upid: NUM});
-    const ts = row.ts;
+    const ts = Time.fromRaw(row.ts);
     const type = profileType(row.type);
     const upid = row.upid;
     globals.dispatch(Actions.selectHeapProfile({id: 0, upid, ts, type}));
@@ -691,19 +699,20 @@
     publishThreads(threads);
   }
 
-  private async loadTimelineOverview(trace: Span<TPTime>) {
+  private async loadTimelineOverview(trace: Span<time, duration>) {
     clearOverviewData();
 
     const engine = assertExists<Engine>(this.engine);
-    const stepSize = BigintMath.max(1n, trace.duration / 100n);
+    const stepSize = Duration.max(1n, trace.duration / 100n);
     let hasSchedOverview = false;
-    for (let start = trace.start; start < trace.end; start += stepSize) {
+    for (let start = trace.start; start < trace.end;
+         start = Time.add(start, stepSize)) {
       const progress = start - trace.start;
       const ratio = Number(progress) / Number(trace.duration);
       this.updateStatus(
           'Loading overview ' +
           `${Math.round(ratio * 100)}%`);
-      const end = start + stepSize;
+      const end = Time.add(start, stepSize);
 
       // Sched overview.
       const schedResult = await engine.query(
@@ -752,8 +761,8 @@
       const upid = it.upid;
       const load = it.load;
 
-      const start = trace.start + stepSize * bucket;
-      const end = start + stepSize;
+      const start = Time.add(trace.start, stepSize * bucket);
+      const end = Time.add(start, stepSize);
 
       const upidStr = upid.toString();
       let loadArray = slicesData[upidStr];
@@ -971,64 +980,62 @@
   }
 }
 
-async function computeFtraceBounds(engine: Engine): Promise<TPTimeSpan|null> {
+async function computeFtraceBounds(engine: Engine): Promise<TimeSpan|null> {
   const result = await engine.query(`
     SELECT min(ts) as start, max(ts) as end FROM ftrace_event;
   `);
   const {start, end} = result.firstRow({start: LONG_NULL, end: LONG_NULL});
   if (start !== null && end !== null) {
-    return new TPTimeSpan(start, end);
+    return new TimeSpan(Time.fromRaw(start), Time.fromRaw(end));
   }
   return null;
 }
 
-async function computeTraceReliableRangeStart(engine: Engine): Promise<TPTime> {
+async function computeTraceReliableRangeStart(engine: Engine): Promise<time> {
   const result =
     await engine.query(`SELECT RUN_METRIC('chrome/chrome_reliable_range.sql');
        SELECT start FROM chrome_reliable_range`);
   const bounds = result.firstRow({start: LONG});
-  return bounds.start;
+  return Time.fromRaw(bounds.start);
 }
 
 async function computeVisibleTime(
-    traceStart: TPTime, traceEnd: TPTime, isJsonTrace: boolean, engine: Engine):
+    traceStart: time, traceEnd: time, isJsonTrace: boolean, engine: Engine):
     Promise<Span<HighPrecisionTime>> {
   // if we have non-default visible state, update the visible time to it
   const previousVisibleState = globals.stateVisibleTime();
   const defaultTraceSpan =
-      new TPTimeSpan(defaultTraceTime.start, defaultTraceTime.end);
+      new TimeSpan(defaultTraceTime.start, defaultTraceTime.end);
   if (!(previousVisibleState.start === defaultTraceSpan.start &&
         previousVisibleState.end === defaultTraceSpan.end) &&
       (previousVisibleState.start >= traceStart &&
        previousVisibleState.end <= traceEnd)) {
-    return HighPrecisionTimeSpan.fromTpTime(
+    return HighPrecisionTimeSpan.fromTime(
         previousVisibleState.start, previousVisibleState.end);
   }
 
   // initialise visible time to the trace time bounds
-  let visibleStartSec = traceStart;
-  let visibleEndSec = traceEnd;
+  let visibleStart = traceStart;
+  let visibleEnd = traceEnd;
 
   // compare start and end with metadata computed by the trace processor
   const mdTime = await engine.getTracingMetadataTimeBounds();
   // make sure the bounds hold
-  if (BigintMath.max(visibleStartSec, mdTime.start) <
-      BigintMath.min(visibleEndSec, mdTime.end)) {
-    visibleStartSec = BigintMath.max(visibleStartSec, mdTime.start);
-    visibleEndSec = BigintMath.min(visibleEndSec, mdTime.end);
+  if (Time.max(visibleStart, mdTime.start) < Time.min(visibleEnd, mdTime.end)) {
+    visibleStart = Time.max(visibleStart, mdTime.start);
+    visibleEnd = Time.min(visibleEnd, mdTime.end);
   }
 
   // Trace Processor doesn't support the reliable range feature for JSON
   // traces.
   if (!isJsonTrace && ENABLE_CHROME_RELIABLE_RANGE_ZOOM_FLAG.get()) {
     const reliableRangeStart = await computeTraceReliableRangeStart(engine);
-    visibleStartSec = BigintMath.max(visibleStartSec, reliableRangeStart);
+    visibleStart = Time.max(visibleStart, reliableRangeStart);
   }
 
   const ftraceBounds = await computeFtraceBounds(engine);
   if (ftraceBounds !== null) {
-    visibleStartSec = ftraceBounds.start;
+    visibleStart = ftraceBounds.start;
   }
-
-  return HighPrecisionTimeSpan.fromTpTime(visibleStartSec, visibleEndSec);
+  return HighPrecisionTimeSpan.fromTime(visibleStart, visibleEnd);
 }
diff --git a/ui/src/controller/track_controller.ts b/ui/src/controller/track_controller.ts
index 7934a07..c95b72b 100644
--- a/ui/src/controller/track_controller.ts
+++ b/ui/src/controller/track_controller.ts
@@ -16,19 +16,19 @@
 import {assertExists} from '../base/logging';
 import {Engine} from '../common/engine';
 import {Registry} from '../common/registry';
-import {TraceTime, TrackState} from '../common/state';
+import {RESOLUTION_DEFAULT, TraceTime, TrackState} from '../common/state';
 import {
-  TPDuration,
-  TPTime,
-  tpTimeFromSeconds,
-  TPTimeSpan,
+  Duration,
+  duration,
+  time,
+  Time,
+  TimeSpan,
 } from '../common/time';
 import {LIMIT, TrackData} from '../common/track_data';
 import {globals} from '../frontend/globals';
 import {publishTrackData} from '../frontend/publish';
 
-import {Controller} from './controller';
-import {ControllerFactory} from './controller';
+import {Controller, ControllerFactory} from './controller';
 
 interface TrackConfig {}
 
@@ -68,7 +68,7 @@
   // Must be overridden by the track implementation. Is invoked when the track
   // frontend runs out of cached data. The derived track controller is expected
   // to publish new track data in response to this call.
-  abstract onBoundsChange(start: TPTime, end: TPTime, resolution: TPDuration):
+  abstract onBoundsChange(start: time, end: time, resolution: duration):
       Promise<Data>;
 
   get trackState(): TrackState {
@@ -127,7 +127,7 @@
   }
 
   shouldRequestData(traceTime: TraceTime): boolean {
-    const tspan = new TPTimeSpan(traceTime.start, traceTime.end);
+    const tspan = new TimeSpan(traceTime.start, traceTime.end);
     if (this.data === undefined) return true;
     if (this.shouldReload()) return true;
 
@@ -154,7 +154,7 @@
   // data. Returns the bucket size (in ns) if caching should happen and
   // undefined otherwise.
   // Subclasses should call this in their setup function
-  calcCachedBucketSize(numRows: number): TPDuration|undefined {
+  calcCachedBucketSize(numRows: number): duration|undefined {
     // Ensure that we're not caching when the table size isn't even that big.
     if (numRows < TrackController.MIN_TABLE_SIZE_TO_CACHE) {
       return undefined;
@@ -208,7 +208,7 @@
     // resolution levels we want to go down, bail out because this cached
     // table is really not going to be used enough.
     if (outermostResolutionLevel < resolutionLevelsCovered) {
-      return BigintMath.INT64_MAX;
+      return Duration.MAX;
     }
 
     // Another way to look at moving down resolution levels is to consider how
@@ -247,13 +247,14 @@
               this.isSetup = true;
               let resolution = visibleState.resolution;
 
+              // If resolution is not a power of 2, reset to the default value
               if (BigintMath.popcount(resolution) !== 1) {
-                resolution = BigintMath.bitFloor(tpTimeFromSeconds(1000));
+                resolution = RESOLUTION_DEFAULT;
               }
 
               return this.onBoundsChange(
-                  visibleTimeSpan.start - dur,
-                  visibleTimeSpan.end + dur,
+                  Time.sub(visibleTimeSpan.start, dur),
+                  Time.add(visibleTimeSpan.end, dur),
                   resolution);
             })
             .then((data) => {