How to subtract duration from NSDate but not include weekend?

Using today as an example, how to determine what date was, 230 working days ago?

I know how to do this iteratively with the date of checking the while loop and subtracting 1 if it's a business day, but I'm wondering if there is a better method.

Also, let's take 1 PM as an example on Sunday and subtract 3 business days and 2 hours from now. Firstly, it makes no sense to deduct working hours from the weekend. Thus, he will have to move the time until 23:59:59 on Friday, and then subtract these 3 days and 2 hours.

If it is Monday at 1:30 in the morning, and I subtract 5 days and 3 hours of work from this time, then the result should be on Friday at 22:30 in comparison with the previous week.


Code to test Kevin's method:

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]); 

Output Log:

 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 

It should never be 12-18, as it is Sunday.

+6
source share
3 answers

Determine how long your date has been since last week, subtract this amount from your date and your offset. Now you can divide your offset by 5 to find out how many full weeks are in your offset, and then multiply this by 7 and subtract this new value from your date. Make your previous offset (the one you divided by 5) and change it to 5 to get the number of days left. If it is greater than 0, subtract this offset + 2 (on the weekend) from your date.

Note. It is assumed that each working day is a working day. Corporate holidays tend to invalidate this assumption. If you need to deal with the holidays, you will have a much more difficult problem.

Refresh . Here's an attempt to fix David's code to express the idea here:

 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 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]; } 
+4
source

I do not exhaustively test this, but it is based on some category methods that I use regularly. To determine how many weekdays are between date1 and date2 (takes date1 and lt2), divide the return value of this function by 24 * 60 * 60 (number of seconds per day).

This breaks down the calculation by the number of days before the first weekend, the number of days after the last weekend, and the number of days in the intervening weeks. Weekends begin on Saturday at 00:00:00 and end on Sunday at 23:59:59. As a rule, you want to avoid the assumption that he has 24 hours during the day, because there may be special cases associated with daylight saving time. Therefore, I recommend using NSCalendar to calculate time intervals when it matters. But this happens on weekends, so for this case it is not important.

There are two methods here. The first returns the end date of NSDate if you specify a start date and the number of working days (weekdays) that you want to extend. (An earlier date is returned if the number of working days is negative.) The second returns the number of seconds corresponding to the number of working days (including fractional days) between the two NSDate dates.

I tried to do the calculations in the time zone, but the system time zone was set by default. (By the way, if you want to calculate with fractional days, change the weekdays parameter to float. Or you can calculate using the parameter in seconds. If so, also change the calculation of totalInterval in the first way: you do not have to convert to seconds. All subsequent calculations this method executes in seconds.)

 - (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, eg, 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 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 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; } 

Files for category methods in NSDate are NSDate + help.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 

and NSDate + help.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 

The ordinality category ordinality returns the ordinality number of the recipient. Sunday = 1, Saturday = 7. This is used to find out how many days are left until the end of the first week and how many days are left after the beginning of last week. (Calculations are done in seconds.)

The methods of theFollowingWeekend and thePreviousWeekend return the NSDate at midnight Saturday morning, which follows the date of the receiver and the NSDate one second before midnight on Sunday, which follows the date of the receiver. These methods assume that you have already confirmed that the recipient’s date is not on weekends. I processed this in basic methods. Look for ordinality checks == 1 or 7.

dateByMovingToBeginningOfDayInTimeZone: and dateByMovingToEndOfDayInTimeZone: set the hours, minutes and seconds of the receiver date at 00:00:00 and 23:59:59 respectively. This is to distinguish between days off from midnight Saturday morning to midnight Sunday evening in the time zone.

Hope this helps. It was an exercise for me to better understand the functionality of time and date.

I will be lending to Keith Lazuka and its iPhone calendar component for the germination of this code.

Here is a snapshot of the user interface of the test program that uses these features: enter image description here

Here is your example, skip the first method. Items of interest are highlighted. enter image description here . To do this, I made a simple modification to accept fractional days (which I mentioned above, but did not include in the code shown above)

+2
source

Using the information above, I made a simple method for working with weekdays between two dates. I could not find it anywhere, so I thought I would send it.

  - (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; } 
0
source

Source: https://habr.com/ru/post/902715/


All Articles