NSFetchRequest fails when using date as predicate - objective-c

I am trying to fetch all records from a core data entity that was created "today", I have a field in the Entity called createdDate that stores the date as string using local timeZone of the device in the following format
2017-06-26T11:06:43+08:00
I create the following date string for comparison:
NSDateFormatter *dateFormatterDayOnly = [[NSDateFormatter alloc] init];
[dateFormatterDayOnly setDateFormat:#"yyyy-MM-dd"];
[dateFormatterDayOnly setTimeZone:[NSTimeZone localTimeZone]];
NSString *todaysDate = [dateFormatterDayOnly stringFromDate:[NSDate date]];
Debugs tell me the Predicate looks ok as follows
2017-06-26 11:33:22.277 NWMobileTill[1842:482539] -[EodView tillTotalCashIn] todaysDate:2017-06-26
and then I create the predicate as follows
NSPredicate *predicateTodaysDate = [NSPredicate predicateWithFormat:#"createdDate like[cd] %#", todaysDate];
fetchPts.predicate = predicateTodaysDate;
NSArray *todaysTendersArray = [[context executeFetchRequest:fetchPts error:&errorPts] mutableCopy];
But this returns 0 hits
I would have expected this to match all that were created today.
What do I need to change to make this return all records created today?

instead of like[cd] use BEGINSWITH
[NSPredicate predicateWithFormat:#"createdDate BEGINSWITH %#", todaysDate];
or regular expression evaluation:
NSString* regex = [NSString stringWithFormat:#"^%#.*$", todaysDate];
[NSPredicate predicateWithFormat:#"createdDate MATCHES %#", regex];

Storing a date object as a string is WRONG. It is not a little wrong. It is not 'reasonable people will disagree wrong. It is complete wrong.
As a string you can't compare which is greater. You can't select a range. You can't sort by date. And on top of that the one thing that you would think a string would do better - displaying the date as a string - is also harder! Because of localization you have to parse the string into a date and then back into a correct localized string.
If you haven't release your app yet then simply change the type in the core-data model, delete the app, and reinstall. Make sure that everyone else also deletes the app or it will crash for them.
If you have already release you app then create a new model version with ANOTHER property with the createdDate as a date. When the app starts (but not in app delegate - because it may take more than 5 seconds and if it does then the OS will force quit the app) go though the database and parse all of the createdDate Strings and put them into the createdDate date field.
To do a search when the value is a date get the start and end of the date and search for values that are between the two.
NSDate* startOfDay = [self.calendar startOfDayForDate:[NSDate date]];
NSDate* endOfDay = [self.calendar dateByAddingUnit:NSCalendarUnitDay value:1 toDate:startOfDay options:0];
NSPredicate *predicate = [NSCompoundPredicate andPredicateWithSubpredicates:#[[NSPredicate predicateWithFormat:#"createdDate > %#", startOfDay],[NSPredicate predicateWithFormat:#"createdDate < %#", endOfDay]]];

Related

NSArray filter using NSPredicate, comparing dates

I have a Person entity where the attribute date_of_birth is declared as NSString. If I have an array of 'Person' instances and I need to filter them down to only those whose date_of_birth is less that 25/11/2005 I am using a predicate whose format when NSLogged is:
FUNCTION(date_of_birth, "yyyy_MM_dd_dateFormat") <[cd] CAST(154575908.000000, "NSDate")
where yyyy_MM_dd_dateFormat() is a category method on NSString that returns the string instance as a date.
I am not getting the expected results. Am I doing something wrong, and what is the bit where it says CAST(154575908.000000, "NSDate" and is that valid?
UPDATE: changing the date_of_birth attribute type to NSDate is not an option at the moment due to the size, maturity and complexity of the project.
Dates are best represented by NSDate, which implements inequality via earlierDate: and laterDate: methods. These answer the earlier/later date between the receiver and the parameter.
Your conversion method probably looks something like this ...
// return an NSDate for a string given in dd/MM/yyyy
- (NSDate *)dateFromString:(NSString *)string {
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:#"dd/MM/yyyy"];
return [formatter dateFromString:string];
}
The array can be filtered with a block-based NSPredicate that uses NSDate comparison...
NSDate *november25 = [self dateFromString:#"25/11/2005"];
NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(Person *person, NSDictionary *bind){
// this is the important part, lets get things in NSDate form so we can use them.
// of course it would be quicker to alter the data type, but we can covert on the fly
NSDate *dob = [self dateFromString:person.date_of_birth];
return date_of_birth == [november25 earlierDate:dob];
}];
// assumes allPeople is an NSArray of Person objects to be filtered
// and assumes Person has an NSString date_of_birth property
NSArray *oldPeople = [allPeople filteredArrayUsingPredicate:predicate];
Here, person date is type of NSDate.
NSPredicate *predicate2 = [NSPredicate predicateWithFormat:#"date >= %#",person.date];
list=[list filteredArrayUsingPredicate:predicate2];
But in case you saved with NSString type , than you need to cast comparison date into string than use like instead of >=

compare two dates in 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;
}

How do you convert date time including milliseconds and timezone info to NSDate

I'm trying to convert a string to an NSDate,
however the format always comes out as nil
The date I'm trying to convert is:
2012-08-16T16:20:52.619000+00:00
The date format I'm trying is:
#"yyyy-MM-dd'T'HH:mm:ss.SSSSSSZZZZ"
If I change the date to:
#"2012-08-16T16:20:52.619000+0000" // removing the : from +00:00
it works a treat, however I would
(I have also tried
#"yyyy-MM-dd'T'HH:mm:ss.SSSSSSZZ:ZZ"
#"yyyy-MM-dd'T'HH:mm:ss.SSSSSSZZ':'ZZ"
but that didn't work either).
Is it even possible to do this without doing string manipulation and removing the final ":"?
I did a final search around this and found out that you have to use
getObjectValue
rather than
dateFromString
In case someone else runs in to this issue, I post my method for converting such strings to NSDate
+ (NSDate *)dateFromString:(NSString *)dateString {
NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setDateFormat:#"yyyy-MM-dd'T'HH:mm:ss.SSSSSSZ"];
NSDate *theDate = nil;
NSError *error = nil;
[dateFormat getObjectValue:&theDate forString:dateString range:nil error:&error];
[dateFormat release];
return theDate;
}
It looks like you are using ISO 8601 formatted dates. If you are getting these from a web service, the format changes according to the format. Check this out:
http://boredzo.org/iso8601parser/
This will convert dates according to the format, and even when the format changes slightly.
How about something like
[formatter setDateFormat:#"yyyy-MM-dd'T'HH:mm:ss'Z'"];
the Z has to be in single quotes.

Error when combining a subquery in a NSPredicate with valueForKeyPath

I store time periods in Core Data. Each time period has an DateTime attribute called EndDate. I am trying to get the maximum end date, which is before (<) the date specified.
This is how I have coded this using a subquery and ValueForKeyPath:
NSString *keyPath = [NSString stringWithFormat:#"SUBQUERY(SELF, $x, $x.EndDate < %#).#max.EndDate", date];
IBFinPeriod *periodBeforeCurrentDate = [self.finperiod valueForKeyPath:keyPath];
However, when running this code, I get the runtime error: the entity IBFinPeriod is not key value coding-compliant for the key "SUBQUERY(SELF, $x, $x".'
What is wrong with my code?
Do I need to specify the subquery differently?
Thank you for your help!!
You could use a fetch request with fetchLimit set to 1 and a descending sort descriptor.
If you insist on the valueForKeyPath: I would first filter the results with filteredArrayUsingPredicate: (with a straight forward predicate selecting the records with dates prior to your date) and then simply using #"#max.EndDate" as the key path.
If you need the entire object rather than just the date, just sort your set:
NSSet *periodsBeforeCurrentDate = [self.finperiod
filteredSetUsingPredicate:[NSPredicate predicateWithFormat:
#"EndDate < %#", date]];
if (periodsBeforeCurrentDate.count) {
*sort = [NSSortDescriptor sortDescriptorWithKey:#"EndDate" ascending:NO];
NSArray *sortDescriptors = [NSArray arrayWithObject:sort];
IBFinPeriod *lastPeriodBeforeCurrentDate =[[periodsBeforeCurrentDate
sortedArrayUsingDescriptors:sortDescriptors] objectAtIndex:0];
}
In my opinion it would be easier to just fetch.

Getting an NSDate via NSDateFormatter from a string of unknown format

I know there are many NSDateFormatter questions on here, so if I duplicate, I'm sorry. I just couldn't find anything that was quite what Im asking.
From all the questions here on SO, I have come to the conclusion that -[NSDateFormatter dateFromString:] will always return NULL if your formatter object doesn't have the correct date format. How do you get a date from a string if you don't know the format? I'm trying to get a date from a UITextField.
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setLocale:[NSLocale currentLocale]];
[formatter setLenient:YES];
NSDate *tempDate = [formatter dateFromString:self.birthdayTxtfld.text];
self.currentCustomer.birthday = ([self.birthdayTxtfld.text isEqualToString:#""]) ? NULL : tempDate;
[formatter release];
tempDate is always NULL.
I think your taking the wrong approach. I would on the other hand restrict and format the UITextField so the user has to enter the date in a specific format. Or just use a date picker. There are just way too may different inputs the user could give you.
Or you can read through this: NSDate
Another option is to create a list of accepted date formats:
#define DATEFORMATS #[#"MM/dd/yyyy", #"MM/dd/yy",...
Then Have a method that you pass the date string to and check if you can format it:
+ (NSDateFormatter*)getDateFormat:(NSString*)dateString {
NSArray *dateFormats = DATEFORMATS;
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
NSDate *date = nil;
for (NSString *dateFormat in dateFormats) {
[formatter setDateFormat:dateFormat];
date = [formatter dateFromString:dateString];
if (date) {
return formatter;
}
}
return nil;
}
If you get nil its not a date or its in a format you don't support. Otherwise you will have the correct format you need. You can switch this around to return the date instead of the format. I have it this way because I needed the format not the date for a project.
While I'm in agreement with #Jaybit that you probably need to ditch the text box and use a better input, the answer to this specific question lies in some crafty string parsing. Whenever you are doing string parsing, RegEx is your friend. Web developers end up having to do this crap all the time. This example is in JavaScript, but the RegEx ought to be portable enough that it works in ObjC:
http://www.codingoptimist.com/2009/07/using-javascript-and-regex-to-parse.html
You can do this with RegExKit or NSRegularExpression