compare two dates in objective-c - objective-c

I am sure this question came up before I am pulling my hair out. I have two dates - one from an Object on Parse.com and the other one local. I try to determine whether the remote object has been updated so that I can trigger actions locally.
When looking at the NSDate of both objects they seem identical but a comparison reveals that the remote object is newer - when checking the time internal (since1970) it becomes obvious that there is a difference but why? When I first created the local object all I did was
localObject.updatedAt = remoteObject.updatedAt //both NSDate
But when looking closer I get this:
Local Time Interval: 1411175940.000000
Local Time: 2014-09-20 01:19:00 +0000
Remote Time Interval: 1411175940.168000
Remote Time: 2014-09-20 01:19:00 +0000
Does anyone have an idea why that is and whether I can ignore this detail? Does iOS round up or something?
Adding more code:
#property (strong, nonatomic) NSDate *date;
...
PFQuery *query = [PFObject query];
[query whereKey:#"Product" equalTo:#"123456"]
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error)
{
self.date = objects[0].updatedAt;
NSTimeInterval localTime = [self.date timeIntervalSince1970];
NSTimeInterval remoteTime = [objects[0].updatedAt timeIntervalSince1970];
NSLog(#"Local Time Interval: %f", localTime);
NSLog(#"Local Time: %#", self.date);
NSLog(#"Remote Time Interval: %f", remoteTime);
NSLog(#"Remote Time: %#", objects[0].updatedAt);
}
else
{
NSLog(#"Error with query");
}
}];
That results in the console output above - and I don't understand why these dates are different.

I cannot explain why there is a difference, but the important thing to understand is that there can be a difference and that when comparing dates you have to use a tolerance value.
The Apple Date and Time Programming Guide has an example of how to compare two dates within a given tolerance:
To compare dates, you can use the isEqualToDate:, compare:,
laterDate:, and earlierDate: methods. These methods perform exact
comparisons, which means they detect sub-second differences between
dates. You may want to compare dates with a less fine granularity. For
example, you may want to consider two dates equal if they are within a
minute of each other. If this is the case, use timeIntervalSinceDate:
to compare the two dates. The following code fragment shows how to use
timeIntervalSinceDate: to see if two dates are within one minute (60
seconds) of each other.
if (fabs([date2 timeIntervalSinceDate:date1]) < 60) ...
It's up to you decide on the tolerance value, but something like 0.5 seconds seems reasonable:
+ (BOOL)date:(NSDate *)date1
equalsDate:(NSDate *)date2
{
return fabs([date2 timeIntervalSinceDate:date1]) < 0.5;
}

Parse stores dates as iso8601 format. This makes things very complex as Apple does not manage the format well. While the idea of the standard is awesome, until everyone plays by the same rules, anarchy rules..
I convert everything inbound from parse into usable format before attempting anything on their date time values..
Drop this into a library somewhere, and save yourself tons of headaches. This took weeks of searching and scratching to overcome.
+ (NSDate *)convertParseDate:(NSDate *)sourceDate {
NSDateFormatter *dateFormatter = [NSDateFormatter new];
NSString *input = (NSString *)sourceDate;
dateFormatter.dateFormat = #"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
// Always use this locale when parsing fixed format date strings
NSLocale* posix = [[NSLocale alloc] initWithLocaleIdentifier:#"en_US_POSIX"];
dateFormatter.locale = posix;
NSDate *convertedDate = [dateFormatter dateFromString:input];
assert(convertedDate != nil);
return convertedDate;
}

Related

Check if string is in format dd.mm.yyyy

I wanna check if a string is in format dd.mm.yyyy, like
if(myString is in format dd.mm.yyyy) {
NSLog(#"Ok");
}
else{
NSLog(#"No");
You can use a code like this:
NSString *dateString = #"01.01.1970";
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
dateFormatter.dateFormat = #"dd.MM.yyyy";
if ([dateFormatter dateFromString:dateString]) {
//YES
}
Why NSDateFormatter: it was created to parse dates. Plus, this way you get an easy way to change date format and as a side effect, get a valid NSDate object that you can work with.
However, if you are intending to use this dateFormatter often (for instance, it is used inside a UITableViewCell), you might encounter performance issues. If you did, and you're sure that NSDateFormatter is what is causing it (Instruments might help you here), you could reuse your dateFormatter. For example, like so:
NSString *dateString = #"01.01.1970";
static NSDateFormatter *dateFormatter = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
dateFormatter = [[NSDateFormatter alloc] init];
dateFormatter.dateFormat = #"dd.MM.yyyy";
});
if ([dateFormatter dateFromString:dateString]) {
//YES
}
It seems (it is my guess based on tests that I ran) that the first call to dateFromString triggers lazy initialization inside the dateFormatter, and it takes some time (several tests results are below). If you reuse it, the NSDateFormatter initialization is performed only once.
On my iPhone 5S with iOS 8.4.1 the dispatch_once version works about 10 times faster:
dispatch_once: 0.536 seconds, recreation: 5.475 seconds
(time to parse 10.000 dates)
In the same test #zaph got a 2x performance difference on his iPhone 6S with iOS 9.x:
dispatch_once: 0.57 seconds, recreation: 1.01 seconds
(time to parse 10.000 dates)
So, it depends on the device, the OS version and on how intensively you're going to use that dateFormatter. In some cases the second code fragment might be an overkill. But I encountered situations where it provided a noticeable performance boost.
UPD: Also note that quite often American dates are also written with a dot as a separator, so what seems to be a dd.MM.yyyy date might actually be a MM.dd.yyyy date (e.g. April 5th instead of May 4th). For dates with dd > 12 it's not an issue, but otherwise it is. I don't think there's anything you could do about that without an additional context though, so just be warned.
You can using Regex to check it.
For example this code using Regex to check "mm.dd.yyyy":
NSError *error = NULL;
NSString *expForreg = #"^((((0[13578])|([13578])|(1[02]))[\\.](([1-9])|([0-2][0-9])|(3[01])))|(((0[469])|([469])|(11))[\\.](([1-9])|([0-2][0-9])|(30)))|((2|02)[\\.](([1-9])|([0-2][0-9]))))[\\.]\\d{4}$|^\\d{4}$";
NSRegularExpression *regex = [NSRegularExpression
regularExpressionWithPattern:expForreg
options:NSRegularExpressionCaseInsensitive
error:&error];
NSArray *matches = [regex matchesInString:#"10.30.1989" options:0 range:NSMakeRange(0, [#"10.30.1989" length])];
if(matches && matches.count > 0)
{
NSLog(#"Ok");
}else
{
NSLog(#"No");
}
Hope this help!
You can use a regular expression:
NSString *testString = #"11.22.3333";
NSString *pattern = #"^\\d{2}\\.\\d{2}\\.\\d{4}$";
NSRange range = [testString rangeOfString:pattern options:NSRegularExpressionSearch];
if (range.location != NSNotFound) {
NSLog(#"Ok");
}
else{
NSLog(#"No");
}
Bit that is not really a good date test.

Find NSDate in sorted NSArray

I have a sorted array of NSDate objects. What I'd like to do is create a method that takes in a date and returns YES or NO depending on whether that date can be found in the date array.
NSArray *dateArray;
-(BOOL)arrayContainsDate(NSDate *)d {
// return YES if d is found in dateArray
}
I know how to do this by going through each element of the array one by one, but I need a quicker way.
When determining whether an object exists in a set of objects, consider using an NSSet/NSMutableSet object (or NSOrderedSet/NSMutableOrderedSet if you are developing for Mac OS X 10.7 or iOS 5.0 and want to retain the order of elements in the set). An NSSet container is designed for efficient lookups. When an object has a decent hash (which most Foundation objects do), the lookup is effectively O(1), which is faster than a binary search.
NSSet *dateSet = [NSSet setWithArray:dateArray];
if ([dateSet containsObject:date1])
{
// do something
}
Note that it is important to construct the set once rather than converting it from an array each time, or else you'll lose any performance benefit.
For more information, see here.
Since you are wanting to check for specified dates regardless of time, you need to truncate the date values before adding them to the set. For example (pick better names, this is only an example):
// potentially add as a category method to NSDate
- (NSDate *) dateByTruncatingTime
{
NSDateComponents *components = [[NSCalendar currentCalendar] components:NSYearCalendarUnit|NSMonthCalendarUnit|NSDayCalendarUnit fromDate:aDate];
return [[NSCalendar currentCalendar] dateFromComponents:components];
}
// ------------- somewhere else -------------
- (void) actionHappened
{
[myMutableSet addObject:[[NSDate date] dateByTruncatingTime]];
}
- (BOOL) didActionHappenOnDate:(NSDate *) aDate
{
return [myMutableSet containsObject:[aDate dateByTruncatingTime]];
}
you can use hash.
NSDictionary *dict = {[NSString stringWithFormat:#"%#",date1]:#"",[NSString stringWithFormat:#"%#",date2]:#""}
- (BOOL) containsDate:(NSDate*)_d
{
return [dict valueForKey:[NSString stringWithFormat:#"%#",_d]] != nil;
}
As your array is sorted use binary search. Start by comparing your date with the middle element of the array (use compare:) - if it is equal you found it. If it is less or greater then repeat considering just the first half or second half of the array. Etc.
You do this by using two indices - min and max of the range you are considering. Calculate the middle index, do the comparison and then your new range to consider is min, middle-1 or middle+1, max.
This algorithm is O(log2 N) - you won't do better.
Code is left as an exercise!
HTH

EKEventStore time zone isn't GMT

I'm trying to retrieve all the events for a single day from an instance of EKEventStore using eventsMatchingPredicate:, but as I read, the NSDate objects are by default set to GMT while the EKEventStore isn't. So my question is how do I change the timezone of the EKEventStore or adjust the NSDate objects so that the times aren't off for each timezone?
For example, I'm in GMT -0600, and clicking on January 16th and 17th in the TKCalendarMonthView I'm using for a calendar UI shows Martin Luther King Day on both dates. The start time is 6 AM on 16 January, and the end time is 5:59 AM on 17 January (as a result of my timezone), rather than beginning at 12:00 AM and lasting until 11:59 PM. The code used to retrieve events follows.
- (void)calendarMonthView:(TKCalendarMonthView *)monthView didSelectDate:(NSDate *)d {
// Update tableData with event data from date
[tableData removeAllObjects];
NSArray *a = [systemCalendar eventsMatchingPredicate:[systemCalendar predicateForEventsWithStartDate:d endDate:[NSDate dateWithTimeInterval:84600 sinceDate:d] calendars:nil]];
[tableData addObjectsFromArray:a];
[self.eventsTable reloadData];
}
Given that I'm on a short timeline, I came up with a solution, and it seems to work. My only concern is that I had to multiply the offset by -1 even though the time interval offset itself is negative. This doesn't make sense because we are trying to subtract from the NSDate rather than add to it. A positive number minus a negative number gives us a larger number, so I'm slightly worried about the GMT zones on the other side of the PM and wondering whether I should actually be multiplying all time intervals by -1. Anyone have any thoughts?
- (void)calendarMonthView:(TKCalendarMonthView *)monthView didSelectDate:(NSDate *)d {
[NSTimeZone resetSystemTimeZone];
NSTimeZone *tz = [NSTimeZone systemTimeZone];
NSArray *comps = [[tz description] componentsSeparatedByString:#" "];
NSTimeInterval offset = (NSTimeInterval)[[comps lastObject] floatValue];
if (offset < 0) {
offset *= -1;
}
NSDate *startDate = [d dateByAddingTimeInterval:offset];
NSArray *a = [systemCalendar eventsMatchingPredicate:[systemCalendar predicateForEventsWithStartDate:startDate endDate:[NSDate dateWithTimeInterval:84600 sinceDate:startDate] calendars:nil]];
NSLog(#"Events for the date: %#", a);
[tableData addObjectsFromArray:a];
[self.eventsTable reloadData];
}

What's the simplest way to parse RFC3339 date string in iOS?

Youtube API returns date string in RFC3339 format. I found how to parse it on manual, anyway, this is too long.
- (NSString *)userVisibleDateTimeStringForRFC3339DateTimeString:(NSString *)rfc3339DateTimeString
// Returns a user-visible date time string that corresponds to the
// specified RFC 3339 date time string. Note that this does not handle
// all possible RFC 3339 date time strings, just one of the most common
// styles.
{
NSString * userVisibleDateTimeString;
NSDateFormatter * rfc3339DateFormatter;
NSLocale * enUSPOSIXLocale;
NSDate * date;
NSDateFormatter * userVisibleDateFormatter;
userVisibleDateTimeString = nil;
// Convert the RFC 3339 date time string to an NSDate.
rfc3339DateFormatter = [[[NSDateFormatter alloc] init] autorelease];
enUSPOSIXLocale = [[[NSLocale alloc] initWithLocaleIdentifier:#"en_US_POSIX"] autorelease];
[rfc3339DateFormatter setLocale:enUSPOSIXLocale];
[rfc3339DateFormatter setDateFormat:#"yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'"];
[rfc3339DateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
date = [rfc3339DateFormatter dateFromString:rfc3339DateTimeString];
if (date != nil) {
// Convert the NSDate to a user-visible date string.
userVisibleDateFormatter = [[[NSDateFormatter alloc] init] autorelease];
assert(userVisibleDateFormatter != nil);
[userVisibleDateFormatter setDateStyle:NSDateFormatterShortStyle];
[userVisibleDateFormatter setTimeStyle:NSDateFormatterShortStyle];
userVisibleDateTimeString = [userVisibleDateFormatter stringFromDate:date];
}
return userVisibleDateTimeString;
}
I can make a function contains this, but I want to know is there pre-defined way on Cocoa foundations or standard C or POSIX library to do this. And I want to use it if there it is. Can you let me know is there more simpler way? Or It will be very appreciate if you confirm this is most simple way :)
The pure stuff-that-comes-with-Cocoa way is exactly what you're doing. You can make this method both shorter and faster by creating the date formatters elsewhere, probably in init, and using/reusing them in this method.
You need two formats because fractional seconds are optional, and the timezone should be Z5, not Z. So you create two formatters with formats
#"yyyy'-'MM'-'dd'T'HH':'mm':'ssX5"
#"yyyy'-'MM'-'dd'T'HH':'mm':'ss.SSSSSSX5"
and try them both. That's obviously for RFC3339; your strings might not be in that format. Glad you didn't ask for RFC822 which is a pain to do correctly. But you should really have a method first that returns NSDate, because most uses don't actually need a string formatted for the user.
I had some problems with parsing RFC 3339 in Obj-c since the fractional seconds and zone seems to be optional.
The most reliable function that I found was this Gist (of which I am not the author): https://gist.github.com/mwaterfall/953664

2 NSDates that should be equal aren't?

I'm using the JSON library from Stig Brautaset(http://code.google.com/p/json-framework) and I need to serialize an NSDate. I was considering converting it into a string before JSONifying it, however, I ran into this weird behavior:
Why aren't these NSDates considered equal?
NSDate *d = [[NSDate alloc] init];
NSDate *dd = [NSDate dateWithString:[d description]];
NSLog(#"%#", d);
NSLog(#"%#", dd);
if( [d isEqualToDate:dd] ){
NSLog(#"Yay!");
}
When you describe the original date object you lose some sub-second precision from the original object — in other words, -description shaves off fractional seconds, and returns
A string representation of the receiver in the international format YYYY-MM-DD HH:MM:SS ±HHMM, where ±HHMM represents the time zone offset in hours and minutes from GMT
When you create a new date object based on the description, you get it in whole seconds because the string is only precise to a whole second. So -isEqualToDate: returns NO because there is a difference of a fraction of a second between your two date objects, which it's sensitive to.
This method detects sub-second differences between dates. If you want to compare dates with a less fine granularity, use timeIntervalSinceDate: to compare the two dates.
So you'd do something like this instead (NSTimeInterval measures in seconds):
if ([d timeIntervalSinceDate:dd] == 0) {
NSLog(#"Yay!");
}
isEqualToDate detects subseconds differences between dates, but the description method does not include subseconds.
Because they're not equivalent:
NSDate *d = [NSDate date];
NSDate *dd = [NSDate dateWithString:[d description]];
NSLog(#"%f", [d timeIntervalSinceReferenceDate]);
NSLog(#"%f", [dd timeIntervalSinceReferenceDate]);
Produces:
2011-04-28 11:58:11.873 EmptyFoundation[508:903] 325709891.867788
2011-04-28 11:58:11.874 EmptyFoundation[508:903] 325709891.000000
In other words, the +dateWithString: method does not maintain sub-second precision.