Reputation: 948
Given a string, reqDayOf
, that's a weekday name, how do you figure out weekday as decimal (and then return the next instance of it based on a datetime
object)?
Getting the datetime
object's year and iso Week of Year and then using strptime
with the year + week of year + the weekday name works, but it feels like a hack.
import datetime
def getDateFromDayOf(dateTimeObj,reqDayOf):
#reqDayOf is one of ['monday','tuesday','wednesday','thursday','friday','saturday','sunday']
#return the next instance of reqDayOf
#after dateTimeObj
#as a datetime object
#Get the WeekOfYear from dateTimeObj and then
#get the date based on Year + WeekOfYear + reqDayOf
(dtoYear,dtoIsoWeek,x)=dateTimeObj.isocalendar()
checkDate= '{}-{}-{}'.format( dtoYear,dtoIsoWeek,reqDayOf)
dateOfDay=datetime.datetime.strptime(checkDate,"%Y-%U-%A")
#return dateOfDay if it's greater than the original date
if dateOfDay > dateTimeObj:
return dateOfDay
else:
#this is needed on Sundays
#add a week
return dateOfDay + datetime.timedelta(days=7)
>>> datetime.datetime.now().strftime('%a %Y %b %d')
'Fri 2013 Apr 05'
>>> getDateFromDayOf(datetime.datetime.now(),'Monday').strftime('%a %Y %b %d')
'Mon 2013 Apr 08'
Upvotes: 6
Views: 3898
Reputation: 123491
This is similar to the weekday method in @unutbu's anwser, but implemented in a way that doesn't require the day names (or their abbreviations) to be hardcoded into it. It uses the datetime.date
's toordinal()
method to convert the date to a single number, then figures out how many days to add to it to get the desired weekday, and lastly converts the new number back into a date using the date.fromordinal()
method.
It creates and uses a dictionary named DAYNUMS
to convert the reqDayOf
weekday name into a number from 1 to 7, and the date.isoweekday()
method is used to do likewise for the dateTimeObj
argument which was passed to it. The DAYNUMS
dictionary is created without using hardcoded day names, so it will also work in non-English locales and languages given an appropriate reqDayOf
argument, such as 'lundi'
instead of 'Monday'
for French locales.
import datetime
# Create dictionary based on fact that 2013-April-01 was a Monday.
DAYNUMS = {datetime.date(2013, 4, i).strftime('%A').lower(): i
for i in range(1, 8)}
def getDateFromDayOf(dateTimeObj, reqDayOf):
daysDiff = (DAYNUMS[reqDayOf.lower()] - dateTimeObj.isoweekday() - 1) % 7 + 1
return datetime.date.fromordinal(dateTimeObj.toordinal() + daysDiff)
a_date = datetime.datetime.strptime('2013-04-05', '%Y-%m-%d')
print(a_date.strftime('%a %Y %b %d')) # -> Fri 2013 Apr 05
print(getDateFromDayOf(a_date, 'Monday').strftime('%a %Y %b %d')) # -> Mon 2013 Apr 08
Upvotes: 2
Reputation: 880339
The day of the week (as an integer) is returned by the weekday method:
import datetime as DT
dow = dict(zip('monday tuesday wednesday thursday friday saturday sunday'.split(),
range(7)))
def getDateFromDayOf(dateTimeObj, reqDayOf):
weekday = dateTimeObj.weekday()
return dateTimeObj + DT.timedelta(days=(dow[reqDayOf.lower()]-weekday-1)%7+1)
In [90]: getDateFromDayOf(DT.datetime.now(), 'Monday').date()
Out[90]: datetime.date(2013, 4, 8)
In [91]: getDateFromDayOf(DT.datetime.now(), 'Tuesday').date()
Out[91]: datetime.date(2013, 4, 9)
In [92]: getDateFromDayOf(DT.datetime.now(), 'Friday').date()
Out[92]: datetime.date(2013, 4, 12)
Or, using dateutil,
import datetime as DT
import dateutil
import dateutil.relativedelta as rdelta
import dateutil.rrule as rrule
dow = dict(zip('monday tuesday wednesday thursday friday saturday sunday'.split(),
(getattr(rdelta, d) for d in 'MO TU WE TH FR SA SU'.split())))
def getDateFromDayOf(dateTimeObj, reqDayOf):
rr = rrule.rrule(
rrule.DAILY, # step by days
byweekday = dow[reqDayOf.lower()], # return only this day of the week
dtstart = dateTimeObj) # start on this day
res = rr.after(dateTimeObj, inc=False) # inc=False means don't include the dtstart day
return res
Using dateutil you can express the idea in a high-level manner, without having to worry about off-by-one errors in grubby details like
DT.timedelta(days=(dow[reqDayOf.lower()]-weekday-1)%7+1)
Upvotes: 5