Skip to content
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

Persist calendar when discarding cftime microsecond #183

Merged
merged 3 commits into from
Jun 30, 2021
Merged
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
32 changes: 24 additions & 8 deletions cf_units/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -406,17 +406,33 @@ def _discard_microsecond(date):
dates = np.asarray(date)
shape = dates.shape
dates = dates.ravel()
result = None
# Create date objects of the same type returned by cftime.num2date()
# (either datetime.datetime or cftime.datetime), discarding the
# microseconds
dates = np.array(
[
d
and d.__class__(d.year, d.month, d.day, d.hour, d.minute, d.second)
for d in dates
]
)
result = dates[0] if shape == () else dates.reshape(shape)
discard = []
for dt in dates:
if dt:
bjlittle marked this conversation as resolved.
Show resolved Hide resolved
kwargs = dict(
year=dt.year,
month=dt.month,
day=dt.day,
hour=dt.hour,
minute=dt.minute,
second=dt.second,
)
if isinstance(dt, cftime.datetime):
kwargs["calendar"] = dt.calendar
discard.append(dt.__class__(**kwargs))
bjlittle marked this conversation as resolved.
Show resolved Hide resolved
if discard:
dates = np.array(discard)
if shape == ():
result = dates[0]
else:
if np.prod(shape) == dates.size:
result = dates.reshape(shape)
else:
result = dates
return result


Expand Down
109 changes: 109 additions & 0 deletions cf_units/tests/unit/test__discard_microsecond.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# Copyright cf-units contributors
#
# This file is part of cf-units and is released under the LGPL license.
# See COPYING and COPYING.LESSER in the root of the repository for full
# licensing details.
"""Unit tests for the `cf_units._discard_microsecond` function."""

import datetime
import unittest

import cftime
import numpy as np

from cf_units import _discard_microsecond as discard_microsecond


class Test__datetime(unittest.TestCase):
def setUp(self):
self.kwargs = dict(year=1, month=2, day=3, hour=4, minute=5, second=6)
self.expected = datetime.datetime(**self.kwargs, microsecond=0)

def test_single(self):
dt = datetime.datetime(**self.kwargs, microsecond=7)
actual = discard_microsecond(dt)
self.assertEqual(self.expected, actual)

def test_multi(self):
shape = (5, 2)
n = np.prod(shape)

dates = np.array(
[datetime.datetime(**self.kwargs, microsecond=i) for i in range(n)]
).reshape(shape)
actual = discard_microsecond(dates)
expected = np.array([self.expected] * n).reshape(shape)
np.testing.assert_array_equal(expected, actual)


class Test__cftime(unittest.TestCase):
def setUp(self):
self.kwargs = dict(year=1, month=2, day=3, hour=4, minute=5, second=6)
self.calendars = cftime._cftime._calendars

def test_single(self):
for calendar in self.calendars:
dt = cftime.datetime(**self.kwargs, calendar=calendar)
actual = discard_microsecond(dt)
expected = cftime.datetime(
**self.kwargs, microsecond=0, calendar=calendar
)
self.assertEqual(expected, actual)

def test_multi(self):
shape = (2, 5)
n = np.prod(shape)

for calendar in self.calendars:
dates = np.array(
[
cftime.datetime(**self.kwargs, calendar=calendar)
for i in range(n)
]
).reshape(shape)
actual = discard_microsecond(dates)
expected = np.array(
[
cftime.datetime(
**self.kwargs, microsecond=0, calendar=calendar
)
]
* n
).reshape(shape)
np.testing.assert_array_equal(expected, actual)


class Test__falsy(unittest.TestCase):
def setUp(self):
kwargs = dict(year=1, month=2, day=3, hour=4, minute=5, second=6)
self.cftime = cftime.datetime(
**kwargs, microsecond=0, calendar="gregorian"
)
self.datetime = datetime.datetime(**kwargs, microsecond=0)

def test_single__none(self):
self.assertIsNone(discard_microsecond(None))

def test_single__false(self):
self.assertIsNone(discard_microsecond(False))

def test_multi__falsy(self):
self.assertIsNone(discard_microsecond([None, False, 0]))

def test_multi__mixed(self):
dates = [None, self.cftime, False, self.datetime]
actual = discard_microsecond(dates)
expected = np.array([self.cftime, self.datetime])
np.testing.assert_array_equal(expected, actual)

def test_multi__mixed_ravel(self):
dates = np.array([None, self.cftime, False, self.datetime]).reshape(
2, 2
)
actual = discard_microsecond(dates)
expected = np.array([self.cftime, self.datetime])
np.testing.assert_array_equal(expected, actual)


if __name__ == "__main__":
unittest.main()
2 changes: 1 addition & 1 deletion cf_units/tests/unit/unit/test_Unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def test_non_gregorian_calendar_conversion_dtype(self):
(np.float64, True),
(np.int32, False),
(np.int64, False),
(np.int, False),
(int, False),
rcomer marked this conversation as resolved.
Show resolved Hide resolved
):
data = np.arange(4, dtype=start_dtype)
u1 = Unit("hours since 2000-01-01 00:00:00", calendar="360_day")
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ exclude = '''
| build
| dist
)/
| _version.py
Copy link
Member Author

Choose a reason for hiding this comment

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

This file is auto-generated by setuptools-scm and should not be linted

Copy link
Member

Choose a reason for hiding this comment

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

Full disclosure: I don't understand this file. But if it's auto-generated and @bjlittle is happy with it then it should be 👍

)
'''

Expand Down