Closed spacemanspiff2007 closed 3 months ago
Hi @spacemanspiff2007, thanks for reaching out. Let me see if I understand your use case correctly:
if:
then: starting with SystemDateTime
and using .instant()
is the way to go.
# example: local system set to Europe/Amsterdam
>>> my_dt = SystemDateTime(2023, 4, 1, 12)
SystemDateTime(2023-04-01T12:00:00+02:00)
>>> my_dt.instant()
Instant(2023-04-01T10:00:00Z)
I thought the Idea is to normalize timestamps to Instant and do all calculations there.
In general, yes—if your doing calculations in "exact" units like hours, minutes, seconds etc. If you'd like to do calendar arithmetic (like adding months, calendar days) you need to consider more carefully: you may be better off doing arithmetic on the .date()
first.
The Instant
is the "normalized" type. It strips all concepts of local time away. The old UTCDateTime
still kept concepts like "months" which didn't make sense.
edit: clarifications
Thank you for your quick and thorough reply.
Yes - my goal is to do "exact" calculations so thank you for the confirmation that I am on the right track.
I missed the function because it does not seem to align with the other names:
to_tz
, to_system_to
and to_fixed_offset
.
Imho it would make sense to rename it as to_instant
- what do you think?
Edit: Can you elaborate why is it not possible to replace parts of the time any more? If I want to have the next full second it's now only possible like this:
now = Instant.now()
target = (now + TimeDelta(seconds=1)).subtract(nanoseconds=now.to_system_tz().nanosecond)
@spacemanspiff2007 rounding an Instant
is indeed a missing functionality! I'll add a .round()
method for this purpose soon, but I'll have to give the API some thought.
About your suggestion: I had named instant()
to be consistent with local()
, but probably they should both be to_instant()
and to_local()
to avoid confusion.
BTW: A less hacky workaround for you until then. Still ugly, but handles nanoseconds properly.
now = Instant.now()
target = Instant.from_timestamp(math.ceil(now.timestamp_nanos() / 1_000_000_000))
@spacemanspiff2007 follow-up question that would help me out: What is the reason in particular you need to round an Instant
? Is this because you're converting it to a string for another system? Or does your functionality depend on whole seconds?
I am reworking EAScheduler - an easy to use asyncio scheduler - which lets you run coros/functions at specific points in time. It's mostly used by HABApp, a smart home rule engine which works with MQTT and/or openHAB. One option is e.g. to run something at e.g. sunrise or sunset. Since sometimes these timestamps are logged or further propagated it's unnecessary and imho not very pretty to have anything below seconds because it makes the timestamps hard to read. So I am trying to round the calculated time stamps to the second. The other use case is testing where I do some calculations which should return the next full second starting from now. Obviously I can work around both issues by rounding the SystemDateTime and then converting the result to Instant or rework my tests so that they work different. It's just something I stumbled when going from 0.5 to 0.6.2.
Another thing I realized is that Instant.add does not take days as an argument any more. I understand that months is ambiguous so removing that makes sense. Have you removed days because of the leap second?
The SkippedTime
and RepeatedTime
Exceptions are also hard to find. May I suggest appending an Error
suffix so it would be SkippedTimeError
and RepeatedTimeError
?
Thanks for explaining, that helps. Rounding methods should be added to the other datetimes as well, I think. In fact, I probably think it'd be best to do this rounding before converting to Instant
. This is because Instant
(in theory) doesn't have a concept of a 'whole second'. It just identifies a moment on the timeline, and 'whole second' is only determined by local human interpretation. For example, historically sub-second UTC offsets exist (even though TZDB and whenever don't support them): this would mean rounding a second in that location would lead to a different result than rounding in another.
Relating to your suggestion about exception names: While I'm personally not a fan of redundant suffixes, I have to admit I'm in the minority here when it comes to exceptions. I'll probably bundle this with a bunch of other renames in the next release.
edit: I've created an issue to track naming discussions here: https://github.com/ariebovenberg/whenever/issues/151
I'm closing this issue since the original question has been answered.
Relevant follow-ups:
I probably think it'd be best to do this rounding before converting to
Instant
.
I agree! That's how I'm doing it now and it seems to be the most straightforward and logic implementation.
Before 0.6 I called
LocalSystemDateTime(...).as_utc()
to get anUTCDateTime
. NowUTCDateTime
Is calledInstant
andLocalSystemDateTime
is calledSystemDateTime
, however it's unclear how I can get it to normalize. I thought the Idea is to normalize timestamps toInstant
and do all calculations there. Is this a misunderstanding from my side?