2011-10-24 19 views
5

Tengo un problema relacionado con el cálculo de días hábiles en Objective-C.¿Cómo agregaría solo días hábiles a un NSDate?

Necesito agregar X días hábiles a un dado NSDate.

Por ejemplo, si tengo una fecha: Viernes 22-oct-2010, y agrego 2 días hábiles, que debería obtener: Martes 26-Oct-2010.

Gracias de antemano.

+4

salte los fines de semana y las vacaciones usted mismo. –

Respuesta

19

Hay dos partes en este

:
  • fines de semana
  • turismo

que voy a tirar de otros dos puestos para ayudarme.

Para los fines de semana, voy a necesitar saber el día de una determinada fecha de la semana. Por eso, este post viene muy bien: How to check what day of the week it is (i.e. Tues, Fri?) and compare two NSDates?

Para vacaciones, @vikingosegundo tiene una muy gran sugerencia sobre esta entrada: List of all American holidays as NSDates

En primer lugar, vamos a tratar con los fines de semana;

he envuelto la sugerencia en el post que he citado anteriormente en este pequeño función auxiliar que nos dice si una fecha es un día de la semana:

BOOL isWeekday(NSDate * date) 
{ 
    int day = [[[NSCalendar currentCalendar] components:NSWeekdayCalendarUnit fromDate:date] weekday]; 

    const int kSunday = 1; 
    const int kSaturday = 7; 

    BOOL isWeekdayResult = day != kSunday && day != kSaturday; 

    return isWeekdayResult; 
} 

Vamos a necesitar una manera de incrementar una fecha por un número determinado de días:

NSDate * addDaysToDate(NSDate * date, int days) 
{ 
    NSDateComponents * components = [[NSDateComponents alloc] init]; 
    [components setDay:days]; 

    NSDate * result = [[NSCalendar currentCalendar] dateByAddingComponents:components toDate:date options:0]; 

    [components release]; 

    return result; 
} 

necesitamos una manera de pasar por alto los fines de semana:

NSDate * ensureDateIsWeekday(NSDate * date) 
{ 
    while (!isWeekday(date)) 
    { 
     // Add one day to the date: 
     date = addDaysToDate(date, 1); 
    } 

    return date; 
} 

y necesitamos una manera de añadir un número arbitrario de días a una fecha:

NSDate * addBusinessDaysToDate(NSDate * start, int daysToAdvance) 
{ 
    NSDate * end = start; 

    for (int i = 0; i < daysToAdvance; i++) 
    { 
     // If the current date is a weekend, advance: 
     end = ensureDateIsWeekday(end); 

     // And move the date forward by one day: 
     end = addDaysToDate(end, 1); 
    } 

    // Finally, make sure we didn't end on a weekend: 
    end = ensureDateIsWeekday(end); 

    return end; 
} 
  • Nota; Hay una optimización obvia que salté, podría agregar fácilmente más de un día a la fecha actual, pero el objetivo de mi publicación es mostrarle cómo hacerlo usted mismo, y no necesariamente para llegar a la mejor manera posible. solución.

Ahora vamos a atar eso y ver lo que tenemos hasta ahora:

int main() { 

    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; 

    NSDate * start = [NSDate date]; 
    int daysToAdvance = 10; 

    NSDate * end = addBusinessDaysToDate(start, daysToAdvance); 

    NSLog(@"Result: %@", [end descriptionWithCalendarFormat:@"%Y-%m-%d" 
            timeZone:nil 
             locale:nil]); 

    [pool drain]; 

    return 0; 
} 

Por lo tanto, tenemos cubiertas fines de semana, ahora tenemos que tirar en las vacaciones.

Sacar algunos datos RSS o datos de otra fuente está definitivamente fuera del alcance de mi publicación ... así que, supongamos que tiene algunas fechas que usted sabe que son vacaciones o, de acuerdo con su calendario de trabajo, son días apagado.

Ahora, voy a hacer esto con un NSArray ... pero, una vez más, deja mucho espacio para mejorar, como mínimo debe ser ordenado. Mejor aún, algún tipo de hash configurado para búsquedas rápidas de fechas. Pero, este ejemplo debería ser suficiente para explicar el concepto. (Aquí se construye una matriz que indica que hay dos días de fiesta y tres días a partir de ahora)

NSMutableArray * holidays = [[NSMutableArray alloc] init]; 
[holidays addObject:addDaysToDate(start, 2)]; 
[holidays addObject:addDaysToDate(start, 3)]; 

Y, la puesta en práctica de este va a ser muy similar a los fines de semana. Nos aseguraremos de que el día no sea feriado. Si es así, avanzaremos al día siguiente. Por lo tanto, una colección de métodos para ayudar con lo siguiente:

BOOL isHoliday(NSDate * date, NSArray * holidays) 
{ 
    BOOL isHolidayResult = NO; 

    const unsigned kUnits = NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit; 
    NSDateComponents * components = [[NSCalendar currentCalendar] components:kUnits fromDate:date]; 

    for (int i = 0; i < [holidays count]; i++) 
    { 
     NSDate * holiday = [holidays objectAtIndex:i]; 
     NSDateComponents * holidayDateComponents = [[NSCalendar currentCalendar] components:kUnits fromDate:holiday]; 

     if ([components year] == [holidayDateComponents year] 
      && [components month] == [holidayDateComponents month] 
      && [components day] == [holidayDateComponents day]) 
      { 
       isHolidayResult = YES; 
       break; 
      } 
    } 

    return isHolidayResult; 
} 

y:

NSDate * ensureDateIsntHoliday(NSDate * date, NSArray * holidays) 
{ 
    while (isHoliday(date, holidays)) 
    { 
     // Add one day to the date: 
     date = addDaysToDate(date, 1); 
    } 

    return date; 
} 

Y, por último, hacer algunas modificaciones a nuestra función además de tener en cuenta los días festivos:

NSDate * addBusinessDaysToDate(NSDate * start, int daysToAdvance, NSArray * holidays) 
{ 
    NSDate * end = start; 

    for (int i = 0; i < daysToAdvance; i++) 
    { 
     // If the current date is a weekend, advance: 
     end = ensureDateIsWeekday(end); 

     // If the current date is a holiday, advance: 
     end = ensureDateIsntHoliday(end, holidays); 

     // And move the date forward by one day: 
     end = addDaysToDate(end, 1); 
    } 

    // Finally, make sure we didn't end on a weekend or a holiday: 
    end = ensureDateIsWeekday(end); 
    end = ensureDateIsntHoliday(end, holidays); 

    return end; 
} 

Adelante, pruébelo:

int main() { 

    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; 

    NSDate * start = [NSDate date]; 
    int daysToAdvance = 10; 

    NSMutableArray * holidays = [[NSMutableArray alloc] init]; 
    [holidays addObject:addDaysToDate(start, 2)]; 
    [holidays addObject:addDaysToDate(start, 3)]; 

    NSDate * end = addBusinessDaysToDate(start, daysToAdvance, holidays); 

    [holidays release]; 

    NSLog(@"Result: %@", [end descriptionWithCalendarFormat:@"%Y-%m-%d" 
            timeZone:nil 
             locale:nil]); 

    [pool drain]; 

    return 0; 
} 

Si quieres todo el proyecto, aquí tienes: http://snipt.org/xolnl

3

No hay nada incorporado en NSDate o NSCalendar que cuente los días hábiles para usted. Días hábiles depend to some degree on the business en cuestión. En los EE. UU., "Día hábil" generalmente significa días de la semana que no son días festivos, pero cada compañía determina qué días festivos observar y cuándo. Por ejemplo, algunas empresas cambian la celebración de festividades menores a la última semana del año para que los empleados puedan ausentarse entre Navidad y Año Nuevo sin tomar vacaciones.

Por lo tanto, tendrá que decidir exactamente qué quiere decir por día hábil. Entonces debería ser lo suficientemente simple escribir un pequeño método para calcular una fecha futura agregando algunos días hábiles. Luego use una categoría para agregar un método como -dateByAddingBusinessDays: a NSDate.

1

esta respuesta es tarde para la fiesta pero .... Pensé que podría mejorar las respuestas anteriores para determinar los días hábiles al trabajar con NSDateComponents directamente de su fecha en un agradable bucle.

#define CURRENTC [NSCalendar currentCalendar] 
#define CURRENTD [NSDate date] 

NSInteger theWeekday; 

    NSDateComponents* temporalComponents = [[NSDateComponents alloc] init]; 

[temporalComponents setCalendar:CURRENTC]; 
[temporalComponents setDay: 13]; 
[temporalComponents setMonth: 2]; 
[temporalComponents setYear: theYear]; 

// CURRENTC =the current calendar which determines things like how 
// many days in week for local, also the critical “what is a weekend” 
// you can also convert a date directly to components. but the critical thing is 
// to get the CURRENTC in, either way. 

    case 3:{ // the case of finding business days 
     NSDateComponents* startComp = [temporalComponents copy]; // start date components 

     for (int i = 1; i <= offset; i++) //offset is the number of busi days you want. 
     { 
      do { 
       [temporalComponents setDay: [temporalComponents day] + 1]; 
       NSDate* tempDate = [CURRENTC dateFromComponents:temporalComponents]; 
       theWeekday = [[CURRENTC components:NSWeekdayCalendarUnit fromDate:tempDate] weekday]; 
      } while ((theWeekday == 1) || (theWeekday == 7)); 
     } 
     [self findHolidaysStart:startComp end:temporalComponents]; // much more involved routine. 

     [startComp release]; 
     break; 
    } 

// use startComp and temporalcomponents before releasing 

// temporalComponents now contain an offset of the real number of days 
// needed to offset for busi days. startComp is just your starting date….(in components) 
// theWeekday is an integer between 1 for sunday, and 7 for saturday, (also determined 
// by CURRENTC 

convirtiéndolo de nuevo en NSDate, y listo. Las vacaciones son mucho más complicadas ... pero en realidad se pueden calcular si solo se usan vacaciones federales y algunas otras. porque siempre son algo así como "3er lunes de enero"

esto es lo que findHolidaysStart: startComp end: comienza como, se puede imaginar el resto.

// imported 

    [holidayArray addObject:[CURRENTC dateFromComponents:startComp]]; 
    [holidayArray addObject:[CURRENTC dateFromComponents:endComp]]; 


// hardcoded 

    dateComponents = [[NSDateComponents alloc] init]; 
    [dateComponents setCalendar:CURRENTC]; 
    [dateComponents setDay: 1]; 
    [dateComponents setMonth: 1]; 
    [dateComponents setYear: theYear]; 

    theWeekday = [[CURRENTC components:NSWeekdayCalendarUnit fromDate:[CURRENTC dateFromComponents:dateComponents]] weekday]; 

    if (theWeekday == 1) [dateComponents setDay:2]; 
    if (theWeekday == 7) {[dateComponents setDay:31]; [dateComponents setYear: theYear-1];} 

    [holidayArray addObject:[CURRENTC dateFromComponents:dateComponents]]; 
    [dateComponents release]; 
1

Tomé @ respuesta de Steve y añade un método para calcular los días de todos los días festivos federales en EE.UU. y ponerlo todo en una categoría. Lo he probado y funciona muy bien. Echale un vistazo.

#import "NSDate+BussinessDay.h" 

@implementation NSDate (BussinessDay) 

-(NSDate *)addBusinessDays:(int)daysToAdvance{ 
    NSDate * end = self; 
    NSArray *holidays = [self getUSHolidyas]; 
    for (int i = 0; i < daysToAdvance; i++) 
    { 
     // Move the date forward by one day: 
     end = [self addDays:1 toDate:end]; 

     // If the current date is a weekday, advance: 
     end = [self ensureDateIsWeekday:end]; 

     // If the current date is a holiday, advance: 
     end = [self ensureDateIsntHoliday:end forHolidays:holidays]; 
    } 

    return end; 
} 

#pragma mark - Bussiness Days Calculations 

-(BOOL)isWeekday:(NSDate *) date{ 
    int day = (int)[[[NSCalendar currentCalendar] components:NSWeekdayCalendarUnit fromDate:date] weekday]; 

    const int kSunday = 1; 
    const int kSaturday = 7; 

    BOOL isWeekdayResult = day != kSunday && day != kSaturday; 
    return isWeekdayResult; 
} 

-(NSDate *)addDays:(int)days toDate:(NSDate *)date{ 
    NSDateComponents * components = [[NSDateComponents alloc] init]; 
    [components setDay:days]; 

    NSDate * result = [[NSCalendar currentCalendar] dateByAddingComponents:components toDate:date options:0]; 
    return result; 
} 

-(NSDate *)ensureDateIsWeekday:(NSDate *)date{ 
    while (![self isWeekday:date]) 
    { 
     // Add one day to the date: 
     date = [self addDays:1 toDate:date]; 
    } 

    return date; 
} 

-(BOOL)isHoliday:(NSDate *)date forHolidays:(NSArray *)holidays{ 
    BOOL isHolidayResult = NO; 

    const unsigned kUnits = NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit; 
    NSDateComponents * components = [[NSCalendar currentCalendar] components:kUnits fromDate:date]; 

    for (int i = 0; i < [holidays count]; i++) 
    { 
     NSDate * holiday = [holidays objectAtIndex:i]; 
     NSDateComponents * holidayDateComponents = [[NSCalendar currentCalendar] components:kUnits fromDate:holiday]; 

     if ([components year] == [holidayDateComponents year] 
      && [components month] == [holidayDateComponents month] 
      && [components day] == [holidayDateComponents day]) 
     { 
      isHolidayResult = YES; 
      break; 
     } 
    } 

    return isHolidayResult; 
} 

-(NSDate *)ensureDateIsntHoliday:(NSDate *)date forHolidays:(NSArray *)holidays{ 
    while ([self isHoliday:date forHolidays:holidays]) 
    { 
     // Add one day to the date: 
     date = [self addDays:1 toDate:date]; 
    } 

    return date; 
} 

-(NSArray *)getUSHolidyas{ 
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; 
    formatter.dateFormat = @"yyyy"; 

    NSString *year = [formatter stringFromDate:[NSDate date]]; 
    NSString *nextYear = [formatter stringFromDate:[NSDate dateWithTimeIntervalSinceNow:(60*60*24*365)]]; 
    formatter.dateFormat = @"M/d/yyyy"; 

    //Constant Holidays 
    NSDate *newYearsDay = [formatter dateFromString:[NSString stringWithFormat:@"1/1/%@",nextYear]]; //Use next year for the case where we are adding days near end of december. 
    NSDate *indDay = [formatter dateFromString:[NSString stringWithFormat:@"7/4/%@",year]]; 
    NSDate *vetDay = [formatter dateFromString:[NSString stringWithFormat:@"11/11/%@",year]]; 
    NSDate *xmasDay = [formatter dateFromString:[NSString stringWithFormat:@"12/25/%@",year]]; 


    //Variable Holidays 
    NSInteger currentYearInt = [[[NSCalendar currentCalendar] 
           components:NSYearCalendarUnit fromDate:[NSDate date]] year]; 

    NSDate *mlkDay = [self getTheNth:3 occurrenceOfDay:2 inMonth:1 forYear:currentYearInt]; 
    NSDate *presDay = [self getTheNth:3 occurrenceOfDay:2 inMonth:2 forYear:currentYearInt]; 
    NSDate *memDay = [self getTheNth:5 occurrenceOfDay:2 inMonth:5 forYear:currentYearInt]; // Let's see if there are 5 Mondays in May 
    NSInteger month = [[[NSCalendar currentCalendar] components:NSYearCalendarUnit fromDate:memDay] month]; 
    if (month > 5) { //Check that we are still in May 
     memDay = [self getTheNth:4 occurrenceOfDay:2 inMonth:5 forYear:currentYearInt]; 
    } 
    NSDate *labDay = [self getTheNth:1 occurrenceOfDay:2 inMonth:9 forYear:currentYearInt]; 
    NSDate *colDay = [self getTheNth:2 occurrenceOfDay:2 inMonth:10 forYear:currentYearInt]; 
    NSDate *thanksDay = [self getTheNth:4 occurrenceOfDay:5 inMonth:11 forYear:currentYearInt]; 

    return @[newYearsDay,mlkDay,presDay,memDay,indDay,labDay,colDay,vetDay,thanksDay,xmasDay]; 
} 

-(NSDate *)getTheNth:(NSInteger)n occurrenceOfDay:(NSInteger)day inMonth:(NSInteger)month forYear:(NSInteger)year{ 

    NSDateComponents *dateComponents = [[NSDateComponents alloc] init]; 

    dateComponents.year = year; 
    dateComponents.month = month; 
    dateComponents.weekday = day; // sunday is 1, monday is 2, ... 
    dateComponents.weekdayOrdinal = n; // this means, the first of whatever weekday you specified 
    return [[NSCalendar currentCalendar] dateFromComponents:dateComponents]; 
} 

@end 
Cuestiones relacionadas