-
Notifications
You must be signed in to change notification settings - Fork 0
bugfix: correct timezone handling in as_dt and iso_tz #5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
e6e5202
cf5d9a2
b6e109c
970bf8b
a06ebe8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -227,7 +227,7 @@ def from_parts_utc(cls, y: int, m: int = 1, d: int = 1, hh: int = 0, mm: int = 0 | |
|
|
||
| @classmethod | ||
| def from_parts(cls, y: int, m: int = 1, d: int = 1, hh: int = 0, mm: int = 0, ss: int = 0, ms: int = 0, us: int = 0, ns: int = 0, | ||
| tzinfo: Union[str, dt_tzinfo] = timezone.utc) -> Self: | ||
| tzinfo: dt_tzinfo | str = timezone.utc) -> Self: | ||
| total_us = ms * 1000 + us | ||
| if isinstance(tzinfo, str): | ||
| tzinfo = pytz.timezone(tzinfo) | ||
|
|
@@ -380,30 +380,31 @@ def timestamp(self) -> "TS": | |
| """ | ||
| raise NotImplementedError() | ||
|
|
||
| def as_dt(self, tz: Union[dt_tzinfo, str] = timezone.utc) -> datetime: | ||
| def as_dt(self, tz: dt_tzinfo | str = timezone.utc) -> datetime: | ||
| """ | ||
| Returns an "aware" datetime object in UTC by default | ||
| """ | ||
| ts = float(self.timestamp()) | ||
| try: | ||
| if isinstance(tz, str): | ||
| tz = pytz.timezone(tz) | ||
| naive_dt = datetime.fromtimestamp(int(self)) | ||
| dt = tz.localize(naive_dt) | ||
| else: | ||
| dt = datetime.fromtimestamp(ts, tz=tz) | ||
| dt = datetime.fromtimestamp(ts, tz=timezone.utc) | ||
| if tz != timezone.utc: | ||
| dt = dt.astimezone(tz) | ||
| return dt | ||
| except OSError: | ||
| # can't convert due to overflow error, so we need to do it using other method | ||
| # return datetime.utcfromtimestamp(float(ts)) also fails | ||
| days = ts // SECONDS_PER_DAY | ||
| years = int(days / AVG_DAYS_PER_YEAR) # Average considering leap years | ||
| year = 1970 + years | ||
| year_date = datetime(year, 1, 1, tzinfo=tz) | ||
| td_to_year_beginning = year_date - datetime(1970, 1, 1, tzinfo=tz) | ||
| year_date = datetime(year, 1, 1, tzinfo=timezone.utc) | ||
| td_to_year_beginning = year_date - datetime(1970, 1, 1, tzinfo=timezone.utc) | ||
| year_remaining_sec = ts - td_to_year_beginning.total_seconds() | ||
| td = timedelta(seconds=year_remaining_sec) | ||
| res = year_date + td | ||
| if tz != timezone.utc: | ||
| res = res.astimezone(tz) | ||
| return res | ||
|
|
||
| def as_local_dt(self) -> datetime: | ||
|
|
@@ -466,7 +467,7 @@ def iso_date_basic(self, use_zulu: bool = False) -> str: | |
| """ | ||
| return self.iso_date(sep="", use_zulu=use_zulu) | ||
|
|
||
| def iso_tz(self, tz: Union[str, dt_tzinfo]) -> str: | ||
| def iso_tz(self, tz: dt_tzinfo | str) -> str: | ||
| """ | ||
| Returns ISO date format with TZ info. | ||
| Example: 2021-01-01 | ||
|
Comment on lines
+470
to
473
|
||
|
|
@@ -660,7 +661,7 @@ def as_iso_date_basic(self) -> str: | |
| s = self.as_dt().strftime("%Y%m%d") | ||
| return s | ||
|
|
||
| def as_iso_tz(self, tz: Union[str, dt_tzinfo]) -> str: | ||
| def as_iso_tz(self, tz: dt_tzinfo | str) -> str: | ||
| if isinstance(tz, str): | ||
| tz = pytz.timezone(tz) | ||
| dt = self.as_dt(tz=tz) | ||
|
|
@@ -842,7 +843,7 @@ def __setattr__(self, name: str, value: Any) -> None: | |
| object.__setattr__(self, name, value) | ||
|
|
||
| @classmethod | ||
| def from_year(cls, year: int, tz: Union[str, dt_tzinfo] = timezone.utc, dtype:Optional[Type[BaseTS]]=None) -> "TSInterval": | ||
| def from_year(cls, year: int, tz: dt_tzinfo | str = timezone.utc, dtype:Optional[Type[BaseTS]]=None) -> "TSInterval": | ||
| """ | ||
| Create an interval covering the full calendar year in the requested timezone (defaults to UTC). | ||
| """ | ||
|
|
@@ -1107,7 +1108,7 @@ class iBaseTS(BaseTS, int): | |
| @override | ||
| @classmethod | ||
| def from_parts(cls, y: int, m: int = 1, d: int = 1, hh: int = 0, mm: int = 0, ss: int = 0, ms: int = 0, us: int = 0, ns: int = 0, | ||
| tzinfo: Union[str, dt_tzinfo] = timezone.utc) -> Self: | ||
| tzinfo: dt_tzinfo | str = timezone.utc) -> Self: | ||
| if isinstance(tzinfo, str): | ||
| tzinfo = pytz.timezone(tzinfo) | ||
| naive_dt = datetime(y, m, d, hh, mm, ss, 0) | ||
|
|
@@ -1256,16 +1257,16 @@ def _get_auto_timespec(self) -> str: | |
| """ | ||
| return "seconds" | ||
|
|
||
| def as_dt(self, tz: Union[str, dt_tzinfo] = timezone.utc) -> datetime: | ||
| def as_dt(self, tz: dt_tzinfo | str = timezone.utc) -> datetime: | ||
| """ | ||
| Returns an "aware" datetime object in UTC by default | ||
| """ | ||
|
Comment on lines
+1260
to
1263
|
||
| if isinstance(tz, str): | ||
| tz = pytz.timezone(tz) | ||
| naive_dt = datetime.fromtimestamp(int(self)) | ||
| return tz.localize(naive_dt) | ||
| assert isinstance(tz, dt_tzinfo) | ||
| utc_dt = datetime.fromtimestamp(int(self), tz=tz) | ||
| utc_dt = datetime.fromtimestamp(int(self), tz=timezone.utc) | ||
| if tz != timezone.utc: | ||
| utc_dt = utc_dt.astimezone(tz) | ||
| return utc_dt | ||
|
|
||
|
|
||
|
|
@@ -1306,12 +1307,16 @@ def __new__(cls, ts: Union[int, float, str], utc: bool = True): | |
| def as_msec(self) -> "iTSms": | ||
| return self | ||
|
|
||
| def as_dt(self, tz: dt_tzinfo = timezone.utc) -> datetime: | ||
| def as_dt(self, tz: dt_tzinfo | str = timezone.utc) -> datetime: | ||
| """ | ||
| Returns an "aware" datetime object in UTC by default | ||
| """ | ||
| seconds, ms = divmod(self, 1000) | ||
| return datetime(1970, 1, 1, tzinfo=timezone.utc) + timedelta(seconds=seconds, milliseconds=ms) | ||
| utc_dt = datetime(1970, 1, 1, tzinfo=timezone.utc) + timedelta(seconds=seconds, milliseconds=ms) | ||
| if isinstance(tz, str): | ||
| tz = pytz.timezone(tz) | ||
| assert isinstance(tz, dt_tzinfo) | ||
| return utc_dt.astimezone(tz) | ||
|
|
||
| def _get_auto_timespec(self) -> str: | ||
| """ | ||
|
|
@@ -1376,19 +1381,16 @@ def _get_auto_timespec(self) -> str: | |
| """ | ||
| return "microseconds" | ||
|
|
||
| def as_dt(self, tz: Union[str, dt_tzinfo] = timezone.utc) -> datetime: | ||
| def as_dt(self, tz: dt_tzinfo | str = timezone.utc) -> datetime: | ||
| """ | ||
| Returns an "aware" datetime object in UTC by default | ||
| """ | ||
| seconds, us = divmod(self, 1_000_000) | ||
| utc_dt = datetime(1970, 1, 1, tzinfo=timezone.utc) + timedelta(seconds=seconds, microseconds=us) | ||
| if isinstance(tz, str): | ||
| tz = pytz.timezone(tz) | ||
| naive_dt = EPOCH_DT + timedelta(seconds=seconds, microseconds=us) | ||
| dt = tz.localize(naive_dt) | ||
| else: | ||
| assert isinstance(tz, dt_tzinfo) | ||
| dt = datetime(1970, 1, 1, tzinfo=tz) + timedelta(seconds=seconds, microseconds=us) | ||
| return dt | ||
| assert isinstance(tz, dt_tzinfo) | ||
| return utc_dt.astimezone(tz) | ||
|
|
||
| def iso_basic(self, sep="-", use_zulu: bool = True) -> str: | ||
| """ | ||
|
|
@@ -1505,7 +1507,7 @@ def as_usec(self) -> "iTSus": | |
| us += 1 | ||
| return iTSus(us) | ||
|
|
||
| def as_dt(self, tz: Union[str, dt_tzinfo] = timezone.utc) -> datetime: | ||
| def as_dt(self, tz: dt_tzinfo | str = timezone.utc) -> datetime: | ||
| """ | ||
| Returns an "aware" datetime object in UTC by default; | ||
| Since the datetime object has a microsecond resolution, we'll convert to iTSus and return it | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BaseTS.as_dt()still handlestzpassed as a string by building a naivedatetime.fromtimestamp(int(self))(local time) and thenlocalize()-ing it into the requested timezone. That makes the represented instant host-dependent and breaksiso_tz("...")for types that rely onBaseTS.as_dt(e.g.,TS). Consider constructing the datetime as UTC-aware first (or usingdatetime.fromtimestamp(ts, tz=timezone.utc)) and then converting with.astimezone(target_tz)for both string and tzinfo inputs.