Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ matrix:
before_install:
# Install codecov
- if [[ $TRAVIS_PYTHON_VERSION == '3.2' ]]; then pip install coverage==3.7.1; fi
- if [[ $TRAVIS_PYTHON_VERSION < '3.0' ]]; then pip install mock; fi
- pip install codecov
- pip install freezegun

install:
- pip install six
Expand Down
24 changes: 23 additions & 1 deletion dateutil/test/test_utils.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,32 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from datetime import timedelta, datetime
from dateutil.utils import within_delta

import unittest

from dateutil import tz
from dateutil.utils import within_delta, today

from freezegun import freeze_time

UTC = tz.tzutc()
NYC = tz.gettz("America/New_York")


class UtilsTest(unittest.TestCase):
@freeze_time(datetime(2014, 12, 15, 1, 21, 33, 4003))
def testToday(self):
self.assertEqual(today(), datetime(2014, 12, 15, 0, 0, 0))

@freeze_time(datetime(2014, 12, 15, 12), tz_offset=5)
def testTodayTzInfo(self):
self.assertEqual(today(NYC),
datetime(2014, 12, 15, 0, 0, 0, tzinfo=NYC))

@freeze_time(datetime(2014, 12, 15, 23), tz_offset=5)
def testTodayTzInfoDifferentDay(self):
self.assertEqual(today(UTC),
datetime(2014, 12, 16, 0, 0, 0, tzinfo=UTC))

def testWithinDelta(self):
d1 = datetime(2016, 1, 1, 12, 14, 1, 9)
Expand Down
18 changes: 18 additions & 0 deletions dateutil/utils.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,24 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from datetime import datetime, time


def today(tzinfo=None):
"""
Returns a :py:class:`datetime` representing the current day at midnight

:param tzinfo:
The time zone to attach (also used to determine the current day).

:return:
A :py:class:`datetime.datetime` object representing the current day
at midnight.
"""

dt = datetime.now(tzinfo)
return datetime.combine(dt.date(), time(0, tzinfo=tzinfo))

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Responding to this comment by @jbrockmendel:

+1 for today(tzinfo), but I'm very wary of implementing behavior that differs from same-named stdlib behavior. btw:

>>> tz = pytz.timezone('US/Pacific')
>>> pd.Timestamp.today(tz)
Timestamp('2017-10-14 17:19:04.081315-0700', tz='US/Pacific')

My reason for calling it today was because, frankly, I think that this is close to datetime.datetime.today() should do, and I think it violates principle of least surprise to find that datetime.today() is basically just an alias for datetime.now(). That said, the name overloading is my main concern about this function.

I think there are a few reasonable ways forward:

  1. Leave as is because this is what datetime.today() should have been and people are more likely to be shocked that datetime.datetime.today() does something else than that dateutil.utils.today() does what it does.
  2. Change the signature of this so that utils.today() takes an optional time argument in addition to an optional tzinfo argument. This makes it more clear that what it does is give you "today at this time, defaulting to midnight". The reason I don't like this is that there could be a conflict between time (which may have a tzinfo of its own) and tzinfo, and also the interface isn't nearly as clean.
  3. Change the name to something like today_midnight() or today_datetime() or something.

I think I'm inclined to leave it as is, considering pendulum has a similar real distinction between its now and today behavior:

>>> import pendulum
>>> print(pendulum.now())
2017-10-15T17:30:33.887480-04:00
>>>> print(pendulum.today())
2017-10-15T00:00:00-04:00



def within_delta(dt1, dt2, delta):
"""
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
package_data={"dateutil.zoneinfo": ["dateutil-zoneinfo.tar.gz"]},
zip_safe=True,
requires=["six"],
tests_require=["freezegun"],

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should note that I'm not sure that this is the most foolproof way of installing test-specific dependencies, as the only thing that cares about this particular line is, AFAIK, python setup.py test, and if we're going to move to pytest, we'll either want our own setup.py test runner that uses pytest, and/or we should have a separate script that declares and installs the test dependencies independent of python setup.py test. Not sure what most people do.

install_requires=["six >=1.5"], # XXX fix when packaging is sane again
classifiers=[
'Development Status :: 5 - Production/Stable',
Expand Down