Repository navigation
Show Spotnana's hotel cancellation deadline instead of check-in minus duration #105325
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
c53c6d3
cf0b6e6
329151e
a1bef9f
68c3394
ac89863
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,8 +8,6 @@ import type IconAsset from '@src/types/utils/IconAsset'; | |
|
|
||
| import type {ArrayValues} from 'type-fest'; | ||
|
|
||
| import {format, subSeconds} from 'date-fns'; | ||
| import {fromZonedTime, toZonedTime} from 'date-fns-tz'; | ||
| import {SafeString} from 'expensify-common'; | ||
|
|
||
| import {getMoneyRequestSpendBreakdown} from './ReportUtils'; | ||
|
|
@@ -241,35 +239,21 @@ function getAirReservations(pnr: Pnr, travelers: PnrTraveler[]): ReservationItem | |
| } | ||
|
|
||
| function getCancellationDeadline(pnrData: HotelPnr): string | undefined { | ||
| // A deadline would show "Free cancellation until …", which is wrong for a non-refundable room. | ||
| if (pnrData.room.cancellationPolicy?.policy === CONST.CANCELLATION_POLICY.NON_REFUNDABLE) { | ||
| return undefined; | ||
| } | ||
| const deadlineUtc = pnrData.room.cancellationPolicy?.deadlineUtc?.iso8601; | ||
| const duration = pnrData.room.cancellationPolicy?.durationBeforeArrivalDeadline?.iso8601; | ||
| const checkIn = pnrData.checkInDateTime?.iso8601; | ||
| if (deadlineUtc && duration && checkIn) { | ||
| // 1. Extract the target UTC offset from deadlineUtc (e.g. "-04:00") | ||
| const match = deadlineUtc.match(/(Z|[+-]\d{2}:\d{2})$/); | ||
| if (!match) { | ||
| return pnrData.room.cancellationPolicy?.deadlineUtc?.iso8601; | ||
| } | ||
| const utcOffset = match[1] === 'Z' ? '+00:00' : match[1]; | ||
|
|
||
| // 2. Convert the check-in wall-clock time to an absolute UTC instant, | ||
| // treating it as being expressed in the target offset timezone. | ||
| const checkInUtc = fromZonedTime(checkIn, utcOffset); | ||
|
|
||
| // 3. Subtract the duration in UTC-space (preserves wall-clock intent for | ||
| // day/hour units; DST transitions are irrelevant for fixed offsets). | ||
| const deadlineUtcInstant = subSeconds(checkInUtc, parseDurationToSeconds(duration)); | ||
|
|
||
| // 4. Convert back to the target offset's wall-clock representation. | ||
| const deadlineZoned = toZonedTime(deadlineUtcInstant, utcOffset); | ||
|
|
||
| // 5. Format as ISO-8601 with the explicit offset suffix. | ||
| // `format` with "yyyy-MM-dd'T'HH:mm:ss" gives the bare local datetime; | ||
| // we then append the offset so the result is a proper fixed-offset string. | ||
| const localPart = format(deadlineZoned, "yyyy-MM-dd'T'HH:mm:ss"); | ||
| return `${localPart}${utcOffset}`; | ||
| // `deadlineUtc` with an offset is the exact deadline Spotnana shows, in the hotel's timezone. | ||
| if (deadlineUtc && CONST.REGEX.ISO_8601_UTC_OFFSET_SUFFIX.test(deadlineUtc)) { | ||
| return deadlineUtc; | ||
| } | ||
| // `deadline` is usually the hotel's wall-clock time with no offset. | ||
| const deadline = pnrData.room.cancellationPolicy?.deadline?.iso8601; | ||
| if (deadline) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If Spotnana sends a
This comment was marked as resolved.
Sorry, something went wrong.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @MelvinBot Can you verify my response to the comment based on this earlier Slack feedback comment ?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The linked comment doesn't support your reply. It covers only the
If Spotnana ever sends a My suggestion: Even if the Slack answer holds, add a one-line guard. Skip the deadline when Details
Reply
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @MelvinBot add the guard
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I added the guard in 68c3394. A room whose policy is
Correction to my last comment: The Checks: |
||
| return deadline; | ||
| } | ||
| return pnrData.room.cancellationPolicy?.deadlineUtc?.iso8601; | ||
| return deadlineUtc; | ||
| } | ||
|
|
||
| function getHotelReservations(pnr: Pnr, travelers: PnrTraveler[]): ReservationItem[] { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When a device timezone crosses a year boundary relative to UTC, this conversion makes the subsequent
isThisYear(date)check use a different calendar year fromformatInTimeZone. For example, on Dec 31, 2026 in Los Angeles, the wall-clock deadline2027-01-01T00:30:00becomes a Date that is still Dec 31 locally, so the formatter outputs Jan 1 without2027. Compare the year invenueTimezone(or directly from the wall-clock value) before choosing the pattern.Useful? React with 👍 / 👎.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟢 Codex's comment is valid but low impact; I'd treat it as optional (NAB).
Why it happens: isThisYear(date) checks the year in the device's timezone, while formatInTimeZone prints the date in the hotel's timezone. Near New Year the two can fall in different years, so the year gets dropped.
Three caveats:
The fix is one line, so it's fine for Melvin to take it. Check the year in the hotel's timezone instead of the device's:
@trjExpensify quick product check: when Spotnana sends a deadline with no timezone (car bookings, or the hotel fallback), we now show the hotel's local time with no label, e.g.
Monday, Oct 19 11:59 PM. Before, it showed a shifted time labeledUTC, which was wrong. Hotel deadlines that do have an offset still show it, e.g.GMT+1. Are you ok with no label in that case?Note
Asked on Slack as well and got a response.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ended up going with:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it's 0.5% of bookings, I think it's fine.
I seem to recall this topic before somewhere, and there's some Spotnana limitation of sending us the actual timezone abbreviation for the hotel - which would make this a lot cleaner overall. I.e
OCT 19 11:59 PM, BST.