2012-10-02 34 views
11

Estoy tratando de agregar n (enteros) días hábiles a una fecha determinada, la fecha adicional para evitar las vacaciones y los fines de semana (no está incluido en días laborables)Agregar n días hábiles a una fecha dada ignorando días festivos y fines de semana en python

+2

¿Puede mostrarnos lo que ha intentado hasta ahora y también cómo desea que funcione el código? ¿Una función?o simplemente cómo hacerlo? –

+0

tendrías que codificar las fechas de las vacaciones en (creo que de todos modos) –

Respuesta

7

fines de semana Saltarse serían bastante fácil hacer algo como esto:

import datetime 
def date_by_adding_business_days(from_date, add_days): 
    business_days_to_add = add_days 
    current_date = from_date 
    while business_days_to_add > 0: 
     current_date += datetime.timedelta(days=1) 
     weekday = current_date.weekday() 
     if weekday >= 5: # sunday = 6 
      continue 
     business_days_to_add -= 1 
    return current_date 

#demo: 
print '10 business days from today:' 
print date_by_adding_business_days(datetime.date.today(), 10) 

El problema con las vacaciones es que varían mucho por país o incluso por región, religión, etc. Sería n Haga una lista/conjunto de días festivos para su caso de uso y omítalos de manera similar. Un punto de partida puede ser el calendario que Apple publica para iCal (en el formato ics), el de Estados Unidos sería http://files.apple.com/calendars/US32Holidays.ics

Puede usar el módulo icalendar para analizar esto.

0

Esto requerirá cierto trabajo ya que no hay ningún constructo definido para las vacaciones en ninguna biblioteca (por lo que sé, al menos). Tendrá que crear su propia enumeración de esos.

La comprobación de los días de fin de semana se realiza fácilmente llamando al .weekday() < 6 en su objeto datetime.

2

No hay un atajo real para hacer esto. Pruebe este enfoque:

  1. crear una clase que tiene un método que devuelve skip(self, d)True para las fechas que deben ser omitidos.
  2. Cree un diccionario en la clase que contiene todas las vacaciones como date objects. No use datetime o similar porque las fracciones de un día lo matarán.
  3. Volver True para cualquier fecha que está en el diccionario o d.weekday() >= 5

Para añadir N días, utilice este método:

def advance(d, days): 
    delta = datetime.timedelta(1) 

    for x in range(days): 
     d = d + delta 
     while holidayHelper.skip(d): 
      d = d + delta 

    return d 
+0

Tu respuesta fue útil pero no puedo marcarla :(¡No tengo suficientes puntos de reputación! ¡Gracias! – cyberbrain

4

Gracias a base de código omz he hecho algunos pequeños cambios ... que tal vez útil para otros usuarios:

import datetime 
def date_by_adding_business_days(from_date, add_days,holidays): 
    business_days_to_add = add_days 
    current_date = from_date 
    while business_days_to_add > 0: 
     current_date += datetime.timedelta(days=1) 
     weekday = current_date.weekday() 
     if weekday >= 5: # sunday = 6 
      continue 
     if current_date in holidays: 
      continue 
     business_days_to_add -= 1 
    return current_date 

#demo: 
Holidays =[datetime.datetime(2012,10,3),datetime.datetime(2012,10,4)] 
print date_by_adding_business_days(datetime.datetime(2012,10,2), 10,Holidays) 
11

Si no les importa usar una biblioteca tercera parte dateutil entonces es práctico

from dateutil.rrule import * 
print "In 4 business days, it's", rrule(DAILY, byweekday=(MO,TU,WE,TH,FR))[4] 

también puede mirar en rruleset y el uso de .exdate() para proporcionar las vacaciones para saltar los del cálculo, y opcionalmente hay una opción cache para evitar la re-cálculo que podría ser digno de mirar en a.

+0

Parece que podría haber una extensión de 'dateutil' que proporciona este soporte: https://pypi.python.org/pypi/bdateutil/0.1 – Blairg23

+0

o pandas: https://stackoverflow.com/a/19036752/2097 – BlackShift

4

Quería una solución que no fuera O (N) y parecía un poco divertido de código de golf. Esto es lo que saqué en caso de que alguien esté interesado. Funciona para números positivos y negativos. Avísame si me perdí algo.

def add_business_days(d, business_days_to_add): 
    num_whole_weeks = business_days_to_add/5 
    extra_days  = num_whole_weeks * 2 

    first_weekday = d.weekday() 
    remainder_days = business_days_to_add % 5 

    natural_day  = first_weekday + remainder_days 
    if natural_day > 4: 
     if first_weekday == 5: 
      extra_days += 1 
     elif first_weekday != 6: 
      extra_days += 2 

    return d + timedelta(business_days_to_add + extra_days) 
+0

Sí, necesitamos algo que no sea O (N), pero ¿es eso realmente posible? ¿Cómo evitar las vacaciones en el medio? – Ethan

+1

Solución impresionante, aunque no maneja las vacaciones como el OP quería. Además, debe usar 'math.floor (business_days_to_add/5)' en la segunda línea, también funciona en Python3. – lufte

0

Espero que esto ayude. No es O(N) sino O(holidays). Además, las vacaciones solo funcionan cuando el desplazamiento es positivo.

def add_working_days(start, working_days, holidays=()): 
    """ 
    Add working_days to start start date , skipping weekends and holidays. 

    :param start: the date to start from 
    :type start: datetime.datetime|datetime.date 
    :param working_days: offset in working days you want to add (can be negative) 
    :type working_days: int 
    :param holidays: iterator of datetime.datetime of datetime.date instances 
    :type holidays: iter(datetime.date|datetime.datetime) 
    :return: the new date wroking_days date from now 
    :rtype: datetime.datetime 
    :raise: 
     ValueError if working_days < 0 and holidays 
    """ 
    assert isinstance(start, (datetime.date, datetime.datetime)), 'start should be a datetime instance' 
    assert isinstance(working_days, int) 
    if working_days < 0 and holidays: 
     raise ValueError('Holidays and a negative offset is not implemented. ') 
    if working_days == 0: 
     return start 
    # first just add the days 
    new_date = start + datetime.timedelta(working_days) 
    # now compensate for the weekends. 
    # the days is 2 times plus the amount of weeks are included in the offset added to the day of the week 
    # from the start. This compensates for adding 1 to a friday because 4+1 // 5 = 1 
    new_date += datetime.timedelta(2 * ((working_days + start.weekday()) // 5)) 
    # now compensate for the holidays 
    # process only the relevant dates so order the list and abort the handling when the holiday is no longer 
    # relevant. Check each holiday not being in a weekend, otherwise we don't mind because we skip them anyway 
    # next, if a holiday is found, just add 1 to the date, using the add_working_days function to compensate for 
    # weekends. Don't pass the holiday to avoid recursion more then 1 call deep. 
    for hday in sorted(holidays): 
     if hday < start: 
      # ignore holidays before start, we don't care 
      continue 
     if hday.weekday() > 4: 
      # skip holidays in weekends 
      continue 
     if hday <= new_date: 
      # only work with holidays up to and including the current new_date. 
      # increment using recursion to compensate for weekends 
      new_date = add_working_days(new_date, 1) 
     else: 
      break 
    return new_date 
Cuestiones relacionadas