YADTR (Yet Another DateTime Rant)

R

Roy Smith

One of my roles on this newsgroup is to periodically whine about
stupidities in the Python datetime module. This is one of those times.

I have some code which computes how long ago the sun set. Being a nice
pythonista, I'm using a timedelta to represent this value. It would be
difficult to imagine a less useful default way to print a timedelta:

previous sunset: -1 day, 22:25:26.295993

The idea of str() is that it's supposed to return a human-friendly
representation of a value. Humans do not say things like, "The sun set
1 day ago plus 22 hours and 25 minutes".
 
E

Ethan Furman

One of my roles on this newsgroup is to periodically whine about
stupidities in the Python datetime module. This is one of those times.

I have some code which computes how long ago the sun set. Being a nice
pythonista, I'm using a timedelta to represent this value. It would be
difficult to imagine a less useful default way to print a timedelta:

previous sunset: -1 day, 22:25:26.295993

The idea of str() is that it's supposed to return a human-friendly
representation of a value. Humans do not say things like, "The sun set
1 day ago plus 22 hours and 25 minutes".

I'm not sure whether to admire you for your stick-to-it-iveness, or pity you for the plight you are in. Either the
first or second time I hit a datetime WTF moment I wrote my own wrapper classes.
 
M

Mark Lawrence

I'm not sure whether to admire you for your stick-to-it-iveness, or pity
you for the plight you are in. Either the first or second time I hit a
datetime WTF moment I wrote my own wrapper classes.

dateutils for me.
 
S

Skip Montanaro

It's not clear to me what the correct str should be. I think the
desired format changes depending on the relative magnitude of the
timedelta object. For small values (less than a day), I agree, the
behavior is, well, odd. You can get around that easily enough:
'-0:00:02'

The problem gets more challenging once you get into magnitudes > one
day:
-4 days, 1:00:05

Hmmm... It's printing just what we said, negative four days, positive
one hour, five minutes. Let's try the trick from above:
'-3 days, 22:59:55'

Ehhh... not so much. The fundamental problem here is the scope of the
minus sign. My trick assumes it applied to the entire string
representation. It's clear that in the first case that it applies to
the entire displayed value, as there are no spaces. In the second
case, we know it only applies to the days, because it's spitting back
exactly what I fed the constructor. So, that means the simple minus
sign trick won't work in the third case. Complicating things are that
timedelta objects are normalized internally so that the seconds field
is always non-negative (explaining the weird "-1 day, 23:59:58" string
representation of the first case).

I'm not sure there's a one-size-fits-all solution to this problem. For
offsets of less than a day, I suppose you could argue that the string
representation shouldn't include the "-1 day" bit. Beyond that, I
think you kind of have to live with what it gives you.

Skip
 
A

Antoon Pardon

One of my roles on this newsgroup is to periodically whine about
stupidities in the Python datetime module. This is one of those times.

I have some code which computes how long ago the sun set. Being a nice
pythonista, I'm using a timedelta to represent this value. It would be
difficult to imagine a less useful default way to print a timedelta:

previous sunset: -1 day, 22:25:26.295993

The idea of str() is that it's supposed to return a human-friendly
representation of a value. Humans do not say things like, "The sun set
1 day ago plus 22 hours and 25 minutes".
There is a difference between how people say things and what is useful.
I remember when I was studying logarithms, a negative number like -5.73
was written down as Ì…6.27 (with a bar only over the six). That notation
had the advantage that the part after the decimal point stayed the same
if you added or subtracted whole numbers. Even if that would change the
sign.

I can't give an honest estimation of how useful this notation is because
I don't use the datetime module often enough. But I can imagine that those
who wrote the module and I expect used it often found it a useful
representation.
 
C

Chris Angelico

There is a difference between how people say things and what is useful.
I remember when I was studying logarithms, a negative number like -5.73
was written down as Ì…6.27 (with a bar only over the six). That notation
had the advantage that the part after the decimal point stayed the same
if you added or subtracted whole numbers. Even if that would change the
sign.

That's highly significant with logs, especially when you're working
with base 10 logs.
-1.9086848403027772

By showing those last ones as 1Ì….091... and 2Ì….091..., you emphasize
the floating-point nature of the data: everything after the decimal is
the mantissa, and everything before the decimal is the exponent. I
can't say for sure as I don't really use that, but I can imagine that
there might be something similar with dates and times - it's three
days ago at 2:51, not two days and 21:09 ago.

Or maybe it's just "print it out in a way that more closely matches
the internal representation, to simplify debugging" :)

ChrisA
 
M

Marko Rauhamaa

R

Roy Smith

The problem gets more challenging once you get into magnitudes > one
day:

-4 days, 1:00:05

Hmmm... It's printing just what we said, negative four days, positive
one hour, five minutes. Let's try the trick from above:

No, what you said was "negative four days, positive 3605 seconds". Why does it make sense to normalize 3605 seconds to "1 hour, 5 seconds", but not extend that normalization to the days portion?
Complicating things are that timedelta objects are normalized internally so that
the seconds field is always non-negative

The whole idea of datatypes is to hide the internal representation. If it's convenient on your platform, you can store timedeltas internally as fempto-years. That should not affect how it prints.
 
S

Skip Montanaro

No, what you said was "negative four days, positive 3605 seconds".

My apologies for not showing all my work, professor. I use datetime
and timedelta objects all the time. I did the arithmetic to go from
3605 to one hour, five minutes in my head.

The point of my post was that there is no obvious one best way to
present negative timedeltas in a human readable form. In my usage it's
not generally a big deal, as most of the time I want to display
datetime objects. In your case, it's obviously a problem. If Tim
thought that timedelta objects would be presented to users as a
regular course of doing business, maybe he would have given them a
strftime() method. I suggest you write a function to format it the way
you want and be done with it.

You might consider taking a crack at a strftime() (and strptime?)
implementation for timedelta objects that uses the ISO 8601 notation
and submit it as a patch for datetimemodule.c. Note though, that the
ISO8601 representation isn't without its own flaws (which might
explain why Tim avoided it BITD):

* It doesn't appear to provide a way to represent fractions of a
second (though perhaps all the fields can be fractional)
* How many days are in a month or a year? (It has format codes for
both. Writing a useful strptime is probably impossible.)
* It has other ambiguities ("M" represents both months and minutes -
what were they thinking?)

Skip
 
R

Roy Smith

The point of my post was that there is no obvious one best way to
present negative timedeltas in a human readable form.

I agree that there are a number of possible representations which might make sense. But, using negative days and positive hours:minutes:seconds is just bizarre. I can't think of any possible application where that would be the desired format.
 
M

Marko Rauhamaa

Skip Montanaro said:
Note though, that the ISO8601 representation isn't without its own
flaws (which might explain why Tim avoided it BITD):

* It doesn't appear to provide a way to represent fractions of a
second (though perhaps all the fields can be fractional)
* How many days are in a month or a year? (It has format codes for
both. Writing a useful strptime is probably impossible.)
* It has other ambiguities ("M" represents both months and minutes -
what were they thinking?)


I don't have the (nonfree) ISO 8601 at hand, but

<URL: http://www.schemacentral.com/sc/xsd/t-xsd_duration.html>

contains the practical answers -- xsd is one important use case for
str(timedelta), after all.

Fractions of seconds are supported -- the other fields can't be
fractional. The letter "T" separates months and minutes so there's no
ambiguity there.

str(timedelta()), unfortunately probably can't use anything but seconds
since PT1M can be 61 seconds and P1D can be 23 or 25 hours (DST). As you
say, P1M really means one month and P1Y means one year. The ambiguity is
intentional; if you mean to pay your employees monthly, the interval is
one month.


Marko
 
S

Skip Montanaro

Fractions of seconds are supported -- the other fields can't be
fractional.

Actually, it appears that whatever the last value you give can be
fractionated. From the Wikipedia page you referenced:

"The smallest value used may also have a decimal fraction, as in
"P0.5Y" to indicate half a year."
As you say, P1M really means one month and P1Y means one year. The
ambiguity is intentional; if you mean to pay your employees monthly,
the interval is one month.

True. Your comment about monthly intervals makes me realize that you
probably can't map timedelta objects onto this ISO8601 duration
stuff. It seems like if you want to specify "pay my employees on the
first of every month", then timedelta objects are the wrong thing. You
want a recurrence relation, which is a more complex beast. For that,
you want something like dateutil.rrule. There is a good reason that
the internal units of timedelta objects are days, seconds, and
microseconds. They are well-defined outside of a calendar context.

So, I guess Roy is back to square one. He can always roll his own
timedelta subclass and give it a __str__ implementation...

S
 
M

Marko Rauhamaa

Skip Montanaro said:
There is a good reason that the internal units of timedelta objects
are days, seconds, and microseconds. They are well-defined outside of
a calendar context.

So, I guess Roy is back to square one. He can always roll his own
timedelta subclass and give it a __str__ implementation...

If you don't want to mix the intricacies of calendars to datetime and
timedelta, why use them?

(Epoch) seconds do everything for you unambiguously.

Yes, yes. The physicists ought to stop fricking around with leap
seconds. They should declare a 3000-year moratorium on leap seconds.


Marko
 
G

Grant Edwards

Actually, it appears that whatever the last value you give can be
fractionated. From the Wikipedia page you referenced:

We're still just papering-over the basic problem: the entire
time/calendar system use by "western civilization" is a mess. I don't
know a lot about other systems in use, but from what I have seen they
were all pretty much just as bad. We should fix the basic problem
first, then I bet the Python library problems will sort themselves out
pretty easily.

Of course to simplify matters, we're going to have to stop worrying so
much about seasons and whether it's light or dark outside...
 
G

Gregory Ewing

Chris said:
By showing those last ones as 1Ì….091... and 2Ì….091..., you emphasize
the floating-point nature of the data: everything after the decimal is
the mantissa, and everything before the decimal is the exponent.

The reason for writing them that way is so that you
can look the last part up in your log tables to
find the antilog.

I can't think of any corresponding justification
for timedeltas.
 
D

Dennis Lee Bieber

One of my roles on this newsgroup is to periodically whine about
stupidities in the Python datetime module. This is one of those times.

I have some code which computes how long ago the sun set. Being a nice
pythonista, I'm using a timedelta to represent this value. It would be
difficult to imagine a less useful default way to print a timedelta:

previous sunset: -1 day, 22:25:26.295993

The idea of str() is that it's supposed to return a human-friendly
representation of a value. Humans do not say things like, "The sun set
1 day ago plus 22 hours and 25 minutes".

Makes sense to me -- the key being time DELTA... IE, a difference from
some (unspecified) instance in time...

If you want an instance of time, you need to add some known instance
and the delta value.
 
E

Ethan Furman

Makes sense to me -- the key being time DELTA... IE, a difference from
some (unspecified) instance in time...

If you want an instance of time, you need to add some known instance
and the delta value.

Making sense is not the same as user friendly...

"Hey, when did Bob get here?"

"About 15 minutes ago."

vs

"About an hour ago, plus 45 minutes."
 
S

Steven D'Aprano

Makes sense to me -- the key being time DELTA... IE, a difference
from some (unspecified) instance in time...

If you want an instance of time, you need to add some known
instance and the delta value.


I think you have missed the point of the rant. Roy is not ranting that he
has a timedelta. He wants a timedelta. He is ranting that the timedelta
displays in a totally unintuitive fashion. Paraphrasing:

"the previous sunset was (one day less 22 hours and 25 minutes) ago"

instead of

"the previous sunset was (1 hour and 35 minutes) ago"

where the parts in the brackets come directly from the timedelta object.

I think that you misread Roy's example as:

"1 day plus 22 hours 25 minutes ago"

instead of:

"1 day ago plus 22 hours 25 minutes"

(that is, 22 hours and 25 minutes after 1 day ago).

The problem here, I believe, is that there are two ways of interpreting
remainders for negative numbers. Dividing -5 by 2 can give either -2 with
remainder -1, or -3 with remainder +1.

Mathematically, it is *usually* more useful to go with the version with
the positive remainder, and that's what Python does. But I think it's the
wrong choice for timedelta. Let's take a simple example: a timedelta of
30 hours. What's that in days and hours?

py> divmod(30, 24)
(1, 6)

That makes perfect intuitive sense: 30 hours is 1 day with 6 hours
remaining. In human-speak, we'll say that regardless of whether the
timedelta is positive or negative: we'll say "1 day and 6 hours from now"
or "1 day and 6 hours ago". But when we specify the sign:

py> divmod(-30, 24)
(-2, 18)

If an event happened 30 hours ago, it is correct to say that it occurred
"18 hours after 2 days ago", but who talks that way?
 
C

Chris Angelico

If an event happened 30 hours ago, it is correct to say that it occurred
"18 hours after 2 days ago", but who talks that way?

That response demonstrates real genius. Rue the datetime? Who talks like that?

ChrisA
 
R

Roy Smith

Chris Angelico said:
That response demonstrates real genius. Rue the datetime? Who talks like that?

"I had a dangling pointer error, and blew my stack to half past last
wednesday"
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,582
Members
45,071
Latest member
MetabolicSolutionsKeto

Latest Threads

Top