Core Data Predicate Date Comparison - objective-c

Im trying to fetch all the objects in an entity matching a user selectedDate (it's an NSDate).
The Core Data code is fine but my predicate keeps returning 0 results, the dates are the same in the database as the user is selecting.
How should the selectedDate be compared with a date from an entity using a predicate?
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(eDate = %#)", selectedDate];

Your predicate looks to be fine.
The reason you're finding zero result are returned however may be that the dates aren't entirely the same.
For example:
05/04/2012 13:37:00 will not match with 05/04/2012 13:37:01 as these two values are not exactly the same.
Do you want to check the date (day, month, year) as well as the time?
If you only want to check the date, you should create a start date and end date and compare them using the user selected date as a frame of reference.
Something similar to this should create a date and time for 00:00:00.
//gather current calendar
NSCalendar *calendar = [NSCalendar currentCalendar];
//gather date components from date
NSDateComponents *dateComponents = [calendar components:(NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit) fromDate:[NSDate date]];
//set date components
[dateComponents setHour:0];
[dateComponents setMinute:0];
[dateComponents setSecond:0];
//return date relative from date
return [calendar dateFromComponents:dateComponents];
Create another date by setting the hours, minutes and seconds to 23:59:59 and check that your user selected date falls between these ranges.
[NSPredicate predicateWithFormat:#"date BETWEEN %#", [NSArray arrayWithObjects:startOfDay, endOfDay, nil]]

While in human communication date often is equal with day, it is not the same with NSDate objects: A NSDate represents a single moment in time. Dates that are different just in seconds aren't equal. And actually they dont represent days, month, year at all, as this is different from calendar system to calendar system
you must define for yourself if dates in the same minute, hour, day … are equal. So basically you must teach the program to allow some fuzziness.
here for minute resolution
NSDate *startDate = ....;
NSTimeInterval length;
[[NSCalendar currentCalendar] rangeOfUnit:NSMinuteCalendarUnit
startDate:&startDate
interval:&length
forDate:startDate];
NSDate *endDate = [startDate dateByAddingTimeInterval:length];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(eDate >= %#) AND (eDate < %#)", startDate, endDate];
For dates on the same day (aware of Timezones and Daylight Saving Times) you just would change this:
[[NSCalendar currentCalendar] rangeOfUnit:NSDayCalendarUnit
startDate:&startDate
interval:&length
forDate:startDate];

I can get working with some modifications to the accepted answer and using the iOS 8 API for create dates with time offset. The code:
NSCalendar *calendar = [[FFSharedCalendar singleton] getGregorianCalendar];
NSDate *startOfDay = [calendar dateBySettingHour:0 minute:0 second:0 ofDate:[NSDate date] options:0];
NSDate *endOfDay = [calendar dateBySettingHour:23 minute:59 second:59 ofDate:[NSDate date] options:0];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"createdAt > %# AND createdAt < %#", startOfDay, endOfDay];
NSArray* plans = [Plan MR_findAllWithPredicate:predicate];
Hope it helps someone

I've recently spent some time attempting to solve this same problem and have resolved the following, now updated for iOS 8 and above...
NSDate *dateDay = nil;
NSDate *dateDayStart = nil;
NSDate *dateDayNext = nil;
dateDay = <<USER_INPUT>>; //for example - selectedDate
dateDayStart = [[NSCalendar currentCalendar] startOfDayForDate:dateDay];
// dateDayNext EITHER
dateDayNext = [dateDayStart dateByAddingTimeInterval:(24 * 60 * 60)];
// dateDayNext OR
NSDateComponents *dateComponentDay = nil;
dateComponentDay = [[NSDateComponents alloc] init];
[dateComponentDay setDay:1];
dateDayNext = [[NSCalendar currentCalendar] dateByAddingComponents:dateComponentDay
toDate:dateDayStart
options:NSCalendarMatchNextTime];
...and the NSPredicate for the Core Data NSFetchRequest (as already shown above in other answers)...
[NSPredicate predicateWithFormat:#"(dateAttribute >= %#) AND (dateAttribute < %#)", dateDayStart, dateDayNext]]

I query in between 1 second above and below my actual Date. In my case its ok to add and subtract 1 second as I query for the restaurant orders so I know that it takes some time to punch a new order, for safe side I have 1 more orderNo field too.
let fetchRequest: NSFetchRequest = Order.fetchRequest()
let date_formatter = DateFormatter()
date_formatter.dateFormat = "yyyy-MM-dd-HH-mm-ss"
date_formatter.timeZone = TimeZone(abbreviation: "UTC")
let createdDate = date_formatter.date(from: clientCreatedStr)
let calendar = NSCalendar.current
//as matching exact same datetime doesnt return anything
let onesecondafter = calendar.date(byAdding: .second, value: 1, to: createdDate!)
let onesecondbefore = calendar.date(byAdding: .second, value: -1, to: createdDate!)
let predicate = NSPredicate(format: "offlineUniqueId == %# AND (clientCreatedDate >= %# AND clientCreatedDate <= %#)", offlineUniqueId, onesecondbefore! as NSDate, onesecondafter! as NSDate)
fetchRequest.predicate = predicate

Related

Realm date range query in Objective-C

I'm working with Realm and I have an array sorted by dates for all the entries of a specific card. I need to create a NSPredicate (or some other way) that will give me all the dates for that card in order for a specific month. My trouble is some months have different amounts of days in them.
Here is what I have so far.
// Filter specific card, create date and string for x axis
NSString *filteredName = [NSString stringWithFormat:#"%#", self.cardType];
NSDate *dateElement = [[NSDate alloc]init];
NSString *datedString = [[NSString alloc]initWithFormat:#""];
//Formatting datedString
NSDateFormatter *df = [[NSDateFormatter alloc] init];
df.dateFormat = #"MM/dd/YYYY";
NSSortDescriptor *dateSort = [[NSSortDescriptor alloc]initWithKey:#"date" ascending:YES];
NSArray *sortedArray = #[dateSort];
//self.orderHistory is all of the cards from the Realm database filtered down to a specific type of card.
for (Order *uniqueOrder in [self.orderHistory objectsWhere:#"name = [c] %#", filteredName]) {
// [self.uniqueCardArray addObject:uniqueOrder];
dateElement = uniqueOrder.date;
datedString = [df stringFromDate:dateElement];
[self.uniqueDatesForCardArray addObject:datedString];
}
[self.uniqueCardArray sortUsingDescriptor:sortedArray];
Code above gives me all the dates for a specific card I need the dates for a given month so I have tried this tacky solution but I would think there is something more elegant to say the least.
NSDate *todaysDate = [NSDate date];
NSTimeInterval secondsInMonth =- (60 * 60 * 24 * 365) / 12;
NSDate *dateOneMonthBack = [todaysDate dateByAddingTimeInterval:secondsInMonth];
NSPredicate *backDatesPred = [NSPredicate predicateWithFormat: #"date BETWEEN {%#, %#}", dateOneMonthBack, todaysDate];
Besides it being nasty code it will also take me back a month from the current date and not only back to the start of the month. Been looking at the Realm documentation, NSCalendar stuff, and NSPredicate guide and now my head is swimming trying to count for all the possible problems that can go wrong with this. Any advice or code snippets would be a great help.
I'm not sure whether I understand your question correctly, the following code is what you want?
To determine the date of the previous month, add NSDateComponet that has minus month to the date.
Realm supports BETWEEN query. So you can just query by two days.
NSDate *todaysDate = [NSDate date];
NSDateComponents *comps = [NSDateComponents new];
comps.month = -1;
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDate *dateOneMonthBack = [calendar dateByAddingComponents:comps toDate:todaysDate options:kNilOptions];
RLMResults *filteredHistory = [self.orderHistory objectsWhere:#"name = [c] %#", filteredName];
RLMResults *rangedHistory = [filteredHistory objectsWhere:#"date BETWEEN {%#, %#}", dateOneMonthBack, todaysDate];

Wrong Days Between Dates for Month of March

I have a very interesting problem: When calculating the number of days between two dates, my calculator gives the wrong results for the month of March only. I have two text fields, one for each date. If I enter 3/7/12 in date1, and 3/13/12 in date2, the result is 7, which is correct (I am counting the first day as well). But when I enter date1 = 3/7/12 and date2 = 3/14/12, the result is still 7, but it should be 8. Likewise, if I enter date1 = 3/7/12 and date2 = 3/23/12, the result should be 17, but it is 16. If I change the month to April so that date1 = 4/7/12 and date2 = 4/23/12, the result is 17. Every month is working as intended, only the month of March is giving me wrong results. Does anyone have any idea what I am missing? Is this a timezone problem? How do I fix it? Here is my code:
NSDateFormatter *dateFormatter1 = [[NSDateFormatter alloc] init];
[dateFormatter1 setDateFormat:#"MM/dd/yyyy"];
NSDate *startdate1 = [dateFormatter1 dateFromString: date1.text];
NSDateFormatter *dateFormatter2 = [[NSDateFormatter alloc] init];
[dateFormatter2 setDateFormat:#"MM/dd/yyyy"];
NSDate *enddate2 = [dateFormatter2 dateFromString: date2.text];
int start1 = [startdate1 timeIntervalSince1970];
int end2 = [enddate2 timeIntervalSince1970];
double difference12 = end2-start1;
int days12;
days12 =(int)((double)difference12/(3600.0*24.00)+1);
result12.text = [[NSString alloc] initWithFormat:#"%i", days12]
If you use NSCalendar, you won't need to deal with Daylight Savings Time calculation.
int days = [[[NSCalendar currentCalendar] components:NSDayCalendarUnit
fromDate:startDate1
toDate:endDate2
options:0] day] + 1;
Most like this is a daylight savings issue (March 11th, 2012). Set the date formatters' timezones to:
[NSTimeZone timeZoneForSecondsFromGMT:0];
This should fix the issue.
To add to others, please do not rely on number of seconds in a day being 86400 all the time, it won't. Be sure to watch Session 211 Performing Calendar Calculations from WWDC 2011.
You can think of NSDate as a point in time. If you want to calculate number of days between two dates, this is a calendar issue and there are more than one calendars (like the Mayan calendar).
What you need is NSDateComponents:
NSDate *dateToday = [NSDate date];
NSDateComponents *dateBComponent = [[NSDateComponents alloc] init];
[dateBComponent setDay:-10];
NSDate *date10DaysAgo = [[NSCalendar currentCalendar] dateByAddingComponents:dateBComponent
toDate:dateToday
options:0];
NSCalendar *calendar = [[[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar] autorelease];
NSDateComponents *components = [calendar components:NSDayCalendarUnit
fromDate:date10DaysAgo
toDate:dateToday
options:0];
NSLog(#"Difference: %d", components.day);

Get number of days between two NSDate dates in a particular timezone

I found the codes to calculate days difference between two dates here.
I write a method :
-(NSInteger)daysWithinEraFromDate:(NSDate *) startDate toDate:(NSDate *) endDate
{
NSCalendar *gregorian = [[NSCalendar alloc]
initWithCalendarIdentifier:NSGregorianCalendar];
NSInteger startDay=[gregorian ordinalityOfUnit:NSDayCalendarUnit
inUnit: NSEraCalendarUnit forDate:startDate];
NSInteger endDay=[gregorian ordinalityOfUnit:NSDayCalendarUnit
inUnit: NSEraCalendarUnit forDate:endDate];
return endDay-startDay;
}
This method has a problem: it can't consider the timezone thing. Even I add a line like this:
[gregorian setTimeZone:[NSTimeZone localTimeZone]];
My test code is like this:
NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setDateFormat:#"yyyy-MM-dd HH:mm:ss"];
NSString *strDate = #"2012-09-03 23:00:00";
NSDate *dateStart = [dateFormat dateFromString:strDate];
strDate = #"2012-09-04 01:00:00";
NSDate *dateEnd = [dateFormat dateFromString:strDate];
NSLog(#"Days difference between %# and %# is: %d days",[dateFormat stringFromDate:dateStart],[dateFormat stringFromDate:dateEnd],[self daysWithinEraFromDate:dateStart toDate:dateEnd]);
The result is:
Days difference between 2012-09-03 23:00:00 and 2012-09-04 01:00:00 is: 0 days
I want to get 1 day as result by the number of midnights between the two dates. My timezone is GMT +8. But this calculation is based on GMT, so I get the wrong days number. Is there anyway to solve this problem? Thank you.
Scott Lemmon's method can solve my problem. I rewrite my code like this:
-(NSInteger)daysWithinEraFromDate:(NSDate *) startDate toDate:(NSDate *) endDate
{
NSCalendar *gregorian = [[NSCalendar alloc]
initWithCalendarIdentifier:NSGregorianCalendar];
[gregorian setTimeZone:[NSTimeZone localTimeZone]];
NSDate *newDate1 = [startDate dateByAddingTimeInterval:[[NSTimeZone localTimeZone] secondsFromGMT]];
NSDate *newDate2 = [endDate dateByAddingTimeInterval:[[NSTimeZone localTimeZone] secondsFromGMT]];
NSInteger startDay=[gregorian ordinalityOfUnit:NSDayCalendarUnit
inUnit: NSEraCalendarUnit forDate:newDate1];
NSInteger endDay=[gregorian ordinalityOfUnit:NSDayCalendarUnit
inUnit: NSEraCalendarUnit forDate:newDate2];
return endDay-startDay;
}
If the time zone offset isn't working, how about just add or subtract it manually instead?
In your case NSDate *newDate = [oldDate dateByAddingTimeInterval:(-8 * 60 * 60)]; to subtract off your +8 hours.
Or if you want to find the GMT offset automatically as well then it would simply be NSDate *newDate = [oldDate dateByAddingTimeInterval:(-[[NSTimeZone localTimeZone] secondsFromGMT])
Another thought:
A perhaps easier solution would be to just disregard the time information altogether. Just set it to the same arbitrary number for both dates, then as long as the dates come from the same timezone you will always get the correct number of mid-nights between them, regardless of GMT offset.
What you really want is the NSDate method timeIntervalSinceDate:, and take that result and if it's more than 0 but less than 86400 (the number of seconds in a day), that's one day. Otherwise, divide your result by 86400 and you'll get the number of days.
The way you currently have your code, there's only 2 hours between the two days and that's why you are seeing a result of 0 and not one.
Edit - and to determine if midnight has happened, let's try this function I just wrote off the top of my head:
- (NSDate *) getMidnightDateFromDate: (NSDate *) originalDate
{
NSDateComponents *components = [[NSCalendar currentCalendar] components:NSIntegerMax fromDate:originalDate];
[components setHour:0];
[components setMinute:0];
[components setSecond:0];
NSDate *midnight = [[NSCalendar currentCalendar] dateFromComponents:components];
return(midnight);
}
- (BOOL) howManyDaysDifferenceBetween: startDate and: endDate
{
NSDate * firstMidnight = [self getMidnightDateFromDate: startDate];
NSDate * secondMidnight = [self getMidnightDateFromDate: endDate];
NSTimeInterval timeBetween = [firstMidnight timeIntervalSinceDate: secondMidnight];
NSInteger numberOfDays = (timeBetween / 86400);
return(numberOfDays);
}
which I'm basing off Dave Delong's answer to this question. No guarantees that my code will work (I didn't test it), but I think the concept is sound.

How to make a fetch request for entities that is added in the same day?

My application is an expense list that has entity like this:
Expense:
date [NSDate]
location [NSString]
name [NSString]
price [float]
I'ld like to show the expenses on UITableView group into section by month and later by date of month in another table view (drill down design). NSDate itself includes time so, the results that are filtered by date will also filter by time.
I've try:
NSPredicate *chooseDateAt = [NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
Expense *expense = (Expense *)evaluatedObject;
return expense.dateWithoutTime == preferedDateWithoutTime;
}];
But it has this error:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Unknown predicate type for predicate: BLOCKPREDICATE(0x12048)'
And I can't see the other ways to work it out without using predicateWithBlock, can anyone help?
1) Determine the start of the day (0:00 on the same day) --> startDate
2) Form the predicate like this:
[NSPredicate predicateWithFormat:#"date > %# && date < %#",
startDate,
[startDate dateByAddingTimeInterval:60*60*24]];
Actually, it has been pointed out that one should not use the seconds to add one day as I have done in dateByAddingTimeInterval. Better to use this as the end date:
NSDateComponents* dateComponents = [[NSDateComponents alloc]init];
[dateComponents setDay:1];
NSCalendar* calendar = [NSCalendar currentCalendar];
NSDate* endDate = [calendar dateByAddingComponents:dateComponents
toDate:startDate options:0];

Date comparison between two NSDate

This is my first time comparing dates in Objective-C. I've been searching the web for a while and all the examples I found involve building a NSDate from a string so I decided to ask a new question here...
My question is as follows:
I need to know if two NSDates are in the same day, ignoring the time. I got two NSArray's containing a set of dates and one by one I need to determine which one from the first NSArray is in the same day as in the second array.
- (void)setActiveDaysColor:(UIColor *)activeDaysColor
{
for (DayView *actualDay in _days)
{
NSDate *actualDayDate = [actualDay date];
for (NSDate *activeDayDate in self.dates)
{
// Comparison code
// If both dates are the same, tint actualDay with a different color
}
}
}
Thank you in advance and have a nice day.
Alex.
create new dates by omitting the time components. and use one of the compare methods
earlierDate:
laterDate:
compare:
isEqualToDate
example
NSCalendar *calendar = [NSCalendar currentCalendar];
NSInteger components = (NSDayCalendarUnit | NSMonthCalendarUnit | NSYearCalendarUnit);
NSDateComponents *firstComponents = [calendar components:components fromDate:firstDate];
NSDateComponents *secondComponents = [calendar components:components fromDate:secondDate];
NSDate *date1 = [calendar dateFromComponents:firstComponents];
NSDate *date2 = [calendar dateFromComponents:secondComponents];
NSComparisonResult result = [date1 compare:date2];
if (result == NSOrderedAscending) {
} else if (result == NSOrderedDescending) {
} else {
}
-(BOOL)isSameDay:(NSDate*)date1 otherDay:(NSDate*)date2 {
NSCalendar* calendar = [NSCalendar currentCalendar];
unsigned unitFlags = NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit;
NSDateComponents* comp1 = [calendar components:unitFlags fromDate:date1];
NSDateComponents* comp2 = [calendar components:unitFlags fromDate:date2];
return [comp1 day] == [comp2 day] &&
[comp1 month] == [comp2 month] &&
[comp1 year] == [comp2 year];}
Check NSDate documentation, These are the methods to compare date
isEqualToDate
earlierDate
laterDate
compare
in your case
if([actualDayDate isEqualToDate:activeDayDate])
{
}
Thanks for all your answers.
I found a cleaner answer to my question answered in a totally unrelated post but that actually works perfectly.
if ((unsigned int)[actualDayDate timeIntervalSinceDate:activeDayDate] / 60 / 60 / 24 == 0)
{
// do Stuff
}
Instead of the loops that you have in your code you could use a predicate to filter out all the objects that are today. Filtering out what dates are today is done by comparing it to the beginning of today and the end of today.
You can set any NSDate to the beginning of that date like this (see this answer)
NSDate *beginDate = [NSDate date];
[[NSCalendar currentCalendar] rangeOfUnit:NSDayCalendarUnit startDate:&beginDate interval:NULL forDate:beginDate];
Then to get the end date you simply add one day to it. Don't add days by calculating the number of seconds. This won't work with daylight savings! Do it like this (also see this answer):
NSDateComponents *oneDay = [[NSDateComponents alloc] init];
[oneDay setDay:1];
// one day after begin date
NSDate *endDate = [[NSCalendar currentCalendar] dateByAddingComponents:oneDay toDate:beginDate options:0];
Now that you have the two dates that define the range for today you can filter all your DayViews using a NSPredicate to get a new array of all the DayViews who's day is today, like this (see this answer):
// filter DayViews to only include those where the day is today
NSArray *daysThatAreToday = [_days filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"(date >= %#) AND (date <= %#)", beginDate, endDate]];
Now you can apply the tint color to all the DayViews by enumerating the new array (that contains todays DayViews)
[daysThatAreToday enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
// Set the tint color here...
}];
This, in my opinion, is a clean but more importantly a correct way of solving your problem. It reads clearly and handles daylight savings and other calendars then gregorian. It can also easily be reused if you want to tint all the DayViews for a certain week (or any other time period).