Display real timestamp where possible

Fixes: 258782161

Test: Load traces and make sure timestamps displayed everywhere are real timestamps when available
Change-Id: I283232ab9308f452ff8fea2a55fc56af23a9b3ba
diff --git a/tools/winscope-ng/package-lock.json b/tools/winscope-ng/package-lock.json
index 1fb8606..d0f3f43 100644
--- a/tools/winscope-ng/package-lock.json
+++ b/tools/winscope-ng/package-lock.json
@@ -27,6 +27,7 @@
         "@types/three": "^0.143.0",
         "angular2-template-loader": "^0.6.2",
         "auth0": "^2.42.0",
+        "dateformat": "^5.0.3",
         "gl-matrix": "^3.4.3",
         "html-loader": "^3.1.0",
         "html-webpack-inline-source-plugin": "^1.0.0-beta.2",
@@ -54,6 +55,7 @@
         "@angular/cli": "~14.0.0",
         "@angular/compiler-cli": "^14.0.0",
         "@ngxs/devtools-plugin": "^3.7.4",
+        "@types/dateformat": "^5.0.0",
         "@types/jasmine": "~4.0.0",
         "@types/jquery": "^3.5.14",
         "@types/node": "^18.0.4",
@@ -3393,6 +3395,12 @@
       "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==",
       "dev": true
     },
+    "node_modules/@types/dateformat": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/@types/dateformat/-/dateformat-5.0.0.tgz",
+      "integrity": "sha512-SZg4JdHIWHQGEokbYGZSDvo5wA4TLYPXaqhigs/wH+REDOejcJzgH+qyY+HtEUtWOZxEUkbhbdYPqQDiEgrXeA==",
+      "dev": true
+    },
     "node_modules/@types/eslint": {
       "version": "8.4.5",
       "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.5.tgz",
@@ -5849,6 +5857,14 @@
         "node": ">=4.0"
       }
     },
+    "node_modules/dateformat": {
+      "version": "5.0.3",
+      "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-5.0.3.tgz",
+      "integrity": "sha512-Kvr6HmPXUMerlLcLF+Pwq3K7apHpYmGDVqrxcDasBg86UcKeTSNWbEzU8bwdXnxnR44FtMhJAxI4Bov6Y/KUfA==",
+      "engines": {
+        "node": ">=12.20"
+      }
+    },
     "node_modules/debug": {
       "version": "4.3.4",
       "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@@ -18135,6 +18151,12 @@
       "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==",
       "dev": true
     },
+    "@types/dateformat": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/@types/dateformat/-/dateformat-5.0.0.tgz",
+      "integrity": "sha512-SZg4JdHIWHQGEokbYGZSDvo5wA4TLYPXaqhigs/wH+REDOejcJzgH+qyY+HtEUtWOZxEUkbhbdYPqQDiEgrXeA==",
+      "dev": true
+    },
     "@types/eslint": {
       "version": "8.4.5",
       "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.5.tgz",
@@ -20024,6 +20046,11 @@
       "integrity": "sha512-VS20KRyorrbMCQmpdl2hg5KaOUsda1RbnsJg461FfrcyCUg+pkd0b40BSW4niQyTheww4DBXQnS7HwSrKkipLw==",
       "dev": true
     },
+    "dateformat": {
+      "version": "5.0.3",
+      "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-5.0.3.tgz",
+      "integrity": "sha512-Kvr6HmPXUMerlLcLF+Pwq3K7apHpYmGDVqrxcDasBg86UcKeTSNWbEzU8bwdXnxnR44FtMhJAxI4Bov6Y/KUfA=="
+    },
     "debug": {
       "version": "4.3.4",
       "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
diff --git a/tools/winscope-ng/package.json b/tools/winscope-ng/package.json
index 570ab0e..c38fb46 100644
--- a/tools/winscope-ng/package.json
+++ b/tools/winscope-ng/package.json
@@ -36,6 +36,7 @@
     "@types/three": "^0.143.0",
     "angular2-template-loader": "^0.6.2",
     "auth0": "^2.42.0",
+    "dateformat": "^5.0.3",
     "gl-matrix": "^3.4.3",
     "html-loader": "^3.1.0",
     "html-webpack-inline-source-plugin": "^1.0.0-beta.2",
@@ -63,6 +64,7 @@
     "@angular/cli": "~14.0.0",
     "@angular/compiler-cli": "^14.0.0",
     "@ngxs/devtools-plugin": "^3.7.4",
+    "@types/dateformat": "^5.0.0",
     "@types/jasmine": "~4.0.0",
     "@types/jquery": "^3.5.14",
     "@types/node": "^18.0.4",
diff --git a/tools/winscope-ng/src/app/app.module.ts b/tools/winscope-ng/src/app/app.module.ts
index 8b0a1c9..fbd8bff 100644
--- a/tools/winscope-ng/src/app/app.module.ts
+++ b/tools/winscope-ng/src/app/app.module.ts
@@ -73,6 +73,7 @@
 import { ExpandedTimelineComponent } from "./components/timeline/expanded_timeline.component";
 import { SingleTimelineComponent } from "./components/timeline/single_timeline.component";
 import { MatDrawerContent, MatDrawer, MatDrawerContainer } from "./components/bottomnav/bottom_drawer.component";
+import { TimeUtils } from "common/utils/time_utils";
 
 @NgModule({
   declarations: [
diff --git a/tools/winscope-ng/src/app/components/timeline/timeline.component.ts b/tools/winscope-ng/src/app/components/timeline/timeline.component.ts
index 5f1bb5d..fa0372a 100644
--- a/tools/winscope-ng/src/app/components/timeline/timeline.component.ts
+++ b/tools/winscope-ng/src/app/components/timeline/timeline.component.ts
@@ -32,7 +32,7 @@
 import { TRACE_INFO } from "app/trace_info";
 import { TimelineCoordinator, TimestampChangeObserver } from "app/timeline_coordinator";
 import { MiniTimelineComponent } from "./mini_timeline.component";
-import { Timestamp } from "common/trace/timestamp";
+import { Timestamp, TimestampType } from "common/trace/timestamp";
 import { TimeUtils } from "common/utils/time_utils";
 
 @Component({
@@ -69,10 +69,24 @@
                 <mat-icon>chevron_left</mat-icon>
             </button>
             <form [formGroup]="timestampForm" class="time-selector-form">
-                <mat-form-field class="time-input" appearance="fill" (change)="inputTimeChanged($event)">
-                    <input matInput name="humanTimeInput" [formControl]="selectedTimeFormControl" />
+                <mat-form-field
+                  class="time-input"
+                  appearance="fill"
+                  (change)="humanElapsedTimeInputChange($event)"
+                  *ngIf="!usingRealtime()">
+                    <input matInput name="humanElapsedTimeInput" [formControl]="selectedElapsedTimeFormControl" />
                 </mat-form-field>
-                <mat-form-field class="time-input" appearance="fill" (change)="inputTimeChanged($event)">
+                <mat-form-field
+                  class="time-input"
+                  appearance="fill"
+                  (change)="humanRealTimeInputChanged($event)"
+                  *ngIf="usingRealtime()">
+                    <input matInput name="humanRealTimeInput" [formControl]="selectedRealTimeFormControl" />
+                </mat-form-field>
+                <mat-form-field
+                  class="time-input"
+                  appearance="fill"
+                  (change)="nanosecondsInputTimeChange($event)">
                     <input matInput name="nsTimeInput" [formControl]="selectedNsFormControl" />
                 </mat-form-field>
             </form>
@@ -295,14 +309,18 @@
   selectedTraces: TraceType[] = [];
   selectedTracesFormControl = new FormControl();
 
-  selectedTimeFormControl = new FormControl("", Validators.compose([
+  selectedElapsedTimeFormControl = new FormControl("undefined", Validators.compose([
     Validators.required,
-    Validators.pattern(TimeUtils.HUMAN_TIMESTAMP_REGEX)]));
-  selectedNsFormControl = new FormControl(BigInt(0), Validators.compose([
+    Validators.pattern(TimeUtils.HUMAN_ELAPSED_TIMESTAMP_REGEX)]));
+  selectedRealTimeFormControl = new FormControl("undefined", Validators.compose([
+    Validators.required,
+    Validators.pattern(TimeUtils.HUMAN_REAL_TIMESTAMP_REGEX)]));
+  selectedNsFormControl = new FormControl("undefined", Validators.compose([
     Validators.required,
     Validators.pattern(TimeUtils.NS_TIMESTAMP_REGEX)]));
   timestampForm = new FormGroup({
-    selectedTime: this.selectedTimeFormControl,
+    selectedElapsedTime: this.selectedElapsedTimeFormControl,
+    selectedRealTime: this.selectedRealTimeFormControl,
     selectedNs: this.selectedNsFormControl,
   });
 
@@ -375,14 +393,19 @@
     this.timelineCoordinator.updateCurrentTimestamp(timestamp);
   }
 
+  usingRealtime(): boolean {
+    return this.timelineCoordinator.getTimestampType() === TimestampType.REAL;
+  }
+
   updateSeekTimestamp(timestamp: Timestamp|undefined) {
     this.seekTimestamp = timestamp;
     this.updateTimeInputValuesToCurrentTimestamp();
   }
 
   private updateTimeInputValuesToCurrentTimestamp() {
-    this.selectedTimeFormControl.setValue(TimeUtils.nanosecondsToHuman(this.currentTimestamp.getValueNs(), false));
-    this.selectedNsFormControl.setValue(this.currentTimestamp.getValueNs());
+    this.selectedElapsedTimeFormControl.setValue(TimeUtils.nanosecondsToHumanElapsed(this.currentTimestamp.getValueNs(), false));
+    this.selectedRealTimeFormControl.setValue(TimeUtils.nanosecondsToHumanReal(this.currentTimestamp.getValueNs()));
+    this.selectedNsFormControl.setValue(`${this.currentTimestamp.getValueNs()} ns`);
   }
 
   isOptionDisabled(trace: TraceType) {
@@ -451,26 +474,37 @@
     this.timelineCoordinator.moveToNextEntryFor(this.wrappedActiveTrace);
   }
 
-  inputTimeChanged(event: Event) {
-    console.error("Input time changed to", event);
+  humanElapsedTimeInputChange(event: Event) {
     if (event.type !== "change") {
       return;
     }
-
     const target = event.target as HTMLInputElement;
+    const timestamp = new Timestamp(this.timelineCoordinator.getTimestampType()!,
+      TimeUtils.humanElapsedToNanoseconds(target.value));
+    this.timelineCoordinator.updateCurrentTimestamp(timestamp);
+    this.updateTimeInputValuesToCurrentTimestamp();
+  }
 
-    if (TimeUtils.NS_TIMESTAMP_REGEX.test(target.value)) {
-      const timestamp = new Timestamp(this.timelineCoordinator.getTimestampType()!, BigInt(target.value));
-      this.timelineCoordinator.updateCurrentTimestamp(timestamp);
-    } else if (TimeUtils.HUMAN_TIMESTAMP_REGEX.test(target.value)) {
-      const timestamp = new Timestamp(this.timelineCoordinator.getTimestampType()!,
-        TimeUtils.humanToNanoseconds(target.value));
-      this.timelineCoordinator.updateCurrentTimestamp(timestamp);
-    } else {
-      console.warn(`Invalid timestamp input provided "${target.value}" and can't be processed.`);
+  humanRealTimeInputChanged(event: Event) {
+    if (event.type !== "change") {
       return;
     }
+    const target = event.target as HTMLInputElement;
 
+    const timestamp = new Timestamp(this.timelineCoordinator.getTimestampType()!,
+      TimeUtils.humanRealToNanoseconds(target.value));
+    this.timelineCoordinator.updateCurrentTimestamp(timestamp);
+    this.updateTimeInputValuesToCurrentTimestamp();
+  }
+
+  nanosecondsInputTimeChange(event: Event) {
+    if (event.type !== "change") {
+      return;
+    }
+    const target = event.target as HTMLInputElement;
+
+    const timestamp = new Timestamp(this.timelineCoordinator.getTimestampType()!, BigInt(target.value));
+    this.timelineCoordinator.updateCurrentTimestamp(timestamp);
     this.updateTimeInputValuesToCurrentTimestamp();
   }
 }
diff --git a/tools/winscope-ng/src/app/timeline_coordinator.ts b/tools/winscope-ng/src/app/timeline_coordinator.ts
index 3834e37..bced7d1 100644
--- a/tools/winscope-ng/src/app/timeline_coordinator.ts
+++ b/tools/winscope-ng/src/app/timeline_coordinator.ts
@@ -81,7 +81,7 @@
 
   public getActiveTimestampForTraceAt(type: TraceType, timestamp: Timestamp): TimestampWithIndex|undefined {
     if (timestamp.getType() !== this.timestampType) {
-      throw Error("Invalid timestampt type");
+      throw Error("Invalid timestamp type");
     }
 
     const timeline = this.timelines.get(type);
diff --git a/tools/winscope-ng/src/common/trace/flickerlib/common.js b/tools/winscope-ng/src/common/trace/flickerlib/common.js
index d208120..af6376f 100644
--- a/tools/winscope-ng/src/common/trace/flickerlib/common.js
+++ b/tools/winscope-ng/src/common/trace/flickerlib/common.js
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+import { TimeUtils } from 'common/utils/time_utils';
+
 // Imports all the compiled common Flicker library classes and exports them
 // as clean es6 modules rather than having them be commonjs modules
 
@@ -22,6 +24,8 @@
     common.windowmanager.WindowManagerTrace;
 const WindowManagerState = require('flicker').com.android.server.wm.traces.
     common.windowmanager.WindowManagerState;
+const WindowManagerTraceEntryBuilder = require('flicker').com.android.server.wm.
+    traces.common.windowmanager.WindowManagerTraceEntryBuilder;
 const Activity = require('flicker').com.android.server.wm.traces.common.
     windowmanager.windows.Activity;
 const Configuration = require('flicker').com.android.server.wm.traces.common.
@@ -277,6 +281,7 @@
   WindowManagerPolicy,
   WindowManagerTrace,
   WindowManagerState,
+  WindowManagerTraceEntryBuilder,
   // SF
   BaseLayerTraceEntry,
   Layer,
diff --git a/tools/winscope-ng/src/common/trace/flickerlib/layers/LayerTraceEntry.ts b/tools/winscope-ng/src/common/trace/flickerlib/layers/LayerTraceEntry.ts
index 9c56bba..592b7a3 100644
--- a/tools/winscope-ng/src/common/trace/flickerlib/layers/LayerTraceEntry.ts
+++ b/tools/winscope-ng/src/common/trace/flickerlib/layers/LayerTraceEntry.ts
@@ -17,20 +17,38 @@
 import { Display, LayerTraceEntry, LayerTraceEntryBuilder, toRect, toSize, toTransform } from "../common";
 import {Layer} from "./Layer";
 import {getPropertiesForDisplay} from "../mixin";
+import { TimeUtils } from "common/utils/time_utils";
 
-LayerTraceEntry.fromProto = function (protos: any[], displayProtos: any[],
-        timestamp: number, hwcBlob: string, where: string = ''): LayerTraceEntry {
+LayerTraceEntry.fromProto = function (
+    protos: any[],
+    displayProtos: any[],
+    elapsedTimestamp: number,
+    vSyncId: number,
+    hwcBlob: string,
+    where: string = '',
+    realToElapsedTimeOffsetNs: bigint|undefined = undefined,
+    useElapsedTime = false
+): LayerTraceEntry {
     const layers = protos.map(it => Layer.fromProto(it));
     const displays = (displayProtos || []).map(it => newDisplay(it));
-    const builder = new LayerTraceEntryBuilder(timestamp, layers, displays, hwcBlob, where);
+    const builder = new LayerTraceEntryBuilder(
+        `${elapsedTimestamp}`,
+        layers,
+        displays,
+        vSyncId,
+        hwcBlob,
+        where,
+        `${realToElapsedTimeOffsetNs ?? 0}`
+    );
     const entry: LayerTraceEntry = builder.build();
 
-    addAttributes(entry, protos);
+    addAttributes(entry, protos,
+        realToElapsedTimeOffsetNs === undefined || useElapsedTime);
     return entry;
 }
 
-function addAttributes(entry: LayerTraceEntry, protos: any) {
-    entry.kind = "entry"
+function addAttributes(entry: LayerTraceEntry, protos: any, useElapsedTime = false) {
+    entry.kind = "entry";
     // There no JVM/JS translation for Longs yet
     entry.timestampMs = entry.timestamp.toString()
     // Avoid parsing the entry root because it is an array of layers
@@ -43,7 +61,13 @@
     if (newObj.physicalDisplayBounds) delete newObj.physicalDisplayBounds;
     if (newObj.isVisible) delete newObj.isVisible;
     entry.proto = newObj;
-    entry.shortName = entry.name;
+    if (useElapsedTime || entry.clockTimestamp == undefined) {
+        entry.name = TimeUtils.nanosecondsToHumanElapsed(BigInt(entry.elapsedTimestamp));
+        entry.shortName = entry.name;
+    } else {
+        entry.name = TimeUtils.nanosecondsToHumanReal(BigInt(entry.clockTimestamp));
+        entry.shortName = entry.name;
+    }
     entry.isVisible = true;
 }
 
diff --git a/tools/winscope-ng/src/common/trace/flickerlib/windows/WindowManagerState.ts b/tools/winscope-ng/src/common/trace/flickerlib/windows/WindowManagerState.ts
index 02f5348..8b4044c 100644
--- a/tools/winscope-ng/src/common/trace/flickerlib/windows/WindowManagerState.ts
+++ b/tools/winscope-ng/src/common/trace/flickerlib/windows/WindowManagerState.ts
@@ -14,16 +14,24 @@
  * limitations under the License.
  */
 
+import { TimeUtils } from "common/utils/time_utils";
 import {
     KeyguardControllerState,
     RootWindowContainer,
     WindowManagerPolicy,
-    WindowManagerState
+    WindowManagerState,
+    WindowManagerTraceEntryBuilder
 } from "../common"
 
 import WindowContainer from "./WindowContainer"
 
-WindowManagerState.fromProto = function (proto: any, timestamp: number = 0, where: string = ""): WindowManagerState {
+WindowManagerState.fromProto = function (
+    proto: any,
+    elapsedTimestamp: number = 0,
+    where: string = "",
+    realToElapsedTimeOffsetNs: bigint|undefined = undefined,
+    useElapsedTime = false,
+): WindowManagerState {
     var inputMethodWIndowAppToken = "";
     if (proto.inputMethodWindow != null) {
         proto.inputMethodWindow.hashCode.toString(16)
@@ -36,8 +44,8 @@
         proto.rootWindowContainer.keyguardController);
     const policy = createWindowManagerPolicy(proto.policy);
 
-    const entry = new WindowManagerState(
-        where,
+    const entry = new WindowManagerTraceEntryBuilder(
+        `${elapsedTimestamp}`,
         policy,
         proto.focusedApp,
         proto.focusedDisplayId,
@@ -48,14 +56,15 @@
         proto.rootWindowContainer.pendingActivities.map((it: any) => it.title),
         rootWindowContainer,
         keyguardControllerState,
-        /*timestamp */ `${timestamp}`
-    );
+        where,
+        `${realToElapsedTimeOffsetNs ?? 0}`,
+    ).build();
 
-    addAttributes(entry, proto);
+    addAttributes(entry, proto, realToElapsedTimeOffsetNs === undefined || useElapsedTime);
     return entry
 }
 
-function addAttributes(entry: WindowManagerState, proto: any) {
+function addAttributes(entry: WindowManagerState, proto: any, useElapsedTime = false) {
     entry.kind = entry.constructor.name;
     // There no JVM/JS translation for Longs yet
     entry.timestampMs = entry.timestamp.toString();
@@ -63,7 +72,13 @@
         entry.isIncompleteReason = entry.getIsIncompleteReason();
     }
     entry.proto = proto;
-    entry.shortName = entry.name;
+    if (useElapsedTime || entry.clockTimestamp == undefined) {
+        entry.name = TimeUtils.nanosecondsToHumanElapsed(BigInt(entry.elapsedTimestamp));
+        entry.shortName = entry.name;
+    } else {
+        entry.name = TimeUtils.nanosecondsToHumanReal(BigInt(entry.clockTimestamp));
+        entry.shortName = entry.name;
+    }
     entry.isVisible = true;
 }
 
diff --git a/tools/winscope-ng/src/common/trace/protolog.ts b/tools/winscope-ng/src/common/trace/protolog.ts
index a93528f..1b58d7b 100644
--- a/tools/winscope-ng/src/common/trace/protolog.ts
+++ b/tools/winscope-ng/src/common/trace/protolog.ts
@@ -15,6 +15,7 @@
  */
 import {TimeUtils} from "common/utils/time_utils";
 import configJson from "../../../../../../frameworks/base/data/etc/services.core.protolog.json";
+import { TimestampType } from "./timestamp";
 
 class ProtoLogTraceEntry {
   constructor(public messages: LogMessage[], public currentMessageIndex: number) {
@@ -27,9 +28,9 @@
   tag: string;
   level: string;
   at: string;
-  timestamp: number;
+  timestamp: bigint;
 
-  constructor(text: string, time: string, tag: string, level: string, at: string, timestamp: number) {
+  constructor(text: string, time: string, tag: string, level: string, at: string, timestamp: bigint) {
     this.text = text;
     this.time = time;
     this.tag = tag;
@@ -40,7 +41,7 @@
 }
 
 class FormattedLogMessage extends LogMessage {
-  constructor(proto: any) {
+  constructor(proto: any, timestampType: TimestampType, realToElapsedTimeOffsetNs: bigint|undefined) {
     const text = (
       proto.messageHash.toString() +
       " - [" + proto.strParams.toString() +
@@ -48,25 +49,46 @@
       "] [" + proto.doubleParams.toString() +
       "] [" + proto.booleanParams.toString() + "]"
     );
+
+    let time: string;
+    let timestamp: bigint;
+    if (timestampType === TimestampType.REAL && realToElapsedTimeOffsetNs !== undefined && realToElapsedTimeOffsetNs != 0n) {
+      timestamp = realToElapsedTimeOffsetNs + BigInt(proto.elapsedRealtimeNanos);
+      time = TimeUtils.nanosecondsToHumanReal(realToElapsedTimeOffsetNs + BigInt(proto.elapsedRealtimeNanos));
+    } else {
+      timestamp = BigInt(proto.elapsedRealtimeNanos);
+      time = TimeUtils.nanosecondsToHumanElapsed(timestamp);
+    }
+
     super(
       text,
-      TimeUtils.nanosecondsToHuman(proto.elapsedRealtimeNanos),
+      time,
       "INVALID",
       "invalid",
       "",
-      Number(proto.elapsedRealtimeNanos));
+      timestamp);
   }
 }
 
 class UnformattedLogMessage extends LogMessage {
-  constructor(proto: any, message: any) {
+  constructor(proto: any, timestampType: TimestampType, realToElapsedTimeOffsetNs: bigint|undefined, message: any) {
+    let time: string;
+    let timestamp: bigint;
+    if (timestampType === TimestampType.REAL && realToElapsedTimeOffsetNs !== undefined && realToElapsedTimeOffsetNs != 0n) {
+      timestamp = realToElapsedTimeOffsetNs + BigInt(proto.elapsedRealtimeNanos);
+      time = TimeUtils.nanosecondsToHumanReal(realToElapsedTimeOffsetNs + BigInt(proto.elapsedRealtimeNanos));
+    } else {
+      timestamp = BigInt(proto.elapsedRealtimeNanos);
+      time = TimeUtils.nanosecondsToHumanElapsed(timestamp);
+    }
+
     super(
       formatText(message.message, proto),
-      TimeUtils.nanosecondsToHuman(proto.elapsedRealtimeNanos),
+      time,
       (<any>configJson).groups[message.group].tag,
       message.level,
       message.at,
-      Number(proto.elapsedRealtimeNanos)
+      timestamp
     );
   }
 }
diff --git a/tools/winscope-ng/src/common/trace/trace_tree_node.ts b/tools/winscope-ng/src/common/trace/trace_tree_node.ts
index 0836c85..cd8cefd 100644
--- a/tools/winscope-ng/src/common/trace/trace_tree_node.ts
+++ b/tools/winscope-ng/src/common/trace/trace_tree_node.ts
@@ -26,7 +26,8 @@
   inputMethodService?: any;
   inputMethodManagerService?: any;
   where?: string;
-  elapsedRealtimeNanos?: number;
+  elapsedRealtimeNanos?: number|bigint;
+  clockTimeNanos?: number|bigint;
   shortName?: string;
   type?: string;
   id?: string | number;
diff --git a/tools/winscope-ng/src/common/trace/transactions.ts b/tools/winscope-ng/src/common/trace/transactions.ts
index c984077..d259c55 100644
--- a/tools/winscope-ng/src/common/trace/transactions.ts
+++ b/tools/winscope-ng/src/common/trace/transactions.ts
@@ -14,8 +14,15 @@
  * limitations under the License.
  */
 
+import { TimestampType } from "./timestamp";
+
 class TransactionsTraceEntry {
-  constructor(public entriesProto: any[], public currentEntryIndex: number) {
+  constructor(
+    public entriesProto: any[],
+    public timestampType: TimestampType,
+    public realToElapsedTimeOffsetNs: bigint|undefined,
+    public currentEntryIndex: number,
+  ) {
   }
 }
 
diff --git a/tools/winscope-ng/src/common/utils/time_utils.spec.ts b/tools/winscope-ng/src/common/utils/time_utils.spec.ts
index face377..cb179a6 100644
--- a/tools/winscope-ng/src/common/utils/time_utils.spec.ts
+++ b/tools/winscope-ng/src/common/utils/time_utils.spec.ts
@@ -13,87 +13,143 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+import { TimestampType } from "common/trace/timestamp";
 import {TimeUtils} from "./time_utils";
 
 describe("TimeUtils", () => {
+  const MILLISECOND = BigInt(1000000);
+  const SECOND = BigInt(1000) * MILLISECOND;
+  const MINUTE = BigInt(60) * SECOND;
+  const HOUR = BigInt(60) * MINUTE;
+  const DAY = BigInt(24) * HOUR;
+
   it("nanosecondsToHuman", () => {
-    const MILLISECOND = 1000000;
-    const SECOND = 1000 * MILLISECOND;
-    const MINUTE = 60 * SECOND;
-    const HOUR = 60 * MINUTE;
-    const DAY = 24 * HOUR;
+    expect(TimeUtils.nanosecondsToHumanElapsed(0)) .toEqual("0ms");
+    expect(TimeUtils.nanosecondsToHumanElapsed(0, false)) .toEqual("0ns");
+    expect(TimeUtils.nanosecondsToHumanElapsed(1000)) .toEqual("0ms");
+    expect(TimeUtils.nanosecondsToHumanElapsed(1000, false)) .toEqual("1000ns");
+    expect(TimeUtils.nanosecondsToHumanElapsed(MILLISECOND-1n)).toEqual("0ms");
+    expect(TimeUtils.nanosecondsToHumanElapsed(MILLISECOND)).toEqual("1ms");
+    expect(TimeUtils.nanosecondsToHumanElapsed(10n * MILLISECOND)).toEqual("10ms");
 
-    expect(TimeUtils.nanosecondsToHuman(0)) .toEqual("0ms");
-    expect(TimeUtils.nanosecondsToHuman(0, false)) .toEqual("0ns");
-    expect(TimeUtils.nanosecondsToHuman(1000)) .toEqual("0ms");
-    expect(TimeUtils.nanosecondsToHuman(1000, false)) .toEqual("1000ns");
-    expect(TimeUtils.nanosecondsToHuman(MILLISECOND-1)).toEqual("0ms");
-    expect(TimeUtils.nanosecondsToHuman(MILLISECOND)).toEqual("1ms");
-    expect(TimeUtils.nanosecondsToHuman(10 * MILLISECOND)).toEqual("10ms");
+    expect(TimeUtils.nanosecondsToHumanElapsed(SECOND-1n)).toEqual("999ms");
+    expect(TimeUtils.nanosecondsToHumanElapsed(SECOND)).toEqual("1s0ms");
+    expect(TimeUtils.nanosecondsToHumanElapsed(SECOND + MILLISECOND)).toEqual("1s1ms");
+    expect(TimeUtils.nanosecondsToHumanElapsed(SECOND + MILLISECOND, false)).toEqual("1s1ms0ns");
 
-    expect(TimeUtils.nanosecondsToHuman(SECOND-1)).toEqual("999ms");
-    expect(TimeUtils.nanosecondsToHuman(SECOND)).toEqual("1s0ms");
-    expect(TimeUtils.nanosecondsToHuman(SECOND + MILLISECOND)).toEqual("1s1ms");
-    expect(TimeUtils.nanosecondsToHuman(SECOND + MILLISECOND, false)).toEqual("1s1ms0ns");
+    expect(TimeUtils.nanosecondsToHumanElapsed(MINUTE-1n)).toEqual("59s999ms");
+    expect(TimeUtils.nanosecondsToHumanElapsed(MINUTE)).toEqual("1m0s0ms");
+    expect(TimeUtils.nanosecondsToHumanElapsed(MINUTE + SECOND + MILLISECOND)).toEqual("1m1s1ms");
+    expect(TimeUtils.nanosecondsToHumanElapsed(MINUTE + SECOND + MILLISECOND + 1n)).toEqual("1m1s1ms");
+    expect(TimeUtils.nanosecondsToHumanElapsed(MINUTE + SECOND + MILLISECOND + 1n, false)).toEqual("1m1s1ms1ns");
 
-    expect(TimeUtils.nanosecondsToHuman(MINUTE-1)).toEqual("59s999ms");
-    expect(TimeUtils.nanosecondsToHuman(MINUTE)).toEqual("1m0s0ms");
-    expect(TimeUtils.nanosecondsToHuman(MINUTE + SECOND + MILLISECOND)).toEqual("1m1s1ms");
-    expect(TimeUtils.nanosecondsToHuman(MINUTE + SECOND + MILLISECOND + 1)).toEqual("1m1s1ms");
-    expect(TimeUtils.nanosecondsToHuman(MINUTE + SECOND + MILLISECOND + 1, false)).toEqual("1m1s1ms1ns");
+    expect(TimeUtils.nanosecondsToHumanElapsed(HOUR-1n)).toEqual("59m59s999ms");
+    expect(TimeUtils.nanosecondsToHumanElapsed(HOUR-1n, false)).toEqual("59m59s999ms999999ns");
+    expect(TimeUtils.nanosecondsToHumanElapsed(HOUR)).toEqual("1h0m0s0ms");
+    expect(TimeUtils.nanosecondsToHumanElapsed(HOUR + MINUTE + SECOND + MILLISECOND)).toEqual("1h1m1s1ms");
 
-    expect(TimeUtils.nanosecondsToHuman(HOUR-1)).toEqual("59m59s999ms");
-    expect(TimeUtils.nanosecondsToHuman(HOUR-1, false)).toEqual("59m59s999ms999999ns");
-    expect(TimeUtils.nanosecondsToHuman(HOUR)).toEqual("1h0m0s0ms");
-    expect(TimeUtils.nanosecondsToHuman(HOUR + MINUTE + SECOND + MILLISECOND)).toEqual("1h1m1s1ms");
-
-    expect(TimeUtils.nanosecondsToHuman(DAY-1)).toEqual("23h59m59s999ms");
-    expect(TimeUtils.nanosecondsToHuman(DAY)).toEqual("1d0h0m0s0ms");
-    expect(TimeUtils.nanosecondsToHuman(DAY + HOUR + MINUTE + SECOND + MILLISECOND)).toEqual("1d1h1m1s1ms");
+    expect(TimeUtils.nanosecondsToHumanElapsed(DAY-1n)).toEqual("23h59m59s999ms");
+    expect(TimeUtils.nanosecondsToHumanElapsed(DAY)).toEqual("1d0h0m0s0ms");
+    expect(TimeUtils.nanosecondsToHumanElapsed(DAY + HOUR + MINUTE + SECOND + MILLISECOND)).toEqual("1d1h1m1s1ms");
   });
 
-  it("humanToNanoseconds", () => {
-    const MILLISECOND = BigInt(1000000);
-    const SECOND = BigInt(1000) * MILLISECOND;
-    const MINUTE = BigInt(60) * SECOND;
-    const HOUR = BigInt(60) * MINUTE;
-    const DAY = BigInt(24) * HOUR;
+  it("humanElapsedToNanoseconds", () => {
+    expect(TimeUtils.humanElapsedToNanoseconds("0ns")) .toEqual(BigInt(0));
+    expect(TimeUtils.humanElapsedToNanoseconds("1000ns")) .toEqual(BigInt(1000));
+    expect(TimeUtils.humanElapsedToNanoseconds("0ms")).toEqual(BigInt(0));
+    expect(TimeUtils.humanElapsedToNanoseconds("1ms")).toEqual(MILLISECOND);
+    expect(TimeUtils.humanElapsedToNanoseconds("10ms")).toEqual(BigInt(10) * MILLISECOND);
 
-    expect(TimeUtils.humanToNanoseconds("0ns")) .toEqual(BigInt(0));
-    expect(TimeUtils.humanToNanoseconds("1000ns")) .toEqual(BigInt(1000));
-    expect(TimeUtils.humanToNanoseconds("0ms")).toEqual(BigInt(0));
-    expect(TimeUtils.humanToNanoseconds("1ms")).toEqual(MILLISECOND);
-    expect(TimeUtils.humanToNanoseconds("10ms")).toEqual(BigInt(10) * MILLISECOND);
+    expect(TimeUtils.humanElapsedToNanoseconds("999ms")).toEqual(BigInt(999) * MILLISECOND);
+    expect(TimeUtils.humanElapsedToNanoseconds("1s")).toEqual(SECOND);
+    expect(TimeUtils.humanElapsedToNanoseconds("1s0ms")).toEqual(SECOND);
+    expect(TimeUtils.humanElapsedToNanoseconds("1s0ms0ns")).toEqual(SECOND);
+    expect(TimeUtils.humanElapsedToNanoseconds("1s0ms1ns")).toEqual(SECOND + BigInt(1));
+    expect(TimeUtils.humanElapsedToNanoseconds("0d1s1ms")).toEqual(SECOND + MILLISECOND);
 
-    expect(TimeUtils.humanToNanoseconds("999ms")).toEqual(BigInt(999) * MILLISECOND);
-    expect(TimeUtils.humanToNanoseconds("1s")).toEqual(SECOND);
-    expect(TimeUtils.humanToNanoseconds("1s0ms")).toEqual(SECOND);
-    expect(TimeUtils.humanToNanoseconds("1s0ms0ns")).toEqual(SECOND);
-    expect(TimeUtils.humanToNanoseconds("1s0ms1ns")).toEqual(SECOND + BigInt(1));
-    expect(TimeUtils.humanToNanoseconds("0d1s1ms")).toEqual(SECOND + MILLISECOND);
+    expect(TimeUtils.humanElapsedToNanoseconds("1m0s0ms")).toEqual(MINUTE);
+    expect(TimeUtils.humanElapsedToNanoseconds("1m1s1ms")).toEqual(MINUTE + SECOND + MILLISECOND);
 
-    expect(TimeUtils.humanToNanoseconds("1m0s0ms")).toEqual(MINUTE);
-    expect(TimeUtils.humanToNanoseconds("1m1s1ms")).toEqual(MINUTE + SECOND + MILLISECOND);
+    expect(TimeUtils.humanElapsedToNanoseconds("1h0m")).toEqual(HOUR);
+    expect(TimeUtils.humanElapsedToNanoseconds("1h1m1s1ms")).toEqual(HOUR + MINUTE + SECOND + MILLISECOND);
 
-    expect(TimeUtils.humanToNanoseconds("1h0m")).toEqual(HOUR);
-    expect(TimeUtils.humanToNanoseconds("1h1m1s1ms")).toEqual(HOUR + MINUTE + SECOND + MILLISECOND);
+    expect(TimeUtils.humanElapsedToNanoseconds("1d0s1ms")).toEqual(DAY + MILLISECOND);
+    expect(TimeUtils.humanElapsedToNanoseconds("1d1h1m1s1ms")).toEqual(DAY + HOUR + MINUTE + SECOND + MILLISECOND);
 
-    expect(TimeUtils.humanToNanoseconds("1d0s1ms")).toEqual(DAY + MILLISECOND);
-    expect(TimeUtils.humanToNanoseconds("1d1h1m1s1ms")).toEqual(DAY + HOUR + MINUTE + SECOND + MILLISECOND);
-
-    expect(TimeUtils.humanToNanoseconds("1d")).toEqual(DAY);
-    expect(TimeUtils.humanToNanoseconds("1d1ms")).toEqual(DAY + MILLISECOND);
+    expect(TimeUtils.humanElapsedToNanoseconds("1d")).toEqual(DAY);
+    expect(TimeUtils.humanElapsedToNanoseconds("1d1ms")).toEqual(DAY + MILLISECOND);
   });
 
   it("humanToNanoseconds throws on invalid input format", () => {
-    const invalidFormatError = new Error("Invalid timestamp format");
-    expect(() => TimeUtils.humanToNanoseconds("1d1h1m1s0ns1ms") )
+    const invalidFormatError = new Error("Invalid elapsed timestamp format");
+    expect(() => TimeUtils.humanElapsedToNanoseconds("1d1h1m1s0ns1ms") )
       .toThrow(invalidFormatError);
-    expect(() => TimeUtils.humanToNanoseconds("1dns") )
+    expect(() => TimeUtils.humanElapsedToNanoseconds("1dns") )
       .toThrow(invalidFormatError);
-    expect(() => TimeUtils.humanToNanoseconds("100") )
+    expect(() => TimeUtils.humanElapsedToNanoseconds("100") )
       .toThrow(invalidFormatError);
-    expect(() => TimeUtils.humanToNanoseconds("") )
+    expect(() => TimeUtils.humanElapsedToNanoseconds("") )
       .toThrow(invalidFormatError);
   });
+
+  it("nanosecondsToHumanReal", () => {
+    const NOV_10_2022 = 1668038400000n * MILLISECOND;
+    expect(TimeUtils.nanosecondsToHumanReal(0))
+      .toEqual("00h00m00s000ms0ns, 1 Jan 1970 UTC");
+    expect(TimeUtils.nanosecondsToHumanReal(
+      NOV_10_2022 + 22n * HOUR + 4n * MINUTE + 54n * SECOND + 186n * MILLISECOND + 123212n))
+      .toEqual("22h04m54s186ms123212ns, 10 Nov 2022 UTC");
+    expect(TimeUtils.nanosecondsToHumanReal(NOV_10_2022))
+      .toEqual("00h00m00s000ms0ns, 10 Nov 2022 UTC");
+    expect(TimeUtils.nanosecondsToHumanReal(NOV_10_2022 + 1n))
+      .toEqual("00h00m00s000ms1ns, 10 Nov 2022 UTC");
+  });
+
+  it("humanRealToNanoseconds", () => {
+    const NOV_10_2022 = 1668038400000n * MILLISECOND;
+    expect(TimeUtils.humanRealToNanoseconds("22h04m54s186ms123212ns, 10 Nov 2022 UTC"))
+      .toEqual(NOV_10_2022 + 22n * HOUR + 4n * MINUTE + 54n * SECOND + 186n * MILLISECOND + 123212n);
+    expect(TimeUtils.humanRealToNanoseconds("22h04m54s186ms123212ns, 10 Nov 2022")).toEqual(1668117894186123212n);
+    expect(TimeUtils.humanRealToNanoseconds("22h04m54s186ms212ns, 10 Nov 2022 UTC")).toEqual(1668117894186000212n);
+    expect(TimeUtils.humanRealToNanoseconds("22h04m54s6ms2ns, 10 Nov 2022")).toEqual(1668117894006000002n);
+    expect(TimeUtils.humanRealToNanoseconds("06h4m54s6ms2ns, 10 Nov 2022")).toEqual(1668060294006000002n);
+  });
+
+  it("humanToNanoseconds throws on invalid input format", () => {
+    const invalidFormatError = new Error("Invalid real timestamp format");
+    expect(() => TimeUtils.humanRealToNanoseconds("23h59m59s999ms5ns") )
+      .toThrow(invalidFormatError);
+    expect(() => TimeUtils.humanRealToNanoseconds("1d") )
+      .toThrow(invalidFormatError);
+    expect(() => TimeUtils.humanRealToNanoseconds("100") )
+      .toThrow(invalidFormatError);
+    expect(() => TimeUtils.humanRealToNanoseconds("06h4m54s, 10 Nov 2022") )
+      .toThrow(invalidFormatError);
+    expect(() => TimeUtils.humanRealToNanoseconds("") )
+      .toThrow(invalidFormatError);
+  });
+
+  it("nano second regex accept all expected inputs", () => {
+    expect(TimeUtils.NS_TIMESTAMP_REGEX.test("123")).toBeTrue();
+    expect(TimeUtils.NS_TIMESTAMP_REGEX.test("123ns")).toBeTrue();
+    expect(TimeUtils.NS_TIMESTAMP_REGEX.test("123 ns")).toBeTrue();
+    expect(TimeUtils.NS_TIMESTAMP_REGEX.test(" 123 ns ")).toBeTrue();
+    expect(TimeUtils.NS_TIMESTAMP_REGEX.test("   123  ")).toBeTrue();
+
+    expect(TimeUtils.NS_TIMESTAMP_REGEX.test("1a23")).toBeFalse();
+    expect(TimeUtils.NS_TIMESTAMP_REGEX.test("a123 ns")).toBeFalse();
+    expect(TimeUtils.NS_TIMESTAMP_REGEX.test("")).toBeFalse();
+  });
+
+  it("format real", () => {
+    expect(TimeUtils.format(TimestampType.REAL, 100n, 500n)).toEqual("00h00m00s000ms600ns, 1 Jan 1970 UTC");
+    expect(() => {
+      TimeUtils.format(TimestampType.REAL, 100n);
+    }).toThrow(Error("clockTimeOffset required to format real timestamp"));
+  });
+
+  it("format elapsed", () => {
+    expect(TimeUtils.format(TimestampType.ELAPSED, 100n * MILLISECOND, 500n)).toEqual("100ms");
+    expect(TimeUtils.format(TimestampType.ELAPSED, 100n * MILLISECOND)).toEqual("100ms");
+  });
 });
diff --git a/tools/winscope-ng/src/common/utils/time_utils.ts b/tools/winscope-ng/src/common/utils/time_utils.ts
index f3ee6b4..ba98ce0 100644
--- a/tools/winscope-ng/src/common/utils/time_utils.ts
+++ b/tools/winscope-ng/src/common/utils/time_utils.ts
@@ -13,8 +13,30 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
+import { TimestampType } from "common/trace/timestamp";
+import dateFormat, { masks } from "dateformat";
+
 export class TimeUtils {
-  static nanosecondsToHuman(timestampNanos: number|bigint, hideNs = true): string {
+  static format(timestampType: TimestampType,
+    elapsedTime: bigint, clockTimeOffset: bigint|undefined = undefined): string {
+    switch (timestampType) {
+      case TimestampType.ELAPSED: {
+        return TimeUtils.nanosecondsToHumanElapsed(elapsedTime);
+      }
+      case TimestampType.REAL: {
+        if (clockTimeOffset === undefined) {
+          throw Error("clockTimeOffset required to format real timestamp");
+        }
+        return TimeUtils.nanosecondsToHumanReal(elapsedTime + clockTimeOffset);
+      }
+      default: {
+        throw Error("Unhandled timestamp type");
+      }
+    }
+  }
+
+  static nanosecondsToHumanElapsed(timestampNanos: number|bigint, hideNs = true): string {
     timestampNanos = BigInt(timestampNanos);
     const units = TimeUtils.units;
 
@@ -40,9 +62,19 @@
     return parts.join("");
   }
 
-  static humanToNanoseconds(timestampHuman: string): bigint {
-    if (!TimeUtils.HUMAN_TIMESTAMP_REGEX.test(timestampHuman)) {
-      throw Error("Invalid timestamp format");
+  static nanosecondsToHumanReal(timestampNanos: number|bigint): string {
+    timestampNanos = BigInt(timestampNanos);
+    const ms = timestampNanos / 1000000n;
+    const extraNanos = timestampNanos % 1000000n;
+    const formattedTime = dateFormat(new Date(Number(ms)), "HH\"h\"MM\"m\"ss\"s\"l\"ms\"");
+    const formattedDate = dateFormat(new Date(Number(ms)), "d mmm yyyy Z");
+
+    return `${formattedTime}${extraNanos}ns, ${formattedDate}`;
+  }
+
+  static humanElapsedToNanoseconds(timestampHuman: string): bigint {
+    if (!TimeUtils.HUMAN_ELAPSED_TIMESTAMP_REGEX.test(timestampHuman)) {
+      throw Error("Invalid elapsed timestamp format");
     }
 
     const units = TimeUtils.units;
@@ -62,16 +94,55 @@
     return ns;
   }
 
+  static humanRealToNanoseconds(timestampHuman: string): bigint {
+    if (!TimeUtils.HUMAN_REAL_TIMESTAMP_REGEX.test(timestampHuman)) {
+      throw Error("Invalid real timestamp format");
+    }
+
+    const time = timestampHuman.split(",")[0];
+    const date = timestampHuman.split(",")[1];
+
+    let timeRest = time;
+    const hours = parseInt(timeRest.split("h")[0]);
+    timeRest = time.split("h")[1];
+    const minutes = parseInt(timeRest.split("m")[0]);
+    timeRest = time.split("m")[1];
+    const seconds = parseInt(timeRest.split("s")[0]);
+    timeRest = time.split("s")[1];
+    const milliseconds = parseInt(timeRest.split("ms")[0]);
+    timeRest = time.split("ms")[1];
+    const nanoseconds = parseInt(timeRest);
+
+    const dateMilliseconds = new Date(date).getTime();
+
+    return BigInt(hours) * BigInt(TimeUtils.TO_NANO["h"]) +
+      BigInt(minutes) * BigInt(TimeUtils.TO_NANO["m"]) +
+      BigInt(seconds) * BigInt(TimeUtils.TO_NANO["s"]) +
+      BigInt(milliseconds) * BigInt(TimeUtils.TO_NANO["ms"]) +
+      BigInt(nanoseconds) * BigInt(TimeUtils.TO_NANO["ns"]) +
+      BigInt(dateMilliseconds) * BigInt(TimeUtils.TO_NANO["ms"]);
+  }
+
+  static TO_NANO = {
+    "ns": 1,
+    "ms": 1000000,
+    "s": 1000000 * 1000,
+    "m": 1000000 * 1000 * 60,
+    "h": 1000000 * 1000 * 60 * 60,
+    "d": 1000000 * 1000 * 60 * 60 * 24
+  };
+
   static units = [
-    {nanosInUnit: 1, unit: "ns"},
-    {nanosInUnit: 1000000, unit: "ms"},
-    {nanosInUnit: 1000000 * 1000, unit: "s"},
-    {nanosInUnit: 1000000 * 1000 * 60, unit: "m"},
-    {nanosInUnit: 1000000 * 1000 * 60 * 60, unit: "h"},
-    {nanosInUnit: 1000000 * 1000 * 60 * 60 * 24, unit: "d"},
+    {nanosInUnit: TimeUtils.TO_NANO["ns"], unit: "ns"},
+    {nanosInUnit: TimeUtils.TO_NANO["ms"], unit: "ms"},
+    {nanosInUnit: TimeUtils.TO_NANO["s"], unit: "s"},
+    {nanosInUnit: TimeUtils.TO_NANO["m"], unit: "m"},
+    {nanosInUnit: TimeUtils.TO_NANO["h"], unit: "h"},
+    {nanosInUnit: TimeUtils.TO_NANO["d"], unit: "d"},
   ];
 
   // (?=.) checks there is at least one character with a lookahead match
-  static readonly HUMAN_TIMESTAMP_REGEX = /^(?=.)([0-9]+d)?([0-9]+h)?([0-9]+m)?([0-9]+s)?([0-9]+ms)?([0-9]+ns)?$/;
-  static readonly NS_TIMESTAMP_REGEX = /^[0-9]+$/;
+  static readonly HUMAN_ELAPSED_TIMESTAMP_REGEX = /^(?=.)([0-9]+d)?([0-9]+h)?([0-9]+m)?([0-9]+s)?([0-9]+ms)?([0-9]+ns)?$/;
+  static readonly HUMAN_REAL_TIMESTAMP_REGEX = /^[0-9]([0-9])?h[0-9]([0-9])?m[0-9]([0-9])?s[0-9]([0-9])?([0-9])?ms[0-9]([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?ns, [0-9]([0-9])? [A-Za-z][A-Za-z][A-Za-z] [0-9][0-9][0-9][0-9]( [A-Za-z][A-Za-z][A-Za-z])?$/;
+  static readonly NS_TIMESTAMP_REGEX = /^\s*[0-9]+(\s?ns)?\s*$/;
 }
diff --git a/tools/winscope-ng/src/parsers/parser.ts b/tools/winscope-ng/src/parsers/parser.ts
index 596b497..c14b517 100644
--- a/tools/winscope-ng/src/parsers/parser.ts
+++ b/tools/winscope-ng/src/parsers/parser.ts
@@ -78,13 +78,13 @@
     if (index === undefined) {
       return undefined;
     }
-    return this.processDecodedEntry(index, this.decodedEntries[index]);
+    return this.processDecodedEntry(index, timestamp.getType(), this.decodedEntries[index]);
   }
 
   protected abstract getMagicNumber(): undefined|number[];
   protected abstract decodeTrace(trace: Uint8Array): any[];
   protected abstract getTimestamp(type: TimestampType, decodedEntry: any): undefined|Timestamp;
-  protected abstract processDecodedEntry(index: number, decodedEntry: any): any;
+  protected abstract processDecodedEntry(index: number, timestampType: TimestampType, decodedEntry: any): any;
 
   protected trace: File;
   protected decodedEntries: any[] = [];
diff --git a/tools/winscope-ng/src/parsers/parser_accessibility.ts b/tools/winscope-ng/src/parsers/parser_accessibility.ts
index f74895e..520fc07 100644
--- a/tools/winscope-ng/src/parsers/parser_accessibility.ts
+++ b/tools/winscope-ng/src/parsers/parser_accessibility.ts
@@ -53,7 +53,7 @@
     return undefined;
   }
 
-  override processDecodedEntry(index: number, entryProto: any): any {
+  override processDecodedEntry(index: number, timestampType: TimestampType, entryProto: any): any {
     return entryProto;
   }
 
diff --git a/tools/winscope-ng/src/parsers/parser_common.spec.ts b/tools/winscope-ng/src/parsers/parser_common.spec.ts
index 530ce02..a9d00f8 100644
--- a/tools/winscope-ng/src/parsers/parser_common.spec.ts
+++ b/tools/winscope-ng/src/parsers/parser_common.spec.ts
@@ -67,19 +67,19 @@
     it("retrieves trace entry (equal timestamp matches)", () => {
       const timestamp = new Timestamp(TimestampType.REAL, 1659107089075566202n);
       expect(BigInt(parser.getTraceEntry(timestamp)!.timestampMs))
-        .toEqual(14474594000n);
+        .toEqual(1659107089075566202n);
     });
 
     it("retrieves trace entry (equal timestamp matches)", () => {
       const timestamp = new Timestamp(TimestampType.REAL, 1659107089999048990n);
       expect(BigInt(parser.getTraceEntry(timestamp)!.timestampMs))
-        .toEqual(15398076788n);
+        .toEqual(1659107089999048990n);
     });
 
     it("retrieves trace entry (lower timestamp matches)", () => {
       const timestamp = new Timestamp(TimestampType.REAL, 1659107089999048991n);
       expect(BigInt(parser.getTraceEntry(timestamp)!.timestampMs))
-        .toEqual(15398076788n);
+        .toEqual(1659107089999048990n);
     });
   });
 
diff --git a/tools/winscope-ng/src/parsers/parser_input_method_clients.ts b/tools/winscope-ng/src/parsers/parser_input_method_clients.ts
index 0c8056c..e3743a7 100644
--- a/tools/winscope-ng/src/parsers/parser_input_method_clients.ts
+++ b/tools/winscope-ng/src/parsers/parser_input_method_clients.ts
@@ -57,9 +57,19 @@
     return undefined;
   }
 
-  override processDecodedEntry(index: number, entryProto: TraceTreeNode): TraceTreeNode {
+  override processDecodedEntry(index: number, timestampType: TimestampType, entryProto: TraceTreeNode): TraceTreeNode {
+    if (entryProto.elapsedRealtimeNanos === undefined) {
+      throw Error("Missing elapsedRealtimeNanos on entry");
+    }
+
+    let clockTimeNanos: bigint|undefined = undefined;
+    if (this.realToElapsedTimeOffsetNs !== undefined
+      && entryProto.elapsedRealtimeNanos !== undefined) {
+      clockTimeNanos = BigInt(entryProto.elapsedRealtimeNanos) + this.realToElapsedTimeOffsetNs;
+    }
+
     return {
-      name: TimeUtils.nanosecondsToHuman(entryProto.elapsedRealtimeNanos ?? 0) + " - " + entryProto.where,
+      name: TimeUtils.format(timestampType, BigInt(entryProto.elapsedRealtimeNanos), this.realToElapsedTimeOffsetNs) + " - " + entryProto.where,
       kind: "InputMethodClient entry",
       children: [
         {
@@ -75,6 +85,7 @@
       stableId: "entry",
       id: "entry",
       elapsedRealtimeNanos: entryProto.elapsedRealtimeNanos,
+      clockTimeNanos,
     };
   }
 
diff --git a/tools/winscope-ng/src/parsers/parser_input_method_manager_service.ts b/tools/winscope-ng/src/parsers/parser_input_method_manager_service.ts
index 0ab0269..d56ef08 100644
--- a/tools/winscope-ng/src/parsers/parser_input_method_manager_service.ts
+++ b/tools/winscope-ng/src/parsers/parser_input_method_manager_service.ts
@@ -55,9 +55,19 @@
     return undefined;
   }
 
-  protected override processDecodedEntry(index: number, entryProto: TraceTreeNode): TraceTreeNode {
+  protected override processDecodedEntry(index: number, timestampType: TimestampType, entryProto: TraceTreeNode): TraceTreeNode {
+    if (entryProto.elapsedRealtimeNanos === undefined) {
+      throw Error("Missing elapsedRealtimeNanos on entry");
+    }
+
+    let clockTimeNanos: bigint|undefined = undefined;
+    if (this.realToElapsedTimeOffsetNs !== undefined
+      && entryProto.elapsedRealtimeNanos !== undefined) {
+      clockTimeNanos = BigInt(entryProto.elapsedRealtimeNanos) + this.realToElapsedTimeOffsetNs;
+    }
+
     return {
-      name: TimeUtils.nanosecondsToHuman(entryProto.elapsedRealtimeNanos ?? 0) + " - " + entryProto.where,
+      name: TimeUtils.format(timestampType, BigInt(entryProto.elapsedRealtimeNanos), this.realToElapsedTimeOffsetNs) + " - " + entryProto.where,
       kind: "InputMethodManagerService entry",
       children: [
         {
@@ -73,6 +83,7 @@
       stableId: "entry",
       id: "entry",
       elapsedRealtimeNanos: entryProto.elapsedRealtimeNanos,
+      clockTimeNanos,
     };
   }
 
diff --git a/tools/winscope-ng/src/parsers/parser_input_method_service.spec.ts b/tools/winscope-ng/src/parsers/parser_input_method_service.spec.ts
index d159197..3d112f0 100644
--- a/tools/winscope-ng/src/parsers/parser_input_method_service.spec.ts
+++ b/tools/winscope-ng/src/parsers/parser_input_method_service.spec.ts
@@ -52,7 +52,7 @@
         .toEqual(16578752896n);
     });
 
-    it("retrieves trace entry from elapsed timestamp", () => {
+    it("retrieves trace entry from real timestamp", () => {
       const timestamp = new Timestamp(TimestampType.REAL, 1659107091180519857n);
       expect(BigInt(parser.getTraceEntry(timestamp)!.elapsedRealtimeNanos))
         .toEqual(16578752896n);
diff --git a/tools/winscope-ng/src/parsers/parser_input_method_service.ts b/tools/winscope-ng/src/parsers/parser_input_method_service.ts
index 9940ca4..4ef26f9 100644
--- a/tools/winscope-ng/src/parsers/parser_input_method_service.ts
+++ b/tools/winscope-ng/src/parsers/parser_input_method_service.ts
@@ -56,9 +56,19 @@
     return undefined;
   }
 
-  override processDecodedEntry(index: number, entryProto: TraceTreeNode): TraceTreeNode {
+  override processDecodedEntry(index: number, timestampType: TimestampType, entryProto: TraceTreeNode): TraceTreeNode {
+    if (entryProto.elapsedRealtimeNanos === undefined) {
+      throw Error("Missing elapsedRealtimeNanos on entry");
+    }
+
+    let clockTimeNanos: bigint|undefined = undefined;
+    if (this.realToElapsedTimeOffsetNs !== undefined
+      && entryProto.elapsedRealtimeNanos !== undefined) {
+      clockTimeNanos = BigInt(entryProto.elapsedRealtimeNanos) + this.realToElapsedTimeOffsetNs;
+    }
+
     return {
-      name: TimeUtils.nanosecondsToHuman(entryProto.elapsedRealtimeNanos ?? 0) + " - " + entryProto.where,
+      name: TimeUtils.format(timestampType, BigInt(entryProto.elapsedRealtimeNanos), this.realToElapsedTimeOffsetNs) + " - " + entryProto.where,
       kind: "InputMethodService entry",
       children: [
         {
@@ -74,6 +84,7 @@
       stableId: "entry",
       id: "entry",
       elapsedRealtimeNanos: entryProto.elapsedRealtimeNanos,
+      clockTimeNanos,
     };
   }
 
diff --git a/tools/winscope-ng/src/parsers/parser_protolog.spec.ts b/tools/winscope-ng/src/parsers/parser_protolog.spec.ts
index 3802465..1117d9c 100644
--- a/tools/winscope-ng/src/parsers/parser_protolog.spec.ts
+++ b/tools/winscope-ng/src/parsers/parser_protolog.spec.ts
@@ -22,13 +22,22 @@
 describe("ParserProtoLog", () => {
   let parser: Parser;
 
-  const expectedFirstLogMessage = {
+  const expectedFirstLogMessageElapsed = {
     text: "InsetsSource updateVisibility for ITYPE_IME, serverVisible: false clientVisible: false",
     time: "14m10s746ms",
     tag: "WindowManager",
     level: "DEBUG",
     at: "com/android/server/wm/InsetsSourceProvider.java",
-    timestamp: Number(850746266486),
+    timestamp: 850746266486n,
+  };
+
+  const expectedFirstLogMessageReal = {
+    text: "InsetsSource updateVisibility for ITYPE_IME, serverVisible: false clientVisible: false",
+    time: "12h12m05s377ms266486ns, 20 Jun 2022 UTC",
+    tag: "WindowManager",
+    level: "DEBUG",
+    at: "com/android/server/wm/InsetsSourceProvider.java",
+    timestamp: 1655727125377266486n,
   };
 
   beforeAll(async () => {
@@ -67,14 +76,27 @@
       .toEqual(expected);
   });
 
-  it("reconstructs human-readable log message", () => {
+  it("reconstructs human-readable log message (ELAPSED time)", () => {
     const timestamp = new Timestamp(TimestampType.ELAPSED, 850746266486n);
     const entry = parser.getTraceEntry(timestamp)!;
 
     expect(entry.currentMessageIndex).toEqual(0);
 
     expect(entry.messages.length).toEqual(50);
-    expect(Object.assign({}, entry.messages[0])).toEqual(expectedFirstLogMessage);
+    expect(Object.assign({}, entry.messages[0])).toEqual(expectedFirstLogMessageElapsed);
+    entry.messages.forEach((message: any) => {
+      expect(message).toBeInstanceOf(LogMessage);
+    });
+  });
+
+  it("reconstructs human-readable log message (REAL time)", () => {
+    const timestamp = new Timestamp(TimestampType.REAL, 1655727125377266486n);
+    const entry = parser.getTraceEntry(timestamp)!;
+
+    expect(entry.currentMessageIndex).toEqual(0);
+
+    expect(entry.messages.length).toEqual(50);
+    expect(Object.assign({}, entry.messages[0])).toEqual(expectedFirstLogMessageReal);
     entry.messages.forEach((message: any) => {
       expect(message).toBeInstanceOf(LogMessage);
     });
diff --git a/tools/winscope-ng/src/parsers/parser_protolog.ts b/tools/winscope-ng/src/parsers/parser_protolog.ts
index 9e57eb7..5b18d56 100644
--- a/tools/winscope-ng/src/parsers/parser_protolog.ts
+++ b/tools/winscope-ng/src/parsers/parser_protolog.ts
@@ -61,40 +61,42 @@
     if (type == TimestampType.ELAPSED) {
       return new Timestamp(type, BigInt(entryProto.elapsedRealtimeNanos));
     }
-    else if (type == TimestampType.REAL) {
-      return new Timestamp(type, BigInt(entryProto.elapsedRealtimeNanos) + this.realToElapsedTimeOffsetNs!);
+    if (type == TimestampType.REAL && this.realToElapsedTimeOffsetNs !== undefined) {
+      return new Timestamp(type, BigInt(entryProto.elapsedRealtimeNanos) + this.realToElapsedTimeOffsetNs);
     }
     return undefined;
   }
 
-  override processDecodedEntry(index: number, entryProto: any): ProtoLogTraceEntry {
-    if (!this.decodedMessages) {
+  override processDecodedEntry(index: number, timestampType: TimestampType, entryProto: any): ProtoLogTraceEntry {
+    if (!this.decodedMessages || this.decodedTimestampType !== timestampType) {
+      this.decodedTimestampType = timestampType;
       this.decodedMessages = this.decodedEntries.map((entryProto: any) => {
-        return this.decodeProtoLogMessage(entryProto);
+        return this.decodeProtoLogMessage(entryProto, timestampType);
       });
     }
 
     return new ProtoLogTraceEntry(this.decodedMessages, index);
   }
 
-  private decodeProtoLogMessage(entryProto: any): LogMessage {
+  private decodeProtoLogMessage(entryProto: any, timestampType: TimestampType): LogMessage {
     const message = (<any>configJson).messages[entryProto.messageHash];
     if (!message) {
-      return new FormattedLogMessage(entryProto);
+      return new FormattedLogMessage(entryProto, timestampType, this.realToElapsedTimeOffsetNs);
     }
 
     try {
-      return new UnformattedLogMessage(entryProto, message);
+      return new UnformattedLogMessage(entryProto, timestampType, this.realToElapsedTimeOffsetNs, message);
     }
     catch (error) {
       if (error instanceof FormatStringMismatchError) {
-        return new FormattedLogMessage(entryProto);
+        return new FormattedLogMessage(entryProto, timestampType, this.realToElapsedTimeOffsetNs);
       }
       throw error;
     }
   }
 
   private decodedMessages?: LogMessage[];
+  private decodedTimestampType?: TimestampType;
   private realToElapsedTimeOffsetNs: undefined|bigint = undefined;
   private static readonly MAGIC_NUMBER = [0x09, 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x4c, 0x4f, 0x47]; // .PROTOLOG
   private static readonly PROTOLOG_VERSION = "1.0.0";
diff --git a/tools/winscope-ng/src/parsers/parser_screen_recording.ts b/tools/winscope-ng/src/parsers/parser_screen_recording.ts
index 54e776a..c8c2352 100644
--- a/tools/winscope-ng/src/parsers/parser_screen_recording.ts
+++ b/tools/winscope-ng/src/parsers/parser_screen_recording.ts
@@ -81,7 +81,7 @@
     return undefined;
   }
 
-  override processDecodedEntry(index: number, entry: ScreenRecordingMetadataEntry): ScreenRecordingTraceEntry {
+  override processDecodedEntry(index: number, timestampType: TimestampType, entry: ScreenRecordingMetadataEntry): ScreenRecordingTraceEntry {
     const initialTimestampNs = this.getTimestamps(TimestampType.ELAPSED)![0].getValueNs();
     const currentTimestampNs = entry.timestampElapsedNs;
     const videoTimeSeconds = Number(currentTimestampNs - initialTimestampNs) / 1000000000;
diff --git a/tools/winscope-ng/src/parsers/parser_screen_recording_legacy.ts b/tools/winscope-ng/src/parsers/parser_screen_recording_legacy.ts
index 789ee7e..61487ca 100644
--- a/tools/winscope-ng/src/parsers/parser_screen_recording_legacy.ts
+++ b/tools/winscope-ng/src/parsers/parser_screen_recording_legacy.ts
@@ -45,7 +45,7 @@
     return decodedEntry;
   }
 
-  override processDecodedEntry(index: number, entry: Timestamp): ScreenRecordingTraceEntry {
+  override processDecodedEntry(index: number, timestampType: TimestampType, entry: Timestamp): ScreenRecordingTraceEntry {
     const currentTimestamp = entry;
     const initialTimestamp = this.getTimestamps(TimestampType.ELAPSED)![0];
     const videoTimeSeconds =
diff --git a/tools/winscope-ng/src/parsers/parser_surface_flinger.spec.ts b/tools/winscope-ng/src/parsers/parser_surface_flinger.spec.ts
index c5e1ee7..b3c8281 100644
--- a/tools/winscope-ng/src/parsers/parser_surface_flinger.spec.ts
+++ b/tools/winscope-ng/src/parsers/parser_surface_flinger.spec.ts
@@ -82,14 +82,20 @@
       const timestamp = new Timestamp(TimestampType.ELAPSED, 14631249355n);
       const entry = parser.getTraceEntry(timestamp)!;
       expect(entry).toBeInstanceOf(LayerTraceEntry);
-      expect(BigInt(entry.timestampMs)).toEqual(14631249355n);
+      expect(BigInt(entry.timestampMs)).toEqual(1659107089233029344n);
     });
 
-    it("retrieves trace entry from elapsed timestamp", () => {
+    it("retrieves trace entry from real timestamp", () => {
       const timestamp = new Timestamp(TimestampType.REAL, 1659107089233029344n);
       const entry = parser.getTraceEntry(timestamp)!;
       expect(entry).toBeInstanceOf(LayerTraceEntry);
-      expect(BigInt(entry.timestampMs)).toEqual(14631249355n);
+      expect(BigInt(entry.timestampMs)).toEqual(1659107089233029344n);
+    });
+
+    it("formats entry timestamps", () => {
+      const timestamp = new Timestamp(TimestampType.REAL, 1659107089233029344n);
+      const entry = parser.getTraceEntry(timestamp)!;
+      expect(entry.name).toEqual("15h04m49s233ms29376ns, 29 Jul 2022 UTC");
     });
   });
 
@@ -113,5 +119,16 @@
       expect(parser.getTimestamps(TimestampType.REAL))
         .toEqual(undefined);
     });
+
+    it("formats entry timestamps", () => {
+      expect(() => {
+        const timestamp = new Timestamp(TimestampType.REAL, 1659107089233029344n);
+        parser.getTraceEntry(timestamp);
+      }).toThrow(Error("Timestamps with type \"REAL\" not available"));
+
+      const timestamp = new Timestamp(TimestampType.ELAPSED, 850335483446n);
+      const entry = parser.getTraceEntry(timestamp)!;
+      expect(entry.name).toEqual("14m10s335ms");
+    });
   });
 });
diff --git a/tools/winscope-ng/src/parsers/parser_surface_flinger.ts b/tools/winscope-ng/src/parsers/parser_surface_flinger.ts
index 6a6e163..5077ec3 100644
--- a/tools/winscope-ng/src/parsers/parser_surface_flinger.ts
+++ b/tools/winscope-ng/src/parsers/parser_surface_flinger.ts
@@ -59,8 +59,17 @@
     return undefined;
   }
 
-  override processDecodedEntry(index: number, entryProto: any): LayerTraceEntry {
-    return LayerTraceEntry.fromProto(entryProto.layers.layers, entryProto.displays, entryProto.elapsedRealtimeNanos, entryProto.hwcBlob);
+  override processDecodedEntry(index: number, timestampType: TimestampType, entryProto: any): LayerTraceEntry {
+    return LayerTraceEntry.fromProto(
+      entryProto.layers.layers,
+      entryProto.displays,
+      entryProto.elapsedRealtimeNanos,
+      entryProto.vsyncId,
+      entryProto.hwcBlob,
+      entryProto.where,
+      this.realToElapsedTimeOffsetNs,
+      timestampType === TimestampType.ELAPSED /*useElapsedTime*/
+    );
   }
 
   private realToElapsedTimeOffsetNs: undefined|bigint;
diff --git a/tools/winscope-ng/src/parsers/parser_surface_flinger_dump.spec.ts b/tools/winscope-ng/src/parsers/parser_surface_flinger_dump.spec.ts
index e32da20..f3486ef 100644
--- a/tools/winscope-ng/src/parsers/parser_surface_flinger_dump.spec.ts
+++ b/tools/winscope-ng/src/parsers/parser_surface_flinger_dump.spec.ts
@@ -22,6 +22,7 @@
 describe("ParserSurfaceFlingerDump", () => {
   describe("trace with elapsed + real timestamp", () => {
     let parser: Parser;
+    const DUMP_REAL_TIME = 1659176624505188647n;
 
     beforeAll(async () => {
       parser = await UnitTestUtils.getParser("traces/elapsed_and_real_timestamp/dump_SurfaceFlinger.pb");
@@ -49,14 +50,14 @@
       const timestamp = new Timestamp(TimestampType.ELAPSED, 0n);
       const entry = parser.getTraceEntry(timestamp)!;
       expect(entry).toBeInstanceOf(LayerTraceEntry);
-      expect(BigInt(entry.timestampMs)).toEqual(0n);
+      expect(BigInt(entry.timestampMs)).toEqual(DUMP_REAL_TIME);
     });
 
-    it("retrieves trace entry from elapsed timestamp", () => {
+    it("retrieves trace entry from real timestamp", () => {
       const timestamp = new Timestamp(TimestampType.REAL, 0n);
       const entry = parser.getTraceEntry(timestamp)!;
       expect(entry).toBeInstanceOf(LayerTraceEntry);
-      expect(BigInt(entry.timestampMs)).toEqual(0n);
+      expect(BigInt(entry.timestampMs)).toEqual(DUMP_REAL_TIME);
     });
   });
 
diff --git a/tools/winscope-ng/src/parsers/parser_transactions.spec.ts b/tools/winscope-ng/src/parsers/parser_transactions.spec.ts
index 183bbcd..0994ffb 100644
--- a/tools/winscope-ng/src/parsers/parser_transactions.spec.ts
+++ b/tools/winscope-ng/src/parsers/parser_transactions.spec.ts
@@ -88,6 +88,14 @@
       expect(entry.entriesProto[222].transactions[1].displayChanges[0].what)
         .toEqual("eLayerStackChanged | eDisplayProjectionChanged | eFlagsChanged");
     });
+
+    it("includes timestamp type in TransactionsTraceEntry", () => {
+      const timestamp = new Timestamp(TimestampType.REAL, 1659507541118452067n);
+      const entry: TransactionsTraceEntry = parser.getTraceEntry(timestamp)!;
+
+      expect(entry.timestampType).toEqual(TimestampType.REAL);
+      expect(entry.realToElapsedTimeOffsetNs).toEqual(1659507538600499552n);
+    });
   });
 
   describe("trace with elapsed (only) timestamp", () => {
@@ -120,5 +128,12 @@
       expect(parser.getTimestamps(TimestampType.REAL))
         .toEqual(undefined);
     });
+
+    it("includes timestamp type in TransactionsTraceEntry", () => {
+      const timestamp = new Timestamp(TimestampType.ELAPSED, 14884850511n);
+      const entry: TransactionsTraceEntry = parser.getTraceEntry(timestamp)!;
+
+      expect(entry.timestampType).toEqual(TimestampType.ELAPSED);
+    });
   });
 });
diff --git a/tools/winscope-ng/src/parsers/parser_transactions.ts b/tools/winscope-ng/src/parsers/parser_transactions.ts
index f464f77..9eda543 100644
--- a/tools/winscope-ng/src/parsers/parser_transactions.ts
+++ b/tools/winscope-ng/src/parsers/parser_transactions.ts
@@ -100,8 +100,8 @@
     return undefined;
   }
 
-  override processDecodedEntry(index: number, entryProto: any): TransactionsTraceEntry {
-    return new TransactionsTraceEntry(this.decodedEntries, index);
+  override processDecodedEntry(index: number, timestampType: TimestampType, entryProto: any): TransactionsTraceEntry {
+    return new TransactionsTraceEntry(this.decodedEntries, timestampType, this.realToElapsedTimeOffsetNs, index);
   }
 
   private realToElapsedTimeOffsetNs: undefined|bigint;
diff --git a/tools/winscope-ng/src/parsers/parser_window_manager.spec.ts b/tools/winscope-ng/src/parsers/parser_window_manager.spec.ts
index 3c50fc0..9230670 100644
--- a/tools/winscope-ng/src/parsers/parser_window_manager.spec.ts
+++ b/tools/winscope-ng/src/parsers/parser_window_manager.spec.ts
@@ -55,14 +55,20 @@
       const timestamp = new Timestamp(TimestampType.ELAPSED, 15398076788n);
       const entry = parser.getTraceEntry(timestamp)!;
       expect(entry).toBeInstanceOf(WindowManagerState);
-      expect(BigInt(entry.timestampMs)).toEqual(15398076788n);
+      expect(BigInt(entry.timestampMs)).toEqual(1659107089999048990n);
     });
 
     it("retrieves trace entry from real timestamp", () => {
       const timestamp = new Timestamp(TimestampType.REAL, 1659107089999048990n);
       const entry = parser.getTraceEntry(timestamp)!;
       expect(entry).toBeInstanceOf(WindowManagerState);
-      expect(BigInt(entry.timestampMs)).toEqual(15398076788n);
+      expect(BigInt(entry.timestampMs)).toEqual(1659107089999048990n);
+    });
+
+    it("formats entry timestamps", () => {
+      const timestamp = new Timestamp(TimestampType.REAL, 1659107089999048990n);
+      const entry = parser.getTraceEntry(timestamp)!;
+      expect(entry.name).toEqual("15h04m49s999ms48960ns, 29 Jul 2022 UTC");
     });
   });
 
@@ -93,5 +99,16 @@
       expect(entry).toBeInstanceOf(WindowManagerState);
       expect(BigInt(entry.timestampMs)).toEqual(850254319343n);
     });
+
+    it("formats entry timestamps", () => {
+      expect(() => {
+        const timestamp = new Timestamp(TimestampType.REAL, 1659107089999048990n);
+        parser.getTraceEntry(timestamp);
+      }).toThrow(Error("Timestamps with type \"REAL\" not available"));
+
+      const timestamp = new Timestamp(TimestampType.ELAPSED, 850254319343n);
+      const entry = parser.getTraceEntry(timestamp)!;
+      expect(entry.name).toEqual("14m10s254ms");
+    });
   });
 });
diff --git a/tools/winscope-ng/src/parsers/parser_window_manager.ts b/tools/winscope-ng/src/parsers/parser_window_manager.ts
index 5a38f72..b719db2 100644
--- a/tools/winscope-ng/src/parsers/parser_window_manager.ts
+++ b/tools/winscope-ng/src/parsers/parser_window_manager.ts
@@ -54,8 +54,14 @@
     return undefined;
   }
 
-  override processDecodedEntry(index: number, entryProto: any): WindowManagerState {
-    return WindowManagerState.fromProto(entryProto.windowManagerService, entryProto.elapsedRealtimeNanos, entryProto.where);
+  override processDecodedEntry(index: number, timestampType: TimestampType, entryProto: any): WindowManagerState {
+    return WindowManagerState.fromProto(
+      entryProto.windowManagerService,
+      entryProto.elapsedRealtimeNanos,
+      entryProto.where,
+      this.realToElapsedTimeOffsetNs,
+      timestampType === TimestampType.ELAPSED /*useElapsedTime*/
+    );
   }
 
   private realToElapsedTimeOffsetNs: undefined|bigint;
diff --git a/tools/winscope-ng/src/parsers/parser_window_manager_dump.ts b/tools/winscope-ng/src/parsers/parser_window_manager_dump.ts
index f929de2..8bc8075 100644
--- a/tools/winscope-ng/src/parsers/parser_window_manager_dump.ts
+++ b/tools/winscope-ng/src/parsers/parser_window_manager_dump.ts
@@ -40,7 +40,7 @@
     // sure that a trace entry can actually be created from the decoded proto.
     // If the trace entry creation fails, an exception is thrown and the parser
     // will be considered unsuited for this input data.
-    this.processDecodedEntry(0, entryProto);
+    this.processDecodedEntry(0, TimestampType.ELAPSED /*irrelevant for dump*/, entryProto);
 
     return [entryProto];
   }
@@ -52,7 +52,7 @@
     return new Timestamp(TimestampType.ELAPSED, 0n);
   }
 
-  override processDecodedEntry(index: number, entryProto: any): WindowManagerState {
+  override processDecodedEntry(index: number, timestampType: TimestampType, entryProto: any): WindowManagerState {
     return WindowManagerState.fromProto(entryProto);
   }
 }
diff --git a/tools/winscope-ng/src/viewers/viewer_protolog/presenter.spec.ts b/tools/winscope-ng/src/viewers/viewer_protolog/presenter.spec.ts
index 248c2fe..506b445 100644
--- a/tools/winscope-ng/src/viewers/viewer_protolog/presenter.spec.ts
+++ b/tools/winscope-ng/src/viewers/viewer_protolog/presenter.spec.ts
@@ -26,9 +26,9 @@
 
   beforeEach(async () => {
     inputMessages = [
-      new LogMessage("text0", "time", "tag0", "level0", "sourcefile0", 10),
-      new LogMessage("text1", "time", "tag1", "level1", "sourcefile1", 10),
-      new LogMessage("text2", "time", "tag2", "level2", "sourcefile2", 10),
+      new LogMessage("text0", "time", "tag0", "level0", "sourcefile0", 10n),
+      new LogMessage("text1", "time", "tag1", "level1", "sourcefile1", 10n),
+      new LogMessage("text2", "time", "tag2", "level2", "sourcefile2", 10n),
     ];
     inputTraceEntries = new Map<TraceType, any>();
     inputTraceEntries.set(TraceType.PROTO_LOG, [new ProtoLogTraceEntry(inputMessages, 0)]);
diff --git a/tools/winscope-ng/src/viewers/viewer_transactions/presenter.spec.ts b/tools/winscope-ng/src/viewers/viewer_transactions/presenter.spec.ts
index 91c755d..45352a8 100644
--- a/tools/winscope-ng/src/viewers/viewer_transactions/presenter.spec.ts
+++ b/tools/winscope-ng/src/viewers/viewer_transactions/presenter.spec.ts
@@ -24,8 +24,10 @@
 describe("ViewerTransactionsPresenter", () => {
   let parser: Parser;
   let presenter: Presenter;
-  let inputTraceEntry: TransactionsTraceEntry;
-  let inputTraceEntries: Map<TraceType, any>;
+  let inputTraceEntryElapsed: TransactionsTraceEntry;
+  let inputTraceEntriesElapsed: Map<TraceType, any>;
+  let inputTraceEntryReal: TransactionsTraceEntry;
+  let inputTraceEntriesReal: Map<TraceType, any>;
   let outputUiData: undefined|UiData;
   const TOTAL_OUTPUT_ENTRIES = 1504;
 
@@ -34,10 +36,16 @@
   });
 
   beforeEach(() => {
-    const timestamp = new Timestamp(TimestampType.ELAPSED, 2450981445n);
-    inputTraceEntry = parser.getTraceEntry(timestamp)!;
-    inputTraceEntries = new Map<TraceType, any>();
-    inputTraceEntries.set(TraceType.TRANSACTIONS, [inputTraceEntry]);
+    const elapsedTimestamp = new Timestamp(TimestampType.ELAPSED, 2450981445n);
+    inputTraceEntryElapsed = parser.getTraceEntry(elapsedTimestamp)!;
+    inputTraceEntriesElapsed = new Map<TraceType, any>();
+    inputTraceEntriesElapsed.set(TraceType.TRANSACTIONS, [inputTraceEntryElapsed]);
+
+    const realTimestamp = new Timestamp(TimestampType.REAL, 16595075386004995520n);
+    inputTraceEntryReal = parser.getTraceEntry(realTimestamp)!;
+    inputTraceEntriesReal = new Map<TraceType, any>();
+    inputTraceEntriesReal.set(TraceType.TRANSACTIONS, [inputTraceEntryReal]);
+
     outputUiData = undefined;
 
     presenter = new Presenter((data: UiData) => {
@@ -46,13 +54,13 @@
   });
 
   it("is robust to undefined trace entry", () => {
-    inputTraceEntries = new Map<TraceType, any>();
-    presenter.notifyCurrentTraceEntries(inputTraceEntries);
+    inputTraceEntriesElapsed = new Map<TraceType, any>();
+    presenter.notifyCurrentTraceEntries(inputTraceEntriesElapsed);
     expect(outputUiData).toEqual(UiData.EMPTY);
   });
 
   it("processes trace entry and computes output UI data", () => {
-    presenter.notifyCurrentTraceEntries(inputTraceEntries);
+    presenter.notifyCurrentTraceEntries(inputTraceEntriesElapsed);
 
     expect(outputUiData!.allPids).toEqual(["N/A", "0", "515", "1593", "2022", "2322", "2463", "3300"]);
     expect(outputUiData!.allUids).toEqual(["N/A", "1000", "1003", "10169", "10235", "10239"]);
@@ -68,7 +76,7 @@
   });
 
   it("ignores undefined trace entry and doesn't discard previously computed UI data", () => {
-    presenter.notifyCurrentTraceEntries(inputTraceEntries);
+    presenter.notifyCurrentTraceEntries(inputTraceEntriesElapsed);
     expect(outputUiData!.entries.length).toEqual(TOTAL_OUTPUT_ENTRIES);
 
     presenter.notifyCurrentTraceEntries(new Map<TraceType, any>());
@@ -76,18 +84,18 @@
   });
 
   it("processes trace entry and updates current entry and scroll position", () => {
-    presenter.notifyCurrentTraceEntries(inputTraceEntries);
+    presenter.notifyCurrentTraceEntries(inputTraceEntriesElapsed);
     expect(outputUiData!.currentEntryIndex).toEqual(0);
     expect(outputUiData!.scrollToIndex).toEqual(0);
 
-    (<TransactionsTraceEntry>inputTraceEntries.get(TraceType.TRANSACTIONS)[0]).currentEntryIndex = 10;
-    presenter.notifyCurrentTraceEntries(inputTraceEntries);
+    (<TransactionsTraceEntry>inputTraceEntriesElapsed.get(TraceType.TRANSACTIONS)[0]).currentEntryIndex = 10;
+    presenter.notifyCurrentTraceEntries(inputTraceEntriesElapsed);
     expect(outputUiData!.currentEntryIndex).toEqual(13);
     expect(outputUiData!.scrollToIndex).toEqual(13);
   });
 
   it("filters entries according to PID filter", () => {
-    presenter.notifyCurrentTraceEntries(inputTraceEntries);
+    presenter.notifyCurrentTraceEntries(inputTraceEntriesElapsed);
 
     presenter.onPidFilterChanged([]);
     expect(new Set(outputUiData!.entries.map(entry => entry.pid)))
@@ -103,7 +111,7 @@
   });
 
   it("filters entries according to UID filter", () => {
-    presenter.notifyCurrentTraceEntries(inputTraceEntries);
+    presenter.notifyCurrentTraceEntries(inputTraceEntriesElapsed);
 
     presenter.onUidFilterChanged([]);
     expect(new Set(outputUiData!.entries.map(entry => entry.uid)))
@@ -119,7 +127,7 @@
   });
 
   it("filters entries according to type filter", () => {
-    presenter.notifyCurrentTraceEntries(inputTraceEntries);
+    presenter.notifyCurrentTraceEntries(inputTraceEntriesElapsed);
 
     presenter.onTypeFilterChanged([]);
     expect(new Set(outputUiData!.entries.map(entry => entry.type)))
@@ -141,7 +149,7 @@
   });
 
   it("filters entries according to ID filter", () => {
-    presenter.notifyCurrentTraceEntries(inputTraceEntries);
+    presenter.notifyCurrentTraceEntries(inputTraceEntriesElapsed);
 
     presenter.onIdFilterChanged([]);
     expect(new Set(outputUiData!.entries.map(entry => entry.id)).size)
@@ -157,7 +165,7 @@
   });
 
   it ("updates selected entry and properties tree when entry is clicked", () => {
-    presenter.notifyCurrentTraceEntries(inputTraceEntries);
+    presenter.notifyCurrentTraceEntries(inputTraceEntriesElapsed);
     expect(outputUiData!.currentEntryIndex).toEqual(0);
     expect(outputUiData!.selectedEntryIndex).toBeUndefined();
     expect(outputUiData!.scrollToIndex).toEqual(0);
@@ -181,17 +189,17 @@
   });
 
   it("computes current entry index", () => {
-    presenter.notifyCurrentTraceEntries(inputTraceEntries);
+    presenter.notifyCurrentTraceEntries(inputTraceEntriesElapsed);
     expect(outputUiData!.currentEntryIndex).toEqual(0);
 
-    (<TransactionsTraceEntry>inputTraceEntries.get(TraceType.TRANSACTIONS)[0]).currentEntryIndex = 10;
-    presenter.notifyCurrentTraceEntries(inputTraceEntries);
+    (<TransactionsTraceEntry>inputTraceEntriesElapsed.get(TraceType.TRANSACTIONS)[0]).currentEntryIndex = 10;
+    presenter.notifyCurrentTraceEntries(inputTraceEntriesElapsed);
     expect(outputUiData!.currentEntryIndex).toEqual(13);
   });
 
   it("updates current entry index when filters change", () => {
-    (<TransactionsTraceEntry>inputTraceEntries.get(TraceType.TRANSACTIONS)[0]).currentEntryIndex = 10;
-    presenter.notifyCurrentTraceEntries(inputTraceEntries);
+    (<TransactionsTraceEntry>inputTraceEntriesElapsed.get(TraceType.TRANSACTIONS)[0]).currentEntryIndex = 10;
+    presenter.notifyCurrentTraceEntries(inputTraceEntriesElapsed);
 
     presenter.onPidFilterChanged([]);
     expect(outputUiData!.currentEntryIndex).toEqual(13);
@@ -205,4 +213,18 @@
     presenter.onPidFilterChanged(["0", "515", "N/A"]);
     expect(outputUiData!.currentEntryIndex).toEqual(13);
   });
+
+  it("formats real time", () => {
+    (<TransactionsTraceEntry>inputTraceEntriesReal.get(TraceType.TRANSACTIONS)[0]).currentEntryIndex = 10;
+    presenter.notifyCurrentTraceEntries(inputTraceEntriesReal);
+
+    expect(outputUiData!.entries[0].time).toEqual("06h19m01s051ms480997ns, 3 Aug 2022 UTC");
+  });
+
+  it("formats elapsed time", () => {
+    (<TransactionsTraceEntry>inputTraceEntriesElapsed.get(TraceType.TRANSACTIONS)[0]).currentEntryIndex = 10;
+    presenter.notifyCurrentTraceEntries(inputTraceEntriesElapsed);
+
+    expect(outputUiData!.entries[0].time).toEqual("2s450ms");
+  });
 });
diff --git a/tools/winscope-ng/src/viewers/viewer_transactions/presenter.ts b/tools/winscope-ng/src/viewers/viewer_transactions/presenter.ts
index ec1e53e..cb95a69 100644
--- a/tools/winscope-ng/src/viewers/viewer_transactions/presenter.ts
+++ b/tools/winscope-ng/src/viewers/viewer_transactions/presenter.ts
@@ -20,6 +20,7 @@
 import {PropertiesTreeGenerator} from "viewers/common/properties_tree_generator";
 import {PropertiesTreeNode} from "viewers/common/ui_tree_utils";
 import {TimeUtils} from "common/utils/time_utils";
+import { TimestampType } from "common/trace/timestamp";
 
 class Presenter {
   constructor(notifyUiDataCallback: (data: UiData) => void) {
@@ -95,7 +96,7 @@
       return;
     }
 
-    const entries = this.makeUiDataEntries(this.entry!.entriesProto);
+    const entries = this.makeUiDataEntries(this.entry!);
 
     const allPids = this.getUniqueUiDataEntryValues(entries, (entry: UiDataEntry) => entry.pid);
     const allUids = this.getUniqueUiDataEntryValues(entries, (entry: UiDataEntry) => entry.uid);
@@ -166,7 +167,10 @@
     return undefined;
   }
 
-  private makeUiDataEntries(entriesProto: any[]): UiDataEntry[] {
+  private makeUiDataEntries(entry: TransactionsTraceEntry): UiDataEntry[] {
+    const entriesProto: any[] = entry.entriesProto;
+    const timestampType = entry.timestampType;
+    const realToElapsedTimeOffsetNs = entry.realToElapsedTimeOffsetNs;
     const treeGenerator = new PropertiesTreeGenerator();
 
     const entries: UiDataEntry[] = [];
@@ -176,7 +180,7 @@
         for (const layerStateProto of transactionStateProto.layerChanges) {
           entries.push(new UiDataEntry(
             originalIndex,
-            TimeUtils.nanosecondsToHuman(Number(entryProto.elapsedRealtimeNanos)),
+            this.formatTime(entryProto, timestampType, realToElapsedTimeOffsetNs),
             Number(entryProto.vsyncId),
             transactionStateProto.pid.toString(),
             transactionStateProto.uid.toString(),
@@ -189,7 +193,7 @@
         for (const displayStateProto of transactionStateProto.displayChanges) {
           entries.push(new UiDataEntry(
             originalIndex,
-            TimeUtils.nanosecondsToHuman(Number(entryProto.elapsedRealtimeNanos)),
+            this.formatTime(entryProto, timestampType, realToElapsedTimeOffsetNs),
             Number(entryProto.vsyncId),
             transactionStateProto.pid.toString(),
             transactionStateProto.uid.toString(),
@@ -203,7 +207,7 @@
       for (const layerCreationArgsProto of entryProto.addedLayers) {
         entries.push(new UiDataEntry(
           originalIndex,
-          TimeUtils.nanosecondsToHuman(Number(entryProto.elapsedRealtimeNanos)),
+          this.formatTime(entryProto, timestampType, realToElapsedTimeOffsetNs),
           Number(entryProto.vsyncId),
           Presenter.VALUE_NA,
           Presenter.VALUE_NA,
@@ -216,7 +220,7 @@
       for (const removedLayerId of entryProto.removedLayers) {
         entries.push(new UiDataEntry(
           originalIndex,
-          TimeUtils.nanosecondsToHuman(Number(entryProto.elapsedRealtimeNanos)),
+          this.formatTime(entryProto, timestampType, realToElapsedTimeOffsetNs),
           Number(entryProto.vsyncId),
           Presenter.VALUE_NA,
           Presenter.VALUE_NA,
@@ -229,7 +233,7 @@
       for (const displayStateProto of entryProto.addedDisplays) {
         entries.push(new UiDataEntry(
           originalIndex,
-          TimeUtils.nanosecondsToHuman(Number(entryProto.elapsedRealtimeNanos)),
+          this.formatTime(entryProto, timestampType, realToElapsedTimeOffsetNs),
           Number(entryProto.vsyncId),
           Presenter.VALUE_NA,
           Presenter.VALUE_NA,
@@ -242,7 +246,7 @@
       for (const removedDisplayId of entryProto.removedDisplays) {
         entries.push(new UiDataEntry(
           originalIndex,
-          TimeUtils.nanosecondsToHuman(Number(entryProto.elapsedRealtimeNanos)),
+          this.formatTime(entryProto, timestampType, realToElapsedTimeOffsetNs),
           Number(entryProto.vsyncId),
           Presenter.VALUE_NA,
           Presenter.VALUE_NA,
@@ -255,7 +259,7 @@
       for (const removedLayerHandleId of entryProto.removedLayerHandles) {
         entries.push(new UiDataEntry(
           originalIndex,
-          TimeUtils.nanosecondsToHuman(Number(entryProto.elapsedRealtimeNanos)),
+          this.formatTime(entryProto, timestampType, realToElapsedTimeOffsetNs),
           Number(entryProto.vsyncId),
           Presenter.VALUE_NA,
           Presenter.VALUE_NA,
@@ -269,6 +273,14 @@
     return entries;
   }
 
+  private formatTime(entryProto: any, timestampType: TimestampType, realToElapsedTimeOffsetNs: bigint|undefined): string {
+    if (timestampType === TimestampType.REAL && realToElapsedTimeOffsetNs !== undefined) {
+      return TimeUtils.nanosecondsToHumanReal(BigInt(entryProto.elapsedRealtimeNanos) + realToElapsedTimeOffsetNs);
+    } else {
+      return TimeUtils.nanosecondsToHumanElapsed(Number(entryProto.elapsedRealtimeNanos));
+    }
+  }
+
   private getUniqueUiDataEntryValues(entries: UiDataEntry[], getValue: (entry: UiDataEntry) => string): string[] {
     const uniqueValues = new Set<string>();
     entries.forEach((entry: UiDataEntry) => {