Skip to content

fix: resolve timezone-dependent date calculation errors#399

Open
praaatap wants to merge 1 commit intofossasia:mainfrom
praaatap:fix/timezone-date-calculation
Open

fix: resolve timezone-dependent date calculation errors#399
praaatap wants to merge 1 commit intofossasia:mainfrom
praaatap:fix/timezone-date-calculation

Conversation

@praaatap
Copy link

@praaatap praaatap commented Feb 24, 2026

📌 Fixes

#398


📝 Summary of Changes

  • Replaced multiple instances of toISOString().split('T')[0] with a new getLocalDateString utility function to resolve off-by-one timezone date calculation errors.
  • Applied the fix to main.js, popup.js, and scrumHelper.js.
  • Ensures that reports correctly query the local user's "yesterday" and "today" regardless of their UTC offset.

📸 Screenshots / Demo (if UI-related)

N/A - Logic fix


✅ Checklist

  • I’ve tested my changes locally
  • I’ve added tests (if applicable)
  • I’ve updated documentation (if applicable)
  • My code follows the project’s code style guidelines

👀 Reviewer Notes

This fixes an issue where users in timezones like UTC+9 might see the previous day's data when creating a report early in their morning because toISOString() returns the UTC date instead of the user's local date.

Summary by Sourcery

Fix timezone-dependent date calculations by standardizing on a local date utility and updating callers to use it.

Bug Fixes:

  • Ensure date ranges such as 'yesterday' and 'today' are computed using the user’s local timezone instead of UTC, preventing off-by-one errors in reports and queries.

Enhancements:

  • Introduce a reusable getLocalDateString helper for consistent local date formatting across scripts.
  • Simplify the repository filtering callback in the popup script for improved readability.

Copilot AI review requested due to automatic review settings February 24, 2026 07:25
@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Feb 24, 2026

Reviewer's Guide

Replaces UTC-based date string generation with a new local-time-based helper and applies it across main popup and scrum helper logic to fix off-by-one date errors in different timezones, plus a small refactor in the repo filter callback.

Sequence diagram for local date range calculation during report generation

sequenceDiagram
    actor User
    participant PopupUI
    participant MainScript
    participant ScrumHelper
    participant GitHubAPI

    User->>PopupUI: Click generateReport
    PopupUI->>MainScript: requestDateRange()

    alt yesterdayContribution enabled
        MainScript->>MainScript: today = new Date()
        MainScript->>MainScript: yesterday = new Date(today.getTime() - 24h)
        MainScript->>MainScript: startDate = getLocalDateString(yesterday)
        MainScript->>MainScript: endDate = getLocalDateString(today)
    else custom or default range
        MainScript->>MainScript: compute startDate and endDate using getLocalDateString
    end

    MainScript->>ScrumHelper: generateReport(startDate, endDate)

    ScrumHelper->>ScrumHelper: startDateTime = new Date(startDate + T00:00:00Z)
    ScrumHelper->>ScrumHelper: endDateTime = new Date(endDate + T23:59:59Z)

    ScrumHelper->>GitHubAPI: fetchCommits(startDate, endDate)
    GitHubAPI-->>ScrumHelper: commits

    ScrumHelper-->>PopupUI: reportData
    PopupUI-->>User: Show correct yesterday/today report (local timezone)
Loading

Class diagram for local date helper and consumers

classDiagram
    class MainScript {
        +getLocalDateString(date Date) string
        +getToday() string
        +getYesterday() string
    }

    class PopupScript {
        +getToday() string
        +getYesterday() string
    }

    class ScrumHelperScript {
        +getToday() string
        +getYesterday() string
        +computeCacheRange(yesterdayContribution boolean, startingDate string, endingDate string) void
        +computeCommitRange(yesterdayContribution boolean, startingDate string, endingDate string) void
        +computeActivityRange(yesterdayContribution boolean, startingDate string, endingDate string) void
        +buildDateFilters(yesterdayContribution boolean, startingDate string, endingDate string) void
        +fetchUserRepositories(username string, token string, org string) void
    }

    MainScript <.. PopupScript : uses getLocalDateString
    MainScript <.. ScrumHelperScript : uses getLocalDateString

    class RepoFilter {
        +filterAvailableRepos(availableRepos Array, selectedRepos Array, query string) Array
    }

    PopupScript <.. RepoFilter : calls filterAvailableRepos
Loading

File-Level Changes

Change Details Files
Introduce a shared local-date formatting helper and replace direct toISOString-based date string generation to make all date ranges timezone-agnostic.
  • Add getLocalDateString(date) helper that builds YYYY-MM-DD strings using local year, month, and date values.
  • Update all getToday/getYesterday helpers to use getLocalDateString instead of toISOString().split('T')[0].
  • Change all date range computations (start/end dates, cache keys, commit query ranges, filters) to rely on getLocalDateString for both boundary and filter construction.
  • Ensure error-fallback date ranges (e.g., last 30 days) also use local-date formatting.
src/scripts/main.js
src/scripts/popup.js
src/scripts/scrumHelper.js
Minor cleanup of repo filtering predicate for clarity.
  • Convert the multi-line availableRepos.filter callback into a more compact single-arrow-function form without changing behavior.
src/scripts/popup.js

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@github-actions github-actions bot added javascript Pull requests that update javascript code core extension labels Feb 24, 2026
Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 1 issue, and left some high level feedback:

  • Consider moving getLocalDateString into a shared utilities module (or clearly documented global) and importing/attaching it consistently, rather than defining it in main.js and relying on it being available in other scripts.
  • Where you now use getLocalDateString(...)+ 'T00:00:00Z' / 'T23:59:59Z', double-check that treating a local calendar date as UTC midnight is intentional; if you truly want local-day ranges, you may want to construct Date objects in local time and only convert to UTC when sending to APIs.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Consider moving `getLocalDateString` into a shared utilities module (or clearly documented global) and importing/attaching it consistently, rather than defining it in `main.js` and relying on it being available in other scripts.
- Where you now use `getLocalDateString(...)+ 'T00:00:00Z'` / `'T23:59:59Z'`, double-check that treating a local calendar date as UTC midnight is intentional; if you truly want local-day ranges, you may want to construct `Date` objects in local time and only convert to UTC when sending to APIs.

## Individual Comments

### Comment 1
<location path="src/scripts/scrumHelper.js" line_range="1535-1536" />
<code_context>
 					const yesterday = new Date(today.getTime() - 24 * 60 * 60 * 1000);
-					startDateFilter = new Date(yesterday.toISOString().split('T')[0] + 'T00:00:00Z');
-					endDateFilter = new Date(today.toISOString().split('T')[0] + 'T23:59:59Z'); // Use yesterday for start and today for end
+					startDateFilter = new Date(getLocalDateString(yesterday) + 'T00:00:00Z');
+					endDateFilter = new Date(getLocalDateString(today) + 'T23:59:59Z'); // Use yesterday for start and today for end
 				} else if (startingDate && endingDate) {
 					startDateFilter = new Date(startingDate + 'T00:00:00Z');
</code_context>
<issue_to_address>
**issue (bug_risk):** Using a local calendar date with a hardcoded `Z` suffix can shift the effective range by a day depending on the user’s timezone.

`toISOString().split('T')[0]` gave you a UTC date, so appending `'T00:00:00Z'` / `'T23:59:59Z'` kept everything in UTC. `getLocalDateString` instead uses the local calendar date, but you still append `'Z'`, which tells the runtime to interpret that local date as UTC. For users far from UTC, this shifts the actual time window compared to their local day and any UTC-based logic elsewhere.

To avoid this, either:
- keep everything local (e.g. `new Date(getLocalDateString(...) + 'T00:00:00')` with no `Z`), or
- keep everything UTC (continue using `toISOString().split('T')[0]`).

Please pick one (local vs UTC) and apply it consistently wherever these date strings are turned into `Date` objects.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +1535 to +1536
startDateFilter = new Date(getLocalDateString(yesterday) + 'T00:00:00Z');
endDateFilter = new Date(getLocalDateString(today) + 'T23:59:59Z'); // Use yesterday for start and today for end
Copy link
Contributor

Choose a reason for hiding this comment

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

issue (bug_risk): Using a local calendar date with a hardcoded Z suffix can shift the effective range by a day depending on the user’s timezone.

toISOString().split('T')[0] gave you a UTC date, so appending 'T00:00:00Z' / 'T23:59:59Z' kept everything in UTC. getLocalDateString instead uses the local calendar date, but you still append 'Z', which tells the runtime to interpret that local date as UTC. For users far from UTC, this shifts the actual time window compared to their local day and any UTC-based logic elsewhere.

To avoid this, either:

  • keep everything local (e.g. new Date(getLocalDateString(...) + 'T00:00:00') with no Z), or
  • keep everything UTC (continue using toISOString().split('T')[0]).

Please pick one (local vs UTC) and apply it consistently wherever these date strings are turned into Date objects.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR addresses issue #398 by switching date formatting from UTC-based toISOString().split('T')[0] to a local-date formatter so “today/yesterday” calculations don’t shift by timezone.

Changes:

  • Added a getLocalDateString(date) formatter and updated “today/yesterday” helpers to use it.
  • Replaced multiple UTC date-string usages in scrumHelper.js to avoid off-by-one local dates.
  • Minor refactor of repo filtering callback formatting in popup.js.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
src/scripts/scrumHelper.js Replaces UTC date-string formatting with getLocalDateString(...) across several date-range calculations and filters.
src/scripts/popup.js Updates “today/yesterday” to use getLocalDateString and reformats repo filter callback.
src/scripts/main.js Introduces getLocalDateString(date) and updates “today/yesterday” to use it.
Comments suppressed due to low confidence (1)

src/scripts/scrumHelper.js:1211

  • These boundaries are still constructed as UTC times (...T00:00:00Z / ...T23:59:59Z). After switching startDate/endDate to local-date strings, appending Z makes the range UTC midnight, not the user's local midnight, so filtering can still be off by the timezone offset. Build the range in local time (e.g., using new Date(year, monthIndex, day, 0,0,0,0) / end-of-day) instead of parsing a Z-suffixed string.
		const startDateTime = new Date(startDate + 'T00:00:00Z');
		const endDateTime = new Date(endDate + 'T23:59:59Z');


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 361 to +369
function getYesterday() {
const today = new Date();
const yesterday = new Date(today);
yesterday.setDate(today.getDate() - 1);
return yesterday.toISOString().split('T')[0];
return getLocalDateString(yesterday);
}
function getToday() {
const today = new Date();
return today.toISOString().split('T')[0];
return getLocalDateString(today);
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

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

getLocalDateString is not defined anywhere in scrumHelper.js, and main.js is not part of the content_scripts list in manifest.json (only jquery, emailClientAdapter, gitlabHelper, and scrumHelper are injected). This will throw a ReferenceError when the content script runs on mail pages. Define/import getLocalDateString within scrumHelper.js (or extract it into a shared utility file that is included by both the popup page and the content script via manifest.json).

Copilot uses AI. Check for mistakes.
Comment on lines +1535 to 1546
startDateFilter = new Date(getLocalDateString(yesterday) + 'T00:00:00Z');
endDateFilter = new Date(getLocalDateString(today) + 'T23:59:59Z'); // Use yesterday for start and today for end
} else if (startingDate && endingDate) {
startDateFilter = new Date(startingDate + 'T00:00:00Z');
endDateFilter = new Date(endingDate + 'T23:59:59Z');
} else {
// Default to last 7 days if no date range is set
const today = new Date();
const lastWeek = new Date(today.getFullYear(), today.getMonth(), today.getDate() - 7);
startDateFilter = new Date(lastWeek.toISOString().split('T')[0] + 'T00:00:00Z');
endDateFilter = new Date(today.toISOString().split('T')[0] + 'T23:59:59Z');
startDateFilter = new Date(getLocalDateString(lastWeek) + 'T00:00:00Z');
endDateFilter = new Date(getLocalDateString(today) + 'T23:59:59Z');
}
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

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

Same issue here: new Date(getLocalDateString(...) + 'T00:00:00Z') creates a UTC-based day window, not a local-time window. Since the goal is to filter by the user's local day, compute startDateFilter/endDateFilter using local time constructors (or at least omit the trailing Z) so comparisons against created_at/updated_at align with local-day boundaries.

Copilot uses AI. Check for mistakes.
@vedansh-5 vedansh-5 requested a review from Copilot February 26, 2026 15:08
@vedansh-5
Copy link
Member

Thanks @praaatap for your contribution. Please address copilots' and sourcey's concerns!

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.

Comments suppressed due to low confidence (1)

src/scripts/scrumHelper.js:1210

  • After fixing the date string generation to use local dates, these Date objects are constructed incorrectly. Appending 'T00:00:00Z' treats the local date string as if it's in UTC timezone, which reintroduces timezone issues.

For example, if a user in UTC+9 on January 15 wants "yesterday" data:

  • getLocalDateString(yesterday) correctly returns "2024-01-14"
  • But new Date("2024-01-14T00:00:00Z") creates a Date representing January 14 midnight UTC
  • In the user's timezone (UTC+9), this is actually January 14 at 9:00 AM JST, not midnight JST

To properly represent the user's local midnight, remove the 'Z' suffix (which forces UTC interpretation) or use a different approach like creating Date objects directly from the local date variables with time set to midnight.

		const startDateTime = new Date(startDate + 'T00:00:00Z');
		const endDateTime = new Date(endDate + 'T23:59:59Z');

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

const yesterday = new Date(today);
yesterday.setDate(today.getDate() - 1);
return yesterday.toISOString().split('T')[0];
return getLocalDateString(yesterday);
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

The function getLocalDateString is not defined in this file or in any file loaded in the content script context. According to manifest.json, the content scripts only load jquery-3.2.1.min.js, emailClientAdapter.js, gitlabHelper.js, and scrumHelper.js - but main.js (where getLocalDateString is defined) is not included. This will cause a ReferenceError when scrumHelper.js runs as a content script on email client pages.

To fix this issue, you need to either:

  1. Add scripts/main.js to the content_scripts array in manifest.json before scrumHelper.js, OR
  2. Define getLocalDateString directly in scrumHelper.js, OR
  3. Create a shared utility file that's loaded in both the popup and content script contexts

The same issue affects all other calls to getLocalDateString in this file (lines 369, 480, 481, 489, 490, 713, 714, 722, 723, 1196, 1197, 1205, 1206, 1426, 1427, 1435, 1436, 1535, 1536, 1544, 1545, 1916, 1917, 1924, 1925, 1934, 1935).

Copilot uses AI. Check for mistakes.
Comment on lines +1535 to +1536
startDateFilter = new Date(getLocalDateString(yesterday) + 'T00:00:00Z');
endDateFilter = new Date(getLocalDateString(today) + 'T23:59:59Z'); // Use yesterday for start and today for end
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

Same timezone issue as lines 1209-1210. After fixing the date string generation to use local dates, appending 'T00:00:00Z' treats the local date string as UTC timezone, which reintroduces timezone problems. Remove the 'Z' suffix from both date string constructions to properly represent the user's local timezone boundaries.

Copilot uses AI. Check for mistakes.
Comment on lines +1544 to +1545
startDateFilter = new Date(getLocalDateString(lastWeek) + 'T00:00:00Z');
endDateFilter = new Date(getLocalDateString(today) + 'T23:59:59Z');
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

Same timezone issue. The 'Z' suffix in the date string construction forces UTC interpretation, undermining the fix to use local dates. Remove the 'Z' suffix to properly represent local timezone boundaries.

Copilot uses AI. Check for mistakes.
Comment on lines 1538 to 1539
startDateFilter = new Date(startingDate + 'T00:00:00Z');
endDateFilter = new Date(endingDate + 'T23:59:59Z');
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

These lines have the same timezone issue. When startingDate and endingDate come from user input in the date picker, they are already local dates in YYYY-MM-DD format. Appending 'T00:00:00Z' treats them as UTC dates, which causes incorrect filtering for users not in UTC timezone. Remove the 'Z' suffix from both lines to properly interpret these as local timezone boundaries.

Copilot uses AI. Check for mistakes.
const startDate = thirtyDaysAgo.toISOString().split('T')[0];
const endDate = today.toISOString().split('T')[0];
const startDate = getLocalDateString(thirtyDaysAgo);
const endDate = getLocalDateString(today);
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

The startDate and endDate variables defined in this catch block are scoped locally and don't update the dateRange variable that was set before the catch block. This means if the date range determination fails, the function will continue using an empty dateRange string rather than falling back to the 30-day range. This is a pre-existing bug, but while modifying these lines for the timezone fix, consider also fixing this by either: (1) removing the const and declaring these variables at the function level, or (2) setting dateRange = \+created:${startDate}..${endDate}`` inside the catch block.

Suggested change
const endDate = getLocalDateString(today);
const endDate = getLocalDateString(today);
dateRange = `+created:${startDate}..${endDate}`;
console.log(`Falling back to last 30 days for repo search: ${startDate} to ${endDate}`);

Copilot uses AI. Check for mistakes.
@vedansh-5
Copy link
Member

vedansh-5 commented Mar 2, 2026

Thanks @praaatap for your contribution.

Please resolve the merge conflicts and address the Copilot review comments. Also, add a short video or before/after screenshots of the feature implementation (whichever is applicable) to help with review.

This PR has been inactive for a while and is scheduled to be closed soon, so please push the required updates at the earliest.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core extension javascript Pull requests that update javascript code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants