I have a situation where I have a code with which I am processing data for operated shifts.
In it, I have arrays for start and end of shifts (e.g. shift_start[0] and shift_end[0] for shift #1), and for the time between them, I need to know how many weekdays, holidays or weekend days.
The holidays I have already defined in an array of datetime entries, which should represent the holidays of a specific country (it's not the same as here and I do not seek for further more dynamic options here yet).
So basically I have it like that:
started = [datetime.datetime(2022, 2, 1, 0, 0), datetime.datetime(2022, 2, 5, 8, 0), datetime.datetime(2022, 2, 23, 11, 19, 28)]
ended = [datetime.datetime(2022, 2, 2, 16, 0), datetime.datetime(2022, 2, 5, 17, 19, 28), datetime.datetime(2022, 4, 26, 12, 30)]
holidays = [datetime.datetime(2022, 1, 3), datetime.datetime(2022, 3, 3), datetime.datetime(2022, 4, 22), datetime.datetime(2022, 4, 25)]
I'm seeking for options to go thru each of the 3 ranges and match the number of days it contains (e.g. the first range should contain 2 weekdays, the second - one weekend day)
So based on the suggestion by #gimix, I was able to develop what I needed:
for each_start, each_end in zip(started, ended): # For each period
for single_date in self.daterange(each_start, each_end): # For each day of each period
# Checking if holiday or weekend
if (single_date.replace(hour=0, minute=0, second=0) in holidays) or (single_date.weekday() > 4):
set_special_days_worked(1)
# If not holiday or weekend, then it is regular working day
else:
set_regular_days_worked(1)
I need to set the time value of detatime objects to 00:00, so I can easily compare two 'dates'
Now I do:
extracted_start_date = datetime.strptime(extracted_start_date.strftime('%Y-%m-%d'), '%Y-%m-%d')
so I have my datetime object
$ datetime.datetime(2021, 3, 18, 11, 13, 53, 782088),
I extract the date string strftime('%Y-%m-%d')
$ '2021-03-18'
and put this back into a datetime object now wch has time 00:00
$ datetime.datetime(2021, 3, 18, 0, 0)
This seems quite elaborate, is there a more efficient way, or is it OK like this?
datetime.datetime objects have method date which return datetime.date instance and these might be used for day-based comparison, consider following example:
import datetime
d1 = datetime.datetime(2021, 3, 17, 9, 0, 0) # yesterday 9:00
d2 = datetime.datetime(2021, 3, 18, 9, 0, 0) # today 9:00
d3 = datetime.datetime(2021, 3, 18, 12, 0, 0) # today 12:00
print(d1.date() == d2.date())
print(d2.date() == d3.date())
output:
False
True
You could use the replace method instead.
from datetime import datetime
d = datetime(2021, 3, 18, 11, 13, 53, 782088)
d.replace(hour=0, minute=0, second=0, microsecond=0)
> datetime.datetime(2021, 3, 18, 0, 0)
Given a timestamp without time zone (e.g. 2018-03-12 09:30:00) AND the timezone EST5EDT, the goal is to parse the data returning a datetime object that is time zone AND daylight saving aware.
from datetime import datetime
import pytz
datetime(2018, 3, 8, 9, 30, tzinfo=pytz.timezone('EST5EDT')).astimezone(pytz.utc)
# returns:
# datetime.datetime(2018, 3, 8, 14, 30, tzinfo=<UTC>)
datetime(2018, 3, 12, 9, 30, tzinfo=pytz.timezone('EST5EDT')).astimezone(pytz.utc)
# returns:
# datetime.datetime(2018, 3, 12, 14, 30, tzinfo=<UTC>)
# BUT should return (second Sunday of march the daylight saving changes by 1 hour):
# datetime.datetime(2018, 3, 12, 13, 30, tzinfo=<UTC>)
Never set tzinfo directly when creating datetimes. Always use the localize() method of the timezone (see the note at the top of http://pytz.sourceforge.net/):
pytz.timezone('EST5EDT').localize(
datetime(2018, 3, 12, 9, 30)
).astimezone(pytz.utc)
I want to create a mapping of the everyday of the week with its datetime object. So my dictionary should be having keys as "Monday", "Tuesday", .. (so on) so that I can get a datetime object for every day on the next(!) week.
At the moment I have a dictionary with these values:
DAYS_DATETIME_RELATIONS = {
"today": datetime.datetime.now(),
"tomorrow": datetime.datetime.now() + datetime.timedelta(days=1),
"after_tomorrow": datetime.datetime.now() + datetime.timedelta(days=2)
}
Unfortunately I cannot find any algorithmic solution for this and hope anyone of you could help me.
This can be achieved by using 2 dictionaries in the following manner:
import calendar
import datetime
days = {i: calendar.day_name[i-1] for i in range(7)}
today = datetime.datetime.now()
# using i % 7 so an arbitrary range can be used, for example
# range(7, 15) to get the week after the next week
next_week = {days[i % 7]: (today + datetime.timedelta(days=i)).date()
for i in range(7)}
print(next_week)
# {'Tuesday': datetime.date(2018, 1, 9), 'Sunday': datetime.date(2018, 1, 7),
# 'Monday': datetime.date(2018, 1, 8), 'Thursday': datetime.date(2018, 1, 11),
# 'Wednesday': datetime.date(2018, 1, 10), 'Friday': datetime.date(2018, 1, 12),
# 'Saturday': datetime.date(2018, 1, 13)}
print(next_week['Saturday'])
# 2018-01-13
Here is another way to solve your question using datetime and timedelta from datetime module:
from datetime import datetime, timedelta
def generate_dict_relation(_time, _days=0):
keys = {'Yesterday': -1, 'Today': 0, 'Tomorow': 1, 'After_tomorrow': 2}
if not _days:
return {key: _time + timedelta(days=keys.get(key, 0)) for key in keys}
else:
return {(_time + timedelta(days=_days+k)).strftime('%A'): _time + timedelta(days=_days+k)
for k in range(0, 7)}
_date_now = datetime.now()
DAYS_DATETIME_RELATIONS = {}
# get dates: yesterday, today, tomorrow and after tomorrow
DAYS_DATETIME_RELATIONS.update(generate_dict_relation(_date_now, 0))
# get dates after 7 days = 1 week
DAYS_DATETIME_RELATIONS.update(generate_dict_relation(_date_now, 7))
next_tuesday = DAYS_DATETIME_RELATIONS.get('Tuesday')
next_monday = DAYS_DATETIME_RELATIONS.get('Monday')
yesterday = DAYS_DATETIME_RELATIONS.get('Yesterday')
print('{0:%d/%m/%Y %H:%M:%S:%s} \t {1}'.format(next_tuesday, repr(next_tuesday)))
print('{0:%d/%m/%Y %H:%M:%S:%s} \t {1}'.format(next_monday, repr(next_monday)))
print('{0:%d/%m/%Y %H:%M:%S:%s} \t {1}'.format(yesterday, repr(yesterday)))
Output:
16/01/2018 10:56:26:1516096586 datetime.datetime(2018, 1, 16, 10, 56, 26, 659949)
15/01/2018 10:56:26:1516010186 datetime.datetime(2018, 1, 15, 10, 56, 26, 659949)
06/01/2018 10:56:26:1515232586 datetime.datetime(2018, 1, 6, 10, 56, 26, 659949)
One very generic way will be to create a custom iterator to return you the continuos datetime objects as:
from datetime import datetime, timedelta
class RepetetiveDate(object):
def __init__(self, day_range=7, datetime_obj=datetime.now(), jump_days=1):
self.day_range = day_range
self.day_counter = 0
self.datetime_obj = datetime_obj
self.jump_days = jump_days
self.time_deltadiff = timedelta(days=self.jump_days)
def __iter__(self):
return self
# If you are on Python 2.7
# define this function as `next(self)`
def __next__(self):
if self.day_counter >= self.day_range:
raise StopIteration
if self.day_counter != 0: # don't update for the first iteration
self.datetime_obj += self.time_deltadiff
self.day_counter += 1
return self.datetime_obj
Here, this iterator returns continuos datetime object starting from the datetime object you'll initially pass (default starts from current date).
It is using 3 optional params which you may customize as per your need:
day_range: Maximum allowed iteration for the RepetetiveDate iterator. Default value is 7.
jump_days: Integer value for jumping the number of days for the datetime object in next iteration. That means, if jump_days is equal to "2", will return datetime objects of every alternate date. To get the datetime objects of past, pass this value as negative. Default value is 1.
datetime_obj: Accepts the datetime from which date you want to start your iteration. Default value is current date.
If you are new to iterators, take a look at:
What exactly are Python's iterator, iterable, and iteration protocols?
Difference between Python's Generators and Iterators
Sample Run for upcoming dates:
>>> x = RepetetiveDate()
>>> next(x)
datetime.datetime(2018, 1, 8, 15, 55, 39, 124654)
>>> next(x)
datetime.datetime(2018, 1, 9, 15, 55, 39, 124654)
>>> next(x)
datetime.datetime(2018, 1, 10, 15, 55, 39, 124654)
Sample Run for previous dates:
>>> x = RepetetiveDate(jump_days=-1)
>>> next(x)
datetime.datetime(2018, 1, 6, 15, 55, 39, 124654)
>>> next(x)
datetime.datetime(2018, 1, 5, 15, 55, 39, 124654)
>>> next(x)
datetime.datetime(2018, 1, 4, 15, 55, 39, 124654)
How to get your desired dictionary?
Using this, you may create your dictionary using the dict comprehension as:
Dictionary of all days of week
>>> {d.strftime("%A"): d for d in RepetetiveDate(day_range=7)}
{
'Monday': datetime.datetime(2018, 1, 8, 15, 23, 16, 926364),
'Tuesday': datetime.datetime(2018, 1, 9, 15, 23, 16, 926364),
'Wednesday': datetime.datetime(2018, 1, 10, 15, 23, 16, 926364),
'Thursday': datetime.datetime(2018, 1, 11, 15, 23, 16, 926364),
'Friday': datetime.datetime(2018, 1, 12, 15, 23, 16, 926364),
'Saturday': datetime.datetime(2018, 1, 13, 15, 23, 16, 926364),
'Sunday': datetime.datetime(2018, 1, 14, 15, 23, 16, 926364)
}
Here I am using d.strftime("%A") to extract day name from the datetime object.
List of current days for next 4 weeks
>>> [d for d in RepetetiveDate(jump_days=7, day_range=4))]
[
datetime.datetime(2018, 1, 7, 16, 17, 45, 45005),
datetime.datetime(2018, 1, 14, 16, 17, 45, 45005),
datetime.datetime(2018, 1, 21, 16, 17, 45, 45005),
datetime.datetime(2018, 1, 28, 16, 17, 45, 45005)
]
One very clean way to implement this is using rrule from the dateutil library. For example:
>>> from dateutil.rrule import rrule, DAILY
>>> from datetime import datetime
>>> start_date = datetime.now()
>>> {d.strftime("%A"): d for d in rrule(freq=DAILY, count=7, dtstart=start_date)}
which will return your desired dict object:
{
'Sunday': datetime.datetime(2018, 1, 7, 17, 2, 30),
'Monday': datetime.datetime(2018, 1, 8, 17, 2, 30),
'Tuesday': datetime.datetime(2018, 1, 9, 17, 2, 30),
'Wednesday': datetime.datetime(2018, 1, 10, 17, 2, 30),
'Thursday': datetime.datetime(2018, 1, 11, 17, 2, 30),
'Friday': datetime.datetime(2018, 1, 12, 17, 2, 30),
'Saturday': datetime.datetime(2018, 1, 13, 17, 2, 30)
}
(Special thanks to Jon Clements for telling me about rrule)
I have a list of variable that includes several datetime.datetime type variables.
I.e.:
a['PrTimeStamp'] # Type list
[0] datetime.datetime(2014, 10, 19, 10, 0) # Type datetime
[1] datetime.datetime(2014, 12, 3, 12, 0) # Type datetime
[2] datetime.datetime(2014, 12, 4, 0, 0) # Type datetime
[3] datetime.datetime(2014, 12, 10, 13, 0) # Type datetime
[4] datetime.datetime(2014, 12, 16, 20, 0) # Type datetime
[5] datetime.datetime(2014, 12, 17, 2, 0) # Type datetime
E.g. if I make print(a['PrTimeStamp'][0]) it prints to screen: 2014-10-19 10:00:00.
Now I want to convert this list to a format that can be easily read by matlab.
I tried to do the following:
a['PrTimeStamp'] = a['PrTimeStamp'].strftime("%d-%b-%Y %H:%M:%S"))
But I got an error:
AttributeError: 'list' object has no attribute 'strftime'
date[]
for i in a['PrTimeStamp']:
date.append(i.strftime("%d-%b-%Y %H:%M:%S"))
a must be a dictionary has a key PrTimeStamp
I'm pretty sure you need data.strftime to format the dates. Something similar to this.
How to change time formats in python?
Since a['PrTimeStamp'] is a list of date time objects, you need to iterate each item in the list and format the date time object in it!
Since you haven't posted the entire code,
a={}
import datetime
a['PrTimeStamp'] = [ datetime.datetime(2014, 10, 19, 10, 0)
, datetime.datetime(2014, 12, 3, 12, 0)
, datetime.datetime(2014, 12, 4, 0, 0)
, datetime.datetime(2014, 12, 10, 13, 0)
, datetime.datetime(2014, 12, 16, 20, 0)
, datetime.datetime(2014, 12, 17, 2, 0)]
#this is what you need!
print [ts.strftime("%d-%b-%Y %H:%M:%S") for ts in a['PrTimeStamp']]
will print
['19-Oct-2014 10:00:00', '03-Dec-2014 12:00:00', '04-Dec-2014 00:00:00', '10-Dec-2014 13:00:00', '16-Dec-2014 20:00:00', '17-Dec-2014 02:00:00']
Hope it helps!