mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-08-10 21:01:10 +03:00
Replace pytz with native tz-aware objects and zoneinfo library
Supported from Python 3.9, see https://peps.python.org/pep-0615/. Also https://blog.ganssle.io/articles/2019/11/utcnow.html.
This commit is contained in:
@@ -25,7 +25,6 @@ peewee_migrate == 1.14.*
|
|||||||
psutil == 7.1.*
|
psutil == 7.1.*
|
||||||
pydantic == 2.10.*
|
pydantic == 2.10.*
|
||||||
git+https://github.com/fbcotter/py3nvml#egg=py3nvml
|
git+https://github.com/fbcotter/py3nvml#egg=py3nvml
|
||||||
pytz == 2025.*
|
|
||||||
pyzmq == 26.2.*
|
pyzmq == 26.2.*
|
||||||
ruamel.yaml == 0.18.*
|
ruamel.yaml == 0.18.*
|
||||||
tzlocal == 5.2
|
tzlocal == 5.2
|
||||||
|
|||||||
Vendored
+7
-7
@@ -2013,7 +2013,7 @@ paths:
|
|||||||
required: false
|
required: false
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
default: utc
|
default: UTC
|
||||||
title: Timezone
|
title: Timezone
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
@@ -3055,7 +3055,7 @@ paths:
|
|||||||
anyOf:
|
anyOf:
|
||||||
- type: string
|
- type: string
|
||||||
- type: 'null'
|
- type: 'null'
|
||||||
default: utc
|
default: UTC
|
||||||
title: Timezone
|
title: Timezone
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
@@ -4153,7 +4153,7 @@ paths:
|
|||||||
anyOf:
|
anyOf:
|
||||||
- type: string
|
- type: string
|
||||||
- type: 'null'
|
- type: 'null'
|
||||||
default: utc
|
default: UTC
|
||||||
title: Timezone
|
title: Timezone
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
@@ -4405,7 +4405,7 @@ paths:
|
|||||||
anyOf:
|
anyOf:
|
||||||
- type: string
|
- type: string
|
||||||
- type: 'null'
|
- type: 'null'
|
||||||
default: utc
|
default: UTC
|
||||||
title: Timezone
|
title: Timezone
|
||||||
- name: min_score
|
- name: min_score
|
||||||
in: query
|
in: query
|
||||||
@@ -4485,7 +4485,7 @@ paths:
|
|||||||
anyOf:
|
anyOf:
|
||||||
- type: string
|
- type: string
|
||||||
- type: 'null'
|
- type: 'null'
|
||||||
default: utc
|
default: UTC
|
||||||
title: Timezone
|
title: Timezone
|
||||||
- name: has_clip
|
- name: has_clip
|
||||||
in: query
|
in: query
|
||||||
@@ -6780,7 +6780,7 @@ paths:
|
|||||||
required: false
|
required: false
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
default: utc
|
default: UTC
|
||||||
title: Timezone
|
title: Timezone
|
||||||
- name: cameras
|
- name: cameras
|
||||||
in: query
|
in: query
|
||||||
@@ -6830,7 +6830,7 @@ paths:
|
|||||||
required: false
|
required: false
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
default: utc
|
default: UTC
|
||||||
title: Timezone
|
title: Timezone
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
|
|||||||
@@ -7,4 +7,4 @@ class AppTimelineHourlyQueryParameters(BaseModel):
|
|||||||
after: float | None = None
|
after: float | None = None
|
||||||
before: float | None = None
|
before: float | None = None
|
||||||
limit: int | None = 200
|
limit: int | None = 200
|
||||||
timezone: str | None = "utc"
|
timezone: str | None = "UTC"
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ class EventsQueryParams(BaseModel):
|
|||||||
max_length: float | None = None
|
max_length: float | None = None
|
||||||
event_id: str | None = None
|
event_id: str | None = None
|
||||||
sort: str | None = None
|
sort: str | None = None
|
||||||
timezone: str | None = "utc"
|
timezone: str | None = "UTC"
|
||||||
|
|
||||||
|
|
||||||
class EventsSearchQueryParams(BaseModel):
|
class EventsSearchQueryParams(BaseModel):
|
||||||
@@ -66,7 +66,7 @@ class EventsSearchQueryParams(BaseModel):
|
|||||||
has_clip: bool | None = None
|
has_clip: bool | None = None
|
||||||
has_snapshot: bool | None = None
|
has_snapshot: bool | None = None
|
||||||
is_submitted: bool | None = None
|
is_submitted: bool | None = None
|
||||||
timezone: str | None = "utc"
|
timezone: str | None = "UTC"
|
||||||
min_score: float | None = None
|
min_score: float | None = None
|
||||||
max_score: float | None = None
|
max_score: float | None = None
|
||||||
min_speed: float | None = None
|
min_speed: float | None = None
|
||||||
@@ -76,6 +76,6 @@ class EventsSearchQueryParams(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class EventsSummaryQueryParams(BaseModel):
|
class EventsSummaryQueryParams(BaseModel):
|
||||||
timezone: str | None = "utc"
|
timezone: str | None = "UTC"
|
||||||
has_clip: int | None = None
|
has_clip: int | None = None
|
||||||
has_snapshot: int | None = None
|
has_snapshot: int | None = None
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from pydantic.json_schema import SkipJsonSchema
|
|||||||
|
|
||||||
|
|
||||||
class MediaRecordingsSummaryQueryParams(BaseModel):
|
class MediaRecordingsSummaryQueryParams(BaseModel):
|
||||||
timezone: str = "utc"
|
timezone: str = "UTC"
|
||||||
cameras: str | None = "all"
|
cameras: str | None = "all"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ class ReviewSummaryQueryParams(BaseModel):
|
|||||||
cameras: str = "all"
|
cameras: str = "all"
|
||||||
labels: str = "all"
|
labels: str = "all"
|
||||||
zones: str = "all"
|
zones: str = "all"
|
||||||
timezone: str = "utc"
|
timezone: str = "UTC"
|
||||||
|
|
||||||
|
|
||||||
class ReviewActivityMotionQueryParams(BaseModel):
|
class ReviewActivityMotionQueryParams(BaseModel):
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ from datetime import UTC, datetime, timedelta
|
|||||||
from pathlib import Path as FilePath
|
from pathlib import Path as FilePath
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from urllib.parse import unquote
|
from urllib.parse import unquote
|
||||||
|
from zoneinfo import ZoneInfo
|
||||||
|
|
||||||
import cv2
|
import cv2
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pytz
|
|
||||||
from fastapi import APIRouter, Depends, Path, Query, Request, Response
|
from fastapi import APIRouter, Depends, Path, Query, Request, Response
|
||||||
from fastapi.responses import FileResponse, JSONResponse, StreamingResponse
|
from fastapi.responses import FileResponse, JSONResponse, StreamingResponse
|
||||||
from pathvalidate import sanitize_filename
|
from pathvalidate import sanitize_filename
|
||||||
@@ -713,7 +713,7 @@ async def vod_hour(
|
|||||||
parts = year_month.split("-")
|
parts = year_month.split("-")
|
||||||
start_date = (
|
start_date = (
|
||||||
datetime(int(parts[0]), int(parts[1]), day, hour, tzinfo=UTC)
|
datetime(int(parts[0]), int(parts[1]), day, hour, tzinfo=UTC)
|
||||||
- datetime.now(pytz.timezone(tz_name.replace(",", "/"))).utcoffset()
|
- datetime.now(ZoneInfo(tz_name.replace(",", "/"))).utcoffset()
|
||||||
)
|
)
|
||||||
end_date = start_date + timedelta(hours=1) - timedelta(milliseconds=1)
|
end_date = start_date + timedelta(hours=1) - timedelta(milliseconds=1)
|
||||||
start_ts = start_date.timestamp()
|
start_ts = start_date.timestamp()
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import logging
|
|||||||
import os
|
import os
|
||||||
import threading
|
import threading
|
||||||
from datetime import UTC, datetime, timedelta
|
from datetime import UTC, datetime, timedelta
|
||||||
|
from zoneinfo import ZoneInfo
|
||||||
|
|
||||||
import pytz
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException
|
from fastapi import APIRouter, Depends, HTTPException
|
||||||
from fastapi.responses import JSONResponse
|
from fastapi.responses import JSONResponse
|
||||||
|
|
||||||
@@ -126,7 +126,7 @@ def preview_hour(
|
|||||||
parts = year_month.split("-")
|
parts = year_month.split("-")
|
||||||
start_date = (
|
start_date = (
|
||||||
datetime(int(parts[0]), int(parts[1]), int(day), int(hour), tzinfo=UTC)
|
datetime(int(parts[0]), int(parts[1]), int(day), int(hour), tzinfo=UTC)
|
||||||
- datetime.now(pytz.timezone(tz_name.replace(",", "/"))).utcoffset()
|
- datetime.now(ZoneInfo(tz_name.replace(",", "/"))).utcoffset()
|
||||||
)
|
)
|
||||||
end_date = start_date + timedelta(hours=1) - timedelta(milliseconds=1)
|
end_date = start_date + timedelta(hours=1) - timedelta(milliseconds=1)
|
||||||
start_ts = start_date.timestamp()
|
start_ts = start_date.timestamp()
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ def all_recordings_summary(
|
|||||||
@router.get(
|
@router.get(
|
||||||
"/{camera_name}/recordings/summary", dependencies=[Depends(require_camera_access)]
|
"/{camera_name}/recordings/summary", dependencies=[Depends(require_camera_access)]
|
||||||
)
|
)
|
||||||
async def recordings_summary(camera_name: str, timezone: str = "utc"):
|
async def recordings_summary(camera_name: str, timezone: str = "UTC"):
|
||||||
"""Returns hourly summary for recordings of given camera"""
|
"""Returns hourly summary for recordings of given camera"""
|
||||||
|
|
||||||
time_range_query = (
|
time_range_query = (
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ import threading
|
|||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
|
||||||
|
|
||||||
import pytz # type: ignore[import-untyped]
|
|
||||||
from peewee import DoesNotExist
|
from peewee import DoesNotExist
|
||||||
|
|
||||||
from frigate.config import FfmpegConfig, FrigateConfig
|
from frigate.config import FfmpegConfig, FrigateConfig
|
||||||
@@ -371,8 +371,8 @@ class RecordingExporter(threading.Thread):
|
|||||||
tz_name = self.config.ui.timezone
|
tz_name = self.config.ui.timezone
|
||||||
if tz_name:
|
if tz_name:
|
||||||
try:
|
try:
|
||||||
tz = pytz.timezone(tz_name)
|
tz = ZoneInfo(tz_name)
|
||||||
except pytz.UnknownTimeZoneError:
|
except (ValueError, ZoneInfoNotFoundError):
|
||||||
tz = None
|
tz = None
|
||||||
if tz is not None:
|
if tz is not None:
|
||||||
return datetime.datetime.fromtimestamp(timestamp, tz=tz).strftime(
|
return datetime.datetime.fromtimestamp(timestamp, tz=tz).strftime(
|
||||||
@@ -533,8 +533,8 @@ class RecordingExporter(threading.Thread):
|
|||||||
tz: datetime.tzinfo | None = None
|
tz: datetime.tzinfo | None = None
|
||||||
if tz_name:
|
if tz_name:
|
||||||
try:
|
try:
|
||||||
tz = pytz.timezone(tz_name)
|
tz = ZoneInfo(tz_name)
|
||||||
except pytz.UnknownTimeZoneError:
|
except (ValueError, ZoneInfoNotFoundError):
|
||||||
tz = None
|
tz = None
|
||||||
if tz is None:
|
if tz is None:
|
||||||
tz = datetime.UTC
|
tz = datetime.UTC
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
"""Unit tests for recordings/media API endpoints."""
|
"""Unit tests for recordings/media API endpoints."""
|
||||||
|
|
||||||
from datetime import UTC, datetime
|
from datetime import UTC, datetime
|
||||||
|
from zoneinfo import ZoneInfo
|
||||||
|
|
||||||
import pytz
|
|
||||||
from fastapi import Request
|
from fastapi import Request
|
||||||
|
|
||||||
from frigate.api.auth import get_allowed_cameras_for_filter, get_current_user
|
from frigate.api.auth import get_allowed_cameras_for_filter, get_current_user
|
||||||
@@ -51,16 +51,16 @@ class TestHttpMedia(BaseTestHttp):
|
|||||||
In 2024, DST in America/New_York transitions on March 10, 2024 at 2:00 AM
|
In 2024, DST in America/New_York transitions on March 10, 2024 at 2:00 AM
|
||||||
Clocks spring forward from 2:00 AM to 3:00 AM (EST to EDT)
|
Clocks spring forward from 2:00 AM to 3:00 AM (EST to EDT)
|
||||||
"""
|
"""
|
||||||
tz = pytz.timezone("America/New_York")
|
tz = ZoneInfo("America/New_York")
|
||||||
|
|
||||||
# March 9, 2024 at 12:00 PM EST (before DST)
|
# March 9, 2024 at 12:00 PM EST (before DST)
|
||||||
march_9_noon = tz.localize(datetime(2024, 3, 9, 12, 0, 0)).timestamp()
|
march_9_noon = datetime(2024, 3, 9, 12, 0, 0, tzinfo=tz).timestamp()
|
||||||
|
|
||||||
# March 10, 2024 at 12:00 PM EDT (after DST transition)
|
# March 10, 2024 at 12:00 PM EDT (after DST transition)
|
||||||
march_10_noon = tz.localize(datetime(2024, 3, 10, 12, 0, 0)).timestamp()
|
march_10_noon = datetime(2024, 3, 10, 12, 0, 0, tzinfo=tz).timestamp()
|
||||||
|
|
||||||
# March 11, 2024 at 12:00 PM EDT (after DST)
|
# March 11, 2024 at 12:00 PM EDT (after DST)
|
||||||
march_11_noon = tz.localize(datetime(2024, 3, 11, 12, 0, 0)).timestamp()
|
march_11_noon = datetime(2024, 3, 11, 12, 0, 0, tzinfo=tz).timestamp()
|
||||||
|
|
||||||
with AuthTestClient(self.app) as client:
|
with AuthTestClient(self.app) as client:
|
||||||
# Insert recordings for each day
|
# Insert recordings for each day
|
||||||
@@ -124,19 +124,16 @@ class TestHttpMedia(BaseTestHttp):
|
|||||||
In 2024, DST in America/New_York transitions on November 3, 2024 at 2:00 AM
|
In 2024, DST in America/New_York transitions on November 3, 2024 at 2:00 AM
|
||||||
Clocks fall back from 2:00 AM to 1:00 AM (EDT to EST)
|
Clocks fall back from 2:00 AM to 1:00 AM (EDT to EST)
|
||||||
"""
|
"""
|
||||||
tz = pytz.timezone("America/New_York")
|
tz = ZoneInfo("America/New_York")
|
||||||
|
|
||||||
# November 2, 2024 at 12:00 PM EDT (before DST transition)
|
# November 2, 2024 at 12:00 PM EDT (before DST transition)
|
||||||
nov_2_noon = tz.localize(datetime(2024, 11, 2, 12, 0, 0)).timestamp()
|
nov_2_noon = datetime(2024, 11, 2, 12, 0, 0, tzinfo=tz).timestamp()
|
||||||
|
|
||||||
# November 3, 2024 at 12:00 PM EST (after DST transition)
|
# November 3, 2024 at 12:00 PM EST (after DST transition)
|
||||||
# Need to specify is_dst=False to get the time after fall back
|
nov_3_noon = datetime(2024, 11, 3, 12, 0, 0, tzinfo=tz).timestamp()
|
||||||
nov_3_noon = tz.localize(
|
|
||||||
datetime(2024, 11, 3, 12, 0, 0), is_dst=False
|
|
||||||
).timestamp()
|
|
||||||
|
|
||||||
# November 4, 2024 at 12:00 PM EST (after DST)
|
# November 4, 2024 at 12:00 PM EST (after DST)
|
||||||
nov_4_noon = tz.localize(datetime(2024, 11, 4, 12, 0, 0)).timestamp()
|
nov_4_noon = datetime(2024, 11, 4, 12, 0, 0, tzinfo=tz).timestamp()
|
||||||
|
|
||||||
with AuthTestClient(self.app) as client:
|
with AuthTestClient(self.app) as client:
|
||||||
# Insert recordings for each day
|
# Insert recordings for each day
|
||||||
@@ -197,13 +194,13 @@ class TestHttpMedia(BaseTestHttp):
|
|||||||
"""
|
"""
|
||||||
Test recordings summary with multiple cameras across DST boundary.
|
Test recordings summary with multiple cameras across DST boundary.
|
||||||
"""
|
"""
|
||||||
tz = pytz.timezone("America/New_York")
|
tz = ZoneInfo("America/New_York")
|
||||||
|
|
||||||
# March 9, 2024 at 10:00 AM EST (before DST)
|
# March 9, 2024 at 10:00 AM EST (before DST)
|
||||||
march_9_morning = tz.localize(datetime(2024, 3, 9, 10, 0, 0)).timestamp()
|
march_9_morning = datetime(2024, 3, 9, 10, 0, 0, tzinfo=tz).timestamp()
|
||||||
|
|
||||||
# March 10, 2024 at 3:00 PM EDT (after DST transition)
|
# March 10, 2024 at 3:00 PM EDT (after DST transition)
|
||||||
march_10_afternoon = tz.localize(datetime(2024, 3, 10, 15, 0, 0)).timestamp()
|
march_10_afternoon = datetime(2024, 3, 10, 15, 0, 0, tzinfo=tz).timestamp()
|
||||||
|
|
||||||
with AuthTestClient(self.app) as client:
|
with AuthTestClient(self.app) as client:
|
||||||
# Override allowed cameras for this test to include both
|
# Override allowed cameras for this test to include both
|
||||||
@@ -266,15 +263,15 @@ class TestHttpMedia(BaseTestHttp):
|
|||||||
"""
|
"""
|
||||||
Test recordings that span the exact DST transition time.
|
Test recordings that span the exact DST transition time.
|
||||||
"""
|
"""
|
||||||
tz = pytz.timezone("America/New_York")
|
tz = ZoneInfo("America/New_York")
|
||||||
|
|
||||||
# March 10, 2024 at 1:00 AM EST (1 hour before DST transition)
|
# March 10, 2024 at 1:00 AM EST (1 hour before DST transition)
|
||||||
# At 2:00 AM, clocks jump to 3:00 AM
|
# At 2:00 AM, clocks jump to 3:00 AM
|
||||||
before_transition = tz.localize(datetime(2024, 3, 10, 1, 0, 0)).timestamp()
|
before_transition = datetime(2024, 3, 10, 1, 0, 0, tzinfo=tz).timestamp()
|
||||||
|
|
||||||
# Recording that spans the transition (1:00 AM to 3:30 AM EDT)
|
# Recording that spans the transition (1:00 AM to 3:30 AM EDT)
|
||||||
# This is 1.5 hours of actual time but spans the "missing" hour
|
# This is 1.5 hours of actual time but spans the "missing" hour
|
||||||
after_transition = tz.localize(datetime(2024, 3, 10, 3, 30, 0)).timestamp()
|
after_transition = datetime(2024, 3, 10, 3, 30, 0, tzinfo=tz).timestamp()
|
||||||
|
|
||||||
with AuthTestClient(self.app) as client:
|
with AuthTestClient(self.app) as client:
|
||||||
Recordings.insert(
|
Recordings.insert(
|
||||||
@@ -334,7 +331,7 @@ class TestHttpMedia(BaseTestHttp):
|
|||||||
|
|
||||||
# Test with UTC timezone
|
# Test with UTC timezone
|
||||||
response = client.get(
|
response = client.get(
|
||||||
"/recordings/summary", params={"timezone": "utc", "cameras": "all"}
|
"/recordings/summary", params={"timezone": "UTC", "cameras": "all"}
|
||||||
)
|
)
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
@@ -365,8 +362,8 @@ class TestHttpMedia(BaseTestHttp):
|
|||||||
"""
|
"""
|
||||||
Test recordings summary filtered to a single camera.
|
Test recordings summary filtered to a single camera.
|
||||||
"""
|
"""
|
||||||
tz = pytz.timezone("America/New_York")
|
tz = ZoneInfo("America/New_York")
|
||||||
march_10_noon = tz.localize(datetime(2024, 3, 10, 12, 0, 0)).timestamp()
|
march_10_noon = datetime(2024, 3, 10, 12, 0, 0, tzinfo=tz).timestamp()
|
||||||
|
|
||||||
with AuthTestClient(self.app) as client:
|
with AuthTestClient(self.app) as client:
|
||||||
# Insert recordings for both cameras
|
# Insert recordings for both cameras
|
||||||
|
|||||||
@@ -250,7 +250,7 @@ class TestHttpReview(BaseTestHttp):
|
|||||||
"cameras": "front_door",
|
"cameras": "front_door",
|
||||||
"labels": "all",
|
"labels": "all",
|
||||||
"zones": "all",
|
"zones": "all",
|
||||||
"timezone": "utc",
|
"timezone": "UTC",
|
||||||
}
|
}
|
||||||
response = client.get("/review/summary", params=params)
|
response = client.get("/review/summary", params=params)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|||||||
@@ -2,9 +2,8 @@
|
|||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import logging
|
import logging
|
||||||
from zoneinfo import ZoneInfoNotFoundError
|
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
|
||||||
|
|
||||||
import pytz
|
|
||||||
from tzlocal import get_localzone
|
from tzlocal import get_localzone
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -12,7 +11,7 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
def get_tz_modifiers(tz_name: str) -> tuple[str, str, float]:
|
def get_tz_modifiers(tz_name: str) -> tuple[str, str, float]:
|
||||||
seconds_offset = (
|
seconds_offset = (
|
||||||
datetime.datetime.now(pytz.timezone(tz_name)).utcoffset().total_seconds()
|
datetime.datetime.now(ZoneInfo(tz_name)).utcoffset().total_seconds()
|
||||||
)
|
)
|
||||||
hours_offset = int(seconds_offset / 60 / 60)
|
hours_offset = int(seconds_offset / 60 / 60)
|
||||||
minutes_offset = int(seconds_offset / 60 - hours_offset * 60)
|
minutes_offset = int(seconds_offset / 60 - hours_offset * 60)
|
||||||
@@ -25,7 +24,7 @@ def get_tomorrow_at_time(hour: int) -> datetime.datetime:
|
|||||||
"""Returns the datetime of the following day at 2am."""
|
"""Returns the datetime of the following day at 2am."""
|
||||||
try:
|
try:
|
||||||
tomorrow = datetime.datetime.now(get_localzone()) + datetime.timedelta(days=1)
|
tomorrow = datetime.datetime.now(get_localzone()) + datetime.timedelta(days=1)
|
||||||
except ZoneInfoNotFoundError:
|
except (ValueError, ZoneInfoNotFoundError):
|
||||||
tomorrow = datetime.datetime.now(datetime.UTC) + datetime.timedelta(days=1)
|
tomorrow = datetime.datetime.now(datetime.UTC) + datetime.timedelta(days=1)
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"Using utc for maintenance due to missing or incorrect timezone set"
|
"Using utc for maintenance due to missing or incorrect timezone set"
|
||||||
@@ -45,7 +44,7 @@ def is_current_hour(timestamp: int) -> bool:
|
|||||||
|
|
||||||
def get_dst_transitions(
|
def get_dst_transitions(
|
||||||
tz_name: str, start_time: float, end_time: float
|
tz_name: str, start_time: float, end_time: float
|
||||||
) -> list[tuple[float, float]]:
|
) -> list[tuple[float, float, int]]:
|
||||||
"""
|
"""
|
||||||
Find DST transition points and return time periods with consistent offsets.
|
Find DST transition points and return time periods with consistent offsets.
|
||||||
|
|
||||||
@@ -59,8 +58,8 @@ def get_dst_transitions(
|
|||||||
continuous periods with the same UTC offset
|
continuous periods with the same UTC offset
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
tz = pytz.timezone(tz_name)
|
tz = ZoneInfo(tz_name)
|
||||||
except pytz.UnknownTimeZoneError:
|
except (ValueError, ZoneInfoNotFoundError):
|
||||||
# If timezone is invalid, return single period with no offset
|
# If timezone is invalid, return single period with no offset
|
||||||
return [(start_time, end_time, 0)]
|
return [(start_time, end_time, 0)]
|
||||||
|
|
||||||
@@ -68,14 +67,14 @@ def get_dst_transitions(
|
|||||||
current = start_time
|
current = start_time
|
||||||
|
|
||||||
# Get initial offset
|
# Get initial offset
|
||||||
dt = datetime.datetime.utcfromtimestamp(current).replace(tzinfo=pytz.UTC)
|
dt = datetime.datetime.fromtimestamp(current, tz=datetime.UTC)
|
||||||
local_dt = dt.astimezone(tz)
|
local_dt = dt.astimezone(tz)
|
||||||
prev_offset = local_dt.utcoffset().total_seconds()
|
prev_offset = local_dt.utcoffset().total_seconds()
|
||||||
period_start = start_time
|
period_start = start_time
|
||||||
|
|
||||||
# Check each day for offset changes
|
# Check each day for offset changes
|
||||||
while current <= end_time:
|
while current <= end_time:
|
||||||
dt = datetime.datetime.utcfromtimestamp(current).replace(tzinfo=pytz.UTC)
|
dt = datetime.datetime.fromtimestamp(current, tz=datetime.UTC)
|
||||||
local_dt = dt.astimezone(tz)
|
local_dt = dt.astimezone(tz)
|
||||||
current_offset = local_dt.utcoffset().total_seconds()
|
current_offset = local_dt.utcoffset().total_seconds()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user