You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[$175] Travel-Cancellation policy date in ND is different from the cancellation policy in Spotnana #104704
Version Number: v9.5.16-0 Reproducible in staging?: Yes Reproducible in production?: Yes If this was caught during regression testing, add the test name, ID and link from BrowserStack:https://github.com/Expensify/Expensify/issues/658088 Email or phone of affected tester (no customers): - Issue reported by: Applause Internal Team Bug source: Regression TC Execution Device used: Windows 11 Pro - Chrome
NewDot doesn't show the deadline Spotnana sends. It builds its own deadline instead: check-in time minus durationBeforeArrivalDeadline (TripReservationUtils.ts:243-273). It ignores deadline and only reads deadlineUtc for its UTC offset.
That duration is rounded to whole days, so the built date often misses the real deadline. With the sample payload from the earlier PR, NewDot shows Wednesday, Apr 22 3:00 PM, GMT-4. Spotnana's own deadline is Apr 21 11:59 PM, GMT-4, about 15 hours and one calendar day earlier.
Some payloads make it worse:
parseDurationToSeconds (TripReservationUtils.ts:79-90) returns 0 for formats like P1W, P1M, or PT2D. NewDot then shows the check-in time as the deadline.
When deadlineUtc ends in Z, the hotel's local check-in time is treated as UTC.
The earlier PR picked this method to match the Spotnana screen for one booking, and no test covers it.
What changes do you think we should make in order to solve the problem?
In getCancellationDeadline:
Use deadlineUtc.iso8601 as the source when it has an offset. It is the exact deadline Spotnana returns.
Only use check-in minus duration when deadlineUtc is missing. Anchor the duration regex and support weeks.
Fall back to deadline.iso8601 when both are missing. Today the page drops to generic policy text instead.
Before implementing, someone should check which value Spotnana's screen shows for this booking. The earlier PR found its screen matched check-in minus duration for one booking. If that is always true, keep the duration method and fix only the parser and fallbacks.
What alternative solutions did you explore? (Optional)
Asking Spotnana for the hotel's IANA timezone (for example, America/New_York). That would let NewDot show "EST" instead of "GMT-4". It doesn't fix the date mismatch on its own.
Sample payload: checkInDateTime 2026-04-24T15:00:00, deadline 2026-04-21T23:59:00, deadlineUtc 2026-04-21T23:59:00-04:00, duration P2D. The result is 15:00 on Apr 24 minus 2 days, which gives Apr 22 3:00 PM.
If checkInDateTime ever carries an offset, fromZonedTime reads device-local hours. The result would then depend on the viewer's timezone. The current payloads have no offset.
Limits: I couldn't see the raw booking data or Spotnana's value from this report's video. So I can't confirm which payload shape the tester hit. Confidence: medium.
Next Steps for Contributor+ team: To accept:@MelvinBot implement [this](https://github.com/Expensify/App/issues/104704) to create a draft PR. To refine:@MelvinBot <your feedback> To reject: Explain why you are rejecting Melvin's proposal.
changed the title [-]Travel-Cancellation policy date in ND is different from the cancellation policy in Spotnana[/-][+][$175] Travel-Cancellation policy date in ND is different from the cancellation policy in Spotnana[/+]on Oct 22, 2026
Please re-state the problem that we are trying to solve in this issue.
In Travel hotel trip details, the cancellation policy deadline date and time displayed in NewDot differs from the cancellation policy deadline shown in Spotnana.
What is the root cause of that problem?
In src/libs/TripReservationUtils.ts:243-273, getCancellationDeadline attempts to recalculate the cancellation deadline by taking the hotel's checkInDateTime (e.g. 15:00:00) and subtracting durationBeforeArrivalDeadline (e.g. P2D -> 2 days): ypescript const deadlineUtcInstant = subSeconds(checkInUtc, parseDurationToSeconds(duration));
In doing so, it only uses deadlineUtc.iso8601 to extract the trailing UTC offset regex /(Z|[+-]\d{2}:\d{2})$/ and discards the actual, authoritative timestamp provided by Spotnana.
Synthetic vs Real Deadline Mismatch:
Hotels and GDS/Spotnana suppliers typically set the cancellation deadline to end-of-day (e.g., 2026-04-21T23:59:00-04:00). Subtracting 2 days from a 3:00 PM check-in on April 24 artificially computes 2026-04-22T15:00:00-04:00. This leads to a divergence of nearly 15 hours and displays the wrong calendar date in NewDot.
Authoritative Payload Discarded:
Spotnana already computes and delivers the exact pre-calculated supplier cancellation deadline in pnrData.room.cancellationPolicy.deadlineUtc.iso8601 (or deadline.iso8601). getCancellationDeadline ignores this value whenever duration and checkIn are present.
What changes do you think we should make in order to solve the problem?
In src/libs/TripReservationUtils.ts, update getCancellationDeadline:
Directly return Spotnana's authoritative deadlineUtc.iso8601 (or deadline.iso8601). Only fall back to synthetic calculation (checkIn - duration) if Spotnana does not provide a direct deadline timestamp:
100% Spotnana Parity: Guarantees NewDot displays the exact cancellation deadline timestamp determined by the hotel supplier and Spotnana.
Proper Timezone Formatting Preserved: DateUtils.getFormattedCancellationDate already extracts the explicit UTC offset from deadlineUtc.iso8601 (e.g. -04:00) and formats the date in the hotel's venue timezone without local machine drift.
Graceful Fallback: If a provider only supplies duration without a timestamp, the existing fallback computation remains intact.
What alternative solutions did you explore? (Optional)
MelvinBot's suggestion to re-parse duration with weeks/months regex:
Rejected. Even with a perfect duration parser, checkIn - duration will always diverge from Spotnana because hotel policies use fixed end-of-day cutoff hours (e.g. 11:59 PM or 6:00 PM local time), not the hotel's 3:00 PM check-in time. Using Spotnana's authoritative deadlineUtc directly is the only accurate solution.
What specific scenarios should we cover in automated tests to prevent reintroduction of this issue in the future?
In ests/unit/TripReservationUtilsTest.ts:
Provide a HotelPnr fixture with deadlineUtc: {iso8601: '2026-04-21T23:59:00-04:00'} and durationBeforeArrivalDeadline: {iso8601: 'P2D'} with checkInDateTime: {iso8601: '2026-04-24T15:00:00'}.
Assert that getCancellationDeadline returns '2026-04-21T23:59:00-04:00' matching Spotnana's deadline rather than the synthetic '2026-04-22T15:00:00-04:00'.
Please re-state the problem that we are trying to solve in this issue.
In Travel hotel trip details, the free-cancellation deadline shown in NewDot (date and time) doesn't match the deadline Spotnana shows for the same booking. This is a regression of #87076, which PR #87755 was meant to fix.
What is the root cause of that problem?
getCancellationDeadline in src/libs/TripReservationUtils.ts (~L243-273) doesn't display the deadline Spotnana returns. It rebuilds one:
That is checkInDateTime − durationBeforeArrivalDeadline. deadlineUtc.iso8601 is read only to extract the trailing UTC offset, and the timestamp itself is discarded.
This produces the wrong value for several reasons:
The duration is coarse.P2D is a whole-day approximation. Hotel cutoffs are usually a fixed local time such as end of day. With the sample payload (check-in 2026-04-24T15:00, deadline2026-04-21T23:59, deadlineUtc2026-04-21T23:59:00-04:00, duration P2D), NewDot shows Apr 22 3:00 PM and Spotnana's own deadline is Apr 21 11:59 PM. That is a different calendar day and about 15 hours off.
parseDurationToSeconds is brittle. It returns 0 for P1W, P1M, PT2D and similar. NewDot then shows the check-in time as the deadline.
The Z case is wrong. When deadlineUtc ends in Z, the hotel's local wall-clock check-in time is treated as UTC.
There is no test coverage. The existing hotel fixture has no duration and an empty deadlineUtc, so the computed path is never exercised.
What changes do you think we should make in order to solve the problem?
All changes are in getCancellationDeadline, in this order of precedence:
Use the deadline Spotnana returns when it carries an offset. If cancellationPolicy.deadlineUtc.iso8601 ends in ±HH:mm, return it as is. DateUtils.getFormattedCancellationDate already formats in that offset's timezone, so the date and time match Spotnana with no drift.
Handle a Z deadline. An offset of Z would display in UTC, not the hotel's zone.
If cancellationPolicy.deadline.iso8601 (hotel-local wall clock) is also present, derive the hotel's offset from the difference between the two values.
Return the instant re-expressed with that offset, for example 2026-04-21T23:59:00-04:00.
Fall back to deadline.iso8601. Use it if deadlineUtc is missing, together with the check-in offset when one is available. Today the page falls through to generic policy text in this case.
Keep checkIn − duration only as a last resort. Use it when neither deadline field is present. Harden the parser for this path:
Anchor the regex, for example ^P(?:(\d+)W)?(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?)?$.
Support weeks.
Return undefined for unparseable input instead of 0, so the UI shows generic text rather than a wrong date.
Add unit tests in tests/unit/TripReservationUtilsTest.ts (see Testing).
Verification before merge:#87755 chose checkIn − duration because it matched Spotnana's screen for one booking. Compare Spotnana's UI against deadlineUtc / deadline for a few real bookings, including the one in this report's video. If Spotnana's UI turns out to display the computed value, keep the duration path first and apply only the parser and fallback fixes (items 3 and 4). In that case, record the finding in a code comment.
What alternative solutions did you explore? (Optional)
Only fix the duration parser (weeks, months, anchoring), as MelvinBot suggested. This is rejected as the primary fix. Even with a perfect parser, checkIn − duration diverges from an end-of-day cutoff like 23:59. It is kept as a fallback only.
Ask Spotnana for the hotel's IANA timezone. This would allow "EST" instead of "GMT-4", but it doesn't fix the date mismatch, so it is out of scope.
Return deadlineUtc.iso8601 unconditionally, as both contributor proposals do. This is incomplete because it shows UTC when the value ends in Z and has no handling for missing fields.
Testing
Unit tests for getCancellationDeadline (via getReservationDetailsFromSpotnanaReservation/HotelPnr fixtures):
deadlineUtc: 2026-04-21T23:59:00-04:00, duration P2D, check-in 2026-04-24T15:00:00 returns 2026-04-21T23:59:00-04:00, not 2026-04-22T15:00:00-04:00.
deadlineUtc: 2026-04-22T03:59:00Z with deadline: 2026-04-21T23:59:00 returns 2026-04-21T23:59:00-04:00.
deadlineUtc empty with deadline present returns the deadline value.
Both missing with duration P2D returns the check-in minus 2 days fallback.
Formatting: DateUtils.getFormattedCancellationDate renders "Tuesday, Apr 21 11:59 PM, GMT-4" and gives the same result under different TZ env values (device-timezone independence).
Manual: book a refundable hotel through FAB → Book Travel, open the trip details in NewDot, and confirm the cancellation date and time match Spotnana. Repeat for a booking with a non-US timezone and for a car trip, to confirm no regression of #87076.
This issue never had the Help Wanted label, so only MelvinBot's proposal counts.
Frontend or backend ?
Frontend. The backend passes Spotnana's booking data through unchanged, and the date mismatch comes from getCancellationDeadline. The video shows the data is fine:
NewDot shows Tuesday, Oct 20 3:00 PM, GMT+1, which is check-in Oct 21 3:00 PM minus P1D. The +01:00 offset comes from deadlineUtc and is correct for London in October.
Spotnana's cancel page shows Oct 19, 2026, 11:59 PM, which is the deadline / deadlineUtc value.
In April the same Spotnana page (trips/hotel/cancel) showed Apr 22, 2026, 3:00 PM (check-in minus P2D) while deadline was Apr 21 23:59. That's why #87755 copied the check-in minus duration method on purpose. Spotnana now shows the real deadline, so that method no longer matches.
Repro steps
Correct. Any refundable hotel reproduces it, because hotel deadlines are end of day (23:59) and check-in is usually 15:00. Step 5 should say which Spotnana screen to compare against: Trips > trip > Cancel trip (You can cancel this reservation before ...). The regression test from #87076 uses a 3:00 PM example, so it needs updating after the fix.
MelvinBot
Root cause ✅. MelvinBot asked to check which value Spotnana shows before implementing. The video answers that: Spotnana shows deadline, so step 1 (use deadlineUtc.iso8601 when it has an offset) is the fix. 2026-10-19T23:59:00+01:00 renders as Monday, Oct 19 11:59 PM, GMT+1, which matches Spotnana.
🟡 Step 3: deadline.iso8601 has no offset. getFormattedCancellationDate runs it through new Date(), which reads it as the device's local time, then formats it in UTC. Anyone outside UTC would see the wrong time. This fallback needs to keep the hotel's local time as is, not convert it.
🟡 Step 2: I'd remove the check-in minus durationBeforeArrivalDeadline code for hotels instead of improving its duration parsing. Spotnana no longer uses that method, and no payload in this issue or #87076 had P1W. Flights still use parseDurationToSeconds, so keep that function.
🟢 Tests: please add the London booking from the video (deadlineUtc...+01:00, duration P1D, check-in 15:00) and assert Monday, Oct 19 11:59 PM, GMT+1. That covers the case where the old method and Spotnana disagree.
MelvinBot's proposal looks good to me with the changes above.
@MelvinBot proposal LGTM, see Detailed proposals review above for details on why.
The open question is answered by the video: Spotnana's cancel page now shows the deadline value (Oct 19, 2026, 11:59 PM), not check-in minus duration. So step 1 (use deadlineUtc when it has an offset) is the fix.
@MelvinBot implement this to open a draft PR, with these notes:
Use deadlineUtc.iso8601 when it ends in an offset.
Add ISO_8601_UTC_OFFSET_SUFFIX: /([+-]\d{2}:\d{2})$/ to CONST.REGEX and use it in both getCancellationDeadline and DateUtils.getFormattedCancellationDate (DateUtils.ts:973), so the offset check and the formatter can't drift apart.
Remove the check-in minus durationBeforeArrivalDeadline code for hotels instead of improving its duration parsing. Keep parseDurationToSeconds, flights use it.
If you fall back to deadline.iso8601, don't let getFormattedCancellationDate read it as the device's local time.
Add a test with the London booking (deadlineUtc2026-10-19T23:59:00+01:00, P1D, check-in 2026-10-21T15:00:00) that expects Monday, Oct 19 11:59 PM, GMT+1.
It follows all five of your notes. One choice to check:
No-offset times lose the timezone label. A deadline.iso8601 fallback is now shown as the hotel's wall-clock time, with no label. The old UTC label was wrong for a hotel-local time. This also changes car deadlines, which use the same formatter. If you'd rather keep UTC, it's a one-line change.
Jest, typecheck, lint, and spellcheck pass locally. The oxfmt format check couldn't run here. I didn't run a browser test because it needs a real Spotnana hotel booking.
Activity
MelvinBot commentedon Oct 22, 2026
Proposal
This bug came back after the fix in [Due for payment 2026-07-08] [$250] Cancellation policy date in NewDot is different from the cancellation policy in Spotnana. That fix shipped in Fix cancellation policy date timezone conversion for hotel and car trips.
What is the root cause of that problem?
NewDot doesn't show the deadline Spotnana sends. It builds its own deadline instead: check-in time minus
durationBeforeArrivalDeadline(TripReservationUtils.ts:243-273). It ignoresdeadlineand only readsdeadlineUtcfor its UTC offset.That duration is rounded to whole days, so the built date often misses the real deadline. With the sample payload from the earlier PR, NewDot shows Wednesday, Apr 22 3:00 PM, GMT-4. Spotnana's own deadline is Apr 21 11:59 PM, GMT-4, about 15 hours and one calendar day earlier.
Some payloads make it worse:
parseDurationToSeconds(TripReservationUtils.ts:79-90) returns 0 for formats likeP1W,P1M, orPT2D. NewDot then shows the check-in time as the deadline.deadlineUtcends inZ, the hotel's local check-in time is treated as UTC.The earlier PR picked this method to match the Spotnana screen for one booking, and no test covers it.
What changes do you think we should make in order to solve the problem?
In
getCancellationDeadline:deadlineUtc.iso8601as the source when it has an offset. It is the exact deadline Spotnana returns.deadlineUtcis missing. Anchor the duration regex and support weeks.deadline.iso8601when both are missing. Today the page drops to generic policy text instead.deadlineUtc(TripReservationUtilsTest.ts:2095-2101).Before implementing, someone should check which value Spotnana's screen shows for this booking. The earlier PR found its screen matched check-in minus duration for one booking. If that is always true, keep the duration method and fix only the parser and fallbacks.
What alternative solutions did you explore? (Optional)
Asking Spotnana for the hotel's IANA timezone (for example,
America/New_York). That would let NewDot show "EST" instead of "GMT-4". It doesn't fix the date mismatch on its own.Investigation details
HotelTripDetails.tsx:40-42shows the hotel cancellation date. It gets the value fromTripReservationUtils.ts:317and formats it in the offset's timezone viaDateUtils.ts:969-980.checkInDateTime2026-04-24T15:00:00,deadline2026-04-21T23:59:00,deadlineUtc2026-04-21T23:59:00-04:00, durationP2D. The result is 15:00 on Apr 24 minus 2 days, which gives Apr 22 3:00 PM.checkInDateTimeever carries an offset,fromZonedTimereads device-local hours. The result would then depend on the viewer's timezone. The current payloads have no offset.Next Steps for Contributor+ team:
To accept:
@MelvinBot implement [this](https://github.com/Expensify/App/issues/104704)to create a draft PR.To refine:
@MelvinBot <your feedback>To reject: Explain why you are rejecting Melvin's proposal.
view run
melvin-bot commentedon Oct 22, 2026
Triggered auto assignment to Contributor-plus team member for initial proposal review - @eh2077 (
External)[-]Travel-Cancellation policy date in ND is different from the cancellation policy in Spotnana[/-][+][$175] Travel-Cancellation policy date in ND is different from the cancellation policy in Spotnana[/+]melvin-bot commentedon Oct 22, 2026
Job added to Upwork: https://www.upwork.com/jobs/~022113204504339570998
melvin-bot commentedon Oct 22, 2026
✅ Contributor details stored successfully. Thank you for contributing to Expensify!
camesenin commentedon Oct 22, 2026
Contributor details
Your Expensify account email: camesenin@gmail.com
Upwork Profile Link: https://www.upwork.com/freelancers/~0170d6b02e9440e604
Proposal
Please re-state the problem that we are trying to solve in this issue.
In Travel hotel trip details, the cancellation policy deadline date and time displayed in NewDot differs from the cancellation policy deadline shown in Spotnana.
What is the root cause of that problem?
In src/libs/TripReservationUtils.ts:243-273, getCancellationDeadline attempts to recalculate the cancellation deadline by taking the hotel's checkInDateTime (e.g. 15:00:00) and subtracting durationBeforeArrivalDeadline (e.g. P2D -> 2 days):
ypescript const deadlineUtcInstant = subSeconds(checkInUtc, parseDurationToSeconds(duration));In doing so, it only uses deadlineUtc.iso8601 to extract the trailing UTC offset regex /(Z|[+-]\d{2}:\d{2})$/ and discards the actual, authoritative timestamp provided by Spotnana.
Hotels and GDS/Spotnana suppliers typically set the cancellation deadline to end-of-day (e.g., 2026-04-21T23:59:00-04:00). Subtracting 2 days from a 3:00 PM check-in on April 24 artificially computes 2026-04-22T15:00:00-04:00. This leads to a divergence of nearly 15 hours and displays the wrong calendar date in NewDot.
Spotnana already computes and delivers the exact pre-calculated supplier cancellation deadline in pnrData.room.cancellationPolicy.deadlineUtc.iso8601 (or deadline.iso8601). getCancellationDeadline ignores this value whenever duration and checkIn are present.
What changes do you think we should make in order to solve the problem?
In src/libs/TripReservationUtils.ts, update getCancellationDeadline:
Directly return Spotnana's authoritative deadlineUtc.iso8601 (or deadline.iso8601). Only fall back to synthetic calculation (checkIn - duration) if Spotnana does not provide a direct deadline timestamp:
`diff
--- a/src/libs/TripReservationUtils.ts
+++ b/src/libs/TripReservationUtils.ts
@@ -241,6 +241,13 @@ function getAccommodations(pnr: Pnr): ReservationItem[] {
}
function getCancellationDeadline(pnrData: HotelPnr): string | undefined {
const deadlineUtc = pnrData.room.cancellationPolicy?.deadlineUtc?.iso8601;
const duration = pnrData.room.cancellationPolicy?.durationBeforeArrivalDeadline?.iso8601;
const checkIn = pnrData.checkInDateTime?.iso8601;
`
Why this solution is optimal:
What alternative solutions did you explore? (Optional)
Rejected. Even with a perfect duration parser, checkIn - duration will always diverge from Spotnana because hotel policies use fixed end-of-day cutoff hours (e.g. 11:59 PM or 6:00 PM local time), not the hotel's 3:00 PM check-in time. Using Spotnana's authoritative deadlineUtc directly is the only accurate solution.
What specific scenarios should we cover in automated tests to prevent reintroduction of this issue in the future?
In ests/unit/TripReservationUtilsTest.ts:
melvin-bot commentedon Oct 22, 2026
✅ Contributor details stored successfully. Thank you for contributing to Expensify!
Abdallah-1551 commentedon Oct 22, 2026
Proposal
Please re-state the problem that we are trying to solve in this issue.
In Travel hotel trip details, the free-cancellation deadline shown in NewDot (date and time) doesn't match the deadline Spotnana shows for the same booking. This is a regression of #87076, which PR #87755 was meant to fix.
What is the root cause of that problem?
getCancellationDeadlineinsrc/libs/TripReservationUtils.ts(~L243-273) doesn't display the deadline Spotnana returns. It rebuilds one:That is
checkInDateTime − durationBeforeArrivalDeadline.deadlineUtc.iso8601is read only to extract the trailing UTC offset, and the timestamp itself is discarded.This produces the wrong value for several reasons:
P2Dis a whole-day approximation. Hotel cutoffs are usually a fixed local time such as end of day. With the sample payload (check-in2026-04-24T15:00,deadline2026-04-21T23:59,deadlineUtc2026-04-21T23:59:00-04:00, durationP2D), NewDot shows Apr 22 3:00 PM and Spotnana's own deadline is Apr 21 11:59 PM. That is a different calendar day and about 15 hours off.parseDurationToSecondsis brittle. It returns0forP1W,P1M,PT2Dand similar. NewDot then shows the check-in time as the deadline.Zcase is wrong. WhendeadlineUtcends inZ, the hotel's local wall-clock check-in time is treated as UTC.deadlineUtc, so the computed path is never exercised.What changes do you think we should make in order to solve the problem?
All changes are in
getCancellationDeadline, in this order of precedence:Use the deadline Spotnana returns when it carries an offset. If
cancellationPolicy.deadlineUtc.iso8601ends in±HH:mm, return it as is.DateUtils.getFormattedCancellationDatealready formats in that offset's timezone, so the date and time match Spotnana with no drift.Handle a
Zdeadline. An offset ofZwould display in UTC, not the hotel's zone.cancellationPolicy.deadline.iso8601(hotel-local wall clock) is also present, derive the hotel's offset from the difference between the two values.2026-04-21T23:59:00-04:00.deadlineis absent, return theZvalue.localIsomust have no offset.Fall back to
deadline.iso8601. Use it ifdeadlineUtcis missing, together with the check-in offset when one is available. Today the page falls through to generic policy text in this case.Keep
checkIn − durationonly as a last resort. Use it when neither deadline field is present. Harden the parser for this path:^P(?:(\d+)W)?(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?)?$.undefinedfor unparseable input instead of0, so the UI shows generic text rather than a wrong date.Add unit tests in
tests/unit/TripReservationUtilsTest.ts(see Testing).Verification before merge: #87755 chose
checkIn − durationbecause it matched Spotnana's screen for one booking. Compare Spotnana's UI againstdeadlineUtc/deadlinefor a few real bookings, including the one in this report's video. If Spotnana's UI turns out to display the computed value, keep the duration path first and apply only the parser and fallback fixes (items 3 and 4). In that case, record the finding in a code comment.What alternative solutions did you explore? (Optional)
checkIn − durationdiverges from an end-of-day cutoff like 23:59. It is kept as a fallback only.deadlineUtc.iso8601unconditionally, as both contributor proposals do. This is incomplete because it shows UTC when the value ends inZand has no handling for missing fields.Testing
Unit tests for
getCancellationDeadline(viagetReservationDetailsFromSpotnanaReservation/HotelPnr fixtures):deadlineUtc: 2026-04-21T23:59:00-04:00, durationP2D, check-in2026-04-24T15:00:00returns2026-04-21T23:59:00-04:00, not2026-04-22T15:00:00-04:00.deadlineUtc: 2026-04-22T03:59:00Zwithdeadline: 2026-04-21T23:59:00returns2026-04-21T23:59:00-04:00.deadlineUtcempty withdeadlinepresent returns thedeadlinevalue.P2Dreturns the check-in minus 2 days fallback.P1Wreturns check-in minus 7 days. Garbage duration returnsundefined.DateUtils.getFormattedCancellationDaterenders "Tuesday, Apr 21 11:59 PM, GMT-4" and gives the same result under differentTZenv values (device-timezone independence).Manual: book a refundable hotel through FAB → Book Travel, open the trip details in NewDot, and confirm the cancellation date and time match Spotnana. Repeat for a booking with a non-US timezone and for a car trip, to confirm no regression of #87076.
5 remaining items
eh2077 commentedon Oct 28, 2026
reassign issue cuz I'll be ooo
melvin-bot commentedon Oct 28, 2026
Triggered auto assignment to Contributor-plus team member for initial proposal review - @ikevin127 (
External)ikevin127 commentedon Oct 29, 2026
Detailed proposals review
Proposals review
This issue never had the
Help Wantedlabel, so only MelvinBot's proposal counts.Frontend or backend ?
Frontend. The backend passes Spotnana's booking data through unchanged, and the date mismatch comes from
getCancellationDeadline. The video shows the data is fine:Tuesday, Oct 20 3:00 PM, GMT+1, which is check-inOct 21 3:00 PMminusP1D. The+01:00offset comes fromdeadlineUtcand is correct for London in October.Oct 19, 2026, 11:59 PM, which is thedeadline/deadlineUtcvalue.In April the same Spotnana page (
trips/hotel/cancel) showedApr 22, 2026, 3:00 PM(check-in minusP2D) whiledeadlinewasApr 21 23:59. That's why #87755 copied the check-in minus duration method on purpose. Spotnana now shows the real deadline, so that method no longer matches.Repro steps
Correct. Any refundable hotel reproduces it, because hotel deadlines are end of day (23:59) and check-in is usually 15:00. Step 5 should say which Spotnana screen to compare against: Trips > trip > Cancel trip (
You can cancel this reservation before ...). The regression test from #87076 uses a3:00 PMexample, so it needs updating after the fix.MelvinBot
Root cause ✅. MelvinBot asked to check which value Spotnana shows before implementing. The video answers that: Spotnana shows
deadline, so step 1 (usedeadlineUtc.iso8601when it has an offset) is the fix.2026-10-19T23:59:00+01:00renders asMonday, Oct 19 11:59 PM, GMT+1, which matches Spotnana.🟡 Step 3:
deadline.iso8601has no offset.getFormattedCancellationDateruns it throughnew Date(), which reads it as the device's local time, then formats it inUTC. Anyone outside UTC would see the wrong time. This fallback needs to keep the hotel's local time as is, not convert it.🟡 Step 2: I'd remove the check-in minus
durationBeforeArrivalDeadlinecode for hotels instead of improving its duration parsing. Spotnana no longer uses that method, and no payload in this issue or #87076 hadP1W. Flights still useparseDurationToSeconds, so keep that function.🟢 Tests: please add the London booking from the video (
deadlineUtc...+01:00, durationP1D, check-in15:00) and assertMonday, Oct 19 11:59 PM, GMT+1. That covers the case where the old method and Spotnana disagree.MelvinBot's proposal looks good to me with the changes above.
@MelvinBot proposal LGTM, see Detailed proposals review above for details on why.
The open question is answered by the video: Spotnana's cancel page now shows the
deadlinevalue (Oct 19, 2026, 11:59 PM), not check-in minus duration. So step 1 (usedeadlineUtcwhen it has an offset) is the fix.🎀👀🎀 C+ reviewed
melvin-bot commentedon Oct 29, 2026
Triggered auto assignment to @lakchote, see https://stackoverflow.com/c/expensify/questions/7972 for more details.
ikevin127 commentedon Oct 29, 2026
@MelvinBot implement this to open a draft PR, with these notes:
deadlineUtc.iso8601when it ends in an offset.ISO_8601_UTC_OFFSET_SUFFIX: /([+-]\d{2}:\d{2})$/toCONST.REGEXand use it in bothgetCancellationDeadlineandDateUtils.getFormattedCancellationDate(DateUtils.ts:973), so the offset check and the formatter can't drift apart.durationBeforeArrivalDeadlinecode for hotels instead of improving its duration parsing. KeepparseDurationToSeconds, flights use it.deadline.iso8601, don't letgetFormattedCancellationDateread it as the device's local time.deadlineUtc2026-10-19T23:59:00+01:00,P1D, check-in2026-10-21T15:00:00) that expectsMonday, Oct 19 11:59 PM, GMT+1.MelvinBot commentedon Oct 29, 2026
🤖 Draft PR created: Show Spotnana's hotel cancellation deadline instead of check-in minus duration
Please mark it as "Ready for review" or ask me to do that for you when it is ready for review.
It follows all five of your notes. One choice to check:
deadline.iso8601fallback is now shown as the hotel's wall-clock time, with no label. The oldUTClabel was wrong for a hotel-local time. This also changes car deadlines, which use the same formatter. If you'd rather keepUTC, it's a one-line change.Jest, typecheck, lint, and spellcheck pass locally. The
oxfmtformat check couldn't run here. I didn't run a browser test because it needs a real Spotnana hotel booking.