How do I Increment an NSDate object in Objective-C - objective-c

I want to take the next day by giving the current date
The code i used as follows
+(NSDate *)getForDays:(int)days fromDate:(NSDate *) date {
NSTimeInterval secondsPerDay = 24 * 60 * 60 * days;
return [date addTimeInterval:secondsPerDay];
}
this works fine but when the daylight saving enabled this leads to errors. How can I make this work when daylight saving is enabled.

As you have found, what you have now is pretty error-prone. Not only can it trip up over a daylight savings change, but also what if your user has a non-gregorian calendar? Then, days are not 24 hours long.
Instead, use NSCalendar and NSDateComponents which were exactly designed for this:
+ (NSDate *)getForDays:(int)days fromDate:(NSDate *)date
{
NSDateComponents *components= [[NSDateComponents alloc] init];
[components setDay:days];
NSCalendar *calendar = [NSCalendar currentCalendar];
return [calendar dateByAddingComponents:components toDate:date options:0];
}

Use NSCalendar to perform calculations like this. Not only is it more likely to work, but your code will be clearer.

I've no idea what language or system you're using here, but my advice would be to perform all of your calculations using time as UTC and only use local time when you come to display it.
Most operating systems and languages will factor in timzone and daylight saving on the conversion of UTC to local time.

Related

How can I get the amound of seconds from a NSDate ("hh:mm:ss") and vice versa

I am trying to make a timer and yes I have looked at NSTimer and it is not what I'm looking for, I basically need a way to convert a NSDate in the format of ("hh:mm:ss") into the amount of seconds that would be in this. And then I need a way to do the opposite so convert the seconds to the amount of hours, minutes and leftover seconds from that.
You can extract components from NSDate using the NSCalendar class. The following example extracts a single component, NSCalendarUnitSecond, from the current date.
NSDate *today = [NSDate date];
NSCalendar *localCalendar = [NSCalendar currentCalendar];
NSUInteger seconds = [localCalendar component:NSCalendarUnitSecond fromDate:today];
You could have a place holder date e.g. 00:00:00 and then use the NSDate function timeIntervalSinceDate: to get the seconds. Converting back again you could use the dateWithTimeInterval: method.
Obviously both these dates would have to be the same year month and day and you could use any arbitrary values for these as long as they are the same.

What is the best way of getting the CFAbsoluteTime of midnight of today?

Im using CFAbsoluteTimeGetCurrent() to get the current time, as a CFAbsoluteTime struct.
What is the best way of getting the CFAbsoluteTime of midnight of today?
Ive considered getting midnight using a similar method to the one shown here and then converting the result to a CFAbsoluteTime, but this seems ridiculously complicated to do a simple thing.
Is there a better way?
Computing "midnight of the current day" is not completely trivial, because things as
the time zone and daylight savings time transitions must be taken into account.
My preferred way is
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDate *midnight;
[calendar rangeOfUnit:NSDayCalendarUnit
startDate:&midnight
interval:NULL
forDate:[NSDate date]];
which you can then convert with
CFAbsoluteTime cfMidnight = [midnight timeIntervalSinceReferenceDate];

How to best debug NSDate / NSTimezone related code

I know that NSDate does not represent a timezone, but perhaps someone can advise me on how to best debug its relationship to other classes, for example NSDatePicker, CalCalendarStore.
As an example, I want to set the date for a date picker to 01-01-2012. I do something like this:
newDate = [NSDate date];
NSCalendar *calendar = [NSCalendar autoupdatingCurrentCalendar];
NSDateComponents *components = [calendar components:(NSYearCalendarUnit|NSMonthCalendarUnit|NSDayCalendarUnit) fromDate:newDate];
[components setMonth:1];
[components setDay:1];
newDate = [calendar dateFromComponents:components];
This works fine in my datepicker, but as soon as I try debugging date-related code, things get very confusing (with TZ-related shifts in the hour, sometimes even day). Can someone advise me on how to do this?
If I should use a date formatter (probably!), how should I do this so I can easily debug without cluttering up the code too much?
NSDate is just a wrapper around unix time and as such is not time zone aware at all, its actually your date formatter that puts a time zone of any kind on it.
Dealing with similar problems I ended up just forcing all my NSDate objects to use UTC dates (when not worrying about the "time" components themselves).
You have to use a new NSCalendar instance with the timezone property set to UTC to make those dates, but this way you're dealing with consistant dates, just use a UTC time zone set formatter to display them, or a UTC set Calendar to break them up into components as needed.

In Objective-C, to get the current hour and minute as integers, we need to use NSDateComponents and NSCalendar?

I'd like to get the current hour and minute as integers. So if right now is 3:16am, I'd like to get the two integers: 3 and 16.
But it looks like [NSDate date] will give the number of seconds since 1970, or it can give a string of the current time representation, but there is no easy way to get them as integers?
I see a post in Getting current time, but it involved NSDateComponents and NSCalendar? That's way too complicated... all that was need is something like
NSDate *date = [NSDate date];
int hour = [date getHour]; // which is not possible
Is there a simpler way than using 3 classes NSDate, NSDateComponents, and NSCalendar to get the current hour as an integer, or typically, in Objective-C, would we typically still use C language's localtime and tm to get the hour as an integer?
How you interpret the seconds since 1970 depends on the calendar that you are using. There is simply no other option. Fortunately it is not that difficult to set up. See the 'Data and Time Programming Guide' for lots of examples. In your case:
// Assume you have a 'date'
NSCalendar *gregorianCal = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *dateComps = [gregorianCal components: (NSHourCalendarUnit | NSMinuteCalendarUnit)
fromDate: date];
// Then use it
[dateComps minute];
[dateComps hour];
So it really isn't that complicated.
Also note that you could create a 'Class Category' to encapsulate this as:
#interface NSDate (MyGregorianDateComponents)
- (NSInteger) getGregorianHour;
- (NSInteger) getGregorianMinute;
#end
NSDate just holds the time that has passed since a certain reference date, to get more meaningful numbers out of this (eg. after taking care of DST, leap years and all the other stupid time stuff), you have to use NSDateComponents with the appropriate NSCalendar.
My class can help.
https://github.com/TjeerdVurig/Vurig-Calendar/blob/master/Vurig%20Calendar/NSDate%2Bconvenience.m
I'm sure you can figure out the minute part :)

NSCalendar problem with BC era

Greetings,
Recently I faced a big problem (as it seems to me) with NSCalendar class.
In my task I need to work with a large time periods starting from 4000BC to 2000AD (Gregorian calendar). In some place I was forced to increment some NSDate by 100 year interval. When incrementing the years in AD timeline (0->...) everything worked fine, but when I tried the same thing with BC i was a little confused.
The problem is, when you try to add 100 years to 3000BC [edited] year, you get 3100BC [edited] no matter what... Personally i found it strange and illogical. The right result should be 2900BC.
Here is the code sample for you to see this "not right" behavior:
NSCalendar *gregorian = [[[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar] autorelease];
// initing
NSDateComponents *comps = [[[NSDateComponents alloc] init] autorelease];
[comps setYear:-1000];
NSDate *date = [gregorian dateFromComponents:comps];
// math
NSDateComponents *deltaComps = [[[NSDateComponents alloc] init] autorelease];
[deltaComps setYear:100];
date = [gregorian dateByAddingComponents:deltaComps toDate:date options:0];
// output
NSString *dateFormat = #"yyyy GG";
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:dateFormat];
NSLog(#"%#", [formatter stringFromDate:date]);
What can you say about this behavior? Is this how it should work or is this a bug? I'm confused :S.
BTW.: the method [NSCalendar components:fromDate:toDate:options:] doesn't allow us to calculate the difference between years in BC era... additional 'WHY?' in this Pandora's box.
P.S.: I was digging through official documentation and other resources but found nothing regarding this problem (or maybe it's intended to work so and I'm an idiot?).
I found a simple workaround for this bug.
Here it is:
#interface NSCalendar (EraFixes)
- (NSDate *)dateByAddingComponentsRegardingEra:(NSDateComponents *)comps toDate:(NSDate *)date options:(NSUInteger)opts;
#end
#implementation NSCalendar (EraFixes)
- (NSDate *)dateByAddingComponentsRegardingEra:(NSDateComponents *)comps toDate:(NSDate *)date options:(NSUInteger)opts
{
NSDateComponents *toDateComps = [self components:NSEraCalendarUnit fromDate:date];
NSDateComponents *compsCopy = [[comps copy] autorelease];
if ([toDateComps era] == 0) //B.C. era
{
if ([comps year] != NSUndefinedDateComponent) [compsCopy setYear:-[comps year]];
}
return [self dateByAddingComponents:compsCopy toDate:date options:opts];
}
#end
If you wonder why I invert only years, the answer is simple, every other component except years is incrementing and decrementing in the right way (I haven't tested them all, but months and days seem to work fine).
EDIT: removed mistakenly added autorelease, thanks John.
It's a bug and or a feature. The Apple doc never says what they mean by adding components to the calendrical date. It's perfectly free for them to define "adding a component" to the BCE date as just the addition to the year component.
Yes I agree with you that it's counterintuitive and I think it's a bug.
You need to convert your NSDate to either
the second from the UNIX epoch (1.1.1970) using -timeIntervalSince1970
the second from the OS X epoch (1.1.2001) using -timeIntervalSinceReferenceDate
You can then perform the calculation, and convert it back to an NSDate. I think it's a bad idea to work in the Gregorian calendar all the time... It would be better to convert to the Gregorian calendar just before you show it on the GUI.
Imagine that you have Date with 1st moment of our era - AD 0001-01-01 00:00:00. What was the moment before? BC 0001-01-01 00:00:01. If Cocoa developers used basic arithmetic's for this task, you would get AD 0000-12-31 23:59:59. Is that reasonable for Gregorian calendar? I guess not. So, it seems to me that the most convenient way to implement calendar was to use Era flag, and change "time direction" when dealing with BC era to get human-readable dates in every case.
BTW.: [NSCalendar dateByAddingComponents:toDate:options:] really behaves strange and is unable to count time interval between BC dates, I checked too. So, for BC dates you may use workaround, e.g. by translating dates to AD and then finding diff.