Skip to content

Fix cancellation policy date timezone conversion for hotel and car trips - #87755

Merged
marcochavezf merged 17 commits into
mainfrom
claude-fixCancellationDateTimezone
Jun 29, 2026
Merged

Fix cancellation policy date timezone conversion for hotel and car trips#87755
marcochavezf merged 17 commits into
mainfrom
claude-fixCancellationDateTimezone

Conversation

@MelvinBot

@MelvinBot MelvinBot commented Apr 13, 2026

Copy link
Copy Markdown
Contributor

Explanation of Change

For Spotnana hotel reservation data

{
    "ancillaries": [],
    "checkInDateTime": {
        "iso8601": "2026-04-24T15:00:00"
    },
    "room": {
        "cancellationPolicy": {
            "deadlineUtc": {
                "iso8601": "2026-04-21T23:59:00-04:00"
            },
            "durationBeforeArrivalDeadline": {
                "iso8601": "P2D"
            }
        }
    }
}

calculate cancellation date by subtracting durationBeforeArrivalDeadline from checkInDateTime and apply timezone from deadlineUtc. So we can have cancellation message like

Free cancellation until Tuesday, Apr 22 3:00 PM, GMT-4

Fixed Issues

$ #87076
PROPOSAL: #87076 (comment)

Tests

  1. Book a hotel that has a free cancellation policy
  2. Go to the details in Expensify
  3. Look at the cancellation policy and compare it to Spotnana
  4. Verify that cancellation policy date in NewDot should be the same as the cancellation policy in Spotnana and there's venue timezone at the end, eg
Free cancellation until Tuesday, Jul 28 3:00 PM, GMT-4
  • Verify that no errors appear in the JS console

Offline tests

Same as Tests

QA Steps

  1. Book a hotel that has a free cancellation policy
  2. Go to the details in Expensify
  3. Look at the cancellation policy and compare it to Spotnana
  4. Verify that cancellation policy date in NewDot should be the same as the cancellation policy in Spotnana and there's venue timezone at the end, eg
Free cancellation until Tuesday, Jul 28 3:00 PM, GMT-4
  • Verify that no errors appear in the JS console

PR Author Checklist

  • I linked the correct issue in the ### Fixed Issues section above
  • I wrote clear testing steps that cover the changes made in this PR
    • I added steps for local testing in the Tests section
    • I added steps for the expected offline behavior in the Offline steps section
    • I added steps for Staging and/or Production testing in the QA steps section
    • I added steps to cover failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
    • I tested this PR with a High Traffic account against the staging or production API to ensure there are no regressions (e.g. long loading states that impact usability).
  • I included screenshots or videos for tests on all platforms
  • I ran the tests on all platforms & verified they passed on:
    • Android: Native
    • Android: mWeb Chrome
    • iOS: Native
    • iOS: mWeb Safari
    • MacOS: Chrome / Safari
  • I verified there are no console errors (if there's a console error not related to the PR, report it or open an issue for it to be fixed)
  • I followed proper code patterns (see Reviewing the code)
    • I verified that any callback methods that were added or modified are named for what the method does and never what callback they handle (i.e. toggleReport and not onIconClick)
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained "why" the code was doing something instead of only explaining "what" the code was doing.
    • I verified any copy / text that was added to the app is grammatically correct in English. It adheres to proper capitalization guidelines (note: only the first word of header/labels should be capitalized), and is either coming verbatim from figma or has been approved by marketing (in order to get marketing approval, ask the Bug Zero team member to add the Waiting for copy label to the issue)
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I followed the guidelines as stated in the Review Guidelines
  • I tested other components that can be impacted by my changes (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar are working as expected)
  • If any new file was added I verified that:
    • The file has a description of what it does and/or why is needed at the top of the file if the code is not self explanatory
  • If a new CSS style is added I verified that:
    • A similar style doesn't already exist
    • The style can't be created with an existing StyleUtils function (i.e. StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))
  • If new assets were added or existing ones were modified, I verified that:
    • The assets are optimized and compressed (for SVG files, run npm run compress-svg)
    • The assets load correctly across all supported platforms.
  • If the PR modifies code that runs when editing or sending messages, I tested and verified there is no unexpected behavior for all supported markdown - URLs, single line code, code blocks, quotes, headings, bold, strikethrough, and italic.
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
  • If the PR modifies a component or page that can be accessed by a direct deeplink, I verified that the code functions as expected when the deeplink is used - from a logged in and logged out account.
  • If the PR modifies the UI (e.g. new buttons, new UI components, changing the padding/spacing/sizing, moving components, etc) or modifies the form input styles:
    • I verified that all the inputs inside a form are aligned with each other.
    • I added Design label and/or tagged @Expensify/design so the design team can review the changes.
  • I added unit tests for any new feature or bug fix in this PR to help automatically prevent regressions in this user flow.
  • If the main branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the Test steps.

Screenshots/Videos

Android: Native
Android: mWeb Chrome
iOS: Native
iOS: mWeb Safari
MacOS: Chrome / Safari

…imezone

The cancellation deadline ISO string from Spotnana contains the venue's
local time with a timezone offset. Stripping the offset before parsing
ensures the date/time is displayed as-is (venue-local), matching Spotnana.
Applied to both hotel and car trip details.

Co-authored-by: Eric Han <eh2077@users.noreply.github.com>
@codecov

codecov Bot commented Apr 13, 2026

Copy link
Copy Markdown

Codecov Report

❌ Looks like you've decreased code coverage for some files. Please write tests to increase, or at least maintain, the existing level of code coverage. See our documentation here for how to interpret this table.

Files with missing lines Coverage Δ
src/libs/DateUtils.ts 70.02% <100.00%> (+1.32%) ⬆️
src/pages/Travel/HotelTripDetails.tsx 96.66% <ø> (ø)
src/pages/Travel/CarTripDetails.tsx 0.00% <0.00%> (ø)
src/libs/TripReservationUtils.ts 81.85% <57.14%> (-2.70%) ⬇️
... and 13 files with indirect coverage changes

Comment thread src/libs/DateUtils.ts Outdated
Comment on lines +843 to +845
// Strip timezone offset (e.g., +07:00, -04:00, Z) to treat the date/time as venue-local
const naiveDateString = isoDateString.replace(/([+-]\d{2}:\d{2}|Z)$/, '');
const date = new Date(naiveDateString);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@MelvinBot no, you should use method parseISO, according to #87076 (comment). Please update and add necessary unit tests.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Updated to use parseISO from date-fns instead of new Date(), and added unit tests covering:

  • Positive timezone offset stripping (+07:00)
  • Negative timezone offset stripping (-05:00)
  • UTC indicator stripping (Z)
  • Non-current-year formatting (includes year)
  • ISO string without timezone offset

…e parsing

Replace new Date(naiveDateString) with parseISO(naiveDateString) in
getFormattedCancellationDate as recommended in review. Add unit tests
covering timezone offset stripping, current year vs other year formatting,
and ISO strings without timezone offsets.

Co-authored-by: Eric Han <eh2077@users.noreply.github.com>
Comment thread src/libs/DateUtils.ts Outdated
Comment on lines +844 to +846
// Strip timezone offset (e.g., +07:00, -04:00, Z) to treat the date/time as venue-local
const naiveDateString = isoDateString.replace(/([+-]\d{2}:\d{2}|Z)$/, '');
const date = parseISO(naiveDateString);

@eh2077 eh2077 Apr 13, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@MelvinBot Parse isoDateString directly with method parseISO

Suggested change
// Strip timezone offset (e.g., +07:00, -04:00, Z) to treat the date/time as venue-local
const naiveDateString = isoDateString.replace(/([+-]\d{2}:\d{2}|Z)$/, '');
const date = parseISO(naiveDateString);
const date = parseISO(isoDateString);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@MelvinBot check the above comment!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done — removed the timezone offset stripping and now parsing isoDateString directly with parseISO. Updated unit tests accordingly.

Parse isoDateString directly with parseISO as requested in review,
removing the manual timezone offset stripping. Updated unit tests
to reflect the new behavior.

Co-authored-by: Eric Han <eh2077@users.noreply.github.com>
@MelvinBot

Copy link
Copy Markdown
Contributor Author

The only failing check is PR Reviewer Checklist / checklist, which is unrelated to the code changes — it tracks whether reviewers have completed their checklist. All code-related checks (ESLint, Prettier, builds, typecheck, etc.) are passing or still pending. No fix needed.

@eh2077

eh2077 commented Apr 14, 2026

Copy link
Copy Markdown
Contributor

@MelvinBot Mark it ready for review

@MelvinBot

Copy link
Copy Markdown
Contributor Author

🤖 I attempted to mark this PR as ready for review, but the command was not authorized in my current environment.

eh2077 — you can mark it ready yourself by clicking the "Ready for review" button on the PR page, or by running:

gh pr ready 87755 --repo Expensify/App

@eh2077

eh2077 commented Apr 14, 2026

Copy link
Copy Markdown
Contributor

@MelvinBot C+ doesn't have write access to mark PR ready for review. Can you try again?

@MelvinBot
MelvinBot marked this pull request as ready for review April 14, 2026 14:25
@MelvinBot
MelvinBot requested a review from a team as a code owner April 14, 2026 14:25
@MelvinBot

Copy link
Copy Markdown
Contributor Author

🤖 Done — PR is now marked as ready for review.

@melvin-bot
melvin-bot Bot requested review from eh2077 and trjExpensify and removed request for a team April 14, 2026 14:25
@melvin-bot

melvin-bot Bot commented Apr 14, 2026

Copy link
Copy Markdown

@eh2077 Please copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button]

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 00171f9115

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/libs/DateUtils.ts Outdated
Comment on lines 842 to 844
const date = parseISO(isoDateString);
if (isThisYear(date)) {
return format(date, 'EEEE, MMM d h:mm a');

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve venue-local cancellation time when parsing deadlines

Parsing cancellationDeadline with parseISO() still applies the embedded offset and then format() renders in the device timezone, so users outside the venue timezone will continue to see shifted times (and potentially the wrong day) for cancellation cutoffs. For example, 2026-04-19T15:00:00+07:00 will display as early morning in US timezones instead of 3:00 PM, which defeats the intended Spotnana-aligned behavior and can mislead users about the true cancellation window.

Useful? React with 👍 / 👎.

@eh2077

eh2077 commented Apr 14, 2026

Copy link
Copy Markdown
Contributor

@MelvinBot I think we should use a different approach.

  1. use the duration before arrival date time, eg PT2D, define prop in TripData.ts
            /** Deadline in duration before the check-in date time. */
            durationBeforeArrivalDeadline: {
                /** ISO 8601 format. */
                iso8601: string;
            };
  1. in src/libs/TripReservationUtils.ts, make method parseDurationToSeconds support parse PT2D
  2. in src/libs/TripReservationUtils.ts, let
    cancellationDeadline = pnrData.checkInDateTime?.iso8601 subtract durationBeforeArrivalDeadline

Can you try this?

- Add durationBeforeArrivalDeadline prop to HotelPnr cancellationPolicy type
- Update parseDurationToSeconds to support day durations (e.g., P2D, P2DT3H)
- Compute hotel cancellationDeadline by subtracting duration from checkInDateTime
- Fall back to existing deadline.iso8601 if no duration is available

Co-authored-by: Eric Han <eh2077@users.noreply.github.com>
@MelvinBot
MelvinBot requested a review from a team as a code owner April 14, 2026 17:12
@melvin-bot
melvin-bot Bot requested review from marcochavezf and removed request for a team April 14, 2026 17:12
@melvin-bot

melvin-bot Bot commented Apr 14, 2026

Copy link
Copy Markdown

@marcochavezf Please copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button]

@MelvinBot

Copy link
Copy Markdown
Contributor Author

Done — updated to use the duration-based approach:

  1. Added durationBeforeArrivalDeadline (optional) to HotelPnr.room.cancellationPolicy in TripData.ts
  2. Updated parseDurationToSeconds to support day durations (P2D, P2DT3H, etc.)
  3. Added getCancellationDeadline helper in TripReservationUtils.ts that computes checkInDateTime - durationBeforeArrivalDeadline (falls back to deadline.iso8601 if no duration is available)

@trjExpensify trjExpensify left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@stitesExpensify do you have eyes on this? A couple of thoughts:

  • It sounds like the cancellation date and time in Spotnana is the Hotel's local timezone, but it doesn't actually show that. Spotnana would do better here to include the timezone for clarity. Maybe we can suggest it?
Image
  • I agree the date/time we add to the "Cancellation policy" row in the product here should be accurate:
Image
  1. show it "converted" to the local time of the user
  2. or show it in the "venue" time but include the timezone in the string so it's clear

As Spotnana doesn't include the timezone on their side, our "conversion" to local does make it a bit confusing - appearing as though there's a mismatch. So maybe the second option is the one to go for, for now?

Restore src/libs/DateUtils.ts getFormattedCancellationDate to the
formatInTimeZone approach without the GMT timezone label, matching commit
d2694e2. Remove the now-unused parseISO import. Update the unit tests to
expect output without the trailing timezone-label suffix, keeping the
pinned system clock so year display stays deterministic.

Co-authored-by: Eric Han <eh2077@users.noreply.github.com>
@MelvinBot

Copy link
Copy Markdown
Contributor Author

Restored src/libs/DateUtils.ts:840 to the version from commit d2694e2b and updated the tests to match. Pushed as f8ba298.

What changed

  • getFormattedCancellationDate now uses formatInTimeZone(date, venueTimezone, pattern) (venue-local time, no appended , GMT+7 / , UTC label), matching d2694e2b.
  • Removed the now-unused parseISO import (it was only used by the previous implementation).
  • Updated tests/unit/DateUtilsTest.ts: dropped the trailing timezone-label suffix from the three expected strings. I kept the pinned system clock (jest.setSystemTime) from the current tests so the hardcoded 2026 dates don't flip their year display now that the real clock is in 2026 — the year-stability fix is independent of the formatting approach.
Why this is safe even though the prior commit moved away from formatInTimeZone

The earlier switch away from formatInTimeZone was made because it threw a RangeError when combined with the zzz token (added in a later commit 0d8c72d3 to render a timezone abbreviation). The d2694e2b version restored here uses no timezone token in its pattern (EEEE, MMM d, yyyy h:mm a), so that code path is never hit:

  • date-fns-tz/format only resolves a timezone name for tokens matched by /([xXOz]+)|.../ — i.e. x, X, O, z. Our pattern contains none of them, so it just calls toZonedTime (which accepts a raw +07:00 offset) followed by plain format.
  • formatInTimeZone's own JSDoc states the timeZone arg "can be an offset or IANA time zone".

So a raw offset like +07:00 is valid input and no RangeError is possible here.

⚠️ I could not run npm test / npm run lint / npm run typecheck in this environment (JS execution is sandboxed for this run), so I verified correctness statically. CI will run the full suite on this push — please confirm the checks go green.

@eh2077

eh2077 commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

@MelvinBot apply this commit 0d8c72d and fix unit tests

MelvinBot and others added 2 commits June 15, 2026 13:59
Add zzz to the format pattern so the venue timezone is displayed
alongside the date/time (e.g. "Sunday, Apr 19, 2026 3:00 PM, GMT+7").
Updated unit tests to match.

Co-authored-by: Eric Han <eh2077@users.noreply.github.com>
formatInTimeZone's zzz token uses Intl.DateTimeFormat, which throws on raw
offset strings like +07:00. Compute the GMT label from the offset and append
it manually so offsets parsed from the ISO string work.

Co-authored-by: Eric Han <eh2077@users.noreply.github.com>
@MelvinBot

Copy link
Copy Markdown
Contributor Author

Applied 0d8c72d and fixed the unit tests. Pushed two commits:

  1. Cherry-picked the commit — adds the venue timezone to the cancellation date display, and updated the test expectations. There was a conflict in tests/unit/DateUtilsTest.ts (the branch's deterministic fake-timer test vs. the commit's new Date().getFullYear() version); I kept the fake-timer approach and applied the commit's , UTC suffix.

  2. Fixed a real bug the cherry-pick exposed. The commit's zzz format token can never work here: formatInTimeZone's zzz formatter calls Intl.DateTimeFormat, which throws RangeError: Invalid time zone specified on raw offset strings like +07:00 / +00:00 (it only accepts IANA names or UTC). Since venueTimezone is the offset parsed from the ISO string, two of the three tests crashed.

    The fix drops zzz and derives the label from the offset, appending it manually — same output the commit intended (GMT+7, UTC):

    return `${formatInTimeZone(date, venueTimezone, pattern)}, ${getCancellationDateTimezoneLabel(venueTimezone)}`;

Tests: npm test -- tests/unit/DateUtilsTest.ts69 passed, 0 failed.

Other checks
  • npx prettier — clean.
  • npm run typecheck-tsgo reported ~40 errors, but all are pre-existing/environmental (Onyx initWithStoredValues/canEvict options, ViewShot, AnimatedStyleHandle) in unrelated files — none in DateUtils.ts. My change produced no type errors.
  • ESLint could not run locally (Node OOM in this environment); CI will cover it.

@MelvinBot

Copy link
Copy Markdown
Contributor Author

⏱️ Ran out of time before I could finish. This usually means the task was too complex or I got stuck exploring. Please try again with a more specific request or post in #melvinbot-ai.

Run details

@eh2077

eh2077 commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

@MelvinBot merge with main

…nDateTimezone

Co-authored-by: Eric Han <eh2077@users.noreply.github.com>
@MelvinBot

Copy link
Copy Markdown
Contributor Author

🤖 Merged the latest main into this branch — no conflicts. Head is now c675c957ad82.

ℹ️ GitHub's signed-commit validation timed out on the first few push attempts (a transient platform issue), so this took a couple of retries to land.

Comment on lines +237 to +246
function getCancellationDeadline(pnrData: HotelPnr): string | undefined {
const duration = pnrData.room.cancellationPolicy?.durationBeforeArrivalDeadline?.iso8601;
const checkIn = pnrData.checkInDateTime?.iso8601;
if (duration && checkIn) {
const durationSeconds = parseDurationToSeconds(duration);
return subSeconds(parseISO(checkIn), durationSeconds).toISOString();
}
return pnrData.room.cancellationPolicy?.deadlineUtc?.iso8601;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@MelvinBot replace this method with

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;
    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}`;
    }
    return pnrData.room.cancellationPolicy?.deadlineUtc?.iso8601;
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done — replaced getCancellationDeadline with your version in src/libs/TripReservationUtils.ts:237 and added the required imports (format from date-fns, fromZonedTime/toZonedTime from date-fns-tz; dropped the now-unused parseISO).

Checks run locally on the changed file:

  • lint
  • typecheck (tsgo) ✅
  • prettier
  • npm test tests/unit/TripReservationUtilsTest.ts ✅ (37/37)
  • npm test tests/ui/ReservationAddressTest.tsx ✅ (2/2)

Pushed as 73c17ff.

Co-authored-by: Eric Han <eh2077@users.noreply.github.com>
@eh2077

eh2077 commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Reviewer Checklist

  • I have verified the author checklist is complete (all boxes are checked off).
  • I verified the correct issue is linked in the ### Fixed Issues section above
  • I verified testing steps are clear and they cover the changes made in this PR
    • I verified the steps for local testing are in the Tests section
    • I verified the steps for Staging and/or Production testing are in the QA steps section
    • I verified the steps cover any possible failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
  • I checked that screenshots or videos are included for tests on all platforms
  • I included screenshots or videos for tests on all platforms
  • I verified that the composer does not automatically focus or open the keyboard on mobile unless explicitly intended. This includes checking that returning the app from the background does not unexpectedly open the keyboard.
  • I verified tests pass on all platforms & I tested again on:
    • Android: HybridApp
    • Android: mWeb Chrome
    • iOS: HybridApp
    • iOS: mWeb Safari
    • MacOS: Chrome / Safari
  • If there are any errors in the console that are unrelated to this PR, I either fixed them (preferred) or linked to where I reported them in Slack
  • I verified proper code patterns were followed (see Reviewing the code)
    • I verified that any callback methods that were added or modified are named for what the method does and never what callback they handle (i.e. toggleReport and not onIconClick).
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained "why" the code was doing something instead of only explaining "what" the code was doing.
    • I verified any copy / text that was added to the app is grammatically correct in English. It adheres to proper capitalization guidelines (note: only the first word of header/labels should be capitalized), and is either coming verbatim from figma or has been approved by marketing (in order to get marketing approval, ask the Bug Zero team member to add the Waiting for copy label to the issue)
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I verified that this PR follows the guidelines as stated in the Review Guidelines
  • I verified other components that can be impacted by these changes have been tested, and I retested again (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar have been tested & I retested again)
  • If a new component is created I verified that:
    • A similar component doesn't exist in the codebase
    • All props are defined accurately and each prop has a /** comment above it */
    • The file is named correctly
    • The component has a clear name that is non-ambiguous and the purpose of the component can be inferred from the name alone
    • The only data being stored in the state is data necessary for rendering and nothing else
    • For Class Components, any internal methods passed to components event handlers are bound to this properly so there are no scoping issues (i.e. for onClick={this.submit} the method this.submit should be bound to this in the constructor)
    • Any internal methods bound to this are necessary to be bound (i.e. avoid this.submit = this.submit.bind(this); if this.submit is never passed to a component event handler like onClick)
    • All JSX used for rendering exists in the render method
    • The component has the minimum amount of code necessary for its purpose, and it is broken down into smaller components in order to separate concerns and functions
  • If any new file was added I verified that:
    • The file has a description of what it does and/or why is needed at the top of the file if the code is not self explanatory
  • If a new CSS style is added I verified that:
    • A similar style doesn't already exist
    • The style can't be created with an existing StyleUtils function (i.e. StyleUtils.getBackgroundAndBorderStyle(theme.componentBG)
  • If the PR modifies code that runs when editing or sending messages, I tested and verified there is no unexpected behavior for all supported markdown - URLs, single line code, code blocks, quotes, headings, bold, strikethrough, and italic.
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
  • If the PR modifies a component or page that can be accessed by a direct deeplink, I verified that the code functions as expected when the deeplink is used - from a logged in and logged out account.
  • If the PR modifies the UI (e.g. new buttons, new UI components, changing the padding/spacing/sizing, moving components, etc) or modifies the form input styles:
    • I verified that all the inputs inside a form are aligned with each other.
    • I added Design label and/or tagged @Expensify/design so the design team can review the changes.
  • For any bug fix or new feature in this PR, I verified that sufficient unit tests are included to prevent regressions in this flow.
  • If the main branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the Test steps.
  • I have checked off every checkbox in the PR reviewer checklist, including those that don't apply to this PR.

Screenshots/Videos

Android: HybridApp image image
Android: mWeb Chrome image image
iOS: HybridApp image image
iOS: mWeb Safari
Screen.Recording.2026-06-25.at.8.48.05.PM.mov
MacOS: Chrome / Safari
Screen.Recording.2026-06-25.at.8.44.14.PM.mov

@eh2077

This comment was marked as resolved.

@eh2077

eh2077 commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

@MelvinBot apply #87755 (comment) as PR checklist

@MelvinBot

Copy link
Copy Markdown
Contributor Author

🤖 Done — I've applied the referenced comment as the PR description/checklist.

@eh2077 eh2077 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks good!

@eh2077

eh2077 commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

@MelvinBot resolve conflicts

…nDateTimezone

Co-authored-by: Eric Han <eh2077@users.noreply.github.com>

# Conflicts:
#	tests/unit/DateUtilsTest.ts
@MelvinBot

Copy link
Copy Markdown
Contributor Author

🤖 Resolved the merge conflict with main and pushed the merge commit (909a8a3).

Conflict: tests/unit/DateUtilsTest.ts — both branches added a new describe block immediately after normalizeDateToEndOfDay. This branch added getFormattedCancellationDate tests; main added getRemainingSecondsInWindow tests. They're independent, so I kept both blocks (each properly closed). src/libs/DateUtils.ts auto-merged cleanly, and both functions are present and exported in the merged file.

⚠️ I wasn't able to run the Jest suite in this environment (test commands require interactive approval here). The resolution is a purely additive combination of two independent, previously-passing test blocks, but please re-run npx jest tests/unit/DateUtilsTest.ts locally to confirm before merging.

@eh2077

eh2077 commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

@marcochavezf it's your call, thx!

@marcochavezf
marcochavezf merged commit 9895fe5 into main Jun 29, 2026
37 checks passed
@marcochavezf
marcochavezf deleted the claude-fixCancellationDateTimezone branch June 29, 2026 12:58
@OSBotify

Copy link
Copy Markdown
Contributor

✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release.

@github-actions

Copy link
Copy Markdown
Contributor

🚧 marcochavezf has triggered a test Expensify/App build. You can view the workflow run here.

@OSBotify

Copy link
Copy Markdown
Contributor

🚀 Deployed to staging by https://github.com/marcochavezf in version: 9.4.25-0 🚀

platform result
🕸 web 🕸 success ✅
🤖 android 🤖 success ✅
🍎 iOS 🍎 success ✅

@OSBotify

OSBotify commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

🚀 Deployed to production by https://github.com/cristipaval in version: 9.4.25-2 🚀

platform result
🕸 web 🕸 success ✅
🤖 android 🤖 success ✅
🍎 iOS 🍎 success ✅

Bundle Size Analysis (Sentry):

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.

6 participants