For those familiar with Excel, I'm trying to use a similiar NETWORKDAYS function within Cocoa.
Can anyone help with the type of info I'll need to construct an NSDate catagory that can give me only the working days betweek two dates?
many thanks
Nik
I know that I'll give is not optimized, but it's just to give you a way to explore.
You can use the NSCalendar and the NSDateComponents like that:
// Date of today
NSDate *today = [NSDate date];
// init the gregorian calendar
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
// Retrieve the NSDateComponents for the current date
NSDateComponents *weekdayComponents = [gregorian components:(NSDayCalendarUnit | NSWeekdayCalendarUnit) fromDate:today];
// Number of the day in the week (e.g 2 = Monday)
NSInteger weekday = [weekdayComponents weekday];
(see Calendars, Date Components, and Calendar Units)
From there you start from your first date and you iterate this for each day until your end date and using the weekday you can determine if the day is during a weekend or not. (I repeated that's not optimized but it's just a track)
Related
This question already has answers here:
Current Week Start and End Date
(18 answers)
Closed 6 years ago.
please have a look to following lines of code:
NSCalendar *calendar = [NSCalendar currentCalendar];
NSInteger weekNumber = [[calendar components: NSWeekCalendarUnit fromDate:date] week];
now how we can get the first and last day of the given week number (assume that the week starts from monday to sunday).
also if it possible to get the array of all the dates belongs to the same week number.
To get the first day of the week the following code can be used.
NSDate *now = [NSDate date];
NSCalendar *calendar = [NSCalendar currentCalendar];
NSInteger weekNumber = [[calendar components: NSWeekCalendarUnit fromDate:now] week];
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *comp = [gregorian components:NSYearCalendarUnit fromDate:now];
[comp setWeek:weekNumber]; //Week number.
[comp setWeekday:1]; //First day of the week. Change it to 7 to get the last date of the week
NSDate *resultDate = [gregorian dateFromComponents:comp];
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:#"MMM d, yyyy"];
NSString *myDateString = [formatter stringFromDate:resultDate];
NSLog(#"%#",myDateString);
as commented in the above code change the parameter of setWeekday method to 7 to get the last day of the week.
Once u have the first day and last day of the week u can get all the dates as mentioned in the below link.
Array for dates between two dates
Note: The week starts from Monday for the region setting UK.
To change the iPhone Calendar to show Monday as the first day of the week.
1. tap "Settings"
2. tap "General"
3. tap "International"
4. tap "Region Format"
5. select "united kingdom"
So i try to get this weeks date range.
When i get current date components (only year and date) these are the values:
I noticed that there is "Obsolescent" written near week. Did try searching apple docs but found nothing about this.
Then i try to get the NSDate with current calendar (or gregorian calendar, same value) but the value i get is:
So weeks got ignored. So i added few months and one week to my components and had such components:
Then i tried to get my date out of this, this is what i got:
Now clearly week components are getting ignored here. Why is that so? Is it deprecated? Or is it some bug?
So i found the solution finally.
It appears that it all sorts out (starts working) if you just set weekday:
[startDate setWeekday:1];
Then the date with week is returned correctly.
Do not know if it is still a bug when week components get ignored without setting weekday.
To compute the start end date of the a week, the rangeOfUnit:startDate:interval:forDate: method of NSCalendar is quite useful:
NSDate *now = [NSDate date];
NSDate *startOfWeek;
NSTimeInterval length;
[[NSCalendar currentCalendar] rangeOfUnit:NSWeekCalendarUnit
startDate:&startOfWeek
interval:&length
forDate:now];
NSDate *endOfWeek = [startOfWeek dateByAddingTimeInterval:length];
NSLog(#"%#", startOfWeek);
NSLog(#"%#", endOfWeek);
To compute the same date a week before, use dateByAddingComponents:
NSDate *now = [NSDate date];
NSDateComponents *comp = [[NSDateComponents alloc] init];
[comp setWeek:-1]; // One week back in time
NSDate *date = [[NSCalendar currentCalendar] dateByAddingComponents:comp toDate:now options:0];
NSLog(#"%#", now);
NSLog(#"%#", date);
Even though i do not have an answer to my question i will post solution to this issue that i used.
So i took weeks and multiplied them with 7. This gave me current week end date. I know it is not good since weeks can have different days set, etc. But for my most default issue (gregorian calender, 7 days a week) this solution was fast workaround in already existing date time calculator.
[components setDay:components.week * 7 - 7]; //Start of week
startDate = [[NSCalendar currentCalendar] dateFromComponents:components];
[components setDay:components.day + 7]; //End of week
endDate = [[NSCalendar currentCalendar] dateFromComponents:components];
[components setDay:0]; //I am using this not just here so i am setting back my changes
Using NSDateComponents I know how to get the day component, but this gives me a value from 1 - 365, right? I need to get the days 1-30 of a specific month, how can I?
For example, I have an NSDate which might be 16th May. I want to be able to get the day, so it returns 16. Being the 16th day of that month (not the 16th day of the year).
Thanks.
EDIT:
I have this:
NSCalendar *cal = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *comps = [cal components:NSDayCalendarUnit fromDate:[[NSUserDefaults standardUserDefaults] objectForKey:#"dateSaved"]];
However comps.day returns 1 when it should equal what the date is saved as, say 4.
The day value of NSDateComponents gives you the day of month according to the given calendar:
NSDate *date = [NSDate date];
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *comps = [gregorian components:NSDayCalendarUnit fromDate:date];
NSLog(#"%# / %d", date, comps.day);
gives
2012-02-04 15:27:36.682 testapp[1533:f803] 2012-02-04 14:27:36 +0000 / 4
although February, 4th would be the 35th day of the year.
I'm trying to learn Objective-C/iPhone SDK and right now I'm doing a kind of to-do app playing with local notifications.
I have a "timeOfDay" ivar stored as an NSDate from a DatePicker and a "numberOfDays" ivar stored as an NSNumber.
When I press a specific button, I would like to schedule a local notification x numberOfDays from the time the button is pressed but at the specific timeOfDay.
I seems easy to add an NSTimeInterval to the current date which would give me the a way to schedule the notification numberOfDays from current time but adding the timeOfDay feature makes it more complex.
What would be the correct way of achieving this?
Thanks
Use NSDateComponents to add time intervals to an existing date while respecting all the quirks of the user's current calendar.
NSCalendar *calendar = [NSCalendar autoupdatingCurrentCalendar];
// Get the year, month and day of the current date
NSDateComponents *dateComponents = [calendar components:(NSYearCalendarUnit | NSMonthCalendarUnit| NSDayCalendarUnit) fromDate:[NSDate date]];
// Extract the hour, minute and second components from self.timeOfDay
NSDateComponents *timeComponents = [calendar components:(NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit) fromDate:self.timeOfDay];
// Apply the time components to the components of the current day
dateComponents.hour = timeComponents.hour;
dateComponents.minute = timeComponents.minute;
dateComponents.second = timeComponents.second;
// Create a new date with both components merged
NSDate *currentDateWithTimeOfDay = [calendar dateFromComponents:dateComponents];
// Create new components to add to the merged date
NSDateComponents *futureComponents = [[NSDateComponents alloc] init];
futureComponents.day = [self.numberOfDays integerValue];
NSDate *newDate = [calendar dateByAddingComponents:futureComponents toDate:currentDateWithTimeOfDay options:0];
There is a pretty simple method to do this that won't involve as many lines of code.
int numDays = 5;
myDate = [myDate dateByAddingTimeInterval:60*60*24*numDays];
+ (id)dateWithTimeInterval:(NSTimeInterval)seconds sinceDate:(NSDate *)date
That should give you what you're looking for.
I am writing an app which uses core-data to store my data. Included in this is a date field of which I am only interested in the date not the time.
I need to select records based on the date (not time) and so I have created a category on NSDate to return a date, normalised to a set time as follows:
+ (NSDate *)dateWithNoTime:(NSDate *)dateTime {
if( dateTime == nil ) {
dateTime = [NSDate date];
}
NSDateComponents* comps = [[NSCalendar currentCalendar] components:NSYearCalendarUnit|NSMonthCalendarUnit|NSDayCalendarUnit fromDate:dateTime];
NSDate *dateOnly = [[NSCalendar currentCalendar] dateFromComponents:comps];
[dateOnly dateByAddingTimeInterval:(60.0 * 60.0 * 12.0)]; // Push to Middle of day.
return dateOnly;
}
I then use this when I add data to the core-data store (I have a setter which uses this method to set the primitive date value) and then I use this method to create a date that I use to compare the dates when performing a fetch request. So in theory this should always work - ie pick out the dates I'm looking for.
I'm slightly nervous though as I'm not totally sure what effect changing time-zone or locale will have. Will it still work ?
What is considered best practice when storing and searching on a date only when you aren't interested in the time.
Cheers.
EDIT
After reading the discussion recommended I think that I should modify my code to be as follows. The thinking being that if I ensure that I push it to a specific calendar system and a specific timezone (UTC) then the dates should always be the same regardless of where you are when you set the date and when you read the date. Any comments on this new code appreciated.
+ (NSDate *)dateWithNoTime:(NSDate *)dateTime {
if( dateTime == nil ) {
dateTime = [NSDate date];
}
NSCalendar *calendar = [[[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar] autorelease];
[calendar setTimeZone:[NSTimeZone timeZoneWithAbbreviation:#"UTC"]];
NSDateComponents *components = [[[NSDateComponents alloc] init] autorelease];
components = [calendar components:NSYearCalendarUnit|NSMonthCalendarUnit|NSDayCalendarUnit
fromDate:dateTime];
NSDate *dateOnly = [calendar dateFromComponents:components];
[dateOnly dateByAddingTimeInterval:(60.0 * 60.0 * 12.0)]; // Push to Middle of day.
return dateOnly;
}
You have a few issues to deal with here. First, as you noted, timezones. You also need to worry about daylight savings, which change the concept of “midday.”
Take a look at this discussion on CocoaDev where an Apple Engineer gives some answers and discusses some best practices.
A simpler solution is to use a predicate that looks for dates within a certain range.
Use NSCalendarComponent to create a start date and an end date for you "day" and then include those in the predicate.
NSPredicate *p=[NSPredicate predicateWithFormat:#"$# <= date <= $#",startDate,endDate];
That will provide the maximum flexibility without to much complexity. Date and time programing is deceptively complex. Be prepared to do some work.
I also ended up in a scenario where I needed to have an NSDate to compare a date without taking into consideration time.
I hade a filter mechanisme and as part of the UI, if the user chose today the minimum date as start date and the end date as today, I would display "All time periods" as opposed to a string in the format:
1/5/2006 - 24/12/2009
So I needed to take todays date using +date of NSDate, and compare it to the end date. That end date came from a UIDatePicker set to without time, but +date returned the date and time of right now.
So I wrote this short handy method, it receives a date object, uses NSDateComponents and the NSCalendar class to extract the day, month and year.
These 3 parameters are then used to create a new NSDate using NSDateFormatter's -dateFromString: method, the result is an NSDate corresponding to the same "date" (in the traditional human concept) as the parameter date but without time.
- (NSDate *)strictDateFromDate:(NSDate *)date{
NSUInteger flags = NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit;
NSDateComponents *components = [[NSCalendar currentCalendar] components:flags
fromDate:[NSDate date]];
NSString *stringDate = [NSString stringWithFormat:#"%d/%d/%d", components.day, components.month, components.year];
NSDateFormatter *formatter = [[[NSDateFormatter alloc] init] autorelease];
formatter.dateFormat = #"dd/MM/yyyy";
return [formatter dateFromString:stringDate];
}
I hope you can use and enjoy this function in future.