I currently have a float value like 12.5, 4, 17.5. I want these to correspond to the times 12:30PM, 4:00AM, and 5:30PM.
I've currently achieved something close to this with the hack
if (time > 12.5) {
time = abs(roundedValue - 12);
[lab setText:[NSString stringWithFormat:#"%i:00PM",(int)time]];
} else {
[lab setText:[NSString stringWithFormat:#"%i:00AM",(int)time]];
}
But I know this is bad practice. What's a better way to convert these numbers to times?
This is just basic math, you have a value, say 12.5, which consists of a number of hours, 12, and a fraction of an hour, 0.5. There are 60 mins in an hour so the number of minutes is just the fraction times 60.
If you want to use the 12 hour clock there is a small quirk, hours > 12 need to be reduced by 12 but noon (12) is pm and midnight (0 or 24) is am. So the test for am/pm is not the same test as whether to subtract 12.
Here is one way to do it (with minimal checking):
NSString *hoursToString(double floatHours)
{
int hours = trunc(floatHours); // number of hours
int mins = round( (floatHours - hours) * 60 ); // mins is the fractional part times 60
// rounding might result in 60 mins...
if (mins == 60)
{
mins = 0;
hours++;
}
// we haven't done a range check on floatHours, also the above can add 1, so reduce to 0 -> 23
hours %= 24;
// if you are using 24 hour clock you can finish here and format to the two values
BOOL pm = hours >= 12; // 0 - 11 = am, 12 - 23 = pm
if (hours > 12) hours -= 12; // 13 - 23 -> 1 -> 11
return [NSString stringWithFormat:#"%d:%02d %s", hours, mins, (pm ? "pm" : "am")];
}
You call this simply as:
hoursToString(13.1) // returns 1:06 pm
No need to use NSDate et al.
HTH
Solved by doing the following:
NSDate *now = [NSDate date];
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
NSDateComponents *components = [calendar components:NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay fromDate:now];
[components setHour:0];
NSDate *today10am = [calendar dateFromComponents:components];
NSDate *newDate = [NSDate dateWithTimeInterval:roundedValue*60*60 sinceDate:today10am];
NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setDateFormat:#"h:mm aa"];
[lab setText:[dateFormat stringFromDate:newDate]];
Sorry Hot Licks, guess this was within my comprehension.
You can use the following -
NSNumber *time = [NSNumber numberWithDouble:([yourTime doubleValue] - 3600)];
NSTimeInterval interval = [time doubleValue];
NSDate *yourDate = [NSDate date];
yourDate = [NSDate dateWithTimeIntervalSince1970:interval];
NSDateFormatter *dateFormatter = [[[NSDateFormatter alloc] init] autorelease];
[dateFormatter setDateFormat:#"HH:mm:ss"];
NSLog(#"result: %#", [dateFormatter stringFromDate:yourDate]);
Related
I want to code an alarm clock for iOS.
My code for calculating the difference between current time and alarm time:
NSDate *date = picker.date;
NSLog(#"[date description] %#",[date description]);
NSDateFormatter *outputFormatter = [[NSDateFormatter alloc] init];
[outputFormatter setDateFormat:#"HH:mm:ss"]; //24h time format
NSString *dateString = [outputFormatter stringFromDate:picker.date];
NSLog(#"[date description] %#",dateString);
NSDate *startDate = [outputFormatter dateFromString:dateString];
NSDate *currentTime = [NSDate date];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"HH:mm:ss"];
NSString *resultString = [dateFormatter stringFromDate: currentTime];
NSDate *endDate = [dateFormatter dateFromString:resultString];
NSTimeInterval timeDifference = [endDate timeIntervalSinceDate:startDate];
double minutes = timeDifference / 60;
double hours = minutes / 60;
double seconds = timeDifference;
...which leads to these variables:
startDate = 2000-01-01 06:45:26 +0000
endDate = 2000-01-01 22:46:36 +0000
seconds = 57670 (= 16.01944 hours).
How to get to calculate the real time difference of 28740 seconds?
Just pass timeDifference in below method. It will give you Hours,Minutes and Seconds.
- (NSString *)timeFormatted:(int)totalSeconds{
int seconds = totalSeconds % 60;
int minutes = (totalSeconds / 60) % 60;
int hours = totalSeconds / 3600;
return hours==0 ? [NSString stringWithFormat:#"%02d:%02d", minutes, seconds] : [NSString stringWithFormat:#"%02d:%02d:%02d",hours, minutes, seconds];
}
try below code:
Get time difference in hour, minutes,seconds from NSTimeInterval:
//.......
NSTimeInterval timeDifference = [endDate timeIntervalSinceDate:startDate];
long ti = lroundf(timeInterval);
int hour = ti / 3600;
int mins = (ti % 3600) / 60;
int secs = ti % 60;
OR
Get time difference in hour, minutes,seconds from Dates:
// Get the system calendar
NSCalendar *objCalendar = [NSCalendar currentCalendar];
// Create the NSDates
NSDate *currentTime = [[NSDate alloc] init];
NSDate *startDate = picker.date;
// Get conversion to months, days, hours, minutes
unsigned int unitFlags = NSHourCalendarUnit | NSMinuteCalendarUnit | NSDayCalendarUnit | NSMonthCalendarUnit;
NSDateComponents *TimeInfo = [objCalendar components:unitFlags fromDate: currentTime toDate:startDate options:0];
NSLog(#" %dmin %dhours %ddays %dmoths ",[TimeInfo minute], [TimeInfo hour], [TimeInfo day], [TimeInfo month]);
How can I calculate the first date later than now from a date in the past by repeating a certain interval? For example:
// Date in the past
NSDate *pastDate = [NSDate my_dateWithString:#"11/09/2001"];
// Time interval
NSTimeInterval repeatInterval = 14 * 24 * 60 * 60; // 2 weeks
// Current date
NSDate *now = [NSDate date]; // 23/07/2015
// Start calculating next date ->
NSDate *nextDate = /* interval added to past date */
// Is result later than now?
// No? Calculate again
// Abracadabra
NSLog(#"nexDate = %#",nextDate); // 28/07/2015
I don't want to use iterations. I'm concerned about calculating a case like a start date one year in the past and a repeat interval of a day.
Here is my solution, but it has iterations.
NSDateComponents *twoWeeksDateComponents = [NSDateComponents new];
twoWeeksDateComponents.weekOfMonth = 2;
NSDate *date = self.picker.date;
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDate *now = [NSDate date];
NSDate *nextDate = date;
NSComparisonResult result = [nextDate compare:now];
while (result == NSOrderedAscending) {
nextDate = [calendar dateByAddingComponents:kTwoWeeksDateComponents
toDate:nextDate
options:NSCalendarMatchNextTime];
result = [nextDate compare:now];
}
NSLog(#"nexDate = %#",nextDate); // 28/07/2015
You can indeed do this without iteration, using a little arithmetic instead. You're looking for the nearest multiple of some factor f strictly greater than another value n. It's a bit more complicated being a calendrical calculation, but still just arithmetic (and NSCalendar of course does all the heavy lifting for you -- e.g., leap years).
NSDate * pastDate = ...;
NSDate * now = [NSDate date];
Get your repeat interval however you like from the user and construct an NSDateComponents representing it:
NSCalendarUnit repeatUnit = NSCalendarUnitWeekOfYear;
NSUInteger repeatInterval = 2;
NSDateComponents * repeatComps = [NSDateComponents new];
[repeatComps setValue:repeatInterval forComponent:repeatUnit];
Given the repeat unit, find the amount of that unit that occurs between the two dates, as another date components object:
NSCalendar * cal = [NSCalendar currentCalendar];
NSDateComponents * deltaComps = [cal components:repeatUnit
fromDate:pastDate
toDate:now
options:0];
NSInteger deltaVal = [deltaComps valueForComponent:repeatUnit];
Now comes the arithmetical bit. Round the delta down to the nearest multiple of the repeat interval. Then adding that rounded value (using the appropriate unit) will produce a date equal to or earlier than now.
Now, as jawboxer has pointed out, for a repeatUnit of NSCalendarUnitMonth, you get unexpected results if you calculate the date using that value and afterwards add one more repeat interval (as the original version of this answer did). Instead, add one to the number of repeats immediately; then the calendar correctly handles month increments.
NSInteger repeatsJustPastNow = 1 + deltaVal - (deltaVal % repeatInterval);
NSDateComponents * toNowComps = [NSDateComponents new];
[toNowComps setValue:repeatsJustPastNow
forComponent:repeatUnit];
NSDate * nextDate;
nextDate = [cal dateByAddingComponents:toNowComps
toDate:pastDate
options:0];
Jawboxer's Swift version of this corrected code is on Github.
EDIT: As Josh pointed out this doesn't account for leap years. So this is a good example of an implementation that might seem to work over short term testing but has holes in it. It also will be off by some hours in the log as dateFromString: will compensate for timezone while the log will log it as GMT.
NSDateFormatter* format = [[NSDateFormatter alloc] init];
format.dateFormat = #"yyyy-MM-dd 'at' HH:mm";
NSDate *pastDate = [format dateFromString: #"2001-09-11 at 00:00"];
NSTimeInterval repeatInterval = 14 * 24 * 60 * 60; // 2 weeks
NSDate *nowDate = [NSDate date];
NSTimeInterval timeFromPastDate = [nowDate timeIntervalSinceDate: pastDate];
NSTimeInterval modulus = timeFromPastDate / repeatInterval;
modulus -= floor(modulus);
NSDate *nextDate = [NSDate dateWithTimeInterval: repeatInterval * (1 - modulus) sinceDate: nowDate];
NSLog(#"nexDate = %#",nextDate); // 28th/July/2015, if current date is 23rd/July/2015
I made another solution early.
NSDate *date = ...;
static NSTimeInterval k2WeeksInterval = 14 * 24 * 60 * 60.0;
NSTimeInterval timeInterval = -date.timeIntervalSinceNow;
double repeats = ceil(timeInterval / k2WeeksInterval);
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *components = [NSDateComponents new];
components.weekOfYear = repeats * 2;
NSDate *nextDate = [calendar dateByAddingComponents:components
toDate:date
options:NSCalendarMatchNextTime];
i must check if the interval between 2 times is > 0 then set the text of a label but i get "Expected expression" when i use the if statement..
NSDate * now = [NSDate date];
NSDateFormatter *outputFormatter = [[NSDateFormatter alloc] init];
[outputFormatter setDateFormat:#"HH:mm:ss"];
NSString *newDateString = [outputFormatter stringFromDate:now];
NSDateFormatter *df = [[NSDateFormatter alloc] init];
[df setDateFormat:#"HH:mm:ss"];
NSDate *date1 = [df dateFromString:newDateString];
NSDate *date2 = [df dateFromString:#"15:00:00"];
NSTimeInterval interval = [date2 timeIntervalSinceDate:date1];
int hours = (int)interval / 3600; // integer division to get the hours part
int minutes = (interval - (hours*3600)) / 60; // interval minus hours part (in seconds) divided by 60 yields minutes
int seconds = interval - ((hours*3600)+(minutes*60)) ;
NSString *timeDiff = [NSString stringWithFormat:#"%02d | %02d | %02d", hours, minutes, seconds];
if (interval < 0)
{
self.before3.text = timeDiff;
}
The error is on if (interval < 0)
Can someone help me please?
Regards.
I found the codes to calculate days difference between two dates here.
I write a method :
-(NSInteger)daysWithinEraFromDate:(NSDate *) startDate toDate:(NSDate *) endDate
{
NSCalendar *gregorian = [[NSCalendar alloc]
initWithCalendarIdentifier:NSGregorianCalendar];
NSInteger startDay=[gregorian ordinalityOfUnit:NSDayCalendarUnit
inUnit: NSEraCalendarUnit forDate:startDate];
NSInteger endDay=[gregorian ordinalityOfUnit:NSDayCalendarUnit
inUnit: NSEraCalendarUnit forDate:endDate];
return endDay-startDay;
}
This method has a problem: it can't consider the timezone thing. Even I add a line like this:
[gregorian setTimeZone:[NSTimeZone localTimeZone]];
My test code is like this:
NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setDateFormat:#"yyyy-MM-dd HH:mm:ss"];
NSString *strDate = #"2012-09-03 23:00:00";
NSDate *dateStart = [dateFormat dateFromString:strDate];
strDate = #"2012-09-04 01:00:00";
NSDate *dateEnd = [dateFormat dateFromString:strDate];
NSLog(#"Days difference between %# and %# is: %d days",[dateFormat stringFromDate:dateStart],[dateFormat stringFromDate:dateEnd],[self daysWithinEraFromDate:dateStart toDate:dateEnd]);
The result is:
Days difference between 2012-09-03 23:00:00 and 2012-09-04 01:00:00 is: 0 days
I want to get 1 day as result by the number of midnights between the two dates. My timezone is GMT +8. But this calculation is based on GMT, so I get the wrong days number. Is there anyway to solve this problem? Thank you.
Scott Lemmon's method can solve my problem. I rewrite my code like this:
-(NSInteger)daysWithinEraFromDate:(NSDate *) startDate toDate:(NSDate *) endDate
{
NSCalendar *gregorian = [[NSCalendar alloc]
initWithCalendarIdentifier:NSGregorianCalendar];
[gregorian setTimeZone:[NSTimeZone localTimeZone]];
NSDate *newDate1 = [startDate dateByAddingTimeInterval:[[NSTimeZone localTimeZone] secondsFromGMT]];
NSDate *newDate2 = [endDate dateByAddingTimeInterval:[[NSTimeZone localTimeZone] secondsFromGMT]];
NSInteger startDay=[gregorian ordinalityOfUnit:NSDayCalendarUnit
inUnit: NSEraCalendarUnit forDate:newDate1];
NSInteger endDay=[gregorian ordinalityOfUnit:NSDayCalendarUnit
inUnit: NSEraCalendarUnit forDate:newDate2];
return endDay-startDay;
}
If the time zone offset isn't working, how about just add or subtract it manually instead?
In your case NSDate *newDate = [oldDate dateByAddingTimeInterval:(-8 * 60 * 60)]; to subtract off your +8 hours.
Or if you want to find the GMT offset automatically as well then it would simply be NSDate *newDate = [oldDate dateByAddingTimeInterval:(-[[NSTimeZone localTimeZone] secondsFromGMT])
Another thought:
A perhaps easier solution would be to just disregard the time information altogether. Just set it to the same arbitrary number for both dates, then as long as the dates come from the same timezone you will always get the correct number of mid-nights between them, regardless of GMT offset.
What you really want is the NSDate method timeIntervalSinceDate:, and take that result and if it's more than 0 but less than 86400 (the number of seconds in a day), that's one day. Otherwise, divide your result by 86400 and you'll get the number of days.
The way you currently have your code, there's only 2 hours between the two days and that's why you are seeing a result of 0 and not one.
Edit - and to determine if midnight has happened, let's try this function I just wrote off the top of my head:
- (NSDate *) getMidnightDateFromDate: (NSDate *) originalDate
{
NSDateComponents *components = [[NSCalendar currentCalendar] components:NSIntegerMax fromDate:originalDate];
[components setHour:0];
[components setMinute:0];
[components setSecond:0];
NSDate *midnight = [[NSCalendar currentCalendar] dateFromComponents:components];
return(midnight);
}
- (BOOL) howManyDaysDifferenceBetween: startDate and: endDate
{
NSDate * firstMidnight = [self getMidnightDateFromDate: startDate];
NSDate * secondMidnight = [self getMidnightDateFromDate: endDate];
NSTimeInterval timeBetween = [firstMidnight timeIntervalSinceDate: secondMidnight];
NSInteger numberOfDays = (timeBetween / 86400);
return(numberOfDays);
}
which I'm basing off Dave Delong's answer to this question. No guarantees that my code will work (I didn't test it), but I think the concept is sound.
Using today as an example, how do I determine which date it was, 230 workdays ago?
I know how to do it iteratively with a while loop checking date and subtracting 1 if it's a workday, but I'm wondering if there is a better method.
Also, let's take a Sunday 1 PM as an example, and subtract 3 work days and 2 hours from that time. First, it doesn't make sense to subtract work-time from weekends. So it would have to move the time to 23:59:59 of Friday, and then subtract those 3 days and 2 hours.
If it's a Monday at 1:30 AM, and I'm subtracting 5 days and 3 work-hours from that time, then the result should be Friday 22:30 PM of 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, since that's a Sunday.
Figure out how long from the last weekend your date is, subtract that amount from both your date and your offset. Now you can divide your offset by 5 to figure out how many full weeks are in your offset, and then multiply that by 7 and subtract this new value from your date. Take your previous offset (the one you divided by 5) and mod it by 5, to get the number of remaining days. If it's greater than 0, subtract that offset + 2 (for the weekend) from your date.
Note, this assumes every single weekday is a workday. Corporate holidays tend to make that assumption invalid. If you need to handle holidays, you're in for a much tougher problem.
Update: Here's an attempt to fix David's code to actually 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'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];
}
I haven't exhaustively test this, but it's based on some category methods I use regularly. To determine how many weekdays are between date1 and date2 (assumes date1 < date2), divide the return value of this function by 24*60*60 (the number of seconds in a day).
This splits the calculation into number of days before the first weekend, number of days after the last weekend and number of days in the intervening weeks. A weekend starts on Saturday at 00:00:00 hours and ends on Sunday at 23:59:59 hours. Typically you want to avoid assuming that a day has 24 hours in it, because there may be special cases associated with daylight savings time. So I recommend using NSCalendar to calculate time intervals when this is important. But that happens on weekends, so it is not significant for this case.
There are two methods here. The first returns the NSDate end date if you provide a start date and the number of working days (weekdays) you want to extend out to. (An earlier date is returned if the number of working days is negative.) The second returns the number of seconds that correspond to number of working days (including fractional days) between two given NSDate dates.
I tried to keep calculations within a timezone, but defaulted to the system timezone. (By the way, if you want to calculate with fractional days, change the weekdays parameter to a float. Or you may want to calculate using a parameter in seconds. If so, then also change the calculation of totalInterval in the first method. You won't have to convert to seconds. All subsequent calculations in that method are done 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, 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;
}
The files for category methods on 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 category method ordinality returns the number for the day of the week of the receiver. Sunday = 1, Saturday = 7. This is used to find out how many days there are before the end of the first week and how many days there are after the beginning the last week. (Calculations are actually carried out in seconds.)
The category methods theFollowingWeekend and thePreviousWeekend return the NSDate at midnight on the Saturday morning that follows the receiver date and the NSDate one second before midnight on the Sunday that follows the receiver date. These methods assume you have already validated that the receiver date is not on the weekend. I handled that in the main methods. Look for the checks of ordinality == 1 or 7.
dateByMovingToBeginningOfDayInTimeZone: and dateByMovingToEndOfDayInTimeZone: set the hours, minutes, and seconds of the receiver date to 00:00:00 and 23:59:59 respectively. This is for delimiting weekends, which run from midnight Saturday morning to midnight Sunday night in the timezone.
Hope this helps. This was an exercise for me to become more familiar with the time and date functionality.
I'll credit Keith Lazuka and his calendar component for iPhone for the germination of this code.
Here's a screen shot of a test program user interface that uses these functions:
Here is your example, run through the first method. The items of interest are highlighted.
. For this, I made the simple modification to accept fractional days (which i mentioned above, but did not include in the code shown above)
Using info from above I have made a simple method to work out weekdays between two dates. Could not find this anywhere so I thought I'd post.
- (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;
}