Replies: 2 comments 1 reply
-
|
Thanks for the in-depth feedback, I'll be going over it in depth soon. About the bug: I have an idea what could be causing this, as I've fixed a similar bug in the upper bound before. An oversight of mine to not consider the lower bound may be affected by the same issue. Fortunately, it only affects dates in the far past--but of course still a serious thing to fix ASAP |
Beta Was this translation helpful? Give feedback.
-
|
The Bug should now be fixed, let me know if I missed something Missing features
I'll get to "surprising" and "docs fixes" later. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Hello Arie,
I finally had some time to validate and use the
wheneverlibrary. I downloaded 0.9.2 from PyPI. (BTW, your GitHub Releases page has the "latest" pinned to 0.9.1). I have some bug reports and comments, and I thought I should send them to you beforewheneveris bumped to v1.0.I divide my feedback into 5 sections:
Kudos
datetimeintoZonedDateTimeandInstantis wonderful. Makes the calling code easier to understand and maintain.LocalDateTimetoPlainDateTime. I supported this last year, and I support it now.Bug: Incorrect DST Transitions and UTC Offsets
This is the most serious. I will file a separate Issue after I finish writing this. (Done: #296)
As I understand, you have delegated the TZDB processing to a Rust library. It appears that the Rust library calculates the wrong DST transitions and UTC offsets for a large number zones in the TZDB database, for their earliest years (say < 1960 or so). Since the early DST transitions are incorrect, this produces incorrect UTC offsets from the year 0001 to about 1950 (depending on the zone). The problem affects 286 zones out of 340 zones in TZDB 2025b.
Missing or Desired Features
I ported a bunch of scripts and code to
whenever, and I noticed several missing features. It would be great to have these.DST Offset From ZonedDateTime
Given a
ZonedDateTime, I want to obtain the current DST offset. This allows the calling code to determine if the user in a particular timezone is observing DST time or STD time. The client code can then prompt the user, "You are observing DST time. Did you mean XXX instead?"Abbreviation From ZonedDateTime
The TZDB defines an abbreviation for every timezone at every moment. For example, "PST" for Pacific Standard Time, or "CET" for Central European Time. As far as I can tell,
ZonedDateTimedoes not expose this information. I would like aZonedDateTime.abbrevproperty.Yes, abbreviations are not unique, and shouldn't be used in situations where accuracy is required. But for everyday use by normal people, living in a fixed or limited number of timezones (e.g. USA or Canada), displaying the time using the abbreviation is useful and more familiar than ISO 8601 or RFC 3339 format.
IsGap or IsOverlap from PlainDateTime
Given a
PlainDateTimeand atz, I want to know whether the user-provided plainDateTime is in a gap or an overlap. I don't see way to do that in the current API.The
ZonedDateTime.is_ambiguous()does not do what I want. BecauseZonedDateTimeautomatically disambiguates the invalid plainDateTime before returning the fully constructed object. Callingis_ambiguous()always returnsFalsefor a gap.I think what I need is
PlainDateTime.disambiguation(tz: str), which returns an enum or an int corresponding toUnique,Gap, orOverlap. This allows me to prompt the user like this: "The date you have entered does not exist. Did you mean XXX?" Or, "The date you have entered is a duplicate. Did you mean YYY?"You could argue that the
disambiguation()function does not belong onPlainDateTime, because it is not supposed to know about timezones. You would be right. It really belong in aTimeZoneobject, likeTimeZone.disambiguate(pdt: PlainDateTime) -> int.Unfortunately, the
wheneverlibrary does not expose aTimeZoneobject. I may have mentioned last year that exposing aTimeZoneobject has a lot of advantages. It allows methods likedisambiguate(PlainDateTime) -> int, and things likeTimeZone.transitions(start_year: int, until_year: int) -> List[PlainDateTime]which returns the list of all transitions fromstarttountilyear. But it does make the API slightly more complex.Version Numbers
For validation and testing, it's useful to know the version numbers of the TZDB database (e.g. "2025b") and the version number of the
wheneverlibrary ("0.9.2"). I don't see a way to get this information.Surprising or Confusing
I understand that most of the following is probably bikeshedding. You have probably thought through the pros and cons of various options. But I want to give my feedback on my first impressions of the
wheneverAPI, as I migrated some code over.TimeDelta.in_seconds() Returns a Float?
I wanted to know the
ZonedDateTime.offsetin seconds, so I calledZonedDateTime.offset.in_seconds(), but that returns afloat? Totally unexpected. Am I supposed to cast it to anint? My expectation was thatin_seconds()returns anint,in_millisecondsreturns anint, andin_microseconds()returns anint. [Edit: I expand on this point in #298]TimeDelta.round(mode=) Cannot Truncate
The
TimeDelta.round()takes an optionalmodeparameter, which ishalf_evenby default. Maybe I missed it, but I cannot find the explanation of any of the rounding modes: 'ceil', 'floor', 'half_ceil', 'half_floor', 'half_even'. I don't know what 'half_ceil', 'half_floor, or 'half_evenmeans.My expectation was that
round()truncates by default, because I expected that to be the most common operation. I think that means thatmodemust be set to... nope... I don't see atruncateoption, since 'floor' does not mean 'truncate' for negative numbers. So am I supposed to cast toint()to truncate?Why Does PlainDateTime.add() Requires ignore_dst=True ?
Why does adding and subtracting a
PlainDateTimerequireignore_dst=True? In my mind, aPlainDateTimedoes not know about DST, by definition. It represents an abstract Gregorian calendar object, without knowledge of timezones or DST. I should be able to add and subtract arbitrary hours, minutes, seconds, without limitations.Instant.timestamp()
You are probably following the standard
datetimelibrary conventions, but I wish this was namedInstant.unixseconds()for better self-documentation. I work with different datetime libraries, SQL databases, and other applications using the term "timestamp". Half the time, I don't remember what "timestamp" means in a particular context, so I have to look it up. Is it anint, a string, an object, or something else?There is no such ambiguity with
Instant.unixseconds(). I recommendunixseconds()instead ofepochseconds(), because the Epoch may not be Unix Epoch, it could be something else. Sounixseconds()also makes that clear.to_tz()
A small nit: I think all other methods named
to_XXX()converts the current object to the object namedXXX. Exceptto_tz(). I don't know, maybe something else would be better? Examples:convert_to_tz(), orconvert_tz(), orfor_tz()But I can probably get used to
to_tz()eventually.assume_YYY()
I can't quite understand why some methods are named
to_XXX()and some are namedassume_YYY(). I know there's an explanation in the docs somewhere (I can't find it now), but I did not understand it when I read it.Documentation Fixes
LocalDT -> PlainDT
There is at least one place where
LocalDTis used. After the rename toPlainDateTime, I think you meanPlainDT._LocalTime, _LocalDate
Maybe these should be renamed
_PlainTimeand_PlainDate? They are internal, so I guess it doesn't matter..round(mode) Not Documented
The
modeparameter ofround()is not documented, as far as I can tell._BasicConversions.from_py_datetime()
There is a Note that says:
It took me several attempts to understand this. I think you mean the following:
Beta Was this translation helpful? Give feedback.
All reactions