NSArray filter using NSPredicate, comparing dates - objective-c

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 >=

Related

NSFetchRequest fails when using date as predicate

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]]];

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.

Objective C: Questions regarding time and array

Q1. NSDate *now = [NSDate date];
NSLog(#"Current date:%#",now);
The time shown is 4 hours ahead of the system time. I am wondering why is it so and how can I correct it?
Q2.. In C/C++, strings are treated as an array of characters. Is it the case in Objective-C also?
NSDate *now = [NSDate date];
NSLog(#"Current date:%#",now);
it's "now" not "date".
A1: NSLog(#"%#", now) is effectively the same as NSLog(#"%#", [now description]). The NSDate object doesn't care what the timezone is, so its description method will just give you the time in UTC. If you need to format with the right timezone and locale, you'll need to use an NSDateFormatter object to convert it to a nicely formatted string first.
A2: Yes and no, but mostly no. You can do this:
char *cString = "I am a C string";
to create a C string, which you can treat exactly as you would in C. That's something you very rarely see in Objective-C, though, except when it's absolutely necessary. The "normal" way to use strings is with instances of NSString or NSMutableString, which are fully-fledged objects:
NSString *normalString = #"I'm above all that."; (note the # symbol)

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

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.