You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Inconsistency in datetime.utcfromtimestamp(Decimal) #67795
Here is the python-only fix that eliminates TypeError and produces correct number of milliseconds. The problem with C implementation lies in _PyTime_ObjectToDenominator function where there is an explicit check for a float. This check could be relaxed to include decimals as the rest of the code seem to work fine with decimal. I'm not entirely sure how that could be done though.
Here is a patch that adds Decimal support for datetime.fromtimestamp() and datetime.utcfromtimestamp(). As side effect Decimal timestamps now are supported in few other places, e.g. in os.utime().
As side effect Decimal timestamps now are supported in few other places, e.g. in os.utime().
It would be more consistent to support decimal.Decimal nowhere or "everywhere". IMO the new _PyTime_FromSecondsObject() (very close to _PyTime_ObjectToDenominator, but using time_t) should also be patched.
Please add some tests for decimal.Decimal in test_time directly. Usually, I try to test rounding and overflow. Testing for overflow is not always possible because it may depend on the platform.
--
See also the PEP-410: "PEP-410 - Use decimal.Decimal type for timestamps". The PEP was rejected. The compromise was a new timestamp format for os.utime(): a number of nanoseconds since the UNIX epoch (1970-01-01). I partially implemented a private API for this PEP in the issue bpo-22117: the new _PyTime_t is a number of timestamp using an arbitrary resolution (currently, it's a number of nanoseconds using a 64-bit signed integer type).
skrah commented on Mar 30, 2015
skrahmannequin
Mannequin
More actions
I just looked at this very briefly: Is the patch context insensitive?
IOW, do things still work if you change the thread-local context:
The problem is that the Decimal *was* supported in 2.7. So this issue can be considered not as adding new feature, but as fixing a regression. But changes are too large for just bugfix.
It would be more consistent to support decimal.Decimal nowhere or "everywhere". IMO the new _PyTime_FromSecondsObject() (very close to _PyTime_ObjectToDenominator, but using time_t) should also be patched.
Will add Decimal support in all functions in Python/pytime.c that support floats.
Please add some tests for decimal.Decimal in test_time directly. Usually, I try to test rounding and overflow. Testing for overflow is not always possible because it may depend on the platform.
Will do.
Is the patch context insensitive?
No, the patch is context sensitive. I think the end user is responsible to set an appropriate context if it want to create a datetime from Decimal timestamp. At least until we add functions that return Decimal.
skrah commented on Mar 30, 2015
skrahmannequin
Mannequin
More actions
I think we should try to avoid depending on global state in
the stdlib, at least in new code. Also, if something is not
really a decimal computation, Decimal itself tries to ignore
the context (like Decimal.__repr__).
At least I would expect this datetime function to be context
independent.
I'm not sure if either of these patches got merged, but at some point this was fixed:
Python 3.7.2 (default, Feb 9 2019, 13:18:43)
[GCC 8.2.1 20181127] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from datetime import datetime
>>> from decimal import Decimal
>>> datetime.utcfromtimestamp(Decimal(123456.0))
datetime.datetime(1970, 1, 2, 10, 17, 36)
I recommend that someone add some regression tests to this, then we can close the issue.
I'm also going to mark this as "easy" since adding tests for this particular example should be pretty simple.
Oh actually that's my mistake. I can't reproduce the failure in the constructor in the Python version of the module, and also it seems to be fixed in the pure Python version as of at least 3.6:
Python 3.6.7 (default, Oct 25 2018, 16:11:17)
[GCC 8.2.1 20180831] on linux
>>> import sys
>>> sys.modules['_datetime'] =None
>>> from decimal import Decimal as D
>>> from datetime import datetime
>>> datetime.utcfromtimestamp(D(123456.12345))
datetime.datetime(1970, 1, 2, 10, 17, 36, 123450)
But the truncation behavior is still present in the C version as of Python 3.8.0a1+:
Python 3.8.0a1+ (heads/master:3766f18, Feb 11 2019, 12:52:31)
[GCC 8.2.1 20181127] on linux
>>> from datetime import datetime
>>> from decimal import Decimal as D
>>> datetime.utcfromtimestamp(D(123456.12345))
datetime.datetime(1970, 1, 2, 10, 17, 36)
I still think we need a test for the constructor behavior, but I'm going to remove "easy", since we still need to fix truncation in the C version.
No, the bug is not fixed, and this is not easy issue. You should use non-integer Decimals to reproduce it. In 3.8 this emits a deprecation warning:
>>> import datetime
>>> from decimal import Decimal as D
>>> datetime.datetime.utcfromtimestamp(D(1425808327.307651))
<stdin>:1: DeprecationWarning: an integer is required (got type decimal.Decimal). Implicit conversion to integers using __int__ is deprecated, and may be removed in a future version of Python.
datetime.datetime(2015, 3, 8, 9, 52, 7)
Furthermore, utcfromtimestamp is deprecated, and it seems no one has actually used this, since I can find no complaints, I propose we simply close this effort. Only accepting int/float is fine, converting a Decimal is not difficult.
I only used utcfromtimestamp() in examples because it produces the same result independently from the time zone. All of the above is applied to fromtimestamp() too.
The original issue was because the C code first checked if the argument is float, and used conversion to int (which called __int__) for other types. Now __int__ no longer called in implicit conversion to int, so we get a TypeError instead of truncation.
On other hand, the Python code now uses different method to split a number on the integer and fractional parts -- math.modf(). It accepts any real numbers and converts them to float.
This is a one of rare cases where float is accepted, but other real numbers are not. Usually, real numbers are implicitly converted to float if float is accepted. It is not so difficult to make any real numbers be accepted as timestamp and timeout arguments which will be implicitly converted to floats. Although using more precise numbers will not improve precision. The hard part is updating documentation and adding many new tests for functions that directly use such conversion. #139224 implements this part.
Preserving precision of more precise numbers is more difficult task. My initial patch tried to do this by using arithmetic operations on precise numbers, for example, the fractional part can be calculated as t - int(t) or t % 1. It works iff all operations on more precise numbers has comparable precision. Unfortunately, for Decimal the result of arithmetic operation depends on the local context, not on the precision of operands. It is also no longer work because the half-to-even rounding is now used for timestamp, it is more difficult to implement.
Other way -- using as_integer_ratio() (or numerator and denumerator) to represent the number as rational, and then perform exact operations on rationals.
Activity
serhiy-storchaka commentedon Mar 8, 2015
First, datetime.utcfromtimestamp() drops fractional part of Decimal argument:
Second, it works only with C implementation. With Python implementation Decimals are not supported at all.
In 2.7 Decimals are accepted and are not truncated.
So this can be considered as a regression.
SilentGhost commentedon Mar 8, 2015
Here is the python-only fix that eliminates TypeError and produces correct number of milliseconds. The problem with C implementation lies in _PyTime_ObjectToDenominator function where there is an explicit check for a float. This check could be relaxed to include decimals as the rest of the code seem to work fine with decimal. I'm not entirely sure how that could be done though.
serhiy-storchaka commentedon Mar 10, 2015
The fix for Python implementation LGTM, but a fix for C implementation is needed too.
serhiy-storchaka commentedon Mar 30, 2015
Here is a patch that adds Decimal support for datetime.fromtimestamp() and datetime.utcfromtimestamp(). As side effect Decimal timestamps now are supported in few other places, e.g. in os.utime().
vstinner commentedon Mar 30, 2015
I reviewed datetime_fromtimestamp_decimal.patch.
It would be more consistent to support decimal.Decimal nowhere or "everywhere". IMO the new _PyTime_FromSecondsObject() (very close to _PyTime_ObjectToDenominator, but using time_t) should also be patched.
Please add some tests for decimal.Decimal in test_time directly. Usually, I try to test rounding and overflow. Testing for overflow is not always possible because it may depend on the platform.
--
See also the PEP-410: "PEP-410 - Use decimal.Decimal type for timestamps". The PEP was rejected. The compromise was a new timestamp format for os.utime(): a number of nanoseconds since the UNIX epoch (1970-01-01). I partially implemented a private API for this PEP in the issue bpo-22117: the new _PyTime_t is a number of timestamp using an arbitrary resolution (currently, it's a number of nanoseconds using a 64-bit signed integer type).
skrah commentedon Mar 30, 2015
I just looked at this very briefly: Is the patch context insensitive?
IOW, do things still work if you change the thread-local context:
serhiy-storchaka commentedon Mar 30, 2015
The problem is that the Decimal *was* supported in 2.7. So this issue can be considered not as adding new feature, but as fixing a regression. But changes are too large for just bugfix.
Will add Decimal support in all functions in Python/pytime.c that support floats.
Will do.
No, the patch is context sensitive. I think the end user is responsible to set an appropriate context if it want to create a datetime from Decimal timestamp. At least until we add functions that return Decimal.
skrah commentedon Mar 30, 2015
I think we should try to avoid depending on global state in
the stdlib, at least in new code. Also, if something is not
really a decimal computation, Decimal itself tries to ignore
the context (like Decimal.__repr__).
At least I would expect this datetime function to be context
independent.
pganssle commentedon Feb 27, 2019
I'm not sure if either of these patches got merged, but at some point this was fixed:
Python 3.7.2 (default, Feb 9 2019, 13:18:43) [GCC 8.2.1 20181127] on linux Type "help", "copyright", "credits" or "license" for more information. >>> from datetime import datetime >>> from decimal import Decimal >>> datetime.utcfromtimestamp(Decimal(123456.0)) datetime.datetime(1970, 1, 2, 10, 17, 36)I recommend that someone add some regression tests to this, then we can close the issue.
I'm also going to mark this as "easy" since adding tests for this particular example should be pretty simple.
pganssle commentedon Feb 27, 2019
Oh actually that's my mistake. I can't reproduce the failure in the constructor in the Python version of the module, and also it seems to be fixed in the pure Python version as of at least 3.6:
But the truncation behavior is still present in the C version as of Python 3.8.0a1+:
I still think we need a test for the constructor behavior, but I'm going to remove "easy", since we still need to fix truncation in the C version.
serhiy-storchaka commentedon Feb 27, 2019
No, the bug is not fixed, and this is not easy issue. You should use non-integer Decimals to reproduce it. In 3.8 this emits a deprecation warning:
StanFromIreland commentedon Jul 9, 2025
Ironically it is the opposite now.
Furthermore,
utcfromtimestampis deprecated, and it seems no one has actually used this, since I can find no complaints, I propose we simply close this effort. Only accepting int/float is fine, converting aDecimalis not difficult.pythongh-67795: Accept any real numbers as timestamp and timeout
serhiy-storchaka commentedon Sep 22, 2025
I only used
utcfromtimestamp()in examples because it produces the same result independently from the time zone. All of the above is applied tofromtimestamp()too.The original issue was because the C code first checked if the argument is
float, and used conversion to int (which called__int__) for other types. Now__int__no longer called in implicit conversion to int, so we get a TypeError instead of truncation.On other hand, the Python code now uses different method to split a number on the integer and fractional parts --
math.modf(). It accepts any real numbers and converts them to float.This is a one of rare cases where float is accepted, but other real numbers are not. Usually, real numbers are implicitly converted to float if float is accepted. It is not so difficult to make any real numbers be accepted as timestamp and timeout arguments which will be implicitly converted to floats. Although using more precise numbers will not improve precision. The hard part is updating documentation and adding many new tests for functions that directly use such conversion. #139224 implements this part.
Preserving precision of more precise numbers is more difficult task. My initial patch tried to do this by using arithmetic operations on precise numbers, for example, the fractional part can be calculated as
t - int(t)ort % 1. It works iff all operations on more precise numbers has comparable precision. Unfortunately, for Decimal the result of arithmetic operation depends on the local context, not on the precision of operands. It is also no longer work because the half-to-even rounding is now used for timestamp, it is more difficult to implement.Other way -- using
as_integer_ratio()(ornumeratoranddenumerator) to represent the number as rational, and then perform exact operations on rationals.gh-67795: Accept any real numbers as timestamp and timeout (GH-139224)
pythongh-67795: Improve precision of non-float timestamp and timeout …
serhiy-storchaka commentedon Sep 23, 2025
#139273 implements the hard part -- now you can specify exact time value using Decimal or Fraction.
pythongh-67795: Improve precision of non-float timestamp and timeout …