Skip to content

Fix locale-dependent inbox date serialization breaking the inbox API - #8

Open
fa0311 wants to merge 2 commits into
salesforce-marketingcloud:mainfrom
fa0311:fix/inbox-date-serialization
Open

Fix locale-dependent inbox date serialization breaking the inbox API#8
fa0311 wants to merge 2 commits into
salesforce-marketingcloud:mainfrom
fa0311:fix/inbox-date-serialization

Conversation

@fa0311

@fa0311 fa0311 commented Jul 19, 2026

Copy link
Copy Markdown

The inbox date formatters on both platforms depend on the device locale and timezone. On an iPhone set to a 12-hour clock the formatter emits 2026-05-02 3:56:00 AM (Apple QA1480), which DateTime.parse rejects. Parsing runs in a single .map() over the whole list, so one bad date makes getMessages() and the other inbox getters throw, and inbox listeners silently stop firing. Android has the same defect with non-Latin-digit locales (ar, th, ...), and its *Utc fields actually carry local wall time.

Both bridges now emit yyyy-MM-dd'T'HH:mm:ss'Z' with a fixed locale and UTC, and the Dart side parses leniently (keeping a fallback for the old formats) and skips a malformed message instead of failing the whole list.

Related to #7, which fixes the iOS formatter the same way but doesn't touch the Android side or the all-or-nothing parsing. This also makes the endDateUtc fix from #4 future-proof: every NSDate in the payload gets converted, and NSJSONSerialization is guarded so an unconverted value can no longer crash the app.

11 of the new tests fail on main as it is today.

…lt-tolerant

Inbox message dates were serialized with locale- and timezone-sensitive
formatters on both platforms and parsed with an unguarded DateTime.parse
on the Dart side, so a single unexpected date string or value could break
the entire inbox API.

iOS (InboxUtility.m):
- The NSDateFormatter had no locale or timezone. Per Apple QA1480, a
  device with a 12-hour clock rewrites the "HH" pattern, producing
  strings like "2026-05-02 3:56:00 AM" (the inconsistent sendDateUtc
  format reported in PR salesforce-marketingcloud#7), and non-Gregorian/non-Latin-digit locales
  produce strings Dart cannot parse at all. The formatter now uses
  en_US_POSIX, UTC and a fixed ISO-8601 pattern
  (yyyy-MM-dd'T'HH:mm:ss'Z'), shared via dispatch_once.
- convertDatesInMessage now converts every top-level NSDate in the
  message instead of a hardcoded three-key whitelist, so a date field
  added by a future SDK version cannot reach NSJSONSerialization
  unconverted (the crash class behind PR salesforce-marketingcloud#4's endDateUtc report).
- convertDictionaryToJSONString and convertCustomObjectInMessage now
  check [NSJSONSerialization isValidJSONObject:] first; a non-JSON-safe
  message degrades to nil (skipped by callers) instead of throwing an
  uncatchable NSInvalidArgumentException and crashing the app.

Android (InboxUtils.kt):
- SimpleDateFormat had no Locale or TimeZone, so *Utc fields carried
  device-local wall time, and locales with non-Latin digits (ar, fa, bn)
  or a Buddhist calendar (th) emitted strings DateTime.parse rejects,
  breaking getMessages() entirely. It now uses Locale.US, UTC and the
  same ISO-8601 pattern as iOS so both platforms emit identical strings.

Dart:
- InboxMessage.fromJson now parses dates leniently: DateTime.tryParse
  first, then a fallback for the legacy formats already in the wild
  ("yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm:ss.SSS" and the 12-hour
  "yyyy-MM-dd h:mm:ss a Z" iOS variant), returning null instead of
  throwing on unparseable values.
- _parseMessages and the onInboxMessagesChanged handler now wrap
  per-message parsing in try/catch, so one malformed message is logged
  and skipped instead of failing the whole
  getMessages/getReadMessages/getUnreadMessages/getDeletedMessages
  Future or silently suppressing inbox listeners.

Adds unit tests covering all supported date formats (including 12-hour
AM/PM edge cases and offsets) and the malformed-message-skip behavior
for both the fetch and listener paths.

Fixes the root cause behind upstream PRs salesforce-marketingcloud#7 and salesforce-marketingcloud#4.
…yParse

The rescue regex only recovered English AM/PM strings, while the same
legacy iOS formatter emitted locale-dependent forms (Japanese 午前/午後,
non-Latin digits, non-Gregorian years) that can never be enumerated
exhaustively. Those strings crashed the old unguarded DateTime.parse
anyway, so returning null for every pre-ISO string is not a regression,
and every format both native bridges actually emit today (ISO-8601 UTC,
plus the legacy 24-hour space-separated forms) is already valid
DateTime.tryParse input.
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