Pregunta

Usando hoy como ejemplo, ¿cómo determino qué fecha fue hace 230 días de trabajo?

Sé cómo hacerlo iterativamente con una fecha de verificación de bucle de tiempo y restando 1 si es una jornada laboral, pero me pregunto si hay un método mejor.

Además, tomemos un domingo a la 1 p.m. como ejemplo, y restemos 3 días de trabajo y 2 horas a partir de ese momento. Primero, no tiene sentido restar el tiempo de trabajo de los fines de semana. Por lo tanto, tendría que trasladar el tiempo a las 23:59:59 del viernes, y luego restar esos 3 días y 2 horas.

Si es un lunes a la 1:30 a.m., y estoy restando 5 días y 3 horas de trabajo a partir de ese momento, entonces el resultado debe ser el viernes a las 22:30 p.m. de la semana anterior.


Código para probar el método de Kevin:

NSCalendar *cal = [NSCalendar currentCalendar];
NSDateComponents *dc = [[NSDateComponents new] autorelease];
dc.month = 12;
dc.day = 19;
dc.year = 2011;
dc.hour = 1;
dc.minute = 0;
dc.second = 0;

NSDate *date = [cal dateFromComponents:dc];

NSLog(@"%@", [date descriptionWithCalendarFormat:nil timeZone:nil locale:nil]);
date = dateBySubtractingWorkOffset(date, 0, 2);
NSLog(@"%@", [date descriptionWithCalendarFormat:nil timeZone:nil locale:nil]);

Registro de salida:

2011-12-02 16:33:46.878 otest[7124:707] 2011-12-19 01:00:00 -0500
2011-12-02 16:33:47.659 otest[7124:707] 2011-12-18 23:00:00 -0500

Nunca debería ser 12-18, ya que es un domingo.

¿Fue útil?

Solución

Calcule cuánto tiempo desde el último fin de semana es su cita, reste esa cantidad de su cita y su compensación. Ahora puede dividir su compensación por 5 para determinar cuántas semanas completas hay en su compensación, y luego multiplicarlo por 7 y reste este nuevo valor de su fecha. Tome su desplazamiento anterior (el que dividió por 5) y modifique por 5, para obtener el número de días restantes. Si es mayor que 0, reste esa compensación + 2 (para el fin de semana) de su cita.

Tenga en cuenta que esto supone que cada día laborable es un día de trabajo. Las vacaciones corporativas tienden a hacer que esa suposición sea inválida. Si necesita manejar las vacaciones, tiene un problema mucho más difícil.

Actualizar: Aquí hay un intento de arreglar el código de David para expresar la idea aquí:

NSDate *dateBySubtractingWorkOffset(NSDate *date, NSUInteger days, NSUInteger hours) {
    const int secsInHour = 60*60;
    const int secsInDay = 24*secsInHour;
    NSTimeInterval offset = days*secsInDay + hours*secsInHour;
    NSCalendar *cal = [[[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar] autorelease];

    // figure out distance from last weekend
    {
        NSUInteger units = NSYearCalendarUnit|NSMonthCalendarUnit|NSDayCalendarUnit|NSWeekdayCalendarUnit;
        NSDateComponents *dc = [cal components:units fromDate:date];
        if (dc.weekday == 1 || dc.weekday == 7) {
            // we're in the weekend already. Let's just back up until friday
            // and then we can start our calculations there
        } else {
            // figure out our offset from sunday 23:59:59
            dc.day -= (dc.weekday - 1);
            dc.weekday = 1;
            dc.hour = 23;
            dc.minute = 23;
            dc.second = 23;
            NSDate *sunday = [cal dateFromComponents:dc];
            NSTimeInterval newOffset = [date timeIntervalSinceDate:sunday];
            if (offset < newOffset) {
                // our offset doesn't even go back to sunday, we don't need any calculations
                return [date dateByAddingTimeInterval:-offset];
            }
            offset -= [date timeIntervalSinceDate:sunday];
            // Now we can jump back to Friday with our new offset
        }
        // Calculate last friday at 23:59:59
        dc.day -= (dc.weekday % 7 + 1);
        dc.hour = 23;
        dc.minute = 59;
        dc.second = 59;
        date = [cal dateFromComponents:dc];
    }

    // We're now set to Friday 23:59:59
    // Lets figure out how many weeks we have
    int secsInWorkWeek = 5*secsInDay;
    NSInteger weeks = (NSInteger)trunc(offset / secsInWorkWeek);
    offset -= weeks*secsInWorkWeek;
    if (weeks > 0) {
        // subtract that many weeks from the date
        NSDateComponents *dc = [[NSDateComponents alloc] init];
        dc.week = -weeks;
        date = [cal dateByAddingComponents:dc toDate:date options:0];
        [dc release];
    }
    // now we can just subtract our remaining offset from the date
    return [date dateByAddingTimeInterval:-offset];
}

Otros consejos

No lo he probado exhaustivamente, pero se basa en algunos métodos de categoría que uso regularmente. Para determinar cuántos días laborables se encuentran entre date1 y date2 (supone date1 <date2), divida el valor de devolución de esta función en 24*60*60 (el número de segundos en un día).

Esto divide el cálculo en el número de días antes del primer fin de semana, el número de días después del último fin de semana y el número de días en las semanas intermedias. Un fin de semana comienza el sábado a las 00:00:00 horas y termina el domingo a las 23:59:59 horas. Por lo general, desea evitar asumir que un día tiene 24 horas, porque puede haber casos especiales asociados con el tiempo de ahorro para el día. Por lo tanto, recomiendo usar NSCALENDAR para calcular los intervalos de tiempo cuando esto sea importante. Pero eso sucede los fines de semana, por lo que no es significativo para este caso.

Hay dos métodos aquí. El primero devuelve la fecha de finalización de NSDATE si proporciona una fecha de inicio y la cantidad de días hábiles (días laborables) que desea extender. (Se devuelve una fecha anterior si el número de días hábiles es negativo). El segundo devuelve el número de segundos que corresponden al número de días hábiles (incluidos los días fraccionales) entre dos fechas NSDate dadas.

Traté de mantener los cálculos dentro de una zona horaria, pero predeterminé la zona horaria del sistema. (Por cierto, si desea calcular con días fraccionales, cambie el weekdays parámetro a un flotador. O es posible que desee calcular usando un parámetro en segundos. Si es así, también cambie el cálculo de TotalInterval en el primer método. No tendrá que convertir a segundos. Todos los cálculos posteriores en ese método se realizan en segundos).

- (NSDate*) calculateWeekDaysEndDateFrom:(NSDate*)_date1 and:(int)weekdays {

NSTimeInterval dayInterval = 24*60*60;

NSTimeInterval totalInterval = dayInterval * (float) weekdays;
NSTimeInterval secondsBeforeWeekend;
NSTimeInterval secondsAfterWeekend;
NSTimeInterval secondsInInterveningWeeks;
int numberOfWeeks;

NSDate *dateOfFirstSaturdayMorning;
NSDate *dateOfLastSundayNight;

NSDate *finalDate;

if (weekdays >0) {
    dateOfFirstSaturdayMorning = [_date1 theFollowingWeekend];
    secondsBeforeWeekend = [dateOfFirstSaturdayMorning timeIntervalSinceDate:_date1];  

    numberOfWeeks = (int)((totalInterval - secondsBeforeWeekend)/(5.0 * dayInterval));
    secondsInInterveningWeeks = 5 * (float)(numberOfWeeks * dayInterval);
    secondsAfterWeekend = totalInterval - secondsBeforeWeekend - secondsInInterveningWeeks;

    dateOfLastSundayNight = [[dateOfFirstSaturdayMorning dateByAddingDays:7*numberOfWeeks+2] dateByAddingTimeInterval:-1]; // move from saturday morning to monday morning, then back off 1 second

    finalDate = [dateOfLastSundayNight dateByAddingTimeInterval:secondsAfterWeekend];
}
else {
    dateOfLastSundayNight = [_date1 thePreviousWeekend];
    secondsAfterWeekend = [date1 timeIntervalSinceDate:dateOfLastSundayNight];

    numberOfWeeks = (int)((-totalInterval - secondsAfterWeekend)/(5.0 * dayInterval));
    secondsInInterveningWeeks = 5 * (float)(numberOfWeeks * dayInterval);
    dateOfFirstSaturdayMorning = [[dateOfLastSundayNight dateByAddingDays:-(7*numberOfWeeks+2)] dateByAddingTimeInterval:+1];
    secondsBeforeWeekend = -totalInterval - secondsInInterveningWeeks - secondsAfterWeekend;

    finalDate = [dateOfFirstSaturdayMorning dateByAddingTimeInterval:-secondsBeforeWeekend];
}

    NSLog(@"dateOfFirstSaturdayMorning = %@", [dateOfFirstSaturdayMorning descriptionWithLocale:[NSLocale currentLocale]]);
    NSLog(@"dateOfLastSundayNight = %@",[dateOfLastSundayNight descriptionWithLocale:[NSLocale currentLocale]]);

    NSLog(@"date 1 = %@", date1);
    NSLog (@"daysBeforeWeekend = %.2f", secondsBeforeWeekend/((float)dayInterval));
    NSLog (@"daysBetweenWeekends = %.2f", secondsInInterveningWeeks/((float)(dayInterval)));
    NSLog (@"daysAfterWeekend = %.2f", secondsAfterWeekend/((float)dayInterval));
    NSLog (@"numberOfWeekdays = %.2f", (secondsBeforeWeekend + secondsInInterveningWeeks + secondsAfterWeekend)/((float)dayInterval));

    NSLog(@"endDateFromWeekdays = %@", [finalDate descriptionWithLocale:[NSLocale currentLocale]]);

    return finalDate;

}

- (NSTimeInterval) calculateWeekdaysFrom:(NSDate*)_date1 and:(NSDate*)_date2 {
    if (_date1 && _date2) {

        NSTimeInterval secondsBeforeWeekend;
        NSTimeInterval secondsAfterWeekend;

        NSDate *dateOfFirstSaturdayMorning;
        NSDate *dateOfLastSundayNight;

        NSTimeInterval dayInterval = 24*60*60; // This isn't always true, e.g., if daylight savings intervenes. (But that happens on the weekend in most places.)

        // see if they are in the same week 
        if (([_date1 ordinality] < [_date2 ordinality]) && [_date2 timeIntervalSinceDate:_date1] <= 5*dayInterval) {
            return [_date2 timeIntervalSinceDate:_date1];
        }

        // time interval before a first weekend
        if ([_date1 ordinality] == 1 || [_date1 ordinality] == 7) {
            secondsBeforeWeekend = 0;
            dateOfFirstSaturdayMorning = _date1; // This is just a convenience. It's not true. But, later, rounding takes place to deal with it.
        }
        else {
            dateOfFirstSaturdayMorning = [_date1 theFollowingWeekend];
            secondsBeforeWeekend = [dateOfFirstSaturdayMorning timeIntervalSinceDate:_date1];
        }


        int ordDate2 = [_date2 ordinality];
        int ordFirstSaturday = [dateOfFirstSaturdayMorning ordinality];

        // time interval after a last weekend
        if ([_date2 ordinality] == 1 || [_date2 ordinality] == 7) {
            secondsAfterWeekend = 0;
            dateOfLastSundayNight = _date2;  // Again, this is just a convenience. It's not true.
        }
        else {
            dateOfLastSundayNight = [_date2 thePreviousWeekend];
            secondsAfterWeekend = [_date2 timeIntervalSinceDate:dateOfLastSundayNight];
        }

        NSTimeInterval intervalBetweenWeekends = [dateOfLastSundayNight timeIntervalSinceDate:dateOfFirstSaturdayMorning];

        int numberOfWeeks = (int) (intervalBetweenWeekends/(7*dayInterval));
        int secondsInInterveningWeeks = (float) (5*dayInterval*numberOfWeeks);

        NSLog(@"date 1 = %@", [_date1 descriptionWithLocale:[NSLocale currentLocale]]);
        NSLog(@"date 2 = %@", [_date2 descriptionWithLocale:[NSLocale currentLocale]]);

        NSLog(@"dateOfFirstSaturdayMorning = %@", [dateOfFirstSaturdayMorning descriptionWithLocale:[NSLocale currentLocale]]);
        NSLog(@"dateOfLastSundayNight = %@",[dateOfLastSundayNight descriptionWithLocale:[NSLocale currentLocale]]);

        NSLog (@"daysBeforeWeekend = %.2f", secondsBeforeWeekend/((float)dayInterval));
        NSLog (@"daysBetweenWeekends = %.2f", secondsInInterveningWeeks/((float)(dayInterval)));
    NSLog (@"daysAfterWeekend = %.2f", secondsAfterWeekend/((float)dayInterval));
        NSLog (@"numberOfWeekdays = %.2f", (secondsBeforeWeekend + secondsInInterveningWeeks + secondsAfterWeekend)/((float)dayInterval));
        return secondsBeforeWeekend + secondsInInterveningWeeks + secondsAfterWeekend;
   }

    else 
        return 0;
}

Los archivos para los métodos de categoría en NSDATE son NSDATE+AYUD.H

@interface NSDate (help)

+ (NSDate *) LSExtendedDateWithNaturalLanguageString:(NSString *)dateString WithFormatter:(NSDateFormatter*)dateFormatter;
- (NSUInteger)ordinality;

- (NSDate*) theFollowingWeekend;
- (NSDate *) thePreviousWeekend;

- (NSDate *) dateByAddingDays:(NSInteger) numberOfDays;

- (NSDate *) dateByMovingToBeginningOfDayInTimeZone:(NSTimeZone*)tz;
- (NSDate *) dateByMovingToEndOfDayInTimeZone:(NSTimeZone*)tz;

@end

y nsdate+ayuda.m

#import "NSDate+help.h"

@implementation NSDate (help) 


// thrown in for testing
+ (NSDate *) LSExtendedDateWithNaturalLanguageString:(NSString *)dateString WithFormatter:(NSDateFormatter*)dateFormatter{

    [dateFormatter setDateFormat:@"yyyy-MM-dd HHmm"];
    [dateFormatter setLocale:[NSLocale currentLocale]];

    //NSDate *formattedDate = [dateFormatter dateFromString:@"2008-12-3T22-11-30-123"];

    return [dateFormatter dateFromString:dateString];
}

- (NSUInteger)ordinality {
    NSCalendar *calendar = [NSCalendar currentCalendar];
    [calendar setTimeZone:[NSTimeZone systemTimeZone]];
    return [calendar ordinalityOfUnit:NSDayCalendarUnit inUnit:NSWeekCalendarUnit forDate:self];
}

- (NSDate*) theFollowingWeekend {

    NSUInteger myOrdinality = [self ordinality];
    NSDate *dateOfFollowingWeekend = [self dateByAddingDays:(7-myOrdinality)%7];

    return [dateOfFollowingWeekend dateByMovingToBeginningOfDayInTimeZone:(NSTimeZone*)nil];
}

- (NSDate *) thePreviousWeekend {
    NSUInteger myOrdinality = [self ordinality];
    NSDate *dateOfPreviousWeekend = [self dateByAddingDays:(1-myOrdinality)];

    return [dateOfPreviousWeekend dateByMovingToEndOfDayInTimeZone:(NSTimeZone*)nil];
}

- (NSDate *) dateByAddingDays:(NSInteger) numberOfDays {
    NSDateComponents *dayComponent = [[NSDateComponents alloc] init];
    dayComponent.day = numberOfDays;

    NSCalendar *theCalendar = [NSCalendar currentCalendar];
    return [theCalendar dateByAddingComponents:dayComponent toDate:self options:0];
}

- (NSDate *) dateByMovingToBeginningOfDayInTimeZone:(NSTimeZone*)tz {

    NSTimeZone *timezone;
    if (tz)
        timezone = tz;
    else
        timezone = [NSTimeZone systemTimeZone];

    unsigned int flags = NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit;
    NSDateComponents* parts = [[NSCalendar currentCalendar] components:flags fromDate:self];
    [parts setHour:0];
    [parts setMinute:0];
    [parts setSecond:0];
    NSCalendar *calendar = [NSCalendar currentCalendar];
    [calendar setTimeZone:timezone];
    return [calendar dateFromComponents:parts];
}

- (NSDate *)dateByMovingToEndOfDayInTimeZone:(NSTimeZone*)tz {

    NSTimeZone *timezone;
    if (tz)
        timezone = tz;
    else
        timezone = [NSTimeZone systemTimeZone];

        unsigned int flags = NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit;
        NSDateComponents* parts = [[NSCalendar currentCalendar] components:flags fromDate:self];
        [parts setHour:23];
        [parts setMinute:59];
        [parts setSecond:59];
        NSCalendar *calendar = [NSCalendar currentCalendar];
        [calendar setTimeZone:timezone];
        return [calendar dateFromComponents:parts];
    }

@end

El método de categoría ordinality Devuelve el número para el día de la semana del receptor. Domingo = 1, sábado = 7. Esto se usa para averiguar cuántos días hay antes del final de la primera semana y cuántos días hay después del comienzo la última semana. (Los cálculos en realidad se llevan a cabo en segundos).

Los métodos de categoría theFollowingWeekend y thePreviousWeekend Devuelve el NSDate a la medianoche del sábado por la mañana que sigue a la fecha del receptor y al NSDATE un segundo antes de la medianoche del domingo que sigue a la fecha del receptor. Estos métodos suponen que ya ha validado que la fecha del receptor no es el fin de semana. Lo manejé en los métodos principales. Busque las comprobaciones de Ordinity == 1 o 7.

dateByMovingToBeginningOfDayInTimeZone: y dateByMovingToEndOfDayInTimeZone: Establezca las horas, minutos y segundos de la fecha del receptor a las 00:00:00 y 23:59:59 respectivamente. Esto es para delimitar los fines de semana, que se extienden desde la medianoche del sábado por la mañana hasta la medianoche del domingo por la noche en la zona horaria.

Espero que esto ayude. Este fue un ejercicio para mí para familiarizarme más con la funcionalidad de tiempo y fecha.

Acreditaré Keith Lazuka y su componente calendario para iPhone para la germinación de este código.

Aquí hay una captura de pantalla de una interfaz de usuario del programa de prueba que utiliza estas funciones:enter image description here

Aquí está su ejemplo, ejecute el primer método. Se destacan los elementos de interés.enter image description here. Para esto, hice la simple modificación para aceptar días fraccionales (que mencioné anteriormente, pero no incluí en el código que se muestra arriba)

Usando información de arriba, he hecho un método simple para trabajar entre semana entre dos fechas. No pude encontrar esto en ningún lado, así que pensé en publicar.

    - (NSInteger)endDate:(NSDate *)eDate minusStartDate:(NSDate *)sDate{

    int weekDaysCount;
    weekDaysCount = 0;

    //A method that calculates how many weekdays between two dates
    //firstcompare dates to make sure end date is not in the past
    //using the NScomparisonresult and the NSDate compare: method

    NSComparisonResult result = [sDate compare:eDate];

    if (result == NSOrderedDescending) {
        eDate = sDate;
        //NSLog(@"invalid date so set to end date to start date");
    }

    //Work out the number of days btween the twodates passed in
    //first set up a gregorian calander

    NSCalendar *gregorian = [[NSCalendar alloc]
                             initWithCalendarIdentifier:NSGregorianCalendar];

    NSUInteger unitFlags = NSDayCalendarUnit;

    NSDateComponents *components = [gregorian components:unitFlags
                                                fromDate:sDate
                                                  toDate:eDate options:0];

    //get the number of days
    NSInteger days = [components day];

    //now loop through the days and only count the weekdays

    while (days > 0) {//while days are greater than 0

//        NSLog(@"days = %i", days);


        //get the weekday number of the start date
        NSDateComponents *comps = [gregorian components:NSWeekdayCalendarUnit fromDate:sDate];
//        NSLog(@"sDate %@", sDate);

        int weekday = [comps weekday];

//        NSLog(@"Comps Weekday = %i", weekday);

        //Test for a weekday - if its not a Saturday or Sunday
        if ((weekday!=7) && (weekday !=1)){

            //increase weekDays count
            weekDaysCount ++;
//            NSLog(@"weekDaysCount is %i", weekDaysCount);
//            NSLog(@"-------------------------");


        }

        //decrement the days
        days -=1;

        //increase the date so the next day can be tested
        sDate = [sDate dateByAddingTimeInterval:(60 * 60 * 24)];

    }

    return weekDaysCount;

}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top