arrow-py / arrow

🏹 Better dates & times for Python
https://arrow.readthedocs.io
Apache License 2.0
8.63k stars 669 forks source link

error in Time transform of 9999-12-31 #1159

Open funicia opened 11 months ago

funicia commented 11 months ago

Issue Description

i want to change '9999-12-31' to timestamp, and also in anather scene, i need change the timestamp(253402214400) to datetime. but I got confused when I test this :

 dt2=arrow.get('9999-12-31')
 dt2.timestamp()

then get:

253402214400.0

and change it to datetime: arrow.get(253402214400.0)

but get :

<Arrow [1978-01-11T21:30:14.400000+00:00]>

this code should get result like of : "9999-12-31 "

So, It is a bug , or my code is not correct!

System Info

yiransii commented 11 months ago

I am unsure what the intended behavior would be here as 9999 is an odd input year. I can take on this - but could someone verify the intended behavior here?

funicia commented 11 months ago

some system stored the timestamp of 9999 to mark the data will never be expired, and the passed date like 2022 means has expired 。And the BI System need to show as datetime like of 9999。so i need find a way to resolve it.

On linux, the datetime.fromtimestamp can do this correctly, but , this function will raise error on windows when the datetime over 2038. So I am finding a way to hanle this problem both run well on Linux and Window. At least, the arrow can transform 9999 to timestamp correctly.

By the way, the arrow can only tranform timestamp of 2999-12-31 to the correct datetime, timestamp of 3001 will be 1971 either.

sanskark commented 9 months ago

@funicia Have you solved this bug? If so can you share how you done it

tonyk232 commented 3 months ago

@yiransii Just in case you would still want to work on this one, the root cause for this is not year 9999 being invalid, but there's a rather obscure bug.

When you construct an Arrow object from a timestamp, it calls normalize_timestamp from util.py. The first few lines:

def normalize_timestamp(timestamp: float) -> float:
    """Normalize millisecond and microsecond timestamps into normal timestamps."""
    if timestamp > MAX_TIMESTAMP:
        if timestamp < MAX_TIMESTAMP_MS:
            timestamp /= 1000

MAX_TIMESTAMP is from constants.py:

try:
    # Get max timestamp. Works on POSIX-based systems like Linux and macOS,
    # but will trigger an OverflowError, ValueError, or OSError on Windows
    _MAX_TIMESTAMP = datetime.max.timestamp()
except (OverflowError, ValueError, OSError):  # pragma: no cover
    # Fallback for Windows and 32-bit systems if initial max timestamp call fails

What happens is that despite the comment, datetime.max.timestamp() only works on Linux if the local timezone is UTC or UTC-xx:

$ TZ=UTC python3
>>> from datetime import datetime
>>> datetime.max.timestamp()
253402300800.0

$ TZ=Europe/Berlin python3
>>> from datetime import datetime
>>> datetime.max.timestamp()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: year 10000 is out of range

Going back to normalize_timestamp, this means 253402214400 gets divided by 1000, which then matches the original bug report:

>>> arrow.get(253402214.4)
<Arrow [1978-01-11T21:30:14.400000+00:00]>