10000 API/COMPAT: add pydatetime-style positional args to Timestamp constructor by thejohnfreeman · Pull Request #12482 · pandas-dev/pandas · GitHub
[go: up one dir, main page]

Skip to content

API/COMPAT: add pydatetime-style positional args to Timestamp constructor #12482

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
API/COMPAT: add pydatetime-style positional args to Timestamp constru…
…ctor
  • Loading branch information
thejohnfreeman committed May 13, 2016
commit 0d6884bac947efb522269537e7a5c9c276fbcb34
1 change: 1 addition & 0 deletions doc/source/timeseries.rst
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ time.

pd.Timestamp(datetime(2012, 5, 1))
pd.Timestamp('2012-05-01')
pd.Timestamp(2012, 5, 1)

However, in many cases it is more natural to associate things like change
variables with a time span instead. The span represented by ``Period`` can be
Expand Down
8 changes: 8 additions & 0 deletions doc/source/whatsnew/v0.18.2.txt
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ Other enhancements
idx = pd.Index(["a1a2", "b1", "c1"])
idx.str.extractall("[ab](?P<digit>\d)")

- ``Timestamp``s can now accept positional parameters like :func:`datetime.datetime`:

.. ipython:: python

Timestamp(2012, 1, 1)

Timestamp(2012, 1, 1, 8, 30)

.. _whatsnew_0182.api:

API changes
Expand Down
30 changes: 30 additions & 0 deletions pandas/tseries/tests/test_tslib.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,36 @@ def test_constructor_invalid(self):
with tm.assertRaisesRegexp(ValueError, 'Cannot convert Period'):
Timestamp(Period('1000-01-01'))

def test_constructor_positional(self):
# GH 10758
with tm.assertRaises(TypeError):
Timestamp(2000, 1)
Copy link
Contributor

Choose a reason for hiding this comment

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

can you use assertRaisesRegexp here to check for things

 In [1]: datetime.datetime(2000,1)
TypeError: Required argument 'day' (pos 3) not found

doesn't have to be an exact checking, but need to make sure that it is raising the datetime.datetime constructor error message and not something else.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

For the cases where not enough parameters are passed to Timestamp, the error will be an integer is required (got NoneType) instead of Required argument not found. Matching the datetime exception exactly requires 3 additional checks and calls:

if year is None:
    return datetime.datetime()
if month is None:
    return datetime.datetime(year)
if day is None:
    return datetime.datetime(year, month)

which seems ugly. Do we really want that?

with tm.assertRaises(ValueError):
Timestamp(2000, 0, 1)
with tm.assertRaises(ValueError):
Timestamp(2000, 13, 1)
with tm.assertRaises(ValueError):
Timestamp(2000, 1, 0)
with tm.assertRaises(ValueError):
Timestamp(2000, 1, 32)
with tm.assertRaises(TypeError):
Timestamp(2000, 1, 1, None)
with tm.assertRaises(TypeError):
Timestamp(2000, 1, 1, None, None)
with tm.assertRaises(TypeError):
Timestamp(2000, 1, 1, None, None, None)

ts = Timestamp(2000, 1, 2)

actual = ts.to_pydatetime()
expected = datetime.datetime(2000, 1, 2)
self.assertEqual(actual, expected)
self.assertEqual(type(actual), type(expected))
Copy link
Contributor

Choose a reason for hiding this comment

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

what is the purpose of this test here? .to_pydatetime() is already tested elsewhere.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

To test for equivalence. Let me know if I should use a different pattern.

Copy link
Contributor

Choose a reason for hiding this comment

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

I understand but that's not part of this PR (and its already tested), so not necessary.


pos_args = [2000, 1, 2, 3, 4, 5, 999999]
Copy link
Contributor

Choose a reason for hiding this comment

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

can u also add another with tzinfo (another set of pos_args).

and another with kwargs as well.

self.assertEqual(Timestamp(*pos_args).to_pydatetime(),
datetime.datetime(*pos_args))

def test_conversion(self):
# GH 9255
ts = Timestamp('2000-01-01')
Expand Down
29 changes: 28 additions & 1 deletion pandas/tslib.pyx
6016
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@ cdef inline bint _is_fixed_offset(object tz):


_zero_time = datetime_time(0, 0)
_no_unit = object()

# Python front end to C extension type _Timestamp
# This serves as the box for datetime64
Expand Down Expand Up @@ -288,10 +289,36 @@ class Timestamp(_Timestamp):
def combine(cls, date, time):
return cls(datetime.combine(date, time))

def __new__(cls, object ts_input, object offset=None, tz=None, unit=None):
def __new__(cls,
object ts_input, object offset=None, tz=None, unit=_no_unit,
minute=0, second=0, microsecond=0, tzinfo=None):
# The parameter list folds together legacy parameter names (the first
# four) and positional parameter names from pydatetime (starting with
# `minute`).
#
Copy link
Contributor

Choose a reason for hiding this comment

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

good, this is a nice way to do this.

# When using the pydatetime form, the first three parameters are
# required, but the fourth (called `unit` but standing in for `hour`)
# is optional. However, when using the legacy form, only the first
# parameter is required and the fourth parameter defaults to `None`.
# We use a special non-`None` constant to distinguish when callers
# pass `None` as the fourth pydatetime parameter.

cdef _TSObject ts
cdef _Timestamp ts_base

if offset is not None and is_integer_object(offset):
# User passed positional arguments:
# Timestamp(year, month, day[, hour[, minute[, second[, microsecond[, tzinfo]]]]])
# When using the positional form, the first three parameters are
# required. Assign defaults to the rest.
if unit is _no_unit:
unit = 0
# Forward positional arguments to datetime constructor.
return Timestamp(datetime(ts_input, offset, tz, unit, minute, second, microsecond, tzinfo),
tz=tzinfo)
elif unit is _no_unit:
unit = None

ts = convert_to_tsobject(ts_input, tz, unit, 0, 0)

if ts.value == NPY_NAT:
Expand Down
0