Cálculo del día de la semana en Python: ¿qué hay de malo con este código?

Estoy tratando de calcular el día de la semana nth para una fecha determinada. Por ejemplo, debería poder calcular el tercer miércoles del mes para una fecha determinada.

He escrito 2 versiones de una función que se supone que hace eso:

from datetime import datetime, timedelta ### version 1 def nth_weekday(the_date, nth_week, week_day): temp = the_date.replace(day=1) adj = (nth_week-1)*7 + temp.weekday()-week_day return temp + timedelta(days=adj) ### version 2 def nth_weekday(the_date, nth_week, week_day): temp = the_date.replace(day=1) adj = temp.weekday()-week_day temp += timedelta(days=adj) temp += timedelta(weeks=nth_week) return temp 

Salida de consola

 # Calculate the 3rd Friday for the date 2011-08-09 x=nth_weekday(datetime(year=2011,month=8,day=9),3,4) print 'output:',x.strftime('%d%b%y') # output: 11Aug11 (Expected: '19Aug11') 

La lógica en ambas funciones es obviamente errónea, pero parece que no puedo localizar el error. ¿Alguien puede detectar el error en el código y cómo puedo corregirlo para devolver el valor correcto?

Su problema está aquí:

 adj = temp.weekday()-week_day 

En primer lugar, está restando cosas de la manera incorrecta: necesita restar el día real de la deseada, no al revés.

En segundo lugar, debe asegurarse de que el resultado de la resta no sea negativo; debe colocarse en el rango de 0-6 utilizando % 7 .

El resultado:

 adj = (week_day - temp.weekday()) % 7 

Además, en su segunda versión, debe agregar nth_week-1 semanas como lo hace en su primera versión.

Ejemplo completo:

 def nth_weekday(the_date, nth_week, week_day): temp = the_date.replace(day=1) adj = (week_day - temp.weekday()) % 7 temp += timedelta(days=adj) temp += timedelta(weeks=nth_week-1) return temp >>> nth_weekday(datetime(2011,8,9), 3, 4) datetime.datetime(2011, 8, 19, 0, 0) 

un trazador de líneas

Puede encontrar el día de la semana número uno con una línea que usa el calendario de la biblioteca estándar.

 import calendar calendar.Calendar(x).monthdatescalendar(year, month)[n][0] 

dónde:

  • x: el número entero que representa su día de la semana (0 es lunes)

  • n: la ‘n’ parte de tu pregunta

  • año, mes: los enteros año y mes

Esto devolverá un objeto datetime.date.

desglosado

Se puede descomponer de esta manera:

 calendar.Calendar(x) 

crea un objeto de calendario con días laborables a partir de su día laborable requerido.

 .monthdatescalendar(year, month) 

Devuelve todos los días calendario de ese mes.

 [n][0] 

devuelve el valor indexado 0 de la semana n (el primer día de esa semana, que comienza el día x).

por qué funciona

La razón para comenzar la semana en su día laborable requerido es que, de manera predeterminada, 0 (lunes) se usará como el primer día de la semana y si el mes comienza un miércoles, el calendario considerará la primera semana para comenzar la primera vez que ocurra Lunes (es decir, semana 2) y estarás atrasado una semana.

ejemplo

Si necesitara el tercer sábado de septiembre de 2013 (el día de vencimiento de la opción de compra de acciones de EE. UU. De ese mes), usaría lo siguiente:

 calendar.Calendar(5).monthdatescalendar(2013,9)[3][0] 

El problema con el de una sola línea con más votos es que no funciona.

Sin embargo, se puede utilizar como base para el refinamiento:

Ves esto es lo que obtienes:

 c = calendar.Calendar(calendar.SUNDAY).monthdatescalendar(2018, 7) for c2 in c: print(c2[0]) 

 2018-07-01 2018-07-08 2018-07-15 2018-07-22 2018-07-29 

 c = calendar.Calendar(calendar.SUNDAY).monthdatescalendar(2018, 8) for c2 in c: print(c2[0]) 

 2018-07-29 2018-08-05 2018-08-12 2018-08-19 2018-08-26 

Si lo piensa, está tratando de organizar los calendarios en listas anidadas para imprimir un valor de semanas a la vez. Así que los rezagados de otros meses entran en juego. Al utilizar una nueva lista de días válidos que caen en el mes, esto hace el truco.


Responder con la lista adjunta

 import calendar import datetime def get_nth_DOW_for_YY_MM(dow, yy, mm, nth) -> datetime.date: #dow - Python Cal - 6 Sun 0 Mon ... 5 Sat #nth is 1 based... -1. is ok for last. i = -1 if nth == -1 or nth == 5 else nth -1 valid_days = [] for d in calendar.Calendar(dow).monthdatescalendar(yy, mm): if d[0].month == mm: valid_days.append(d[0]) return valid_days[i] 

Así que aquí está cómo podría llamarse:

 firstSundayInJuly2018 = get_nth_DOW_for_YY_MM(calendar.SUNDAY, 2018, 7, 1) firstSundayInAugust2018 = get_nth_DOW_for_YY_MM(calendar.SUNDAY, 2018, 8, 1) print(firstSundayInJuly2018) print(firstSundayInAugust2018) 

Y aquí está la salida:

 2018-07-01 2018-08-05 

get_nth_DOW_for_YY_MM() puede ser refaccionado usando expresiones lambda de la siguiente manera:

Responder con lambda expresión refactorización

 import calendar import datetime def get_nth_DOW_for_YY_MM(dow, yy, mm, nth) -> datetime.date: #dow - Python Cal - 6 Sun 0 Mon ... 5 Sat #nth is 1 based... -1. is ok for last. i = -1 if nth == -1 or nth == 5 else nth -1 return list(filter(lambda x: x.month == mm, \ list(map(lambda x: x[0], \ calendar.Calendar(dow).monthdatescalendar(yy, mm) \ )) \ ))[i] 

La respuesta de una sola línea no parece funcionar si el día objective cae el primer día del mes. Por ejemplo, si desea el segundo viernes de cada mes, entonces el enfoque de una sola línea

 calendar.Calendar(4).monthdatescalendar(year, month)[2][0] 

para marzo de 2013 volverá el 15 de marzo de 2013, cuando debería ser el 8 de marzo de 2013. Tal vez agregue un cheque como

 if date(year, month, 1).weekday() == x: delivery_date.append(calendar.Calendar(x).monthdatescalendar(year, month)[n-1][0]) else: delivery_date.append(calendar.Calendar(x).monthdatescalendar(year, month)[n][0]) 

Alternativamente, esto funcionará para Python 2, devuelve la ocurrencia del día laborable en dicho mes, es decir, si el 16 de junio de 2018 es la entrada, luego devuelve la ocurrencia del día el 16 de junio de 2018.

Puede sustituir los enteros mes / año / fecha por cualquier cosa que desee, ahora mismo está obteniendo la entrada / fecha del sistema a través de datetime

Omita las declaraciones impresas o use el pass donde no sea necesario

 import calendar import datetime import pprint month_number = int(datetime.datetime.now().strftime('%m')) year_number = int(datetime.datetime.now().strftime('%Y')) date_number = int(datetime.datetime.now().strftime('%d')) day_ofweek = str(datetime.datetime.now().strftime('%A')) def weekday_occurance(): print "\nFinding current date here\n" for week in xrange(5): try: calendar.monthcalendar(year_number, month_number)[week].index(date_number) occurance = week + 1 print "Date %s of month %s and year %s is %s #%s in this month." % (date_number,month_number,year_number,day_ofweek,occurance) return occurance break except ValueError as e: print "The date specified is %s which is week %s" % (e,week) myocc = weekday_occurance() print myocc