Skip to content

date.today() is half as fast as datetime.now().date() #88473

Description

@asottile
mannequin
BPO 44307
Nosy @asottile, @pganssle, @corona10, @rkm

Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

Show more details

GitHub fields:

assignee = None
closed_at = None
created_at = <Date 2021-06-04.01:50:33.984>
labels = ['extension-modules', '3.11', 'performance']
title = 'date.today() is half as fast as datetime.now().date()'
updated_at = <Date 2021-06-04.21:22:03.867>
user = 'https://github.com/asottile'

bugs.python.org fields:

activity = <Date 2021-06-04.21:22:03.867>
actor = 'Anthony Sottile'
assignee = 'none'
closed = False
closed_date = None
closer = None
components = ['Extension Modules']
creation = <Date 2021-06-04.01:50:33.984>
creator = 'Anthony Sottile'
dependencies = []
files = []
hgrepos = []
issue_num = 44307
keywords = []
message_count = 4.0
messages = ['395061', '395116', '395123', '395124']
nosy_count = 4.0
nosy_names = ['Anthony Sottile', 'p-ganssle', 'corona10', 'rkm']
pr_nums = []
priority = 'normal'
resolution = None
stage = None
status = 'open'
superseder = None
type = 'performance'
url = 'https://bugs.python.org/issue44307'
versions = ['Python 3.11']

Linked PRs

Activity

  1. asottile commented on Jun 4, 2021

    asottilemannequin
    MannequinAuthor
    $ python3.10 -m timeit -s 'from datetime import datetime' 'datetime.now().date()'
    500000 loops, best of 5: 708 nsec per loop
    $ python3.10 -m timeit -s 'from datetime import date' 'date.today()'
    200000 loops, best of 5: 1.4 usec per loop

    this surprised me so I dug into it -- it appears a fast path can be added to date.today() to make it faster than datetime.date.now() -- though I'm rather unfamiliar with the functions involved here

    here is my ~sloppy patch attempting to add a fast path, I would need some guidance to improve it and get it accepted:

    $ git diff -w
    diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c
    index 8ef2dad37a..7eaa5d1740 100644
    --- a/Modules/_datetimemodule.c
    +++ b/Modules/_datetimemodule.c
    @@ -2875,6 +2875,17 @@ date_fromtimestamp(PyObject *cls, PyObject *obj)
     static PyObject *
     date_today(PyObject *cls, PyObject *dummy)
     {
    +    /* fast path, don't call fromtimestamp */
    +    if ((PyTypeObject *)cls == &PyDateTime_DateType) {
    +        struct tm tm;
    +        time_t t;
    +        time(&t);
    +        localtime_r(&t, &tm);
    +        return new_date_ex(tm.tm_year + 1900,
    +                           tm.tm_mon + 1,
    +                           tm.tm_mday,
    +                           (PyTypeObject*)cls);
    +    } else {
             PyObject *time;
             PyObject *result;
             _Py_IDENTIFIER(fromtimestamp);
    @@ -2893,6 +2904,7 @@ date_today(PyObject *cls, PyObject *dummy)
             Py_DECREF(time);
             return result;
         }
    +}
     
     /*[clinic input]
     @classmethod

    after this, date.today() is faster!

    $ ./python -m timeit -s 'from datetime import datetime' 'datetime.now().date()'
    500000 loops, best of 5: 764 nsec per loop
    $ ./python -m timeit -s 'from datetime import date' 'date.today()'
    500000 loops, best of 5: 407 nsec per loop

    \o/

  2. pganssle commented on Jun 4, 2021

    @pganssle
    Member

    Yeah, I knew this was slower and it's been on my long list to look at it (tied to this is the fact that datetime.today() is basically just a slow version of datetime.now(), in defiance of user expectations).

    My inclination is that we shouldn't re-implement fromtimestamp in date.today, but rather call date_fromtimestamp in the fast path. I believe that incurs the overhead of creating one additional Python object (an integer), but if it's a sufficiently significant speedup, we could possibly refactor date_fromtimestamp to a version that accepts a C integer and a version that accepts a Python integer, then call the version accepting a C integer.

    I think this won't give any speedup to datetime.today, since datetime.today will still take the slow path. If we care about this, we may be able to implement datetime.today as an alias for datetime.now(None), assuming there are no behavioral differences between the two.

  3. changed the title [-]date.today() is 2x slower than datetime.now().date()[/-] [+]date.today() is half as fast as datetime.now().date()[/+] on Jun 4, 2021
  4. changed the title [-]date.today() is 2x slower than datetime.now().date()[/-] [+]date.today() is half as fast as datetime.now().date()[/+] on Jun 4, 2021
  5. asottile commented on Jun 4, 2021

    asottilemannequin
    MannequinAuthor

    @terry.reddy -- I believe your title change makes this more difficult to understand

  6. asottile commented on Jun 4, 2021

    asottilemannequin
    MannequinAuthor

    *terry.reedy oops typo!

  7. transferred this issue fromon Apr 10, 2022
  8. 2 remaining items

  9. added
    type-featureA feature request or enhancement
    and removed
    3.12only security fixes
    on Mar 8, 2025
  10. added a commit that references this issue on Sep 16, 2025
  11. StanFromIreland commented on Sep 16, 2025

    @StanFromIreland
    Member

    @pganssle do we want to backport?

  12. added
    interpreter-core(Objects, Python, Grammar, and Parser dirs)
    pendingThe issue will be closed if no feedback is provided
    and removed
    interpreter-core(Objects, Python, Grammar, and Parser dirs)
    on Sep 16, 2025
  13. removed
    pendingThe issue will be closed if no feedback is provided
    on Sep 16, 2025
  14. picnixz commented on Sep 16, 2025

    @picnixz
    Member

    Performance improvements are usually not backported if they are considered new features. I would see this as a feature rather than a bugfix.

  15. StanFromIreland commented on Sep 16, 2025

    @StanFromIreland
    Member

    That was my thinking.

  16. AskicV commented on May 14, 2026

    @AskicV

    Interesting, the issue still exist with CPython 3.14.4:

    python -m timeit -s 'from datetime import datetime' 'datetime.now().date()'
    Provides the following outpt:
    500000 loops, best of 5: 416 nsec per loop

    While this code:
    python -m timeit -s 'from datetime import date' 'date.today()'
    Gives the following output:
    500000 loops, best of 5: 834 nsec per loop

    So the ratio still holds.

  17. StanFromIreland commented on May 14, 2026

    @StanFromIreland
    Member

    That's because it didn't make it into 3.14, in 3.15/6:

    $ ./python -m timeit -s 'from datetime import datetime' 'datetime.now().date()' 
    500000 loops, best of 5: 502 nsec per loop
    $ ./python -m timeit -s 'from datetime import date' 'date.today()'
    1000000 loops, best of 5: 269 nsec per loop
    $ ./python --version
    Python 3.16.0a0
    
  18. AskicV commented on May 14, 2026

    @AskicV

    Thanks Stan, I missed that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    extension-modulesC modules in the Modules dirperformancePerformance or resource usagetype-featureA feature request or enhancement

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions