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

Let date be a date, not just datetime; also default _at() methods to now() #235

Merged
merged 9 commits into from
Mar 4, 2021
12 changes: 10 additions & 2 deletions becquerel/core/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import datetime
from dateutil.parser import parse as dateutil_parse
from uncertainties import UFloat, unumpy
import warnings
import numpy as np
try:
# Python 3.x
Expand Down Expand Up @@ -105,7 +106,7 @@ def handle_uncs(x_array, x_uncs, default_unc_func):


def handle_datetime(input_time, error_name='datetime arg', allow_none=False):
"""Parse an argument as a datetime, date+time string, or None.
"""Parse an argument as a date, datetime, date+time string, or None.

Args:
input_time: the input argument to be converted to a datetime
Expand All @@ -115,14 +116,21 @@ def handle_datetime(input_time, error_name='datetime arg', allow_none=False):
(default: False)

Raises:
TypeError: if input_time is not a string, datetime, or None
TypeError: if input_time is not a string, datetime, date, or None

Returns:
a datetime.datetime, or None
"""

if isinstance(input_time, datetime.datetime):
return input_time
elif isinstance(input_time, datetime.date):
warnings.warn(
'datetime.date passed in with no time; defaulting to 0:00 on date'
)
return datetime.datetime(
input_time.year, input_time.month, input_time.day
)
elif isstring(input_time):
return dateutil_parse(input_time)
elif input_time is None and allow_none:
Expand Down
45 changes: 33 additions & 12 deletions becquerel/tools/isotope_qty.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import datetime
import copy
import numpy as np
import warnings
from .isotope import Isotope
from ..core import utils
from collections import OrderedDict
Expand Down Expand Up @@ -263,11 +264,11 @@ def from_comparison(cls, isotope_qty1, counts1, interval1,
# *_at()
# ----------------------------

def quantity_at(self, quantity, date):
def quantity_at(self, quantity, date=None):
"""Return a quantity at a given time.

Args:
date: the date to calculate for
date: the date to calculate for (default now)

Returns:
a float of the number of atoms at date
Expand All @@ -276,16 +277,16 @@ def quantity_at(self, quantity, date):
TypeError: if date is not recognized
"""

date = date if date is not None else datetime.datetime.now()
t1 = utils.handle_datetime(date)
dt = (t1 - self.ref_date).total_seconds()
return self._ref_quantities[quantity] * 2**(-dt / self.half_life)


def atoms_at(self, date):
def atoms_at(self, date=None):
"""Calculate the number of atoms at a given time.

Args:
date: the date to calculate for
date: the date to calculate for (default now)

Returns:
a float of the number of atoms at date
Expand All @@ -296,23 +297,23 @@ def atoms_at(self, date):

return self.quantity_at("atoms", date)

def bq_at(self, date):
def bq_at(self, date=None):
"""Calculate the activity [Bq] at a given time.

As atoms_at() except for return value.
"""

return self.quantity_at("bq", date)

def uci_at(self, date):
def uci_at(self, date=None):
"""Calculate the activity [uCi] at a given time.

As atoms_at() except for return value.
"""

return self.quantity_at("uci", date)

def g_at(self, date):
def g_at(self, date=None):
"""Calculate the mass [g] at a given time.

As atoms_at() except for return value.
Expand All @@ -331,31 +332,51 @@ def atoms_now(self):
a float of the number of atoms at datetime.datetime.now()
"""

return self.quantity_at("atoms", datetime.datetime.now())
warnings.warn(
'atoms_now() is deprecated and will be removed in a future release'
'. Use atoms_at(date=None) instead.',
DeprecationWarning
)
return self.quantity_at("atoms", date=None)

def bq_now(self):
"""Calculate the activity [Bq] now.

As atoms_now() except for return value.
"""

return self.quantity_at("bq", datetime.datetime.now())
warnings.warn(
'bq_now() is deprecated and will be removed in a future release'
'. Use bq_at(date=None) instead.',
DeprecationWarning
)
return self.quantity_at("bq", date=None)

def uci_now(self):
"""Calculate the activity [uCi] now.

As atoms_now() except for return value.
"""

return self.quantity_at("uci", datetime.datetime.now())
warnings.warn(
'uci_now() is deprecated and will be removed in a future release'
'. Use uci_at(date=None) instead.',
DeprecationWarning
)
return self.quantity_at("uci", date=None)

def g_now(self):
"""Calculate the mass [g] now.

As atoms_now() except for return value.
"""

return self.quantity_at("g", datetime.datetime.now())
warnings.warn(
'g_now() is deprecated and will be removed in a future release'
'. Use g_at(date=None) instead.',
DeprecationWarning
)
return self.quantity_at("g", date=None)

# ----------------------------
# *_from()
Expand Down
29 changes: 25 additions & 4 deletions tests/isotope_qty_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ def iq_kwargs(request):

@pytest.fixture(params=[
datetime.datetime.now(),
datetime.date.today(),
'2015-01-08 00:00:00',
None
])
Expand Down Expand Up @@ -143,6 +144,8 @@ def test_isotopequantity_ref_date_rad(radioisotope, iq_date):
iq = IsotopeQuantity(radioisotope, date=iq_date, atoms=1e24)
if isinstance(iq_date, datetime.datetime):
assert iq.ref_date == iq_date
elif isinstance(iq_date, datetime.date):
assert iq.ref_date.date() == iq_date
elif bq.core.utils.isstring(iq_date):
assert iq.ref_date == dateutil_parse(iq_date)
else:
Expand All @@ -155,12 +158,21 @@ def test_isotopequantity_ref_date_stable(stable_isotope, iq_date):
iq = IsotopeQuantity(stable_isotope, date=iq_date, atoms=1e24)
if isinstance(iq_date, datetime.datetime):
assert iq.ref_date == iq_date
elif isinstance(iq_date, datetime.date):
assert iq.ref_date.date() == iq_date
elif bq.core.utils.isstring(iq_date):
assert iq.ref_date == dateutil_parse(iq_date)
else:
assert (datetime.datetime.now() - iq.ref_date).total_seconds() < 5


@pytest.mark.parametrize('date_in', [
'2021-2-20 0:00:00', '2021-2-20', datetime.date(2021, 2, 20)
])
def test_handle_datetime(date_in):
assert bq.utils.handle_datetime(date_in) == datetime.datetime(2021, 2, 20, 0, 0, 0)


@pytest.mark.parametrize('iso, date, kwargs, error', [
(['Cs-137'], None, {'atoms': 1e24}, TypeError),
('Cs-137', 123, {'bq': 456}, TypeError),
Expand Down Expand Up @@ -244,10 +256,19 @@ def test_isotopequantity_time_when_error(stable_isotope):
def test_isotopequantity_activity_now(iq):
"""Test IsotopeQuantity.*_now()"""

assert np.isclose(iq.bq_now(), iq.bq_at(datetime.datetime.now()))
assert np.isclose(iq.uci_now(), iq.uci_at(datetime.datetime.now()))
assert np.isclose(iq.atoms_now(), iq.atoms_at(datetime.datetime.now()))
assert np.isclose(iq.g_now(), iq.g_at(datetime.datetime.now()))
with pytest.warns(DeprecationWarning):
assert np.isclose(iq.bq_now(), iq.bq_at(datetime.datetime.now()))
with pytest.warns(DeprecationWarning):
assert np.isclose(iq.uci_now(), iq.uci_at(datetime.datetime.now()))
with pytest.warns(DeprecationWarning):
assert np.isclose(iq.atoms_now(), iq.atoms_at(datetime.datetime.now()))
with pytest.warns(DeprecationWarning):
assert np.isclose(iq.g_now(), iq.g_at(datetime.datetime.now()))

assert np.isclose(iq.bq_at(), iq.bq_at(datetime.datetime.now()))
jvavrek marked this conversation as resolved.
Show resolved Hide resolved
assert np.isclose(iq.uci_at(), iq.uci_at(datetime.datetime.now()))
assert np.isclose(iq.atoms_at(), iq.atoms_at(datetime.datetime.now()))
assert np.isclose(iq.g_at(), iq.g_at(datetime.datetime.now()))


def test_isotopequantity_decays_from(iq):
Expand Down