Skip to content

fix(calendar): use local timezone for agenda day boundaries#443

Open
anshul-garg27 wants to merge 1 commit intogoogleworkspace:mainfrom
anshul-garg27:fix/agenda-local-timezone
Open

fix(calendar): use local timezone for agenda day boundaries#443
anshul-garg27 wants to merge 1 commit intogoogleworkspace:mainfrom
anshul-garg27:fix/agenda-local-timezone

Conversation

@anshul-garg27
Copy link
Contributor

Summary

  • gws calendar +agenda --today and --tomorrow now use the local timezone for day boundaries instead of UTC
  • Fixes incorrect results after local midnight when local TZ != UTC (e.g. after ~4pm PST, "today" was returning tomorrow's events)

Root cause

Day boundaries were computed by dividing Unix epoch seconds by 86400, which yields UTC midnight. The RFC3339 timestamps were also formatted in UTC. Users in non-UTC timezones got the wrong day's events.

Before (PST, 8pm local = UTC midnight):

  • --today returns tomorrow's events
  • --tomorrow returns the day after tomorrow's events

After:

  • --today returns today's events using local midnight boundaries
  • --tomorrow returns tomorrow's events using local midnight boundaries

Changes

  • Replace UTC epoch arithmetic with chrono::Local day boundary calculation
  • Add explicit --today branch (previously fell through to generic "N days from now")
  • Remove unused epoch_to_rfc3339 helper (now using chrono::DateTime::to_rfc3339() directly)
  • RFC3339 timestamps now carry the local UTC offset (e.g. -07:00)

Test plan

  • Added test verifying day boundaries carry local timezone offset
  • Existing calendar insert tests continue to pass

Closes #259

Previously, --today and --tomorrow computed day boundaries using UTC
epoch arithmetic, so after local midnight \!= UTC midnight the wrong
day's events were returned. Now uses chrono::Local to derive midnight
in the user's timezone.

Also fixes --today which had no explicit branch and fell through to
the generic "N days from now" path.
@changeset-bot
Copy link

changeset-bot bot commented Mar 12, 2026

🦋 Changeset detected

Latest commit: ef0c212

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@googleworkspace/cli Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@googleworkspace-bot googleworkspace-bot added the area: core Core CLI parsing, commands, error handling, utilities label Mar 12, 2026
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses a critical bug in the calendar agenda feature where day boundaries were incorrectly calculated using UTC, leading to users seeing events for the wrong day in non-UTC timezones. The changes refactor the time range determination logic to leverage the chrono::Local library, ensuring that --today and --tomorrow flags align with the user's local wall-clock day. This significantly improves the accuracy and user experience of the calendar agenda.

Highlights

  • Timezone Correction for Agenda: The gws calendar +agenda --today and --tomorrow commands now correctly use the local timezone for day boundaries, resolving issues where events for the wrong day were displayed.
  • Chrono Library Integration: Replaced manual UTC epoch arithmetic with chrono::Local for precise local timezone calculations.
  • Explicit Today Branch: An explicit branch for the --today flag was introduced, improving clarity and handling of the current day's events.
  • RFC3339 Timestamp Format: RFC3339 timestamps now include the local UTC offset (e.g., -07:00), ensuring accurate representation of time.
  • Code Cleanup: The unused epoch_to_rfc3339 helper function was removed, simplifying the codebase.
Changelog
  • .changeset/fix-agenda-local-timezone.md
    • Added a new changeset file documenting the fix for local timezone usage in the calendar agenda.
  • src/helpers/calendar.rs
    • Updated time range calculation to use chrono::Local for accurate local timezone handling.
    • Modified the days variable type from u64 to i64 to accommodate chrono::Duration operations.
    • Implemented explicit logic for --today and --tomorrow flags to define day boundaries based on local midnight.
    • Removed the epoch_to_rfc3339 helper function as chrono::DateTime::to_rfc3339() is now used directly.
    • Added a new test case agenda_day_boundaries_use_local_timezone to verify that RFC3339 timestamps correctly include local timezone offsets.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Generative AI Prohibited Use Policy, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request correctly changes the calendar agenda logic to use the local timezone instead of UTC, fixing a bug for users in non-UTC timezones. The implementation is mostly correct, but I've found a critical issue related to Daylight Saving Time transitions that could lead to incorrect agenda boundaries. The new test also contains duplicated logic with the same issue. My review includes suggestions to make the timezone handling more robust.

Comment on lines +211 to +214
let today_start_local = Local
.from_local_datetime(&today_start)
.single()
.unwrap_or(local_now);
Copy link
Contributor

Choose a reason for hiding this comment

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

critical

The fallback unwrap_or(local_now) can lead to incorrect behavior. If from_local_datetime results in an ambiguous or non-existent time (e.g., during a DST transition), single() will return None, and today_start_local will become local_now. For commands like --today, this means the agenda would start from the current time instead of the beginning of the day, which is not the intended behavior.

To handle ambiguous times correctly, you can use .earliest(). For the rare case of a non-existent midnight, panicking with expect() is safer than silently producing incorrect results.

Suggested change
let today_start_local = Local
.from_local_datetime(&today_start)
.single()
.unwrap_or(local_now);
let today_start_local = Local
.from_local_datetime(&today_start)
.earliest()
.expect("Failed to determine local midnight; this can happen during a DST transition where midnight does not exist.");

Comment on lines +555 to +558
let today_start_local = Local
.from_local_datetime(&today_start)
.single()
.unwrap_or(local_now);
Copy link
Contributor

Choose a reason for hiding this comment

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

high

This logic for determining the start of the day is duplicated from the handle_agenda function and shares the same potential bug with unwrap_or(local_now).

To improve maintainability and correctness, this logic should be extracted into a helper function and used in both handle_agenda and this test. For now, I'll suggest the same fix as for the main logic to ensure the test is correct and robust against timezone edge cases.

Suggested change
let today_start_local = Local
.from_local_datetime(&today_start)
.single()
.unwrap_or(local_now);
let today_start_local = Local
.from_local_datetime(&today_start)
.earliest()
.expect("Failed to determine local midnight; this can happen during a DST transition where midnight does not exist.");

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

Labels

area: core Core CLI parsing, commands, error handling, utilities

Projects

None yet

Development

Successfully merging this pull request may close these issues.

agenda --today uses UTC for day boundaries, wrong results in non-UTC timezones

2 participants