A "timestamp" method for date objects

datetime.date objects have a fromtimestamp method, but they do not have a .timestamp() method, as datetime.datetime objects have. That seems inconsistent to me.

Just as the .fromtimestamp() method truncates the time information given by the timestamp (Unix time), a .timestamp() method would have to implicitly use a time - e.g. 0 hours in the local time zone (as date objects are naive). As a work-around, you can create a datetime object from it by using combine:

from datetime import date, datetime

d = date(2020, 1, 1) # on UTC+1 
dt = datetime(2020, 1, 1) # for comparison

print(dt.timestamp())
# 1577833200.0

print(datetime.combine(d, datetime.min.time()).timestamp())
# 1577833200.0

But why not add this method to the datetime.date class?

The short answer is that dates do not have second precision - they’re
entirely to do with whole days. As you demonstrate you can make a
datetime with just a year/month/day supplied, so if you want seconds
precision, just do that.

The other answer is that the datetime module has a lot of rough edges.
There are various third party modules which build on it to provide
better or more complete interfaces. I recommend you try one of these.

Also, all your things above assume a timezone (which is not simply a
GMT/UTC offset). Always tricky. This ambiguity is one of the reasons
people might be loathe to pretend seconds precision when all you have is
a day.

I’ve been dipping my toe in the arrow module:

https://pypi.org/project/arrow/

See if that makes you happier.

Cheers,
Cameron Simpson cs@cskk.id.au

I know about the “rough edges” and that there are alternatives. But I was surprised by the inconsistency - for example there’s also a timetuple method, which returns an object that represents date/time in a higher resolution than ‘day’! - hence the idea. Not sure if this is really useful for everybody in practice - hence the question here :wink: Thanks for your comment!

The timetuple() method returns a time.struct_time, which directly
mirrors the C “struct tm” returned by the POSIX gmtime() and localtime()
functions. This is why it exists.

Cheers,
Cameron Simpson cs@cskk.id.au

Why we are on this topic, why doesn’t python have date.strptime(), and if one wish to convert string to date, one have to use datetime.data.strptime().date().

@2pi360 since 3.7, you have at least a method for ISO format input, date.fromisoformat.

My point in general is: if the Python standard library provides a date class, it should be full-featured. Other libraries (e.g. numpy, and built on that: pandas) only have a datetime type; if you want only the date, you’ll have to floor/normalize the hour, minute, etc.

1 Like

@2pi360 since 3.7, you have at least a method for ISO format input,
date.fromisoformat.

My point in general is: if the Python standard library provides a date class, it should be full-featured.

For the record, I don’t actually disagree with this as a design
principle or objective. But it can be a fair bit of work, and some
things such as date.timestamp() => seconds require a bunch of clarifying
assumptions like “floor/normalize_
the hour, minute, etc” mentioned below and also the timezone. These
complicate the expected semantics.

Other libraries (e.g. numpy, and built on that: pandas) only have a
datetime type; if you want only the date, you’ll have to
floor/normalize the hour, minute, etc.

This I think is the saner way to go. Just work with datetime throughout
when possible. At least it halves the API space and also you’re now
working with a thing which inherently has seconds precision.

Personally, I work with unixtimes throughout (seconds since the UNIX
epoch) if I can, and ony use datetimes etc for presentation. Not always
feasible, but more reliable because the times are at least not ambiguous
(no timezone weirdness, not repeated or missing times due to
summer/winter timezone offset shifts, no utterly weird arithmetic behind
the scenes because of the object’s value is effectively expressed as a
sum of rational numbers whose demoninators vary (looking at you,
days-in-a-month and days-in-a-year)).

Cheers,
Cameron Simpson cs@cskk.id.au

There is a lot of domains where date is the only thing that needed. So carrying seconds with it is not only unnecessary, but potentially problematic (e.g. I write my code with datetime assuming all time is ‘12:00:00’, so I can write date1 == date2 for comparing dates, but then one second is added somewhere and my code stop working).

And datetime.date is already fully functional, with just a few things missing (absence of strptime is what bothers me the most)

Having said that, the closes analogy I see is unicode and non-unicode strings, where multiple domains use strings but just dont care about unicode characters at all. And Python handles this issue by dropping the non-unicode string…

If you feel its important to add, you could open an issue on BPO to discuss it, and if there’s general agreement that it would be a net improvement, you could submit a PR. It should be quite doable if you have a decent level of Python experience (unless it also needs to be implemented as a C extension, in which case you would need to have some basic C experience too—can’t remember if it is).

There is a lot of domains where date is the only thing that needed. So carrying seconds with it is not only unnecessary, but potentially problematic (e.g. I write my code with datetime assuming all time is ‘12:00:00’, so I can write date1 == date2 for comparing dates, but then one second is added somewhere and my code stop working).

Doesn’t this situation inherently display the trickiness of assuming you
can get a seconds timestamp from a date? The relationship seems very
fragile.

That said, I agree that if you’re working only with dates, use “date”.
Then there are no additional ill specified hours etc fields to break
your comparisons. But going with that, I would not expect to extract a
seconds precision value.

And datetime.date is already fully functional, with just a few things missing (absence of strptime is what bothers me the most)

Yes, strptime would be good. It might merely be missing (nobody
bothered) or the datetime.strptime might call the C/POSIX strptime
directly using a struct tm or similar, and to do that you need to invent
the missing fields (even if simply all zeroes).

If that is the case I would be inclined to argue that date should npt
have a strptime method, and that the required conversion to datetime
should be overt to make it clear what assumptions about hours, timezones
etc were involved in that conversion.

Having said that, the closes analogy I see is unicode and non-unicode strings, where multiple domains use strings but just dont care about unicode characters at all. And Python handles this issue by dropping the non-unicode string…

That’s an unfair comparison.

There aren’t “Unicode characters” as such - it is meant to cover all
existing human character sets. (I guess some new things like emojis
might be defined only in Unicode, so they probably count as “Unicode
characters”.)

What Python did was drop support for the old 8-bit str type, which was
(a) too small for anything but parochial text and (b) had no
association with a character set - it was just bytes underneath and if
they didn’t match up with your external character mapping they were just
nonsense.

Storing all text as Unicode means you can store any text, and the
transcription into files or onto displays like terminals or web pages
involves specifying the encoding of the characters, making overt what is
going on. Likewise the reverse: you can’t get a Unicode string from some
bytes without specifying what encoding the bytes use.

This is analogous to making a datetime from a date - you need to define
the conversion (eg how the new hh/mm/ss and timezone are generated). You
can punt on the timezone with datetime, getting a “naive” datetime. But
the object inherently has a timezone field, even when the field says
“unspecified”.

Cheers,
Cameron Simpson cs@cskk.id.au

3 Likes

Just to clarify one point, it didn’t drop it at all; it was just renamed bytes and the semantics were changed to no longer implicitly and silently treat it like a string of of ASCII/Latin-1 characters when it was really a sequence of arbitrary 8-bit binary bytes, which ends up being a major footgun for those who assumed it was the former.

FWIW: I also would find useful to have a way to directly retrieve the timestamp from the date object, without the need to pass to an intermediate datetime

In my case I’m building a simple 2d diagram (value over months) and, while in my internal data the dates are actually datetime.dates (I don’t have / need bigger precision) the GUIs expect floats.

One reason this doesn’t exist is in your statement above: “I_
don’t have / need bigger precision”. The word “precision” means that you
don’t know a seconds level resolution for the date. Any conversion to
seconds inherently involves an assumption about the start time in
seconds of a given date, and that depends on a timezone. It also assumes
a starting point for a day. usually that is midnight, but I can imagine
domains where midday might be what the “date” idea refers to.

Also, what happens if your dates cross a summer/winter time adjustment?
Are they local time? Should they… step?

So you’re always going to be performing some conversion with some outer
context not part of the date. This is why there’s no presupplied thing -
there are a few things it could mean, and it is probably best for those
things to be explicit in some conversion function you yourself supply.

Indeed, if you’ve only go “day” precision, do you really know
precisely when the data values apply?

I think I’m saying you’re doing presentation (i.e. providing some GUI
ordinates in seconds) and you’ll have to specify it somehow. Put it in a
function with an explainatory docstring and proceed:

 def date_for_gui(d):
     return suitable-datetime-as-timestamp

Then you’ll (a) know that the conversion is a particular function and
what assumptions the function makes.

Cheers,
Cameron Simpson cs@cskk.id.au

It’s not that I don’t know. It’s that I don’t care.
For simplicity I could assume the “timestamp” of a date is equal to the timestamp of a datetime with the same date and “00:00:00” as time, but that’s it. I don’t really care if today’s timestamp is exaclty 1679987083.11146, for me it’s just enough to return 1679954400.0

>>> list(map(datetime.fromtimestamp, [1679954400.0, 1679987083.11146, 1680040799.999999]))
[datetime.datetime(2023, 3, 28, 0, 0), datetime.datetime(2023, 3, 28, 9, 4, 43, 111460), datetime.datetime(2023, 3, 28, 23, 59, 59, 999999)]
>>> list(map(date.fromtimestamp, [1679954400.0, 1679987083.11146, 1680040799.999999]))
[datetime.date(2023, 3, 28), datetime.date(2023, 3, 28), datetime.date(2023, 3, 28)]
>>> 

date objects are naive ones. There is no timezone attached to them. They are local by defintion.

Yes. That data values applied to that day. I choose this level of precision. It’s what I need in my case.

Otherwise… do you really know, with femptoseconds precision, when the data values apply?

specifically for my GUI I have a chart where the x-coordinates are years, and I have roghly speaking, a day thas is “large” as a pair of pixels.

It’s just that the library I’m using, internally, expect the values as floats.

Yeah, I can easily implement my date_for_gui, I’ve already done it. The fact is that my_date.timestamp() is really just something that makes sense to me.

Why not use date.toordinal() if you just need a mapping of date to ordinal number?

2 Likes

In my case all started with QDateTimeAxis and the fact that he expect to work with timestamp to make things works.

(and this is off topic, but the QCharts API are very limited…)

It’s not that I don’t know. It’s that I don’t care.

That’s fine! You should still document that in the docstring of the
conversion function you end up using.

For simplicity I could assume the “timestamp” of a date is equal to the timestamp of a datetime with the same date and “00:00:00” as time, but that’s it. I don’t really care if today’s timestamp is exaclty 1679987083.11146, for me it’s just enough to return 1679954400.0

Ok, but ther’e s an assumed timezone not mentioned above. Sounds like
UTC would suffice though, to make things easy.

date objects are naive ones. There is no timezone attached to them. They are local by defintion.

date objects are indeed naive. That doesn’t mean they’re local time.
It means they have no timezone information associated with them at all.
It sounds like you could treat them as localtime and get working
ordinates for your plotting. Not that they are inherently localtime.

Yes. That data values applied to that day. I choose this level of precision. It’s what I need in my case.

That’s fine.

Otherwise… do you really know, with femptoseconds precision, when the data values apply?

Of course not. But what I’m getting at, which I think you entirely
understand anyway, is that going to seconds implies some kind of
increase in precision unless you’re carrying around some additional
error bars or something.

I’m sure we all have the experience of seeing some foreign news report
which says something like “10am on Tuesday” and absent details, playing
guessing games about what that actually meaning in the local calendar. I
have it all the time here (GMT+11) versus USian news reports (GMT-n) for
various small values of n.

The date isn’t inhenerently localtime, and for such a news report I
know it’s not my local time. It is a date lacking timezone
information.

specifically for my GUI I have a chart where the x-coordinates are years, and I have roghly speaking, a day thas is “large” as a pair of pixels.
It’s just that the library I’m using, internally, expect the values as
floats.

Yeah, I can easily implement my date_for_gui, I’ve already done it.
The fact is that my_date.timestamp() is really just something that
makes sense to me.

It doesn’t to me unless it has a bunch of parameters, or documentation
saying that certain potentially parameterisable things are in fact
assumed to have some particular values.

[Rummages.] Look, here’s a tacky and long obsolete function I used to
use sometimes:

 def localdate2unixtime(d):
     ''' Convert a localtime `date` into a UNIX timestamp.
     '''
     return mktime(date(d.year, d.month, d.day).timetuple())

That does indeed use my localtime as an assumption, but at least it’s
written in the docstring. (I’m not recommending this particular function
to you at all.)

Cheers,
Cameron Simpson cs@cskk.id.au

1 Like

I honestly thing that out in the wild there be a lot of projects with very similar one-liner functions that convert a date to a timestamp apart yours and mine :slight_smile:
and this IMHO add to the reasons to add to the stdlib :slight_smile:
for very obvious reason the function should be documented and with a specific behavior, but in my mind it’s perfectly reasonable to offer a semantic that

  • assume that the date is in local time
  • return the timestamp at 00:00 in that day

FWIW yeah, naive == local is an assumption, but honestly, I don’t really think I’ll ever use plain dates when there are timezones involved (like a date and an external tz string… is calling for troubles)

The trouble is, that’s not the ONLY reasonable semantic. If you asked me, I would have assumed that the date was in UTC, not local time. And while neither of those is inherently wrong, they are incompatible in ways that probably only half the world will find issue with (depending on which direction it’s wrong; if you ask for a date and it gives you the timestamp for 4AM that day, it’s probably fine, but if you get the timestamp for 8PM the previous day, that’s not fine, and the difference between those depends on whether you’re east or west of Greenwich and which way around the thing was written).

So that’s a reason to NOT add it to the stdlib. A naive date object simply doesn’t have enough information to give a timestamp, and whatever you pick will be wrong for some people. My recommendation? Explicitly make a datetime out of it first. You can then attach whatever timezone information you like (including “naive” or “local”, which aren’t timezones but same difference), and it’s documented within your code.

Well, keep in mind that there is already a date.fromtimestamp method that

Return the local date corresponding to the POSIX timestamp, such as is returned by time.time().