Overlap of a Date Range with Other Date Ranges - objective-c

Good Evening,
I am trying to figure out how to count the number of days between date ranges by comparing the date ranges. For example, I have three given ranges:
range_1 01/01/2001 to 01/01/2002
range_2 01/02/2002 to 01/01/2003
range_3 01/02/2003 to 01/01/2004
If I compare my_date_range 12/12/2001 to 01/05/2002 with the ranges above, the result should show that between range_1 and my_date_range there are 19 days, between range_2 and my_date_range there are 5 days, and between range_3 and my_date_range there are 0 days.
In Excel this was easy, I would simply use:
=SUMPRODUCT(ISNUMBER(MATCH(ROW(INDIRECT(A1&":"&B1)),ROW(INDIRECT($C$1&":"&$D$1)),0))*1)
where A1 and B1 are the start and end dates the user enters, and C1 and D1 is one of the three date ranges. I would then use the same formula and compare A1 and B1 to the second date range, then the third.
But how is this translated into objective-c? (I am able to compare two dates and get the number of days between them.)

First you have to convert the date strings to NSDate values:
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"MM/dd/yyyy"];
NSDate *range1Start = [dateFormatter dateFromString:#"01/01/2001"];
NSDate *range1End = [dateFormatter dateFromString:#"01/01/2002"];
NSDate *userStart = [dateFormatter dateFromString:#"12/12/2001"];
NSDate *userEnd = [dateFormatter dateFromString:#"01/05/2002"];
Then you can compute the overlapping interval:
NSDate *overlapFrom = [range1Start laterDate:userStart];
NSDate *overlapTo = [range1End earlierDate:userEnd];
And finally the number of days between the start and end date of the overlapping interval:
NSInteger days;
if ([overlapFrom compare:overlapTo] > 0) {
// Date ranges do not overlap
days = 0;
} else {
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *comp = [calendar components:NSDayCalendarUnit fromDate:overlapFrom toDate:overlapTo options:0];
days = [comp day];
}
NSLog(#"%ld", (long)days);
In this example, the output is 20, because the difference between 12/12/2001 and 01/01/2002 is 20 days. You have to add 1 if both start and end day of the overlapping range should be counted.

Once you have your start/end dates as per Martin's answer, you can use the intersection(with) function on DateInterval:
let interval1 = DateInterval(start: start1, end: end1)
let interval2 = DateInterval(start: start2, end: end2)
let intersection = interval1.insection(with: interval2)
Also helpful if you just want to check for an overlap is interval1.intersects(interval2).

Related

Why is Excel serial date-time to NSDate conversion two days, one hour and 40 minutes ahead?

I'm converting dates from an Excel spreadsheet to NSDate's, but for some reason they always come out two days ahead: Sundays come out as Tuesdays, etc.
My conversion method is based on the following info from cpearson.com:
Excel stores dates and times as a number representing the number of
days since 1900-Jan-0, plus a fractional portion of a 24 hour day:
ddddd.tttttt . This is called a serial date, or serial date-time.
(...) The integer portion of the number, ddddd, represents the number
of days since 1900-Jan-0. (...) The fractional portion of the number,
ttttt, represents the fractional portion of a 24 hour day. For
example, 6:00 AM is stored as 0.25, or 25% of a 24 hour day.
Similarly, 6PM is stored at 0.75, or 75% percent of a 24 hour day.
- (NSDate *)dateFromExcelSerialDate:(double)serialdate
{
if (serialdate == 0)
return nil;
NSTimeInterval theTimeInterval;
NSInteger numberOfSecondsInOneDay = 86400;
double integral;
double fractional = modf(serialdate, &integral);
NSLog(#"%# %# \r serialdate = %f, integral = %f, fractional = %f",
[self class], NSStringFromSelector(_cmd),
serialdate, integral, fractional);
theTimeInterval = integral * numberOfSecondsInOneDay; //number of days
if (fractional > 0) {
theTimeInterval += numberOfSecondsInOneDay / fractional; //portion of one day
}
NSCalendar *nl_gregorianCalendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
NSTimeZone *nl_timezone = [[NSTimeZone alloc] initWithName:#"Europe/Amsterdam"];
[nl_gregorianCalendar setTimeZone:nl_timezone];
NSDateComponents *excelBaseDateComps = [[NSDateComponents alloc] init];
[excelBaseDateComps setMonth:1];
[excelBaseDateComps setDay:1];
[excelBaseDateComps setHour:00];
[excelBaseDateComps setMinute:00];
[excelBaseDateComps setTimeZone:nl_timezone];
[excelBaseDateComps setYear:1900];
NSDate *excelBaseDate = [nl_gregorianCalendar dateFromComponents:excelBaseDateComps];
NSDate *inputDate = [NSDate dateWithTimeInterval:theTimeInterval sinceDate:excelBaseDate];
NSLog(#"%# %# \r serialdate %f, theTimeInterval = %f \r inputDate = %#",
[self class], NSStringFromSelector(_cmd),
serialdate, theTimeInterval,
[self.nl_dateFormatter stringFromDate:inputDate]);
return inputDate;
}
The spreadsheet was produced in the Netherlands, presumably on a Dutch version of Microsoft Excel.
Spreadsheet date Sunday July 6, 2014 00:00 yields the following results:
dateFromExcelSerialDate:
serialdate = 41826.000000, integral = 41826.000000, fractional =
0.000000 theTimeInterval = 3613766400.000000 inputDate = 08 jul. 2014 01:40
Similarly, Sunday July 13, 2014 00:00 yields:
serialdate = 41833.000000, integral = 41833.000000, fractional =
0.000000 theTimeInterval = 3614371200.000000 inputDate = 15 jul. 2014 01:40
I can correct the output by subtracting 2 days, one hour and 40 minutes:
theTimeInterval -= ((60 * 60 * 24 * 2) + (60*60) + (60*40));
but I have no idea how robust that is.
That difference of two days made me think it had something to do with leap year corrections, so I tried to let the calendar do the calculations by adding the NSTimeInterval seconds to the excelBaseDate, like so:
NSDateComponents *comps = [[NSDateComponents alloc] init];
[comps setSecond:theInterval];
NSDate *inputDate = [nl_gregorianCalendar dateByAddingComponents:comps
toDate:excelBaseDate
options:0];
Strangely enough, that gave me dates somewhere in the 1870's. Who knows what is going on?
there are two things here:
your start date is 1900-Jan-1 but your referred description clearly says: the reference is 1900-Jan-0 – you may add an extra day here;
year 1900 was not a leap-year – you may add an extra day here;
I guess, this is pretty much the reason why you get two extra days every occasion.
Microsoft knows about that, see more about the topic here.

Error with how NSCalendar dateByAddingComponents handles Daylight Savings

I'm having a hard time understanding an exception to how dateByAddingComponents handles Daylight Savings. I've read through the Apple Date and Time Programming Guide and expected dateByAddingComponents to take into account DST changes. However, on the date of the DST change, its not working for me.
Here's the code:
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
[gregorian setTimeZone:[NSTimeZone localTimeZone]];
NSDateComponents *midnight = [gregorian components:(NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit) fromDate:self.currentDate];
midnight.hour = 0;
midnight.minute = 0;
midnight.second = 0;
NSDate *startDate = [gregorian dateFromComponents:midnight];
NSDateComponents *offSetComponents = [[NSDateComponents alloc] init];
[offSetComponents setDay:1];
NSDate *endDate = [gregorian dateByAddingComponents:offSetComponents toDate:startDate options:0];
//Calculate start time from config (hours/min from seconds)
int startTimeInMinutes = self.club.clubConfiguration.startTime.integerValue;
int startTimeHours = startTimeInMinutes / 60;
int startTimeMins = startTimeInMinutes % 60;
NSLog(#"---- startTimeHours %i", startTimeHours);
NSLog(#"---- startTImeMins %i", startTimeMins);
NSDateComponents *offSetComponents2 = [[NSDateComponents alloc] init];
[offSetComponents2 setHour:startTimeHours];
[offSetComponents2 setMinute:startTimeMins];
NSDate *firstTeeTime = [gregorian dateByAddingComponents:offSetComponents2 toDate:startDate options:0];
Explanation:
I'm getting a startTimeInMinutes from the server that I use to calculate the firstTeeTime. For example, I'm expecting to add 6 hours to the startDate (12am in my use case) and get 6am (localTimeZone).
Using dateByAddingComponents works both before and after the DST change however, on the day of DST change Sunday Nov 3, I'm getting 5am.
Theory: Since there are actually 2 2am's on Sunday Nov 3rd, I may have to account for that? If thats the case, I'd have to write some logic to account for the actual day of DST change and add an offset if appropriate using daylightSavingTimeOffsetForDate.
What am I missing???
EDIT: Ok, I decided to work around the issue by determining if today was the DST change and add/remove an hour offset. Feels kinda like I'm missing something here about NSDate however, it works. Hope this helps someone else out there scratching their heads all morning.
Work Around Code:
////// Work around for DST
NSTimeZone *currentZone = [gregorian timeZone];
NSDate *dstTransitionDate = [currentZone nextDaylightSavingTimeTransitionAfterDate:startDate];
NSTimeInterval dstOffset = [currentZone daylightSavingTimeOffsetForDate:endDate];
NSDateComponents *startDateComponents = [gregorian components:(NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit) fromDate:startDate];
NSDateComponents *dstTransitionDateComponents = [gregorian components:(NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit) fromDate:dstTransitionDate];
int offset = 0;
if ( [startDateComponents year] == [dstTransitionDateComponents year] &&
[startDateComponents month] == [dstTransitionDateComponents month] &&
[startDateComponents day] == [dstTransitionDateComponents day])
{
if (dstOffset > 0){
offset = -1;
} else {
offset = 1;
}
}
//////
I totally sympathize with you, as I have fallen into the same trap. First, I scratched my head at the results and docs, then attempted a fool's errand by attempting to roll out a custom "adding date components" logic.
Then, came across your answer, which worked beautifully, and my tests finally pass:
// assert!
XCTAssertEqual(dateFormatter.stringFromDate(dstSwitch.date), "11/1/15, 12:00 AM")
// Offseting the date should just work
do {
let dstOffset = dstSwitch + 3.hours
XCTAssertEqual(dateFormatter.stringFromDate(dstOffset.date), "11/1/15, 3:00 AM")
}
... But hang on! Words of wisdom from #RobNapier set me on the right path ... We are adding 6 hours, which means a person at the event will only spend six hours of their life. If the start date is midnight, they will adjust their watches at 2 AM back to 1 AM, and therefor spend the lost hour in oblivion according to the calendar.
So... If the event truly starts at midnight, and ends at 6 AM, heed Rob's words and don't use the duration. Use the exact date components. Because that duration is actually 7 hours.
For fun, I came to that realization when my tests bothered me. Why should I only add the offset on the same day? Why does changing days "just works"? ... Liiiight bulb.
And, btw, my test are for a library I am rolling out that should end the short comings of all date calculation and end the confusion .. With really concise syntax for swift. Will be available as Datez, part of Kitz.

How to get the day of week from a given number

I want to have the day of week name for a given number, here is the pseudo-code :
getDayStringForInt:0 = sunday
getDayStringForInt:1 = monday
getDayStringForInt:2 = tuesday
getDayStringForInt:3 = wenesday
getDayStringForInt:4 = thursday
getDayStringForInt:5 = friday
getDayStringForInt:6 = saturday
I have tried with the follow code, but some thing is not working ...
- (void) setPeriodicityDayOfWeek:(NSNumber *)dayOfWeek{
gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
dateFormatter = [[NSDateFormatter alloc] init];
NSLocale *frLocale = [[NSLocale alloc] initWithLocaleIdentifier:#"en_US"];
[dateFormatter setLocale:frLocale];
[gregorian setLocale:frLocale];
NSDate *today = [NSDate date];
NSDateComponents *nowComponents = [gregorian components:NSYearCalendarUnit | NSWeekCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit fromDate:today];
[nowComponents setWeekday:dayOfWeek];
NSDate *alertDate = [gregorian dateFromComponents:nowComponents];
[dateFormatter setDateFormat:#"EEEE"];
NSLog(#"Day Of Week : %# - Periodicity : %#", dayOfWeek, [dateFormatter stringFromDate:alertDate]);
alert.periodicity = [dateFormatter stringFromDate:alertDate];
}
My log is very strange :
Day Of Week : 0 - Periodicity : monday
Day Of Week : 1 - Periodicity : wenesday
Day Of Week : 2 - Periodicity : friday
Day Of Week : 3 - Periodicity : friday
Day Of Week : 4 - Periodicity : tuesday
Day Of Week : 5 - Periodicity : sunday
Day Of Week : 6 - Periodicity : sunday
Any idea ? any better solution ...
Since this has become the accepted answer, I'll post the "right" solution here too. Credits to Rob's answer.
The whole thing can simply be achieved using the [shortWeekdaySymbols][1] method of NSDateFormatter, so the full solution boils down to
- (NSString *)stringFromWeekday:(NSInteger)weekday {
NSDateFormatter * dateFormatter = [NSDateFormatter new];
dateFormatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:#"en_US"];
return dateFormatter.shortWeekdaySymbols[weekday];
}
Original answer
Beware, you're passing a pointer to NSNumber to a method that requires a NSInteger.
The compiler is not warning you since a pointer is indeed an integer, just not the one you would expect.
Consider this simple test:
- (void)foo:(NSInteger)a {
NSLog(#"%i", a);
}
- (void)yourMethod {
[self foo:#1]; // #1 is the boxed expression for [NSNumber numberWithInt:1]
}
This prints something like 185035664, which is the pointer value, i.e. NSNumber * when cast to NSInteger.
You should either use [dayOfWeek integerValue] or directly turn dayOfWeek into a NSInteger in your method signature.
Also I think you're getting something else wrong: from the doc of setWeekday:
Sets the number of weekday units for the receiver. Weekday units are
the numbers 1 through n, where n is the number of days in the week.
For example, in the Gregorian calendar, n is 7 and Sunday is
represented by 1.
Sunday is 1, so you'd better check the correspondence with your representation too.
See https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSDateFormatter_Class/Reference/Reference.html#//apple_ref/occ/instm/NSDateFormatter/weekdaySymbols
Simply use weekdaySymbols to retrieve the dayname.
Thanx to Every one, here is a clean response :
/**
* getting the day of week string for a given day of week number
*
* #param dayOfWeekNumber 0 return sunday, 6 return saturday
*
* #return a string corresponding at the given day of week.
*/
- (NSString*) getDayOfWeekStringForDayOfWeek:(NSInteger)dayOfWeek{
return [[dateFormatter shortWeekdaySymbols] objectAtIndex:dayOfWeek];
}

How to check if the current time is within a specified range in ios? [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Determine if current local time is between two times (ignoring the date portion)
In iOS, how can I do the following:
I have two NSDate objects that represent the opening and closing times for a store. The times within these objects are accurate but the date is unspecified (the store opens and closes at the same time regardless of the date). How can I check if the current time falls between in this time frame?
Note, if it would help for the opening and closing times to be in another format other than NSDate objects, I'm fine with that. Currently, I'm just reading in a date string such as "12:30" from a file and using date formatter to create a matching NSDate object.
Update: Note that this solution is specific to your case and assumes that store opening hours don't span two days. For example it won't work if the opening hour goes from Monday 9pm to Tuesday 10am. Since 10pm is after 9pm but not before 10am (within a day). So keep that in mind.
I cooked up a function which will tell you if the time of one date is between two other dates (it ignores the year, month and day). There's also a second helper function which gives you a new NSDate with the year, month and day components "neutralized" (eg. set to some static value).
The idea is to set the year, month and day components to be the same between all dates so that the comparison will only rely on the time.
I'm not sure if it's the most efficient approach, but it works.
- (NSDate *)dateByNeutralizingDateComponentsOfDate:(NSDate *)originalDate {
NSCalendar *gregorian = [[[NSCalendar alloc]
initWithCalendarIdentifier:NSGregorianCalendar] autorelease];
// Get the components for this date
NSDateComponents *components = [gregorian components: (NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit) fromDate: originalDate];
// Set the year, month and day to some values (the values are arbitrary)
[components setYear:2000];
[components setMonth:1];
[components setDay:1];
return [gregorian dateFromComponents:components];
}
- (BOOL)isTimeOfDate:(NSDate *)targetDate betweenStartDate:(NSDate *)startDate andEndDate:(NSDate *)endDate {
if (!targetDate || !startDate || !endDate) {
return NO;
}
// Make sure all the dates have the same date component.
NSDate *newStartDate = [self dateByNeutralizingDateComponentsOfDate:startDate];
NSDate *newEndDate = [self dateByNeutralizingDateComponentsOfDate:endDate];
NSDate *newTargetDate = [self dateByNeutralizingDateComponentsOfDate:targetDate];
// Compare the target with the start and end dates
NSComparisonResult compareTargetToStart = [newTargetDate compare:newStartDate];
NSComparisonResult compareTargetToEnd = [newTargetDate compare:newEndDate];
return (compareTargetToStart == NSOrderedDescending && compareTargetToEnd == NSOrderedAscending);
}
I used this code to test it. You can see that the year, month and days are set to some random values and don't affect the time checking.
NSDateFormatter *dateFormatter = [[[NSDateFormatter alloc] init] autorelease];
[dateFormatter setDateFormat:#"yyyy:MM:dd HH:mm:ss"];
NSDate *openingDate = [dateFormatter dateFromString:#"2012:03:12 12:30:12"];
NSDate *closingDate = [dateFormatter dateFromString:#"1983:11:01 17:12:00"];
NSDate *targetDate = [dateFormatter dateFromString:#"2034:09:24 14:15:54"];
if ([self isTimeOfDate:targetDate betweenStartDate:openingDate andEndDate:closingDate]) {
NSLog(#"TARGET IS INSIDE!");
}else {
NSLog(#"TARGET IS NOT INSIDE!");
}

Know if a date(weekday) is bettween two other dates(weekedaysº)

Im trying to know if the current day of the week + and hour is in between of 2 other weekday + hour.
Let's say, right now is "Tuesday 16:26" and there is an interval that starts with "Tuesday 16:00" and "Tuesday 22:00" so it should return YES.
Im creating dates from the previous strings, and this function tells me if it's in the interval.
This function is part of a class, whose attributes "fechaInicio" and "fechaFin" are start date and end adate respectively.
- (BOOL)dateInInterval:(NSDate *)testDate {
// date1 is the instance variable containing the starting date
// date2 is the instance variable containing the ending date
NSDateFormatter* dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setLocale:[[[NSLocale alloc] initWithLocaleIdentifier:#"en"] autorelease]];
[dateFormatter setDateFormat:#"EEEE HH:mm"];
NSString *dateString = [dateFormatter stringFromDate:self.fechaInicio];
NSLog(#"La fecha es: %#", dateString);
dateString = [dateFormatter stringFromDate:self.fechaFin];
NSLog(#"La fecha es: %#", dateString);
NSLog(#"time interval nicio: %d", [testDate timeIntervalSinceDate:self.fechaInicio]);
NSLog(#"time interval fin: %d", [testDate timeIntervalSinceDate:self.fechaFin]);
return ([testDate timeIntervalSinceDate:self.fechaInicio] > 0 &&
[testDate timeIntervalSinceDate:self.fechaFin] < 0);
}
The thing is that in never returns YES, even though I can see the date is in the interval. Im afraid how Im turning the string to date. I input "Tuesday 16:00" what year is it, what month, if I format the actual date to "EEEE HH" will it save the month and year?
Thanks
For your case, I'd advice to find some fixed known date for Monday and apply the full date for each EEEE HH:mm string. This will give correct values inside date and i think your logic will start working