Skip to content

[iOS] SFRestAPI httpDateFormatter uses local timezone and truncates milliseconds - #4190

Merged
JohnsonEricAtSalesforce merged 2 commits into
forcedotcom:devfrom
JohnsonEricAtSalesforce:bugfix/ios-sfrestapi-httpdateformatter-uses-local-timezone-and-truncates-milliseconds
Oct 22, 2026
Merged

JohnsonEricAtSalesforce merged 2 commits into
forcedotcom:devfrom
JohnsonEricAtSalesforce:bugfix/ios-sfrestapi-httpdateformatter-uses-local-timezone-and-truncates-milliseconds

Conversation

@JohnsonEricAtSalesforce

@JohnsonEricAtSalesforce JohnsonEricAtSalesforce commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Use GMT and en_US_POSIX for conditional-update HTTP dates. Preserve fractional-second truncation to avoid advancing the caller's cutoff. A fractional-second false HTTP 412 was not reproduced in the tested scope; date-based validation remains weak.

Route the integration test through the production date parameter, removing its manual-header workaround.

Test plan:

  • Formatter regressions and live unchanged/stale-date controls.
  • Core: 1,115 passed locally and in CI.
  • UI CI: login-form timeout also occurs at the unchanged original base.

This response was generated by an AI agent on behalf of @JohnsonEricAtSalesforce.

@github-actions

Copy link
Copy Markdown
TestsPassedSkippedFailed ❌️
AuthFlowTester UI Test Results all1 ran1 ❌
TestResult
AuthFlowTester UI Test Results all
AuthFlowTesterUITests.xctest
LegacyLoginTests.testCAOpaque_DefaultScopes_WebServerFlow()❌ failure

@codecov

codecov Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 70.06%. Comparing base (01930a9) to head (59dcd84).
⚠️ Report is 6 commits behind head on dev.

Additional details and impacted files
@@            Coverage Diff             @@
##              dev    #4190      +/-   ##
==========================================
- Coverage   72.92%   70.06%   -2.86%     
==========================================
  Files         254      254              
  Lines       23531    23533       +2     
==========================================
- Hits        17160    16489     -671     
- Misses       6371     7044     +673     
Components Coverage Δ
Analytics 70.78% <ø> (ø)
Common 71.20% <ø> (ø)
Core 65.19% <100.00%> (-4.23%) ⬇️
SmartStore 73.45% <ø> (ø)
MobileSync 88.99% <ø> (ø)
Files with missing lines Coverage Δ
...Core/SalesforceSDKCore/Classes/RestAPI/SFRestAPI.m 89.81% <100.00%> (-1.53%) ⬇️

... and 30 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@github-actions

Copy link
Copy Markdown
TestsPassed ✅SkippedFailed
SalesforceSDKCore iOS ^18 Test Results1115 ran1115 ✅
TestResult
No test annotations available

@wmathurin

Copy link
Copy Markdown
Contributor

The GMT and en_US_POSIX corrections look right, but I recommend separating the ceil() change from this fix because it weakens the conditional-update guarantee.

For example:

  • We read a record with LastModifiedDate = 12:00:00.100.
  • Another client modifies it at 12:00:00.800.
  • Rounding our timestamp up sends If-Unmodified-Since: … 12:00:01 GMT.
  • The intervening change is before that cutoff, so our update can succeed and overwrite it instead of returning 412.

There is no lossless client-side rounding solution with this second-precision header: rounding down can produce false 412s, while rounding up can miss conflicts. Re-reading before updating does not close the race between those requests. The PR description acknowledges the rounding limitation, but documenting it alone does not preserve the public API's existing “not modified since the supplied date” contract.

Salesforce documents If-Unmodified-Since as weak validation. Where supported, retaining the ETag from a read and sending it with If-Match provides strong validation; the documentation currently limits that support to Account records. See Conditional Request Headers. For other objects requiring reliable conflict detection, an application-level alternative is a server-side transaction that locks the record, checks an expected version after acquiring the lock, and then updates it; see FOR UPDATE.

My recommendation is to ship the GMT/locale correction and preserve the conservative timestamp behavior for now, treating rounding as a separate decision about the concurrency contract. I also checked all five SDK libraries at this PR head: no production caller supplies a non-nil conditional date, so built-in MobileSync does not execute this formatter; the behavior change affects consumers explicitly using the conditional-update API.

Posted by Codex on behalf of @wmathurin.

@JohnsonEricAtSalesforce

Copy link
Copy Markdown
Contributor Author

Agreed, Wolf. This follow-up commit removes upward rounding while retaining the GMT and en_US_POSIX corrections.

  • Fractional dates retain truncation; the cutoff does not advance past the supplied date.
  • Exact seconds remain unchanged, and a nil date still omits the header.

The revised regression test fails with ceil() and passes after its removal. Focused tests pass in English and French, and the production-path conditional-update integration test passes. The full SalesforceSDKCore suite passed 1,115 tests with no failures or skips on a fresh iOS 18.6 simulator.

The fractional-second false-412 limitation remains unresolved and needs a separate concurrency design. This patch does not provide strong validation.

This response was generated by an AI agent on behalf of @JohnsonEricAtSalesforce.

@JohnsonEricAtSalesforce
JohnsonEricAtSalesforce merged commit 9554dfb into forcedotcom:dev Oct 22, 2026
23 of 24 checks passed
@JohnsonEricAtSalesforce
JohnsonEricAtSalesforce deleted the bugfix/ios-sfrestapi-httpdateformatter-uses-local-timezone-and-truncates-milliseconds branch October 22, 2026 21:14
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