Closed ariebovenberg closed 8 months ago
One idea I've been thinking of, is having two classes:
Duration
for exact durations. Can be measured absolutely in seconds, hours, etc. Cannot handle months, years because the duration of these depends on the context (i.e. months and years have different number of days). Perhaps even days
can be disallowed, since it can be confusing whether you mean "exactly 24 hours" or "the same time the next day" (which can often be 23 or 25 hours later due to DST)d = Duration(hours=34, seconds=1)
# addition/subtraction should work as expected
UTCDateTime.now() + d
# directly convert to common units
d.as_hours()
d.as_minutes()
# arithmetic between other Durations
d + Duration(hours=2)
# but NOT supported
Duration(months=3) # NOT supported
FuzzyDuration
for durations that involve more 'human' durations that don't have an exact interpretation in hours/seconds. f = FuzzyDuration(year=1)
# addition/subtraction should work as expected
UTCDateTime.now() + f
f.as_hours() # NOT supported
# can addition be supported in all cases...I wonder...
f + FuzzyDuration(months=3)
RFC5545 could also be an interestig resource on how to handle durations
Speaking of timedelta
, it would be nice to be able to use seconds()
and microseconds()
in addition to days()
, hours()
, and minutes()
.
(And if you think it doesn't fit in here, I'll be happy to open a separate issue and reference this one.)
@kseistrup agree, I always thought it weird that timedelta exposed the underlying fields (days
, seconds
, milliseconds
). I consider that almost 'implementation details'. I'd much rather have an opaque duration with in_seconds()
, in_microseconds()
, in_days()
, etc.
Here is a survey of how other languages and standards call the concepts of "fuzzy" and "exact" durations:
"fuzzy" duration | "exact" duration | |
---|---|---|
Python | timedelta | |
C# (NodaTime) | Period | Duration |
Rust (chrono) | Duration | |
JS (Temporal) | Duration | |
Haskell | Duration | |
Java | Period | Duration |
ISO8601 | Period |
I Could be wrong but... I would like simplification over a ton of classes... I mean, we all know a month has a relative duration. It depends on the context. But the truth is that Duration(months=1) will never mean or result into nothing if not used with a datetime.
I mean this is not fuzzy: datetime(2023,1,1) + Duration(month=1)
even when Duration(month=1)
is in fact fuzzy.
In other means, Duration(month=1)
has no "result" until it's arithmetically used with a date/datetime.
But either way maybe some assumptions are needed.
Thanks for the feedback 👍 , you're not the only one remarking about 'too many classes'.
A proposal to prevent "class overload" here: a .add
and .subtract
method, so you don't even need to interact with duration classes if you don't want to. This is basically what pendulum
and arrow
do. What do you think?
my_event.add(month=1)
I prefer using operators (+ , -) but either way it's ok.
A good time delta implementation is what's keeping me from dropping python-dateutil.
I prefer using operators (+ , -) but either way it's ok.
Here's a thought: we could expose the units as months()
, days()
, hours()
, etc.
Arithmetic would then automatically create the right classes:
my_event + months(3) # always works
fuzzy_duration = years(1) - weeks(7) + hours(4)
exact_duration = hours(2.5) + seconds(30)
fuzzy_duration.in_seconds() # not allowed. Only known relative to a DateTime
exact_duration.in_seconds() # allowed
I prefer using operators (+ , -) but either way it's ok.
Here's a thought: we could expose the units as
months()
,days()
,hours()
, etc.Arithmetic would then automatically create the right classes:
my_event + months(3) # always works fuzzy_duration = years(1) - weeks(7) + hours(4) exact_duration = hours(2.5) + seconds(30) fuzzy_duration.in_seconds() # not allowed. Only known relative to a DateTime exact_duration.in_seconds() # allowed
Seems awesome!
Giving it a second thought:
Using months, days, etc.... adds complication as well. Maybe going back to duration: Duration(months=1)
is easier (to the user!). As you will end up again with a lot of different clases.
Like:
fuzzy_duration = Duration(years=1) - Duration(weeks=7) + Duration(hours=4)
exact_duration = Duration(hours=2.5) + Duration(seconds=30)
It should not be possible to specify Duration(month=1, day=1)
because can lead to ambiguos results. The operation is not commutative as adding a month and then adding a day can have a different value than adding a day and then a month. Unless some operator precedence is set which I think complicates all a lot..
One way or another should be Duration(month=1)
or Month(1)
not duration(month=1)
or month(1)
Using months, days, etc.... adds complication as well. Maybe going back to duration: Duration(months=1) is easier (to the user!). As you will end up again with a lot of different clases.
You can have the benefits of both (fewer classes + less verbosity) if you do:
def months(i):
return Duration(months=i)
def days(i):
return Duration(days=i)
One way or another should be Duration(month=1) or Month(1) not duration(month=1) or month(1)
Yeah, I didn't make clear that month
is a function; that's why I had them lowercased. Indeed classes should be properly cased 😄
[...] The operation is not commutative as adding a month and then adding a day can have a different value than adding a day and then a month.
It's mathematical properties like this that I'd ideally like to express in different types. I took a first stab at it, you can see the docs for this dev version here. What do you think?
Unless some operator precedence is set which I think complicates all a lot..
Note this isn't such a crazy idea. Established libraries in other languages and RFC 5545 perform these operations from biggest (year) to smallest (day and ) unit.
Ok ok! Sorry I thought month() was a class constructor returning a month instance.
I will read the docs dev version and look into RFC 5545 🤯 didn’t know that this exists.
RFC 5545 🤯 didn’t know that this exists.
It's quite a read. Have a look at NodaTime or JS temporal for ideas on what a duration API can look like
Should there be a 'better' class to wrap
timedelta
?pro:
Duration
con:
timedelta
is fine. It doesn't have pitfalls likedatetime
. Wrapping in another class adds complexity