I have the follow class and method:
class DateTimeHelper(object):
#staticmethod
def get_utc_millisecond_timestamp():
(dt, micro) = datetime.datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S.%f').split('.')
return "%s.%03d" % (dt, int(micro) / 1000) # UTC time with millisecond
How can I unit test it? I am completely stumped although this is simple. It's my first unit test.
Use the unittest.mock library (Python 3.3 and newer, backported as mock), to replace calls to any code external to your code-under-test.
Here, I'd mock out not only utcnow() but strftime() too, to just return a string object:
with mock.patch('datetime.datetime') as dt_mock:
dt_mock.utcnow.return_value.strftime.return_value = '2016-08-04 12:22:44.123456'
result = DateTimeHelper.get_utc_millisecond_timestamp()
If you feel that testing the strftime() argument is important, give dt_mock.utcnow.return_value an explicit datetime object to return instead; you'd have to create that test object before you mock however, as you can't mock out just the datetime.datetime.utcnow class method:
testdt = datetime.datetime(2016, 8, 4, 12, 22, 44, 123456)
with mock.patch('datetime.datetime') as dt_mock:
dt_mock.utcnow.return_value = testdt
result = DateTimeHelper.get_utc_millisecond_timestamp()
or, in your unittests, use from datetime import datetime to keep a reference to the class that isn't mocked.
Demo:
>>> from unittest import mock
>>> import datetime
>>> class DateTimeHelper(object):
... #staticmethod
... def get_utc_millisecond_timestamp():
... (dt, micro) = datetime.datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S.%f').split('.')
... return "%s.%03d" % (dt, int(micro) / 1000) # UTC time with millisecond
...
>>> with mock.patch('datetime.datetime') as dt_mock:
... dt_mock.utcnow.return_value.strftime.return_value = '2016-08-04 12:22:44.123456'
... result = DateTimeHelper.get_utc_millisecond_timestamp()
...
>>> result
'2016-08-04 12:22:44.123'
>>> testdt = datetime.datetime(2016, 8, 4, 12, 22, 44, 123456)
>>> with mock.patch('datetime.datetime') as dt_mock:
... dt_mock.utcnow.return_value = testdt
... result = DateTimeHelper.get_utc_millisecond_timestamp()
...
>>> result
'2016-08-04 12:22:44.123'
Related
I passed filenames such as abcef-1591282803 into this function and the function worked fine:
def parse_unixtimestamp(filename):
ts = re.findall(r'\d{10}', filename)[0]
return ts
However, then I modified the function to this so the it also works when the timestamp is of 13 digits instead of 10. file20-name-1591282803734
def parse_unixtimestamp(filename):
ts = re.findall(r'\d{13}|\d{10}', filename)[0]
return ts
It didn't throw an error until here. But in case of 13 digits, I get ts values like this 1591282803734. Now when I pass this value to this function for year:
def get_dateparts(in_unixtimestamp):
dt_object = datetime.fromtimestamp(int(in_unixtimestamp))
date_string = dt_object.strftime("%Y-%m-%d")
year_string = dt_object.strftime("%Y")
month_string = dt_object.strftime("%m")
day_string = dt_object.strftime("%d")
logger.info(f'year_string: {year_string}')
result = {"date_string": date_string, "year_string": year_string, "month_string": month_string,
"day_string": day_string}
return result
I get an error that:
year 52395 is out of range
I wouldn't get this error when the unixtimestamp passed into parse_unixtimestampIs only 10 digits. How can I modify this function such that it works in both cases?
datetime.fromtimestamp requires you to supply the time in seconds. If your string has 13 digits instead of 9 (i.e. it is milliseconds), you should be using:
>>> datetime.fromtimestamp(1591282803734/1000)
datetime.datetime(2020, 6, 4, 11, 0, 3, 734000)
To do this in your function, you could check the length of your timestamp before calling datetime.fromtimestamp.
Change your function as follows:
def get_dateparts(in_unixtimestamp):
if len(in_unixtimestamp) == 13:
dt_object = datetime.fromtimestamp(int(in_unixtimestamp)/1000)
else:
dt_object = datetime.fromtimestamp(int(in_unixtimestamp))
date_string = dt_object.strftime("%Y-%m-%d")
year_string = dt_object.strftime("%Y")
month_string = dt_object.strftime("%m")
day_string = dt_object.strftime("%d")
logger.info(f'year_string: {year_string}')
result = {"date_string": date_string,
"year_string": year_string,
"month_string": month_string,
"day_string": day_string}
return result
>>> get_dateparts(parse_unixtimestamp("abcef-1591282803"))
{'date_string': '2020-06-04',
'year_string': '2020',
'month_string': '06',
'day_string': '04'}
>>> get_dateparts(parse_unixtimestamp("file20-name-1591282803734"))
{'date_string': '2020-06-04',
'year_string': '2020',
'month_string': '06',
'day_string': '04'}
Can someone help me to unit test this line of code?
from datetime import datetime, timedelta, timezone
def get_timestamp_plus_100_year():
return int((datetime.now(timezone.utc) + timedelta(days=100 * 365)).timestamp())
I try this, but I don't know how to assign the values:
#patch("src.shared.utils.timedelta")
#patch("src.shared.utils.datetime")
def test_get_timestamp_now_plus_100_years(self, mock_datetime, mock_timedelta):
mock_datetime.now.return_value = 2021-09-14 15:54:25.284087+00:00
mock_timedelta.return_value = 36500 days, 0:00:00
self.assertEqual(
get_timestamp_plus_100_year(),
int((mock_datetime.now.return_value
+ mock_timedelta.return_value).timestamp_return_value ),
)
Correct the implementation first. As pointed out by #MrFuppes, not all years are 365 days. Assuming today is 2021-9-15, your original implementation would result to:
>>> datetime.now(timezone.utc) + timedelta(days=100 * 365)
datetime.datetime(2121, 8, 22, 9, 19, 30, 468735, tzinfo=datetime.timezone.utc)
Here, you can see that instead of the expected 2121-9-15, what we got was 2121-8-22.
Option 1: Using datetime.replace() to replace the year with year+100
>>> (dt := datetime.now(timezone.utc)).replace(year=dt.year + 100)
datetime.datetime(2121, 9, 15, 9, 24, 52, 139984, tzinfo=datetime.timezone.utc)
Option 2: Using dateutil.relativedelta.relativedelta to add +100 years. This requires pip install python-dateutil.
>>> datetime.now(timezone.utc) + relativedelta(years=100)
datetime.datetime(2121, 9, 15, 9, 28, 16, 789807, tzinfo=datetime.timezone.utc)
Then, you don't need to mock the timedelta (or relativedelta in our corrected code) since its value will always be 100 years. You just need to mock the current date via datetime.now() since it is the base of the addition and we need to assert the result.
Assuming this is the file tree:
.
├── src.py
└── test_src.py
src.py
from datetime import datetime, timezone
from dateutil.relativedelta import relativedelta
def get_timestamp_plus_100_year():
return int((datetime.now(timezone.utc) + relativedelta(years=100)).timestamp())
Solution 1:
from datetime import datetime
import unittest
from unittest.mock import patch
from dateutil.relativedelta import relativedelta
from src import get_timestamp_plus_100_year
class TestDates(unittest.TestCase):
#patch("src.datetime")
def test_get_timestamp_now_plus_100_years(self, mock_datetime):
frozen_dt = datetime(year=2021, month=9, day=15)
mock_datetime.now.return_value = frozen_dt
# Solution 1.1
self.assertEqual(
get_timestamp_plus_100_year(),
int((frozen_dt + relativedelta(years=100)).timestamp()),
)
# Solution 1.2
frozen_dt_100 = datetime(year=2121, month=9, day=15) # Since we already know the value of +100 years, we can just define it here
self.assertEqual(
get_timestamp_plus_100_year(),
int(frozen_dt_100.timestamp()),
)
Solution 2
This requires freezegun via pip install freezegun
from datetime import datetime
import unittest
from freezegun import freeze_time
from src import get_timestamp_plus_100_year
#freeze_time("2021-09-15") # Either here
class TestDates(unittest.TestCase):
#freeze_time("2021-09-15") # Or here
def test_get_timestamp_now_plus_100_years_2(self):
frozen_dt_100 = datetime(year=2121, month=9, day=15) # Since we already know the value of +100 years, we can just define it here
self.assertEqual(
get_timestamp_plus_100_year(),
int(frozen_dt_100.timestamp()),
)
Somewhat related to this post: dateutil parser for month/year format: return beginning of month
Given a date string of the form 'Sep-2020', dateutil.parser.parse correctly identifies the month and the year but adds the day as well. If a default is provided, it takes the day from it. Else, it will just use today's day. Is there anyway to tell if the parser used any of the default terms?
For example, how can I tell from the three options below that the input date string in the first case did not include day and that the default value was used?
>>> from datetime import datetime
>>> from dateutil import parser
>>> d = datetime(1978, 1, 1, 0, 0)
>>> parser.parse('Sep-2020', default=d)
datetime.datetime(2020, 9, 1, 0, 0)
>>> parser.parse('1-Sep-2020', default=d)
datetime.datetime(2020, 9, 1, 0, 0)
>>> parser.parse('Sep-1-2020', default=d)
datetime.datetime(2020, 9, 1, 0, 0)
``
I did something a little mad to solve this. It's mad since it's not guaranteed to work with future versions of dateutil (since it's relying on some dateutil internals).
Currently I'm using: python-dateutil 2.8.1.
I wrote my own class and passed it as default to the parser:
from datetime import datetime
class SentinelDateTime:
def __init__(self, year=0, month=0, day=0, default=None):
self._year = year
self._month = month
self._day = day
if default is None:
default = datetime.now().replace(
hour=0, minute=0,
second=0, microsecond=0
)
self.year = default.year
self.month = default.month
self.day = default.day
self.default = default
#property
def has_year(self):
return self._year != 0
#property
def has_month(self):
return self._month != 0
#property
def has_day(self):
return self._day != 0
def todatetime(self):
res = {
attr: value
for attr, value in [
("year", self._year),
("month", self._month),
("day", self._day),
] if value
}
return self.default.replace(**res)
def replace(self, **result):
return SentinelDateTime(**result, default=self.default)
def __repr__(self):
return "%s(%d, %d, %d)" % (
self.__class__.__qualname__,
self._year,
self._month,
self._day
)
The dateutils method now returns this SentinelDateTime class:
>>> from dateutil import parser
>>> from datetime import datetime
>>> from snippet1 import SentinelDateTime
>>>
>>> sentinel = SentinelDateTime()
>>> s = parser.parse('Sep-2020', default=sentinel)
>>> s
SentinelDateTime(2020, 9, 0)
>>> s.has_day
False
>>> s.todatetime()
datetime.datetime(2020, 9, 9, 0, 0)
>>> d = datetime(1978, 1, 1)
>>> sentinel = SentinelDateTime(default=d)
>>> s = parser.parse('Sep-2020', default=sentinel)
>>> s
SentinelDateTime(2020, 9, 0)
>>> s.has_day
False
>>> s.todatetime()
datetime.datetime(2020, 9, 1, 0, 0)
I wrote this answer into a little package: https://github.com/foxyblue/sentinel-datetime
I have found a solution that's a little less complicated:
from datetime import datetime
from dataclasses import dataclass
from dateutil import parser
#dataclass
class Result:
dt: datetime
data: dict
class subparser(parser.parser):
def _build_naive(self, res, default):
naive = super()._build_naive(res, default)
return Result(dt=naive, data=res)
In an example:
>>> PARSER = subparser()
>>> info = PARSER.parse("2020")
>>> info.data.year)
2020
>>> info.data.month
None
>>> info.dt
2020-01-10 00:00:00
I'm working on my first python script, which creates and updates and object with different datetime entries.
I'm setting up the object like this:
# Date conversion
import datetime
import time
# 0:01:00 and 0:00:00 threshold and totalseconds
threshold = time.strptime('00:01:00,000'.split(',')[0],'%H:%M:%S')
tick = datetime.timedelta(hours=threshold.tm_hour,minutes=threshold.tm_min,seconds=threshold.tm_sec).total_seconds()
zero_time = datetime.timedelta(hours=0,minutes=0,seconds=0)
zero_tick = zero_time.total_seconds()
format_date = '%d/%b/%Y:%H:%M:%S'
from datetime import datetime
# Response object
class ResponseObject(object):
def __init__(self, dict):
self.__dict__ = dict
# JSON encoding
from json import JSONEncoder
class MyEncoder(JSONEncoder):
def default(self, o):
return o.__dict__
# > check for JSON response object
try:
obj
except NameError:
obj = ResponseObject({})
...
entry = "14/Nov/2012:09:32:31 +0100"
entry_tz = str.join(' ', entry.split(None)[1:6])
entry_notz = entry.replace(' '+entry_tz,'')
this_time = datetime.strptime(entry_notz, format_date)
# > add machine to object if not there, add init time
if not hasattr(obj, "SOFTINST"):
#line-breaks for readability
setattr(obj, "SOFTINST", {
"init":this_time,
"last":this_time,
"downtime":zero_time,
"totaltime":"",
"percentile":100
})
...
print this_time
print MyEncoder().encode({"hello":"bar"})
print getattr(obj, "SOFTINST")
My last 'print' returns this:
{
'totaltime': datetime.timedelta(0),
'uptime': '',
'last': datetime.datetime(2012, 11, 14, 9, 32, 31),
'init': datetime.datetime(2012, 11, 14, 9, 32, 31),
'percentile': 100,
'downtime': 0
}
Which I cannot convert into JSON...
I don't understand why this:
print this_time #2012-11-14 09:32:31
but inside the object, it's stored as
datetime.datetime(2012, 11, 14, 9, 32, 31)
Question:
How do I store datetime objects in "string format" and still have them easily accessible (and modifyable) in Python?
Thanks!
Use the isoformat method on the datetime object. (see reference: http://docs.python.org/release/2.5.2/lib/datetime-datetime.html)
I've been struggling for way too long on dates/timezones in Python and was thinking someone could give me a hand here.
Basically I want to do a conversion in UTC and taking into account DST changes.
I've created the following tzinfo class from one of the Python tutorials (not 100% accurate I know but it doesn't need to):
from datetime import tzinfo, timedelta, datetime
ZERO = timedelta(0)
HOUR = timedelta(hours=1)
def first_sunday_on_or_after(dt):
days_to_go = 6 - dt.weekday()
if days_to_go:
dt += timedelta(days_to_go)
return dt
DSTSTART_2007 = datetime(1, 3, 8, 2)
DSTEND_2007 = datetime(1, 11, 1, 1)
DSTSTART_1987_2006 = datetime(1, 4, 1, 2)
DSTEND_1987_2006 = datetime(1, 10, 25, 1)
DSTSTART_1967_1986 = datetime(1, 4, 24, 2)
DSTEND_1967_1986 = DSTEND_1987_2006
class USTimeZone(tzinfo):
def __init__(self, hours, reprname, stdname, dstname):
self.stdoffset = timedelta(hours=hours)
self.reprname = reprname
self.stdname = stdname
self.dstname = dstname
def __repr__(self):
return self.reprname
def tzname(self, dt):
if self.dst(dt):
return self.dstname
else:
return self.stdname
def utcoffset(self, dt):
return self.stdoffset + self.dst(dt)
def dst(self, dt):
if dt is None or dt.tzinfo is None:
# An exception may be sensible here, in one or both cases.
# It depends on how you want to treat them. The default
# fromutc() implementation (called by the default astimezone()
# implementation) passes a datetime with dt.tzinfo is self.
return ZERO
assert dt.tzinfo is self
# Find start and end times for US DST. For years before 1967, return
# ZERO for no DST.
if 2006 < dt.year:
dststart, dstend = DSTSTART_2007, DSTEND_2007
elif 1986 < dt.year < 2007:
dststart, dstend = DSTSTART_1987_2006, DSTEND_1987_2006
elif 1966 < dt.year < 1987:
dststart, dstend = DSTSTART_1967_1986, DSTEND_1967_1986
else:
return ZERO
start = first_sunday_on_or_after(dststart.replace(year=dt.year))
end = first_sunday_on_or_after(dstend.replace(year=dt.year))
# Can't compare naive to aware objects, so strip the timezone from
# dt first.
if start <= dt.replace(tzinfo=None) < end:
return HOUR
else:
return ZERO
On the other side I have an arbitrary date object in EST, and I want to know the number of hours they differ by taking into account DST.
I've tried something like this:
>>> Eastern = ustimezone.USTimeZone(-5, "Eastern", "EST", "EDT")
>>> x = datetime.date.today() # I actually get an arbitrary date but this is for the example
>>> x_dt = datetime.datetime.combine(x, datetime.time())
>>> x_dt_tz = x_dt.astimezone(Eastern)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: astimezone() cannot be applied to a naive datetime
I've seen several posts who tell to use localize from the pytz module, but unfortunately I am not able to use additional modules, so impossible to use pyzt
Does anyone know how I can get this naive datetime into a timezoned object without using pytz?
For what it's worth, the answer #skyl provided is more-or-less equivalent to what pytz does.
Here is the relevant pytz source. It just calls replace on the datetime object with the tzinfo kwarg:
def localize(self, dt, is_dst=False):
'''Convert naive time to local time'''
if dt.tzinfo is not None:
raise ValueError('Not naive datetime (tzinfo is already set)')
return dt.replace(tzinfo=self)
Use x_dt.replace(tzinfo=Eastern) (found from this Google Groups thread).
x_dt.replace(tzinfo=Eastern).utcoffset() returns datetime.timedelta(-1, 72000) which corresponds to -4 hours! (from Question's comment)