Skip to content

fix: normalize SDK timestamps to UTC - #698

Open
marandaneto wants to merge 5 commits into
mainfrom
fix/normalize-utc-timestamps
Open

fix: normalize SDK timestamps to UTC#698
marandaneto wants to merge 5 commits into
mainfrom
fix/normalize-utc-timestamps

Conversation

@marandaneto

Copy link
Copy Markdown
Member

💡 Motivation and Context

SDK dates were formatted with the process default time zone. The JSON represented the correct instant, but it could use a local offset instead of the canonical UTC Z form. Session replay log capture also used logcat's local threadtime output, so parsing depended on the device time zone.

This change formats SDK dates in UTC while preserving the exact instant. Session replay now requests logcat's epoch format, passes the capture start time as epoch seconds with milliseconds, and converts each epoch timestamp directly into a UTC calendar. Timestamp API documentation now explains that non-UTC inputs keep the same instant and are serialized in UTC.

💚 How did you test it?

  • GsonDateTypeAdapterTest sets the default time zone to America/Los_Angeles and verifies that 2023-07-15T08:30:45.123-07:00 serializes as 2023-07-15T15:30:45.123Z without changing the input instant.
  • PostHogSerializerTest sets the default time zone to America/New_York and verifies the exact UTC values for both timestamp and sent_at near a daylight saving transition.
  • LogcatParserTest verifies exact UTC epoch parsing, independence from the process default time zone, conversion of sub-millisecond epoch fractions to milliseconds, log level and payload parsing, and rejection of ambiguous threadtime lines.
  • PostHogLogCatIntegrationTest verifies the exact logcat -v epoch -T 1721057445.123 *:E command.
  • Ran the focused core and Android unit tests, make checkFormat, pnpm changeset status --since=origin/main, and committed-HEAD autoreview against origin/main.

📝 Checklist

  • I reviewed the submitted code.
  • I added tests to verify the changes.
  • I updated the docs if needed.
  • No breaking change or entry added to the changelog.

If releasing new changes

  • Ran pnpm changeset to generate a changeset file

🤖 Agent context

Autonomy: Human-driven (agent-assisted)

The Pi worker agent prepared the existing implementation for review, added the required changeset, and ran the repository checks and isolated autoreview. The human directed the branch, scope, commit message, PR title, and required validation. This local agent session does not have a shareable session link.

@marandaneto marandaneto self-assigned this Aug 14, 2026
@github-actions

github-actions Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

posthog-android Compliance Report

Date: 2026-08-14 11:15:25 UTC
Duration: 118445ms

✅ All Tests Passed!

46/46 tests passed


Capture Tests

29/29 tests passed

View Details
Test Status Duration
Format Validation.Event Has Required Fields 363ms
Format Validation.Event Has Uuid 33ms
Format Validation.Event Has Lib Properties 34ms
Format Validation.Distinct Id Is String 33ms
Format Validation.Token Is Present 28ms
Format Validation.Custom Properties Preserved 42ms
Format Validation.Event Has Timestamp 37ms
Retry Behavior.Retries On 503 7030ms
Retry Behavior.Does Not Retry On 400 4029ms
Retry Behavior.Does Not Retry On 401 4030ms
Retry Behavior.Respects Retry After Header 7032ms
Retry Behavior.Implements Backoff 17038ms
Retry Behavior.Retries On 500 7020ms
Retry Behavior.Retries On 502 7020ms
Retry Behavior.Retries On 504 7019ms
Retry Behavior.Max Retries Respected 17037ms
Deduplication.Generates Unique Uuids 44ms
Deduplication.Preserves Uuid On Retry 7014ms
Deduplication.Preserves Uuid And Timestamp On Retry 12027ms
Deduplication.Preserves Uuid And Timestamp On Batch Retry 7019ms
Deduplication.No Duplicate Events In Batch 36ms
Deduplication.Different Events Have Different Uuids 26ms
Compression.Sends Gzip When Enabled 20ms
Batch Format.Uses Proper Batch Structure 19ms
Batch Format.Flush With No Events Sends Nothing 12ms
Batch Format.Multiple Events Batched Together 36ms
Error Handling.Does Not Retry On 403 4023ms
Error Handling.Does Not Retry On 413 4020ms
Error Handling.Retries On 408 5028ms

Feature_Flags Tests

17/17 tests passed

View Details
Test Status Duration
Request Payload.Request With Person Properties Device Id 36ms
Request Payload.Flags Request Uses V2 Query Param 27ms
Request Payload.Flags Request Hits Flags Path Not Decide 22ms
Request Payload.Flags Request Omits Authorization Header 27ms
Request Payload.Token In Flags Body Matches Init 21ms
Request Payload.Groups Round Trip 24ms
Request Payload.Groups Default To Empty Object 21ms
Request Payload.Disable Geoip False Propagates As Geoip Disable False 20ms
Request Payload.Disable Geoip Omitted Defaults To False 21ms
Request Payload.Flag Keys To Evaluate Contains Only Requested Key 32ms
Request Lifecycle.No Flags Request On Init Alone 12ms
Request Lifecycle.No Flags Request On Normal Capture 20ms
Request Lifecycle.Two Flag Calls Produce Two Remote Requests 46ms
Request Lifecycle.Mock Response Value Is Returned To Caller 23ms
Retry Behavior.Retries Flags On 502 327ms
Retry Behavior.Retries Flags On 504 322ms
Side Effect Events.Get Feature Flag Captures Feature Flag Called Event 21ms

@github-actions

github-actions Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor
⚠️ `posthog-server` is modified but not declared in any changeset

This is informational — the PR is not blocked. Click the triangle above to collapse, or push a fix and this comment will auto-delete.

Modified in this PR but not in any changeset:

  • posthog-server

If this package should ship the change, add it to the changeset frontmatter:

---
"posthog-server": patch
---

Changesets in this PR:

  • posthog — patch
  • posthog-android — patch

@marandaneto
marandaneto marked this pull request as ready for review August 14, 2026 08:37
@marandaneto
marandaneto requested a review from a team as a code owner August 14, 2026 08:37
@greptile-apps

greptile-apps Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Reviews (1): Last reviewed commit: "docs: clarify logcat timestamp fix" | Re-trigger Greptile

@arnohillen

Copy link
Copy Markdown

(created by claude code)

[arno's agent] Real logcat -v epoch lines start with leading spaces because liblog prints the seconds right-aligned via snprintf("%19lld") (verified in AOSP logprint from android-7.0.0_r1 through Android 14), so LOG_LINE_RE with matcher.matches() never matches and every console log line is dropped; the pattern needs a leading \s* (or trim the line before parse) and a test with a padded line.

@arnohillen

Copy link
Copy Markdown

(created by claude code)

[arno's agent] The epoch format modifier only exists from Android 7.0 (absent in android-6.0.1_r81 logprint.c, added in android-7.0.0_r1, same for the -T %s.%q epoch parsing), so on minSdk 23 devices logcat exits with 'Invalid parameter epoch to -v' and console log capture stops working entirely; this needs an SDK_INT gate or a minSdk bump.

@marandaneto

Copy link
Copy Markdown
Member Author

@arnohillen Good catch on the padding. Fixed in d7c357b: the epoch parser now accepts logcat’s leading whitespace, with a regression test using a right-aligned epoch line.

@marandaneto

Copy link
Copy Markdown
Member Author

@arnohillen Session Replay is only supported on API 26+ (PostHogReplayIntegration.isSupported() requires Build.VERSION.SDK_INT >= Build.VERSION_CODES.O). Console log events are only captured while Session Replay is active, so the API 23 logcat -v epoch limitation is outside the supported Session Replay range and does not require an additional gate here.

@marandaneto
marandaneto requested a review from arnohillen August 14, 2026 11:10

@arnohillen arnohillen left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(created by claude code)

[arno's agent] Verified at d7c357b: LOG_LINE_RE now tolerates the right-aligned epoch padding with a regression test on a 19-char padded line, and your API 26 argument for the second point checks out (console lines only emit while isSessionReplayActive() is true, and isSupported() requires SDK_INT >= O in install()).

@arnohillen

Copy link
Copy Markdown

(created by claude code)

[arno's agent] isSessionReplayActive() can still return true below API 26 because start() sets the active flag without an isSupported() check, so API 23 devices that previously emitted console-only events now stop silently; those recordings have no snapshots so the loss looks acceptable, noting it only because the API 26+ gate is enforced in install() rather than start().

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants