Generate consecutive sequence of NSDates - objective-c

Given some startDate, I would like to degenerate x number of consecutive days after this startDate. I'm attempting to use the following code:
// Generate dates
self.days = [[NSMutableArray alloc] init];
NSDate *startDate = [NSDate date];
NSCalendar *theCalendar = [NSCalendar currentCalendar];
NSDateComponents *dayComponent = [[NSDateComponents alloc] init];
dayComponent.day = 1;
for (int i = 1; i < self.mygoal.goalDays; i++) {
[self.days addObject:startDate];
startDate = [theCalendar dateByAddingComponents:dayComponent toDate:startDate options:0];
}
Question: Is the reassignment of startDate ok, given that I'm adding the same object to self.days?

Creating a sequence of dates is not as trivial as it sounds. Actually it is covered in the great WWDC2011 video «Performing Calendar Calculations».
You are adding in every loop a day to the last date. But actually this will fail in timezones with DST if the time is in the hour that is changed for the day of switching and for any following days as the dates will be nil.
If you instead change the date components i the loop and add it to the original satrtdate, it will only effect the day of DST-switching.
To also handle that you can set the hour of the start date to something safe — as noon — as all switches are performed at night hours.
With all this in mind I would use something like this to create a sequence of days with times set to start of day:
NSUInteger numberOfDays = 10;
NSCalendar *cal = [NSCalendar currentCalendar];
NSDate *startDate = [cal dateFromComponents:({
NSDateComponents *comps = [[NSDateComponents alloc] init];
comps.year = 2015;
comps.month = 1;
comps.day = 2;
comps.hour = 12; // for DST-safety
comps;
})];
NSMutableArray *dates = [#[] mutableCopy];
for (NSUInteger i =0 ; i < numberOfDays; ++i) {
NSDateComponents *comps = [[NSDateComponents alloc] init];
comps.day = i;
NSDate *date = [cal dateByAddingComponents:comps
toDate:startDate
options:0];
// set date to beginning of day
[cal rangeOfUnit:NSCalendarUnitDay
startDate:&date
interval:NULL
forDate:date];
[dates addObject:date];
}
So, yes, reassignment is technically OK, but in this particular case it is might cause unexpected trouble.

It's fine because you're not actually adding the same object. dateByAddingComponents: returns a new object, so when you assign it to startDate you are replacing the reference to your old object to a reference to the new one

Related

How to get next week start and end date in Objective-c?

I have try to get next week start and end date but I have got current week start and end date.How can get next week start and end date in Objective-c.
You can get all dates of this week and next week using following code:-
NSArray * allDatesOfThisWeek = [self daysThisWeek];
NSArray * allDatesOfNextWeek = [self daysNextWeek];
Following methods are used for calculating dates of this week:-
-(NSArray*)daysThisWeek
{
return [self daysInWeek:0 fromDate:[NSDate date]];
}
-(NSArray*)daysNextWeek
{
return [self daysInWeek:1 fromDate:[NSDate date]];
}
-(NSArray*)daysInWeek:(int)weekOffset fromDate:(NSDate*)date
{
NSCalendar *calendar = [NSCalendar currentCalendar];
//ask for current week
NSDateComponents *comps = [[NSDateComponents alloc] init];
comps=[calendar components:NSWeekCalendarUnit|NSYearCalendarUnit fromDate:date];
//create date on week start
NSDate* weekstart=[calendar dateFromComponents:comps];
NSDateComponents* moveWeeks=[[NSDateComponents alloc] init];
moveWeeks.weekOfYear=weekOffset;
weekstart=[calendar dateByAddingComponents:moveWeeks toDate:weekstart options:0];
//add 7 days
NSMutableArray* week=[NSMutableArray arrayWithCapacity:7];
for (int i=1; i<=7; i++) {
NSDateComponents *compsToAdd = [[NSDateComponents alloc] init];
compsToAdd.day=i;
NSDate *nextDate = [calendar dateByAddingComponents:compsToAdd toDate:weekstart options:0];
[week addObject:nextDate];
}
return [NSArray arrayWithArray:week];
}
If you want to get dates of next to next week from today, then pass weekOffset=2 like this:-
NSArray * allDatesOfNextToNextWeek = [self daysInWeek:2 fromDate:now];
If you want to get dates of previous week from today, then pass weekOffset=-1 like this:-
NSArray * allDatesOfPreviousWeek = [self daysInWeek:-1 fromDate:now];
Hope, this is what you're looking for. Any concern get back to me.
NSCalendar contains dedicated methods to do that for example nextDateAfterDate:matchingUnit:value:options: and dateByAddingComponents:toDate:options:
// Get the current calendar
NSCalendar *calendar = [NSCalendar currentCalendar];
// Get the next occurrence for the first weekday of the current calendar
NSDate *startOfNextWeek = [calendar nextDateAfterDate:[NSDate date] matchingUnit:NSCalendarUnitWeekday value:calendar.firstWeekday options:NSCalendarMatchStrictly];
// Create new date components +7 days and -1 seconds
NSDateComponents *endOfNextWeekComponents = [[NSDateComponents alloc] init];
endOfNextWeekComponents.day = 7;
endOfNextWeekComponents.second = -1;
// Add the date components to the start date to get the end date.
NSDate *endOfNextWeek = [calendar dateByAddingComponents:endOfNextWeekComponents toDate:startOfNextWeek options:NSCalendarMatchStrictly];
NSLog(#"%# - %#", startOfNextWeek, endOfNextWeek);

Create an Array of NSDates like in Ruby

I want to create an array of NSDates starting from today to next month. This can easily be done in Ruby using Time.now..(Time.now + 30.days)
How can I create an array of dates just like in Ruby in Objective C?
Any ObjC solution is unfortunately going to be far more verbose than that Ruby code.
The correct way to make the calculation is with NSDateComponents:
NSMutableArray * dateArray = [NSMutableArray array];
NSCalendar * cal = [NSCalendar currentCalendar];
NSDateComponents * plusDays = [NSDateComponents new];
NSDate * now = [NSDate date];
for( NSUInteger day = 0; day < NUMDAYS; day++ ){
[plusDays setDay:day];
[dateArray addObject:[cal dateByAddingComponents:plusDays toDate:now options:0]];
}
To make the procedure more convenient (if you need to do it more than a few times), you could put this loop into a category method on NSCalendar, with NUMDAYS replaced with the argument and substituting self for cal.
There's nothing built in to do this quite as concisely as the Ruby you've posted. Breaking the problem down, you need a way to get the day after a particular date. Here's a function that will do that:
NSDate *CalendarDayAfterDate(NSDate *date)
{
NSDateComponents *components = [[NSDateComponents alloc] init];
components.day = 1;
NSCalendar *calendar = [NSCalendar currentCalendar];
return [calendar dateByAddingComponents:components toDate:date options:0];
}
Next, you need to get an array of days one after the other:
NSDate *today = [NSDate date];
NSMutableArray *dates = [NSMutableArray arrayWithObject:today];
for (NSUInteger i=0; i<30; i++) {
NSDate *tomorrow = CalendarDayAfterDate(today);
[dates addObject:tomorrow];
today = tomorrow;
}
After much downvoting and commenting, here's my REVISED answer...
-(NSDate *)nextDayFromDate:(NSDate *)originalDate {
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *dateComponent = [NSDateComponents new];
dateComponent.day = 1;
NSDate *tomorrow = [calendar dateByAddingComponents:dateComponent toDate:originalDate options:0];
return tomorrow;
}
NSMutableArray *dateArray = [NSMutableArray array];
NSDate *now = [NSDate date];
[dateArray addObject:now];
for (int i=0;i<31;i++) {
NSDate *firstDate = [dateArray objectAtIndex:i];
NSDate *newDate = [self nextDayFromDate:firstDate];
[dateArray addObject:newDate];
}
What this does is use the NSCalendar API to add a "day interval" to any given NSDate. Add "Now" to the array, then do a loop 30 times, each time using the previous NSDate object as input to the logic.

adding current and future dates to an array

I am creating a custom type calendar and I am trying to see if it is possible to store dates in an array without statically assigning each one. For example the 1st date in the array would be the day it was first created and it would save the next week lets say into the relevant indexes in the array.
NSMutableArray *thisWeek = [today, tomorrow, sunday(Feb 24), monday (Feb 25), etc];
What would be the best way to go about storing the future dates?
NSMutableArray *days = [[NSMutableArray alloc] init];
NSCalendar *cal = [NSCalendar autoupdatingCurrentCalendar];
NSDateComponents *tempCop = [cal components:NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit
fromDate:[NSDate date]];
NSDate *today = [cal dateFromComponents:tempCop];
for (int i = 0; i < 8; i++)
{
NSDateComponents *comps = [[NSDateComponents alloc]init];
[comps setDay:i];
[days addObject:[cal dateByAddingComponents:comps toDate:today options:0]];
}
NSMutableArray *days;
days = [[NSMutableArray alloc] init];
NSDate *todayDate = [NSDate Date];
[days addObject:todayDate];
for (int i = 1; i <= 6; i++)
{
NSDate *newDate = [[NSDate date] dateByAddingTimeInterval:60*60*24*i];
[days addObject:newDate];
}
In this way, you can add days in array.
Take a look at dateByAddingTimeInterval: in the NSDate docs (link). It lets you add a given amount of seconds to a date.

Create a range of dates

I want to create a array of range which contains days betweens a specific start and end date.
For example, I have a start date with 1 January 2012 and and an end date with 7 January 2012. The array or range should contain a collection of NSDate objects (7 in total).
How do I do this?
NSCalendar is helpful here, since it knows the calendar related to the dates. So, by using the following (assuming you have startDate and endData and that you want to include both in the list), you can iterate through the dates, adding a single day (NSCalendar will take care of wrapping the months and leap year, etc).
NSMutableArray *dateList = [NSMutableArray array];
NSCalendar *currentCalendar = [NSCalendar currentCalendar];
NSDateComponents *comps = [[NSDateComponents alloc] init];
[comps setDay:1];
[dateList addObject: startDate];
NSDate *currentDate = startDate;
// add one the first time through, so that we can use NSOrderedAscending (prevents millisecond infinite loop)
currentDate = [currentCalendar dateByAddingComponents:comps toDate:currentDate options:0];
while ( [endDate compare: currentDate] != NSOrderedAscending) {
[dateList addObject: currentDate];
currentDate = [currentCalendar dateByAddingComponents:comps toDate:currentDate options:0];
}
[comps release];
Just create them and add them to an array...
NSMutableArray *arr = [NSMutableArray array];
NSDateComponents *comps = [[[NSDateComponents alloc] init] autorelease];
[comps setMonth:1];
[comps setYear:2012];
for(int i=1;i<=7;i++) {
[comps setDay:i];
[arr addObject:[[NSCalendar currentCalendar] dateFromComponents:comps]];
}
From Apple doc:
To compute a sequence of dates, use the enumerateDatesStartingAfterDate:matchingComponents:options:usingBlock: method instead of calling this method (
- nextDateAfterDate:matchingComponents:options:
) in a loop with the previous loop iteration's result.
As I got, it will iterate all dates that matched with "matchingComponents" till you finish iteration with "stop.memory = true"
let calendar = NSCalendar.currentCalendar()
let startDate = calendar.startOfDayForDate(NSDate())
let finishDate = calendar.dateByAddingUnit(.Day, value: 10, toDate: startDate, options: [])
let dayComponent = NSDateComponents()
dayComponent.hour = 1
calendar.enumerateDatesStartingAfterDate(startDate, matchingComponents: dayComponent, options: [.MatchStrictly]) { (date, exactMatch, stop) in
print(date)
if date!.compare(finishDate!) == NSComparisonResult.OrderedDescending {
// .memory gets at the value of an UnsafeMutablePointer
stop.memory = true
}
}

Get only weekends between two dates

I'm trying get only the Saturdays and Sundays between two dates, but I don't know why get me free days on a week.
Here is my code:
- (BOOL)checkForWeekend:(NSDate *)aDate {
BOOL isWeekendDate = NO;
NSCalendar *calendar = [NSCalendar currentCalendar];
NSRange weekdayRange = [calendar maximumRangeOfUnit:NSWeekdayCalendarUnit];
NSDateComponents *components = [calendar components:NSWeekdayCalendarUnit fromDate:aDate];
NSUInteger weekdayOfDate = [components weekday];
if (weekdayOfDate == weekdayRange.location || weekdayOfDate == weekdayRange.length) {
// The date falls somewhere on the first or last days of the week.
isWeekendDate = YES;
}
return isWeekendDate;
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
NSString *strDateIni = [NSString stringWithString:#"28-01-2012"];
NSString *strDateEnd = [NSString stringWithString:#"31-01-2012"];
NSDateFormatter *df = [[NSDateFormatter alloc] init];
[df setDateFormat:#"dd-MM-yyyy"];
NSDate *startDate = [df dateFromString:strDateIni];
NSDate *endDate = [df dateFromString:strDateEnd];
unsigned int unitFlags = NSMonthCalendarUnit | NSDayCalendarUnit;
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *comps = [gregorian components:unitFlags fromDate:startDate toDate:endDate options:0];
// int months = [comps month];
int days = [comps day];
for (int i=0; i<days; i++)
{
NSTimeInterval interval = i;
NSDate * futureDate = [startDate dateByAddingTimeInterval:interval];
BOOL isWeekend = [self checkForWeekend:futureDate]; // Any date can be passed here.
if (isWeekend) {
NSLog(#"Weekend date! Yay!");
}
else
{
NSLog(#"Not is Weekend");
}
}
}
The problem:
The issue was caused by NSTimeInterval interval = i; The logic of the for loop was to iterate by days. Setting the time interval to i was iterating by seconds.
From documentation on NSTimeInterval
NSTimeInterval is always specified in seconds;
The answer:
Changing the NSTimeInterval line to
NSTimeInterval interval = i*24*60*60;
Here is a link to another answer I posted on SO (shameless, I know). It has some code that may help you with dates in the future. The methods are implemented as categories of NSDate, meaning they become methods of NSDate.
There are several functions there that help with weekends. But these two might be most helpful:
- (NSDate*) theFollowingWeekend;
- (NSDate *) thePreviousWeekend;
They return the date of the weekend following and prior to the receiver (self).
Generally, you should not use the notion that a day is 86400 seconds, and should use NSDateComponents and NSCalendar. This works even when daylight savings time transitions occur between dates. Like this:
- (NSDate *) dateByAddingDays:(NSInteger) numberOfDays {
NSDateComponents *dayComponent = [[NSDateComponents alloc] init];
dayComponent.day = numberOfDays;
NSCalendar *theCalendar = [NSCalendar currentCalendar];
return [theCalendar dateByAddingComponents:dayComponent toDate:self options:0];
}
One very important thing to remember is that one day is not (necessarily) equal to 24*60*60 seconds. And you should not do date arithmetic yourself
What you really need to do might seem a little tedious but this is the correct thing to do: use NSCalendar and – dateByAddingComponents:toDate:options:
See Calendrical Calculations guide.