How do I calculating the difference between two dates in Objective-C? - objective-c

I'm working through a challenge in Objective-C Programming, The Big Nerd Ranch Guide, and I'm a little flummoxed by one of the challenges.
Use two instances of NSDate to figure out how many seconds you have been alive. Hint: here is how you create a new date object from the year, month, etc.:
So I need to the difference between now and my date of birth in seconds. Sounds good. Then the hint shows up:
#autoreleasepool {
NSDateComponents *comps = [[NSDateComponents alloc] init];
[comps setYear:1981];
[comps setMonth:7];
[comps setDay:12];
[comps setHour:1];
[comps setMinute:55];
[comps setSecond:33];
NSCalendar *g = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDate *dateOfBirth = [g dateFromComponents:comps];
The first issue is I don't understand what *g is meant to be. I know it's a pointer to an NSCalendar object. But why do we need it? And what is g meant to stand for?
The sample code then uses the g variable to grab a date. In another language, this would be as easy as DateDiff(dateOne, dateTwo, interval). I'm not clear on why the Calendar object is necessary in the first place, and why we have to create date components to feed the object.
This is all new to me, and I've worked with dynamic languages in the past. So a "dummies" like explanation would be great!

The sample code then uses the g variable to grab a date. In another language, this would be as easy as DateDiff(dateOne, dateTwo, interval). I'm not clear on why the Calendar object is necessary in the first place, and why we have to create date components to feed the object.
I'm not an Objective-C programmer, but I know a bit about date/time APIs.
In order to specify your birth date/time, you're giving a year of 1981, a month of 7 etc. What does a year of 1981 mean? To you, it may mean about 31 years ago... but to someone using a different calendar system, it could mean something entirely different. Converting from "year/month/day etc" to "point in time" is a bit like converting from a string to an integer: does "10" mean ten, or sixteen? It all depends on your frame of reference (the base, in this case - the calendar system in the date case).
The calendar - initialized as a Gregorian calendar - takes those date components and is able to give you back an NSDate which is a sort of "absolute" value in time.
As for computing the difference between two NSDate values - I suspect there's a member which gives you something like "seconds since the Unix epoch" (or possible milliseconds). So take that from both dates (your birth and "now"), subtract one from the other, and you'll get the elapsed number of seconds (or milliseconds) between the two.
EDIT: In fact, the timeIntervalSinceNow function is probably the one you want, once you've got your birth date.

You can do it this way
NSDateComponents *comps = [[NSDateComponents alloc] init];
[comps setYear:1981];
[comps setMonth:7];
[comps setDay:12];
[comps setHour:1];
[comps setMinute:55];
[comps setSecond:33];
NSDate *dateOfBirth = [[NSCalendar currentCalendar] dateFromComponents:comps];
NSTimeInterval timeGap=[[NSDate new] timeIntervalSinceDate:dateOfBirth ];

Related

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 :)

NSDate as keys for NSDictionary

Is it possible to add NSDate as keys and some arrays as it values in a NSDictionary?
I dont have any requirement of writing the dictionary to disk - archiving or unarchiving, just as this guy needs it: NSDictionary with NSDates as keys
But, why I want NSDate to be added as keys specifically is so that I can fetch all keys from the dictionary and do some computations like, fetching the dates from within a range.
Whether two objects with same date value share same memory when created?
Is it possible to have NSDate as key? Also, what other problems I might face with this assumption?
Thanks,
Raj
Edit:
After posting my question, I just wrote a sample code:
NSDateComponents *dateComps = [[NSDateComponents alloc] init];
[dateComps setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
[dateComps setDay:1];
[dateComps setMonth:1];
[dateComps setYear:2012];
NSDate *date1 = [[NSCalendar currentCalendar] dateFromComponents:dateComps];
NSDate *date2 = [[NSCalendar currentCalendar] dateFromComponents:dateComps];
NSLog(#"Date 1 = %x, Date 2 = %x", date1, date2);
Output:
Date 1 = 7945610, Date 2 = bd03610
But does the key comparison of NSDictionary interpret these 2 NSDate objects as different?
Another Edit:
Also, if I should convert NSDate to NSString using NSDateFormatter, I cannot directly check for dates within range. I will have to perform the following steps:
Get all NSString array of keys from dictionary
Convert them back to array of NSDate
Perform predicates and fetch dates within range
Again convert back those dates to an array of Strings
Now use this string keys to get values from dictionary!
So, is there any better way?
Yes, NSDate objects can be used as keys for NSDictionary - any object type can be used as a key provided it supports the NSCopying protocol. Keys are compared using isEqual: and not by pointer value. See the Overview section of the NSDictionary documentation for more details.

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.

How do I Increment an NSDate object in 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.