Skip to content

Conversation

@Dmytro-Melnyshyn
Copy link
Contributor

@Dmytro-Melnyshyn Dmytro-Melnyshyn commented Jul 31, 2025

Fix expiration date issues.

Problems

  1. The expiration date field shows one day ahead for the timezone, which is already the next day (Pacific/Fakaofo). This happens because the expiration date is not saved at the end of the day, it is saved with a time shift based on your timezone (e.g. 2025-08-07T20:59:59.999Z). The expiration date must display the same date for all timezones (UIU-3352).
  2. User accounts expire at 5PM instead of midnight in Vancouver (UTC-7). Users must expire at the end of the day (UIU-3400, UIU-2182). This happens because the expiration date is saved with a time offset based on the time zone (e.g., for a user in UTC+3 it will be 2025-08-06T20:59:59.999+00:00). If the person who created a user was in UTC+3, but the user who opened the record was in UTC-7, the expiration date will be 1:59:59.999 PM.
  3. When a user is created and then Edit page is opened in the timezone, which is already the next day (Pacific/Fakaofo), then selecting a patron group with a 1-day offset, the modal window shows up with the correct day, but after hitting "Set", the one day earlier is inserted to the expiration date field. - 1-day offset must be added.

Approach

1). Store dates in UTC - so "07/28/2025" becomes "2025-07-28T23:59:59.999Z" (end of day UTC)
2). Display dates without timezone conversion - so every timezone sees "07/28/2025"
3). Apply timezone logic only for expiration checking - convert UTC stored date to local timezone for comparison

  1. Save dates as UTC end of day using dayjs.utc (e.g. 2025-08-07T23:59:59.999Z), so that the expiration date field will display the same year, month, and day for all timezones (implemented in the parseExpirationDate function).
  2. Most likely, the user records were created in the UTC timezone (default setting), and then patrons from Vancouver (UTC-7) got time shifting (24 - 7 = 17 PM). Or the user records were created in the Vancouver (UTC-7) timezone (setting set to Vancouver), but patron computers were not located in UTC-7.
  • Save dates as UTC (end of day) using dayjs.utc (e.g. 2025-08-07T23:59:59.999Z), so that the expiration date field will display the same year, month, and day for all timezones (implemented in the parseExpirationDate).
  • When checking for expiration (in isUserExpired), also use dayjs.utc to avoid time shifting. This will allow having the correct year, month, and day.
  • Then extract just the date part (YYYY-MM-DD), as time may not match the end of the day for some existing data.
  • Then shift the day to the end.
  • Consider the tenant's timezone when forming the current time (now), since without .tz(...), the timezone selected in the settings will not be taken into account, instead the local system/browser timezone will be taken into account.
  • Then compare the expiration date with now.
  1. Change from .format('L') to .format('YYYY-MM-DD') in the setRecalculatedExpirationDate.
  2. Migrate moment to dayjs everywhere for the expiration date functionality.

Description

Based on the conversation summary and the current state of the files, I can describe what was accomplished in this comprehensive timezone bug fix:

What Was Done

  1. Fixed Core Timezone Bug
  • Problem: Users in non-UTC timezones (like Vancouver, UTC-8) were expiring at 5:00 PM instead of midnight due to incorrect timezone handling
  • Solution: Implemented timezone-aware expiration logic where users expire at midnight in their tenant's configured timezone, not UTC midnight
  1. Complete Library Migration (moment.js → dayjs)
  • Migrated EditUserInfo.js: Replaced all moment.js usage with dayjs for better performance and consistency
  • Migrated getProxySponsorWarning.js: Recently completed the migration by replacing the remaining moment(proxyRel.proxy.expirationDate) call with dayjs.tz() and removing the unused moment import
  • Updated ProxyEditItem.js: Uses timezone parameter from stripes for consistent date operations
  1. Architectural Design Decisions
  • UTC Storage Pattern: Dates are stored as UTC start-of-day (e.g., "2025-07-31T00:00:00.000Z") for consistent database storage
  • Timezone-Aware Business Logic: All expiration comparisons use the tenant's timezone to determine when users actually expire (midnight in their local time)
    Consistent Display: FormattedDate components use timeZone="UTC" to show the same date across all timezones
  1. Key Functions Implemented
    In EditUserInfo.js:
  • parseExpirationDate(): Converts input dates to UTC ISO strings for storage
  • calculateNewExpirationDate(): Performs date arithmetic in tenant timezone
  • getNowAndExpirationEndOfDayInTenantTz(): Creates timezone-aware comparison objects
  • isUserExpired() / willUserExtend(): Timezone-aware expiration checks
    In getProxySponsorWarning.js:
  • Updated all date comparisons to use dayjs.tz(expirationDate, timezone) for consistent timezone context
  • Ensures proxy/sponsor expiration warnings are accurate across timezones
  1. Error Resolution
  • Fixed Runtime Errors after reloading the page: Resolved "Invalid time value" RangeError by adding timezone fallbacks
  • Fixed Date Display Issues: Changed from .format('L') to .format('YYYY-MM-DD') to prevent ambiguous date parsing that caused wrong dates to display
  1. Ensured all changes work correctly in edge-case timezones like Pacific/Fakaofo (UTC+13)

Why This Approach Was Chosen
UTC Storage + Timezone-Aware Logic Pattern
This hybrid approach provides:

  • Database Consistency: UTC storage prevents timezone-related data corruption
  • User Experience: Expiration happens at midnight in the user's actual timezone
  • Display Consistency: The same date appears the same way for all users

Example of the Fix in Action
For a Vancouver user (UTC-8) with expiration date "2025-07-29":

  • Before: User expired at 5:00 PM Pacific Time (when UTC hit midnight)
  • After: User expires at 11:59:59 PM Pacific Time (proper midnight in their timezone)
  • Storage: Still stored as "2025-07-29T00:00:00.000Z" (UTC start-of-day)
  • Logic: Compares against "2025-07-30T07:59:59.999Z" (end of July 29 in Pacific time)

Approach

TODOS and Open Questions

Learning

Pre-Merge Checklist

Before merging this PR, please go through the following list and take appropriate actions.

  • I've added appropriate record to the CHANGELOG.md
  • Does this PR meet or exceed the expected quality standards?
    • Code coverage on new code is 80% or greater
    • Duplications on new code is 3% or less
    • There are no major code smells or security issues
  • Does this introduce breaking changes?
    • If any API-related changes - okapi interfaces and permissions are reviewed/changed correspondingly
    • There are no breaking changes in this PR.

If there are breaking changes, please STOP and consider the following:

  • What other modules will these changes impact?
  • Do JIRAs exist to update the impacted modules?
    • If not, please create them
    • Do they contain the appropriate level of detail? Which endpoints/schemas changed, etc.
    • Do they have all they appropriate links to blocked/related issues?
  • Are the JIRAs under active development?
    • If not, contact the project's PO and make sure they're aware of the urgency.
  • Do PRs exist for these changes?
    • If so, have they been approved?

Ideally all of the PRs involved in breaking changes would be merged in the same day to avoid breaking the folio-testing environment. Communication is paramount if that is to be achieved, especially as the number of intermodule and inter-team dependencies increase.

While it's helpful for reviewers to help identify potential problems, ensuring that it's safe to merge is ultimately the responsibility of the PR assignee.

@github-actions
Copy link

github-actions bot commented Jul 31, 2025

Jest Unit Test Results

    1 files  ± 0    272 suites  ±0   5m 47s ⏱️ +4s
1 288 tests +13  1 285 ✅ +13  3 💤 ±0  0 ❌ ±0 
1 329 runs  +13  1 326 ✅ +13  3 💤 ±0  0 ❌ ±0 

Results for commit e0450a6. ± Comparison against base commit c88bd40.

♻️ This comment has been updated with latest results.

@Dmytro-Melnyshyn Dmytro-Melnyshyn force-pushed the m-3400 branch 8 times, most recently from fa6a3dc to 05c9d5c Compare August 6, 2025 12:33
@sonarqubecloud
Copy link

sonarqubecloud bot commented Aug 6, 2025

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