开发者

Python: Know the second wednesday of the next month with a given date

I would like to know this:

I have for example this date:

2011-08-10 wednesday

and i would like to know 开发者_运维知识库the next second wednesday of the next month: The answer should be 2011-09-14 wednesday.


In the comments it was explained that the OP is looking for a function which maps

  1. 2011-08-25 (the fourth Thursday) to 2011-09-22 (the fourth Thursday of the next month) and
  2. 2011-08-30 (the fifth Tuesday) to 2011-09-27 (the fourth Tuesday, because there is no fifth Tuesday in September.)

Using dateutil:

import datetime
import dateutil.relativedelta as relativedelta

def next_month(date):
    weekday=relativedelta.weekday(date.isoweekday()-1)   
    weeknum=(date.day-1)//7+1
    weeknum=weeknum if weeknum<=4 else 4
    next_date=date+relativedelta.relativedelta(
        months=1,day=1,weekday=weekday(weeknum))
    return next_date

start=datetime.date(2011,8,1)
for i in range(31):
    date=start+datetime.timedelta(days=i)
    next_date=next_month(date)    
    print('{d} --> {n}'.format(d=date,n=next_date))

yields

2011-08-01 --> 2011-09-05
2011-08-02 --> 2011-09-06
2011-08-03 --> 2011-09-07
2011-08-04 --> 2011-09-01
2011-08-05 --> 2011-09-02
2011-08-06 --> 2011-09-03
2011-08-07 --> 2011-09-04
2011-08-08 --> 2011-09-12
2011-08-09 --> 2011-09-13
2011-08-10 --> 2011-09-14
2011-08-11 --> 2011-09-08
2011-08-12 --> 2011-09-09
2011-08-13 --> 2011-09-10
2011-08-14 --> 2011-09-11
2011-08-15 --> 2011-09-19
2011-08-16 --> 2011-09-20
2011-08-17 --> 2011-09-21
2011-08-18 --> 2011-09-15
2011-08-19 --> 2011-09-16
2011-08-20 --> 2011-09-17
2011-08-21 --> 2011-09-18
2011-08-22 --> 2011-09-26
2011-08-23 --> 2011-09-27
2011-08-24 --> 2011-09-28
2011-08-25 --> 2011-09-22 # Oddly non-monotonic, but correct according to specifications
2011-08-26 --> 2011-09-23
2011-08-27 --> 2011-09-24
2011-08-28 --> 2011-09-25
2011-08-29 --> 2011-09-26 # 5th Monday maps to 4th Monday since there is no 5th Monday in September
2011-08-30 --> 2011-09-27
2011-08-31 --> 2011-09-28


Why do you explicit need the second wednesday? It would be more valuable if you knew the weekday position in the month


from time import *
import re
from sys import exit


dico = {'Monday':0,'monday':0,'Tuesday':1,'tuesday':1,
        'Wednesday':2,'wednesday':2,'Thursday':3,'thursday':3,
        'Friday':4,'friday':4,'Saturday':5,'saturday':5,
        'Sunday':6,'sunday':6}

regx = re.compile('((\d{4})-(\d\d)-(\d\d))\s+(%s)' % '|'.join(dico.iterkeys()))



for s in ('2011-08-10  Wednesday', '2011-08-25    Thursday', '2011-08-30  Tuesday',
          '2011-12-04 Sunday', '2011-12-30   Friday',
          '2012-02-18 Saturday', '2012-02-25 Saturday', '2012-02-29   Wednesday '):

    print 's == ' + s

    try:
        the_date,y,m,d,day_name = regx.match(s).groups()
        wday = dico[day_name]
    except:
        mess = "The string isn't expressing a date correctly"
        exit(mess)

    try:
        s_strp = strptime(the_date,'%Y-%m-%d')
    except:
        mess = "The date isn't an existing date"
        exit(mess)

    if s_strp.tm_wday != wday:
        mess = 'The name of week-day in the string is incoherent with the date'
        exit(mess)

    n = (int(d)-1)//7
    print '(y,m,d,day_name,wday,n) ==',(y,m,d,day_name,wday,n)
    print 'The day %s is the %sth %s in the month %s\n' % (d,n+1,day_name,y+'-'+m)


    yp,mp = (int(y)+1, 1) if m=='12' else (int(y), int(m)+1)
    next_month = ('%s-%s-%s' % (yp,mp,dp) for dp in xrange(1,32))
    same_days = []
    for day in next_month:
        try:
            if strptime(day,'%Y-%m-%d').tm_wday==wday:  same_days.append(day)
        except:
            break
    print '%s days in the next month are :\n%s' % (day_name,same_days)
    try:
        print 'The %sth %s in the next month is on date %s' % (n+1,day_name,same_days[n])
    except:
        print 'The last %s (%sth) in the next month is on date %s' % (day_name,n,same_days[n-1])
    print '\n-------------------------------------------------------------'

result

s == 2011-08-10  Wednesday
(y,m,d,day_name,wday,n) == ('2011', '08', '10', 'Wednesday', 2, 1)
The day 10 is the 2th Wednesday in the month 2011-08

Wednesday days in the next month are :
['2011-9-7', '2011-9-14', '2011-9-21', '2011-9-28']
The 2th Wednesday in the next month is on date 2011-9-14

-------------------------------------------------------------
s == 2011-08-25    Thursday
(y,m,d,day_name,wday,n) == ('2011', '08', '25', 'Thursday', 3, 3)
The day 25 is the 4th Thursday in the month 2011-08

Thursday days in the next month are :
['2011-9-1', '2011-9-8', '2011-9-15', '2011-9-22', '2011-9-29']
The 4th Thursday in the next month is on date 2011-9-22

-------------------------------------------------------------
s == 2011-08-30  Tuesday
(y,m,d,day_name,wday,n) == ('2011', '08', '30', 'Tuesday', 1, 4)
The day 30 is the 5th Tuesday in the month 2011-08

Tuesday days in the next month are :
['2011-9-6', '2011-9-13', '2011-9-20', '2011-9-27']
The last Tuesday (4th) in the next month is on date 2011-9-27

-------------------------------------------------------------
s == 2011-12-04 Sunday
(y,m,d,day_name,wday,n) == ('2011', '12', '04', 'Sunday', 6, 0)
The day 04 is the 1th Sunday in the month 2011-12

Sunday days in the next month are :
['2012-1-1', '2012-1-8', '2012-1-15', '2012-1-22', '2012-1-29']
The 1th Sunday in the next month is on date 2012-1-1

-------------------------------------------------------------
s == 2011-12-30   Friday
(y,m,d,day_name,wday,n) == ('2011', '12', '30', 'Friday', 4, 4)
The day 30 is the 5th Friday in the month 2011-12

Friday days in the next month are :
['2012-1-6', '2012-1-13', '2012-1-20', '2012-1-27']
The last Friday (4th) in the next month is on date 2012-1-27

-------------------------------------------------------------
s == 2012-02-18 Saturday
(y,m,d,day_name,wday,n) == ('2012', '02', '18', 'Saturday', 5, 2)
The day 18 is the 3th Saturday in the month 2012-02

Saturday days in the next month are :
['2012-3-3', '2012-3-10', '2012-3-17', '2012-3-24', '2012-3-31']
The 3th Saturday in the next month is on date 2012-3-17

-------------------------------------------------------------
s == 2012-02-25 Saturday
(y,m,d,day_name,wday,n) == ('2012', '02', '25', 'Saturday', 5, 3)
The day 25 is the 4th Saturday in the month 2012-02

Saturday days in the next month are :
['2012-3-3', '2012-3-10', '2012-3-17', '2012-3-24', '2012-3-31']
The 4th Saturday in the next month is on date 2012-3-24

-------------------------------------------------------------
s == 2012-02-29   Wednesday 
(y,m,d,day_name,wday,n) == ('2012', '02', '29', 'Wednesday', 2, 4)
The day 29 is the 5th Wednesday in the month 2012-02

Wednesday days in the next month are :
['2012-3-7', '2012-3-14', '2012-3-21', '2012-3-28']
The last Wednesday (4th) in the next month is on date 2012-3-28

-------------------------------------------------------------

With the string '2011-08-11 Monday' the result is:

Traceback (most recent call last):
  File "I:\wednesday.py", line 37, in <module>
    exit(mess)
SystemExit: The name of week-day in the string is incoherent with the date

With the string '2011-34-58 Monday', error is produced:

Traceback (most recent call last):
  File "I:\wednesday.py", line 33, in <module>
    exit(mess)
SystemExit: The date isn't an existing date

Edit 1

I was unsatisfied of my code: it has poor readability
The following one is more understandable

Note that in this new code, N hasn't the same meaning as the n in the former code

from time import strptime
import re
from sys import exit
from datetime import date,timedelta


dico = {'Monday':0,'monday':0,'Tuesday':1,'tuesday':1,
        'Wednesday':2,'wednesday':2,'Thursday':3,'thursday':3,
        'Friday':4,'friday':4,'Saturday':5,'saturday':5,
        'Sunday':6,'sunday':6}

regx = re.compile('((\d{4})-(\d\d)-(\d\d))\s+(%s)' % '|'.join(dico.iterkeys()))



for s in ('2011-08-10  Wednesday', '2011-08-25    Thursday', '2011-08-30  Tuesday',
          '2011-12-04 Sunday', '2011-12-30   Friday',
          '2012-02-18 Saturday', '2012-02-25 Saturday', '2012-02-29   Wednesday ',
          '2011-07-24 sunday', '2011-07-25 monday',
          '2011-10-28 friday', '2011-10-30 monday'):

    print 's == ' + s

    # Verifications ----------------------------------------------------------------
    try:
        the_date,y,m,d,day_name = regx.match(s).groups()
        wday = dico[day_name]
    except:
        mess = "The string isn't expressing a date correctly"
        exit(mess)
    else:
        try:
            s_strp = strptime(the_date,'%Y-%m-%d')
        except:
            mess = "The date isn't an existing date"
            exit(mess)
        else:
            if s_strp.tm_wday != wday:
                mess = 'The name of week-day in the string is incoherent with the date'
                exit(mess)

    # Extraction of needed info -----------------------------------------------------
    y,m,d = map(int,(y,m,d))  # y,m,d = year,month,day
    N = (d-1)//7 + 1  # N is in (1,2,3,4,5) , it tells if the week-day is the first/2nd/3nd/4th/5th in the month 
    print '(y,m,d,day_name,wday,N) ==',(y,m,d,day_name,wday,N)
    print 'The day %s is the %sth %s in the month %s-%s\n' % (d,N,day_name,y,m)

    # Finding the desired next date ------------------------------------------------- 
    ahead = (date(y,m,d) + timedelta(weeks=i) for i in (1,2,3,4,5))
    # this generator yields the 5 next dates of same week-day name after the date 'y-m-d' because the date 'y-m-d'
    # and the date of same week-day name and same position in the next month can't be separated by more than 5 weeks

    cnt = 0
    for xd in ahead:
        cnt += (xd.month != m) # cnt is incremented only if xd is a same week-day in the next month
        if cnt in (N,4):
            # There is no couple of adjacent months in a year
            # having a given week-day name present 5 times in each of them
            # Then if N==5, cnt can't be 5
            print 'The %sth %s %s in the next month is on date %s' % (cnt,day_name,'(the last)' if N==5 else '',xd)
            break
    print '\n-------------------------------------------------------------'

Edit 2

Aaaaaaaaah ! I was having the feeling that the result could be found in very few lines and I finally got it.
No need of dateutil, no need of my complicated preceding solutions, the following code does the job in 4 lines !

import re
from sys import exit
from datetime import date,timedelta
from time import strptime


dico = {'Monday':0,'monday':0,'Tuesday':1,'tuesday':1,
        'Wednesday':2,'wednesday':2,'Thursday':3,'thursday':3,
        'Friday':4,'friday':4,'Saturday':5,'saturday':5,
        'Sunday':6,'sunday':6}

regx = re.compile('((\d{4})-(\d\d)-(\d\d))\s+(%s)' % '|'.join(dico.iterkeys()))



for s in ('2011-08-10  Wednesday', '2011-08-25    Thursday', '2011-08-30  Tuesday',
          '2011-12-04 Sunday', '2011-12-30   Friday',
          '2012-02-18 Saturday', '2012-02-25 Saturday', '2012-02-29   Wednesday ',
          '2011-07-24 sunday', '2011-07-25 monday',
          '2011-10-28 friday', '2011-10-30 monday'):

    print 's == ' + s

    # --- Verifications ----------------------------------------------------------------
    mess = ("The string isn't expressing a date correctly",
            "The date isn't an existing date",
            'The name of week-day in the string is incoherent with the date')   
    try:
        the_date,y,m,d,weekday_name = regx.match(s).groups()    
    except:
        exit(mess[0])
    else:
        try:
            y,m,d = map(int,(y,m,d))
            xdate = date(y,m,d)
        except:
            exit(mess[1])
        else:
            if strptime(the_date,'%Y-%m-%d').tm_wday != dico[weekday_name]:
                exit(mess[2])


    # --- Extraction of the position of the day in the month ----------------------------
    n = (d-1)//7
    # n is in (0,1,2,3,4) , it tells the position of the input day
    # among the list of same week-days in the month


    # --- Going to the first next same week-day in the next month------------------------
    while xdate.month == m:
        xdate = xdate + timedelta(weeks=1)
    # this loop makes xdate to be incremented of one week until reaching the next month


    # --- Displaying the extracted data and the result ----------------------------------
    print '(y,m,d,weekday_name,dico[weekday_name],n) ==',(y,m,d,weekday_name,dico[weekday_name],n)
    print 'The day %s is the %sth %s in the month %s-%s\n' % (d,n+1,weekday_name,y,m)
    # There is no couple of adjacent months in a year having a given week-day name
    # present 5 times (that is to say at position 4) in each of them.
    # Then if n==4, the desired next date can't be xdate + timedelta(weeks=4))
    print 'The %sth %s %s in the next month is on date %s' \
          % (min(n,3)+1,weekday_name,'(the last)' if n==4 else '',xdate + timedelta(weeks=min(n,3)))


    print '\n-------------------------------------------------------------'

Edit 3

Taking account of the remark of unutbu that found my code unclear concerning the reason why it is necessary to write timedelta(weeks=min(n,3)) , I tried to find another algorithm to avoid this min(n,3) instruction.
But I finally realized that no algorithm is able to decide itself what it must do when it is impossible to find the 5th same weekday in the next month. There's no other option for the writer of the code to be the one who realizes this case exists and to decide to take the 4th same weekday instead of the 5th.

So I kept the general principle of my code. But I changed it slightly to make more understandable the use of a weekday_appear value instead of the former n. I think that the following easily understandable code is the clearer and simpler one that can be written.

for s in ('2011-08-10  Wednesday', '2011-08-25 Thursday', '2011-08-30  Tuesday',
          '2011-12-04 Sunday', '2011-12-30   Friday',
          '2012-02-18 Saturday', '2012-02-25 Saturday', '2012-02-29 Wednesday ',
          '2011-07-24 sunday', '2011-07-25 monday',
          '2011-10-28 friday', '2011-10-30 monday'):

    print 's == ' + s

    # --- Verifications --------------------------------------------------------
    mess = ("The string isn't expressing a date correctly",
            "The date isn't an existing date",
            'The name of week-day in the string is incoherent with the date')   
    try:
        the_date,y,m,d,wkd_name = regx.match(s).groups()
        print '(y,m,d,wkd_name) ==',(y,m,d,wkd_name)
    except:
        exit(mess[0])
    else:
        try:
            y,m,d = map(int,(y,m,d))
            xdate = date(y,m,d)
        except:
            exit(mess[1])
        else:
            if xdate.weekday() != dico[wkd_name]:
                exit(mess[2])


    # --- Extraction of the number of the weekday in the month ---------------
    weekday_appear = (d+6)//7
    print 'weekday_appear == %s' % weekday_appear
    # weekday_appear is 1 if 1<=d<=7, 2 if 8<=d<=14 ... 5 if 29<=d<=31
    # It tells if the input weekday is the 1st/2nd/3rd/4th/5th
    # among the list of same weekdays in the month


    # --- Going to the last same weekday in the month-------------------------
    for deltadays in (7,14,21,28):
        try:
            xdate= date(y,m,d+deltadays)
        except:
            break
    # after this loop, xdate is the last date in the month having the same
    # weekday name as input date


    # --- Displaying the result ----------------------------------------------
    # There is no couple of adjacent months in a year in which a given weekday
    # name can be present 5 times in each of the two months .
    # Then, when it happens that wkd_appear==5 in the input month, it is sure
    # that the desired weekday in the next month can't be the 5th one in this
    # month. In the case, we will take the last of the dates with same weekday
    # name in the next month, that is to say the 4th such date.
    # That's the reason of the following reduction:
    next_appear = min(4, weekday_appear)
    # By the way, the fact that every month in a year have a minimum of 4*7
    # days is the reason why it is sure to find at least 4 times in a month
    # any weekday name, whatever which one it is.

    print 'The %sth %s %s in the next month is on date %s' \
          % (next_appear, wkd_name, ('','(the last)')[weekday_appear==5],
             xdate + timedelta(weeks=next_appear))


    print '\n-------------------------------------------------------------'


You can use a WeekOfMonth offset in Pandas.

Assuming you were starting with the second Wednesday of the first month, or any subsequent day before the second Wednesday of the following month:

import pandas as pd

#Create a starting timestamp:
d1 = pd.Timestamp('2011-08-10')

#Add on a WeekOfMonth offset for the second week of month, and the third weekday:
d2 = d1 + pd.offsets.WeekOfMonth(week=1,weekday=2)

print(f'{d2:%Y-%m-%d %A}')
>>> 2011-09-14 Wednesday

See documentation for pandas.tseries.offsets.WeekOfMonth for more options: https://pandas.pydata.org/pandas-docs/version/1.0.1/reference/api/pandas.tseries.offsets.WeekOfMonth.html


In case your current date is also second Wednesday than you can use timedelta in this case i guess:

from datetime import datetime, timedelta
d = datetime(2011,8,10)
newDate = (d + timedelta(weeks=4))# 4 weeks to next month
if newDate.weekday() == 2 and newDate.day < 8:
    newDate = (d + timedelta(days=1))        
    while newDate.weekday() != 2:
        newDate = (d + timedelta(days=1))                 

Otherwise something like this:

from datetime import datetime, timedelta

d = datetime(2011,8,10)
t = timedelta(days=1)
newDate = d+t

wedCount = 0

while wedCount != 2:
    newDate += t
    if abs(newDate.month-d.month) == 1 and newDate.weekday() == 2:
        wedCount += 1    


If you don't care about second Wednesday in current month you should find 8th day of next month and while current day is not Wednesday add one day.

#!/usr/bin/env python
import datetime                                                                 
from datetime import timedelta                                                  
origDate = datetime.date(2011,8,10)                                              
oneday = timedelta(days=1)                                                      
WEDNESDAY = 2                                                                   
nextMonth = (origDate.month % 12) + 1                                           
newDate = datetime.date(origDate.year, nextMonth, 8)                            
while newDate.weekday() != WEDNESDAY:                                           
    newDate += oneday                                                           
print newDate

which gives

2011-09-14

Last loop can be optimized out by replacing it with:

newDate += timedelta(days=(7 + WEDNESDAY - newDate.weekday()) % 7)
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜