Skip to content

fix: standardize datetime.now() to use UTC-aware datetimes#894

Closed
ashsolei wants to merge 1 commit intohesreallyhim:mainfrom
ashsolei:fix/datetime-utc-standardization
Closed

fix: standardize datetime.now() to use UTC-aware datetimes#894
ashsolei wants to merge 1 commit intohesreallyhim:mainfrom
ashsolei:fix/datetime-utc-standardization

Conversation

@ashsolei
Copy link

@ashsolei ashsolei commented Mar 1, 2026

Summary

Standardize all datetime.now() calls to use datetime.now(timezone.utc) across the entire codebase, preventing potential bugs from naive vs. aware datetime comparisons.

Problem

The codebase uses datetime.now() (which returns timezone-naive local datetimes) throughout. This creates a latent bug: if any code path introduces a timezone-aware datetime (e.g., from an API response or database), comparisons with naive datetimes raise TypeError: can't compare offset-naive and offset-aware datetimes.

Additionally, using local time makes the codebase behave differently depending on the server's timezone, which is unreliable in CI/CD environments and distributed systems.

Changes

12 source files — added from datetime import timezone import and replaced datetime.now()datetime.now(timezone.utc):

  • scripts/badges/badge_notification_core.py
  • scripts/readme/generators/base.py
  • scripts/readme/generators/flat.py (also fixed local strptime and datetime.min sentinel)
  • scripts/readme/helpers/readme_utils.py (parse_resource_date() now returns UTC-aware)
  • scripts/readme/markup/awesome.py
  • scripts/readme/markup/minimal.py
  • scripts/readme/markup/visual.py
  • scripts/resources/create_resource_pr.py
  • scripts/resources/download_resources.py
  • scripts/resources/resource_utils.py
  • scripts/validation/validate_links.py
  • scripts/validation/validate_single_resource.py

3 test files — updated expected datetime values to include tzinfo=timezone.utc:

  • tests/test_flat_list_generator.py
  • tests/test_generate_readme.py
  • tests/test_resource_utils.py

Testing

All 314 tests pass:

314 passed in 0.61s

Risk

Low — this is a mechanical change with no behavioral difference when running in UTC environments (CI). In non-UTC environments, timestamps will now correctly reflect UTC instead of local time.

Replace all datetime.now() calls with datetime.now(timezone.utc) and
ensure all strptime results include tzinfo=timezone.utc for consistent
timezone-aware datetime comparisons across the codebase.

Files changed:
- 12 source files: added timezone import, updated datetime.now() calls
- 3 test files: updated expected values to match UTC-aware datetimes
- readme_utils.py: parse_resource_date() now returns UTC-aware datetimes
- flat.py: fixed local strptime and datetime.min sentinel
Copilot AI review requested due to automatic review settings March 1, 2026 01:22
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 standardizes the codebase’s usage of “current time” by replacing timezone-naive datetime.now() calls with UTC-aware alternatives, reducing the risk of TypeError from mixing naive/aware datetimes and improving cross-environment determinism.

Changes:

  • Replaced datetime.now() with UTC-aware equivalents (primarily datetime.now(timezone.utc), and datetime.now(UTC) where datetime.UTC is used).
  • Updated README generation and resource/validation scripts to consistently generate/compare UTC-based timestamps.
  • Updated tests to expect UTC-aware datetimes and to parse CSV timestamps as UTC-aware.

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
scripts/badges/badge_notification_core.py Store notification timestamps as UTC-aware ISO strings; use UTC-aware “now” for cutoff calculations.
scripts/readme/generators/base.py Use UTC-aware timestamps for backup naming.
scripts/readme/generators/flat.py Use UTC-aware cutoff for releases; parse release dates as UTC-aware; UTC-aware generated date.
scripts/readme/helpers/readme_utils.py parse_resource_date() now returns UTC-aware datetimes.
scripts/readme/markup/awesome.py Weekly cutoff uses UTC-aware “now”.
scripts/readme/markup/minimal.py Weekly cutoff uses UTC-aware “now”.
scripts/readme/markup/visual.py Weekly cutoff uses UTC-aware “now”.
scripts/resources/create_resource_pr.py Branch timestamp uses UTC-aware “now”.
scripts/resources/download_resources.py Track start/end times using UTC-aware “now”.
scripts/resources/resource_utils.py CSV date fields default from UTC-aware “now”.
scripts/validation/validate_links.py Update “last_checked” and summary timestamp using UTC-aware “now” (datetime.UTC).
scripts/validation/validate_single_resource.py Populate last_checked using UTC-aware “now”.
tests/test_flat_list_generator.py Use UTC-aware “now” in release-sort tests.
tests/test_generate_readme.py Update expected parsed dates to be UTC-aware.
tests/test_resource_utils.py Make test window UTC-aware and parse CSV timestamps as UTC-aware.
Comments suppressed due to low confidence (1)

scripts/badges/badge_notification_core.py:452

  • record_notification() now writes UTC-aware ISO timestamps, but get_notification_count() may still read older history entries that were written as offset-naive ISO strings. datetime.fromisoformat(...).timestamp() will interpret those naive values in the current local timezone, which can miscount “recent” notifications if the history file is moved between environments/timezones. Consider normalizing parsed timestamps: if tzinfo is None, assume UTC (or explicitly convert from local) before comparing to cutoff.
        """Get count of recent notifications for a repository"""
        cutoff = datetime.now(timezone.utc).timestamp() - (time_window_hours * 3600)
        count = 0

        for entry in self.history:
            if entry["repo_url"] == repo_url:
                try:
                    timestamp = datetime.fromisoformat(entry["timestamp"]).timestamp()
                    if timestamp > cutoff:

Comment on lines 842 to 845
# Update timestamp if not locked
if "last_checked" not in locked_fields:
row[LAST_CHECKED_HEADER_NAME] = datetime.now().strftime("%Y-%m-%d:%H-%M-%S")
row[LAST_CHECKED_HEADER_NAME] = datetime.now(UTC).strftime("%Y-%m-%d:%H-%M-%S")

Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

This file now uses datetime.now(UTC) (with UTC imported from datetime) rather than datetime.now(timezone.utc). Functionally both are UTC-aware, but it diverges from the PR description’s stated standard (timezone.utc). Consider either switching these calls to timezone.utc for consistency across the PR, or updating the PR description to explicitly allow UTC as the standardized approach.

Copilot uses AI. Check for mistakes.
@ashsolei ashsolei closed this Mar 1, 2026
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