-
-
Notifications
You must be signed in to change notification settings - Fork 61
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
Issues with odd months when restricting to "day" precision (precisedelta) #14
Comments
@eldipa Hi! Any thoughts on this? |
Consider the following examples, similar to the ones that @ddellspe gave but without forcing a >>> import humanize
>>> import datetime
>>> humanize.precisedelta(datetime.timedelta(days=31))
'1 month and 0 days'
>>> humanize.precisedelta(datetime.timedelta(days=62))
'2 months and 1 day'
>>> humanize.precisedelta(datetime.timedelta(days=92))
'3 months and 0 days'
>>> humanize.precisedelta(datetime.timedelta(days=32))
'1 month and 1 days' For even months the delta seems ok but for odd months it looks weird. When the month is odd we get a fractional number of days. These fraction was not properly propagated to the next unit, Therefore the fractional days was formatted with the default format for any integer unit: This explains why:
The fix is to do the propagation correctly. With this, we will get the following results: >>> import humanize
>>> import datetime
>>> humanize.precisedelta(datetime.timedelta(days=31))
'1 month and 12 hours'
>>> humanize.precisedelta(datetime.timedelta(days=62))
'2 months and 1 day'
>>> humanize.precisedelta(datetime.timedelta(days=92))
'3 months and 12 hours'
>>> humanize.precisedelta(datetime.timedelta(days=32))
'1 month, 1 day and 12 hours' Notice how the fractional part of the days was propagated to the If we want to use >>> import humanize
>>> import datetime
>>> humanize.precisedelta(datetime.timedelta(days=31), minimum_unit='days')
'1 month and 0.50 days'
>>> humanize.precisedelta(datetime.timedelta(days=62), minimum_unit='days')
'2 months and 1 day'
>>> humanize.precisedelta(datetime.timedelta(days=92), minimum_unit='days')
'3 months and 0.50 days'
>>> humanize.precisedelta(datetime.timedelta(days=32), minimum_unit='days')
'1 month and 1.50 days' Notice also how the fractional part didn't disappear as the original issue stated. Instead was correctly formatted with the default Requiring an integer day number is just a matter of saying that: >>> import humanize
>>> import datetime
>>> humanize.precisedelta(datetime.timedelta(days=31), minimum_unit='days', format='%d')
'1 month and 0 days'
>>> humanize.precisedelta(datetime.timedelta(days=62), minimum_unit='days', format='%d')
'2 months and 1 day'
>>> humanize.precisedelta(datetime.timedelta(days=92), minimum_unit='days', format='%d')
'3 months and 0 days'
>>> humanize.precisedelta(datetime.timedelta(days=32), minimum_unit='days', format='%d')
'1 month and 1 days' There are some gotchas like '0 days' and '1 days'. The issue here is at the formatting level: using For this we need another fix: the possibility to truncate the fractional part before doing the formatting and if a 0 value is obtained, increase the minimum unit (go from These would be the results: >>> import humanize
>>> import datetime
>>> humanize.precisedelta(datetime.timedelta(days=31), minimum_unit='days', format='%d', truncate=True)
'1 month'
>>> humanize.precisedelta(datetime.timedelta(days=62), minimum_unit='days', format='%d', truncate=True)
'2 months and 1 day'
>>> humanize.precisedelta(datetime.timedelta(days=92), minimum_unit='days', format='%d', truncate=True)
'3 months'
>>> humanize.precisedelta(datetime.timedelta(days=32), minimum_unit='days', format='%d', truncate=True)
'1 month and 1 days' Notice that This is what you get if you use the default >>> import humanize
>>> import datetime
>>> humanize.precisedelta(datetime.timedelta(days=31), minimum_unit='days', truncate=True)
'1 month'
>>> humanize.precisedelta(datetime.timedelta(days=62), minimum_unit='days', truncate=True)
'2 months and 1 day'
>>> humanize.precisedelta(datetime.timedelta(days=92), minimum_unit='days', truncate=True)
'3 months'
>>> humanize.precisedelta(datetime.timedelta(days=32), minimum_unit='days', truncate=True)
'1 month and 1.50 days' |
…dary in precisedelta See python-humanize#14 for discussion.
Thanks for the writeup and PR! I think we always want Are there any cases where we might want to use |
Actually >>> humanize.precisedelta(datetime.timedelta(days=31), minimum_unit='days')
'1 month and 0.50 days'
Now, if the user wants to use a interger-like formatting for the fractional part like >>> humanize.precisedelta(datetime.timedelta(days=31), minimum_unit='days', format='%d')
'1 month and 0 days'
>>> humanize.precisedelta(datetime.timedelta(days=31), minimum_unit='days', format='%d', truncate=True)
'1 month' Also, not very sure if In an ideal world |
I think I stumbled upon a fix for this issue in #39 (originally meant to fix #20). With the changes I made to
These are the test cases I added to that PR to cover this issue, along with their results before and after my changes: >>> precisedelta(dt.timedelta(days=31), "days", format="%d")
BEFORE: "1 month and 0 days"
AFTER: "1 month" >>> precisedelta(dt.timedelta(days=31.01), "days", format="%d")
BEFORE: "1 month and 0 days"
AFTER: "1 month and 1 day" >>> precisedelta(dt.timedelta(days=92), "days", format="%d")
BEFORE: "3 months and 0 days"
AFTER: "3 months" The fixes for both issues are contained in this commit. My solution differs from the one proposed here in that it doesn't make any changes/additions to the public API for |
As a side note, (possibly?) related to this issue only happening with odd months:
(Source: https://realpython.com/python-rounding/#rounding-half-to-even) I learned this just now while putting together my PR and didn't see any mention of Python's rounding strategy in this thread, so figured I'd share it here too just in case it's useful. 😊 |
@nuztalgia I took a look to your PR, nice contribution! I tested a few cases that it looks weird. The following is a comparison between the expected and the obtained output of your PR:
While you PR fixes the "apparent" rounding issue, it is not the "real" issue: the odd months issue. I left a description of the issue above and my PR fixes that in this commit Now, what my PR never does is to define when or when not Instead of letting the user to decide if Having said that, the regex may not be complete. Python supports And I would suggest to use a more strict regex with anchors like Does make this sense to you? Rebase your PR in terms of mine to have the proper fix plus the detection of when |
That does make sense, thank you for the detailed explanation @eldipa! I added the test cases you mentioned (as well as a few more that you outlined in your PR) to mine, and you're right that my changes didn't completely fix this issue. But cherry-picking your commit did the trick to make all the test cases pass! 😄 As for the regex, I believe the snippets we wrote are equivalent - mine uses With regards to supporting |
There are a lot of formats indeed! The user that writes Now, and let me think this from another angle. What if, in addition to a string, we accept a function for From code's perspective checking Something like: def precisedelta(....):
# ...
if iscallable(format):
round_fn = format
format = "%s"
else:
round_fn = None
# ...
if round_fn: # (1)
fmt_value = round_fn(fmt_value) Where (1) is here. From users' perspective calling What do you think? |
@eldipa the |
What did you do?
I'm trying to provide more precision than "months" when the
timedelta
is more than 1 month, but I'm finding some issues usingprecisedelta
set todays
with a format of%d
as seen below. This all seems to stem from the use of 30.5 as a divisor for mod inprecisedelta
and how it andngettext
interact with floats vs. ints when it comes to pluralize and the display of the days itself. For what it's worth, the same thing happens at second precision with microseconds present, etc. It seems to be at theminimum_unit
where this logic takes place.What did you expect to happen?
What actually happened?
What versions are you using?
Please include code that reproduces the issue (included in expected, actually happened above)
The text was updated successfully, but these errors were encountered: