Alex
Alex

Reputation: 948

Given a string that's a weekday name, how to figure out weekday as decimal (and the next date it occurs)?

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

Answers (2)

martineau
martineau

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

unutbu
unutbu

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

Related Questions