Core Data comparisons within aggregate functions - objective-c

Core Data entity that has two properties on it: startDay and startDateTime. Both properties are NSDate objects. startDay has its time components set to midnight, and startDateTime designates a date with a time. Both “calendar days” within startDay and startDateTime are the same.
Here are some objects
(
{
startDateTime = "2014-05-27 08:00:00 +0000";
startDay = "2014-05-27 00:00:00 +0000";
},
{
startDateTime = "2014-05-27 13:00:00 +0000";
startDay = "2014-05-28 00:00:00 +0000";
}
)
Need to get objects grouped by startDay. Need to know if there is event in the "morning" or "afternoon".
Here's how to get results grouped by startDay and have earliest and latest daily events reported.
- (void)fetchEventDays
{
NSExpression *startDateTimeExpression = [NSExpression expressionForKeyPath:#"startDateTime"];
NSExpression *minStartDateTime = [NSExpression expressionForFunction:#"min:" arguments:[NSArray arrayWithObject:startDateTimeExpression]];
NSExpression *maxStartDateTime = [NSExpression expressionForFunction:#"max:" arguments:[NSArray arrayWithObject:startDateTimeExpression]];
NSExpressionDescription *minStartDateTimeExpression = [[NSExpressionDescription alloc] init];
minStartDateTimeExpression.name = #"minEventStartTime";
minStartDateTimeExpression.expression = minStartDateTime;
minStartDateTimeExpression.expressionResultType = NSDateAttributeType;
NSExpressionDescription *maxStartDateTimeExpression = [[NSExpressionDescription alloc] init];
maxStartDateTimeExpression.name = #"maxEventStartTime";
maxStartDateTimeExpression.expression = maxStartDateTime;
maxStartDateTimeExpression.expressionResultType = NSDateAttributeType;
NSManagedObjectContext *context = [RKObjectManager sharedManager].managedObjectStore.mainQueueManagedObjectContext;
NSEntityDescription* entity = [NSEntityDescription entityForName:#"NIModelScheduleData" inManagedObjectContext:context];
NSAttributeDescription* startDayDesc = [entity.attributesByName objectForKey:#"startDay"];
NSFetchRequest* fetch = [[NSFetchRequest alloc] init];
fetch.entity = entity;
fetch.propertiesToFetch = [NSArray arrayWithObjects:startDayDesc, minStartDateTimeExpression, maxStartDateTimeExpression, nil];
fetch.propertiesToGroupBy = [NSArray arrayWithObject:startDayDesc];
fetch.resultType = NSDictionaryResultType;
NSError *error = nil;
NSArray *results = [context executeFetchRequest:fetch error:&error];
NSLog(#"%#", results);
}
That code is returning
(
{
maxEventStartTime = "2014-05-27 13:00:00 +0000";
minEventStartTime = "2014-05-27 08:00:00 +0000";
startDay = "2014-05-27 00:00:00 +0000";
},
{
maxEventStartTime = "2014-05-28 10:00:00 +0000";
minEventStartTime = "2014-05-28 09:00:00 +0000";
startDay = "2014-05-28 00:00:00 +0000";
}
)
It'd be way cooler if it looked like this
(
{
eveningEvent = YES;
morningEvent = YES;
startDay = "2014-05-27 00:00:00 +0000";
},
{
eveningEvent = NO;
morningEvent = YES;
startDay = "2014-05-28 00:00:00 +0000";
}
)
How can I modify my fetch request to get a date comparison into my NSExpression (or NSExpressionDescription?), so that Core Data checks if min/maxEventStartTime are before/after noon, and returns a BOOL instead of the actual NSDate object.

Related

Core Data group by NSDate without Time

I have start Date attribute in core data, and i want to fetch the items along with grouping according to startDate,
But startDate is basically having timeComponent in it, but i want grouping to be based on yyyy-mm-dd,
This is the code i am using
NSError *error = nil;
NSFetchRequest *request = [NSFetchRequest new];
NSManagedObjectContext *context = self.managedObjectContext;
request.entity = [CalendarItem entityInManagedObjectContext:context];
NSExpression *startExpr = [NSExpression expressionForKeyPath:#"start"];
NSExpression *countExpr = [NSExpression expressionForFunction:#"count:" arguments:[NSArray arrayWithObject:startExpr]];
NSExpressionDescription *exprDesc = [[NSExpressionDescription alloc] init];
[exprDesc setExpression:countExpr];
[exprDesc setExpressionResultType:NSInteger64AttributeType];
[exprDesc setName:#"count"];
[request setPropertiesToGroupBy:#[#"start"]];
[request setPropertiesToFetch:[NSArray arrayWithObjects:#"start", exprDesc, nil]];
[request setResultType:NSDictionaryResultType];
NSArray *results = [self.managedObjectContext executeFetchRequest:request error:&error];
This is the Output i am getting:
Printing description of results:
<_PFArray 0x600001e3a700>(
{
count = 1;
start = "2021-09-14 03:30:00 +0000";
},
{
count = 1;
start = "2021-09-14 04:00:00 +0000";
},
{
count = 1;
start = "2021-09-16 09:30:00 +0000";
},
{
count = 1;
start = "2021-11-11 00:00:00 +0000";
},
{
count = 1;
start = "2021-11-11 04:00:00 +0000";
},
{
count = 1;
start = "2021-11-11 06:00:00 +0000";
},
{
count = 1;
start = "2021-11-12 00:00:00 +0000";
}
)
Expected Result:
{
count = 2
start = 2021-09-14
}
{
count = 1
start = 2021-09-16
}
{
count = 3
start = 2021-11-11
}
{
count = 1;
start = "2021-11-12
}
Core Data "date" properties, as you've found, are actually timestamps. They include the time of day, even though they're called "date". You can't tell Core Data to use only part of the value of a date attribute-- it's all or nothing.
To get the kind of grouping you want, you need to create another property where the value is only the value you need instead of a timestamp that includes extra details you don't need. Here, that would be a date where the hour, minute, and second are all set to zero, so that those details don't affect grouping.
One way to do this is to make a new date object with that change and save it in a new property. You could call it trimmedDate. Then any time you set the date on an instance, also set trimmedDate, with code that's something like
NSDate *trimmed = [[NSCalendar currentCalendar] dateBySettingHour:0 minute:0 second:0 ofDate:date options:0];
Then use this new value for grouping.

Get total step count for every date in HealthKit

What's the best way to get a total step count for every day recorded in HealthKit.
With HKSampleQuery's method initWithSampleType (see below) I can set a start and end date for the query using NSPredicate, but the method returns an array with many HKQuantitySamples per day.
- (instancetype)initWithSampleType:(HKSampleType *)sampleType
predicate:(NSPredicate *)predicate
limit:(NSUInteger)limit
sortDescriptors:(NSArray *)sortDescriptors
resultsHandler:(void (^)(HKSampleQuery *query,
NSArray *results,
NSError *error))resultsHandler
I guess I can query all recorded step counts and go through the array and calculate the total step count for each day, but I'm hoping for an easier solution as there will be thousands of HKSampleQuery objects. Is there a way to have initWithSampleType return a total step count per day?
You should use HKStatisticsCollectionQuery:
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *interval = [[NSDateComponents alloc] init];
interval.day = 1;
NSDateComponents *anchorComponents = [calendar components:NSCalendarUnitDay | NSCalendarUnitMonth | NSCalendarUnitYear
fromDate:[NSDate date]];
anchorComponents.hour = 0;
NSDate *anchorDate = [calendar dateFromComponents:anchorComponents];
HKQuantityType *quantityType = [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount];
// Create the query
HKStatisticsCollectionQuery *query = [[HKStatisticsCollectionQuery alloc] initWithQuantityType:quantityType
quantitySamplePredicate:nil
options:HKStatisticsOptionCumulativeSum
anchorDate:anchorDate
intervalComponents:interval];
// Set the results handler
query.initialResultsHandler = ^(HKStatisticsCollectionQuery *query, HKStatisticsCollection *results, NSError *error) {
if (error) {
// Perform proper error handling here
NSLog(#"*** An error occurred while calculating the statistics: %# ***",error.localizedDescription);
}
NSDate *endDate = [NSDate date];
NSDate *startDate = [calendar dateByAddingUnit:NSCalendarUnitDay
value:-7
toDate:endDate
options:0];
// Plot the daily step counts over the past 7 days
[results enumerateStatisticsFromDate:startDate
toDate:endDate
withBlock:^(HKStatistics *result, BOOL *stop) {
HKQuantity *quantity = result.sumQuantity;
if (quantity) {
NSDate *date = result.startDate;
double value = [quantity doubleValueForUnit:[HKUnit countUnit]];
NSLog(#"%#: %f", date, value);
}
}];
};
[self.healthStore executeQuery:query];
Port to Swift with no dependency to SwiftDate library
let calendar = NSCalendar.current
let interval = NSDateComponents()
interval.day = 1
var anchorComponents = calendar.dateComponents([.day, .month, .year], from: NSDate() as Date)
anchorComponents.hour = 0
let anchorDate = calendar.date(from: anchorComponents)
// Define 1-day intervals starting from 0:00
let stepsQuery = HKStatisticsCollectionQuery(quantityType: stepsCount!, quantitySamplePredicate: nil, options: .cumulativeSum, anchorDate: anchorDate!, intervalComponents: interval as DateComponents)
// Set the results handler
stepsQuery.initialResultsHandler = {query, results, error in
let endDate = NSDate()
let startDate = calendar.date(byAdding: .day, value: -7, to: endDate as Date, wrappingComponents: false)
if let myResults = results{
myResults.enumerateStatistics(from: startDate!, to: endDate as Date) { statistics, stop in
if let quantity = statistics.sumQuantity(){
let date = statistics.startDate
let steps = quantity.doubleValue(for: HKUnit.count())
print("\(date): steps = \(steps)")
//NOTE: If you are going to update the UI do it in the main thread
DispatchQueue.main.async {
//update UI components
}
}
} //end block
} //end if let
}
healthStore?.execute(stepsQuery)
Modified #sebastianr's answer using core Swift classes, for just for testing I am returning only steps for just one day, once you have more days you can create a dictionary of Dates and step count and return it
func getStepCountPerDay(completion:#escaping (_ count: Double)-> Void){
guard let sampleType = HKObjectType.quantityType(forIdentifier: .stepCount)
else {
return
}
let calendar = Calendar.current
var dateComponents = DateComponents()
dateComponents.day = 1
var anchorComponents = calendar.dateComponents([.day, .month, .year], from: Date())
anchorComponents.hour = 0
let anchorDate = calendar.date(from: anchorComponents)
let stepsCumulativeQuery = HKStatisticsCollectionQuery(quantityType: sampleType, quantitySamplePredicate: nil, options: .cumulativeSum, anchorDate: anchorDate!, intervalComponents: dateComponents
)
// Set the results handler
stepsCumulativeQuery.initialResultsHandler = {query, results, error in
let endDate = Date()
let startDate = calendar.date(byAdding: .day, value: 0, to: endDate, wrappingComponents: false)
if let myResults = results{
myResults.enumerateStatistics(from: startDate!, to: endDate as Date) { statistics, stop in
if let quantity = statistics.sumQuantity(){
let date = statistics.startDate
let steps = quantity.doubleValue(for: HKUnit.count())
print("\(date): steps = \(steps)")
completion(steps)
//NOTE: If you are going to update the UI do it in the main thread
DispatchQueue.main.async {
//update UI components
}
}
} //end block
} //end if let
}
HKHealthStore().execute(stepsCumulativeQuery)
}
Here is a translation that currently works for Swift 2.0, using the SwiftDate library.
let type = HKSampleType.quantityTypeForIdentifier(HKQuantityTypeIdentifierStepCount)
let startDate = NSDate().beginningOfDay().oneWeekAgo()
let interval = NSDateComponents()
interval.day = 1
let predicate = HKQuery.predicateForSamplesWithStartDate(startDate, endDate: NSDate(), options: .StrictStartDate)
let query = HKStatisticsCollectionQuery(quantityType: type!, quantitySamplePredicate: predicate, options: [.CumulativeSum], anchorDate: NSDate().begginingOfDay(), intervalComponents:interval)
query.initialResultsHandler = { query, results, error in
let endDate = NSDate()
let startDate = NSDate().beginningOfDay().oneWeekAgo()
if let myResults = results{
myResults.enumerateStatisticsFromDate(startDate, toDate: endDate) {
statistics, stop in
if let quantity = statistics.sumQuantity() {
let date = statistics.startDate
let steps = quantity.doubleValueForUnit(HKUnit.countUnit())
print("\(date): steps = \(steps)")
}
}
}
}
healthKitStore.executeQuery(query)
I wrapped mine in a completion block (objective -c). I found what was best was to set the startDate for the query to todays date at midnight. Hope this helps, feel free to copy/paste to get started
-(void)fetchHourlyStepsWithCompletionHandler:(void (^)(NSMutableArray *, NSError *))completionHandler {
NSMutableArray *mutArray = [NSMutableArray new];
NSCalendar *calendar = [NSCalendar autoupdatingCurrentCalendar];
NSDate *startDate = [calendar dateBySettingHour:0 minute:0 second:0 ofDate:[NSDate date] options:0];
NSDate *endDate = [NSDate date]; // Whatever you need in your case
HKQuantityType *type = [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount];
// Your interval: sum by hour
NSDateComponents *intervalComponents = [[NSDateComponents alloc] init];
intervalComponents.hour = 1;
// Example predicate
NSPredicate *predicate = [HKQuery predicateForSamplesWithStartDate:startDate endDate:endDate options:HKQueryOptionStrictStartDate];
HKStatisticsCollectionQuery *query = [[HKStatisticsCollectionQuery alloc] initWithQuantityType:type quantitySamplePredicate:predicate options:HKStatisticsOptionCumulativeSum anchorDate:startDate intervalComponents:intervalComponents];
query.initialResultsHandler = ^(HKStatisticsCollectionQuery *query, HKStatisticsCollection *results, NSError *error) {
[results enumerateStatisticsFromDate:startDate toDate:endDate
withBlock:^(HKStatistics *result, BOOL *stop) {
if (!result) {
if (completionHandler) {
completionHandler(nil, error);
}
return;
}
HKQuantity *quantity = result.sumQuantity;
NSDate *startDate = result.startDate;
NSDateFormatter *formatter = [[NSDateFormatter alloc]init];
formatter.dateFormat = #"h a";
NSString *dateString = [formatter stringFromDate:startDate];
double steps = [quantity doubleValueForUnit:[HKUnit countUnit]];
NSDictionary *dict = #{#"steps" : #(steps),
#"hour" : dateString
};
[mutArray addObject:dict];
}];
if (completionHandler) {
completionHandler(mutArray, error);
}
};
[self.healthStore executeQuery:query];
}
With Updated Swift 2.0 & SwiftDate library.
let type = HKSampleType.quantityTypeForIdentifier(HKQuantityTypeIdentifierStepCount)
let startDate = NSDate().beginningOfDay
let interval = NSDateComponents()
interval.day = 1
let predicate = HKQuery.predicateForSamplesWithStartDate(startDate, endDate: NSDate(), options: .StrictStartDate)
let query = HKStatisticsCollectionQuery(quantityType: type!, quantitySamplePredicate: predicate, options: [.CumulativeSum], anchorDate: NSDate().beginningOfDay, intervalComponents:interval)
query.initialResultsHandler = { query, results, error in
let endDate = NSDate()
let startDate = NSDate().beginningOfDay
if let myResults = results{
myResults.enumerateStatisticsFromDate(startDate, toDate: endDate) {
statistics, stop in
if let quantity = statistics.sumQuantity() {
let date = statistics.startDate
let steps = quantity.doubleValueForUnit(HKUnit.countUnit())
print("\(date): steps = \(steps)")
}
}
}
}
healthKitStore.executeQuery(query)

Sort an array of custom sub-objects in a one-to-many relationship by newest first

I'm trying to sort an array so that the newest ones are at the top.
The problem I have is that I'm sorting on a One to many relationship and using compare doesn't seem to sort it.
The array is a list of completed flights.
NSFetchRequest *fr = [[NSFetchRequest alloc] initWithEntityName:[self entityName]];
fr.predicate = [NSPredicate predicateWithFormat:#"(acceptedDate != nil || booked == %# || ANY legs.flight != nil) && ANY legs.departureDate <= %#", #YES, [NSDate date]];
NSArray *results = [context executeFetchRequest:fr error:nil];
//Check BOTH legs have completed
NSDate *now = [NSDate date];
NSMutableArray *filtered = [NSMutableArray array];
for (Quote *quote in results) {
if ([quote.legs count] > 1) {
BOOL completed = YES;
for (QuoteLeg *leg in quote.legs) {
if ([leg.departureDate compare:now] == NSOrderedDescending) {
completed = NO;
}
}
if (completed) {
[filtered addObject:quote];
}
}
else {
[filtered addObject:quote];
}
}
// Try to sort the array
[filtered sortedArrayWithOptions:NSSortStable usingComparator:^NSComparisonResult(Quote *quote1, Quote *quote2){
QuoteLeg *leg1 = [[quote1.legs allObjects] firstObject];
QuoteLeg *leg2 = [[quote2.legs allObjects] firstObject];
return [leg1.departureDate compare:leg2.departureDate];
}];
// Sort filtered by date
for (Quote *q in filtered) {
QuoteLeg *leg = [[q.legs allObjects] firstObject];
NSLog(#"date = %#", leg.departureDate);
}
This always outputs
date = 2015-01-20 11:00:00 +0000
date = 2015-01-23 12:00:00 +0000
date = 2015-01-29 12:00:00 +0000
date = 2015-01-30 10:40:00 +0000
date = 2015-01-30 10:40:00 +0000
date = 2015-01-29 09:00:00 +0000
date = 2015-01-26 10:00:00 +0000
I'm needing to ensure that the dates are newest first.
Is there something else I can do?
- (NSArray *)sortedArrayWithOptions:(NSSortOptions)opts usingComparator:(NSComparator)cmptr
methods return the results as an NSArray, it does not sort your NSMutableArray in place. Try doing this instead
filtered = [filtered sortedArrayWithOptions:NSSortStable usingComparator:^NSComparisonResult(Quote *quote1, Quote *quote2){
QuoteLeg *leg1 = [[quote1.legs allObjects] firstObject];
QuoteLeg *leg2 = [[quote2.legs allObjects] firstObject];
return [leg1.departureDate compare:leg2.departureDate];
}];
I think I have an answer. I tried wrapping a NSSortDescriptor around it without any key and the results seem to be in the order I require;
// Filtered is a NSMutableArray
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:nil ascending:NO
comparator:^NSComparisonResult(VICQuote *quote1, VICQuote *quote2){
VICQuoteLeg *leg1 = [[quote1.legs allObjects] firstObject];
VICQuoteLeg *leg2 = [[quote2.legs allObjects] firstObject];
return [leg1.departureDate compare:leg2.departureDate];
}];
[filtered sortUsingDescriptors:#[sortDescriptor]];
NSArray *finalResults = [NSArray arrayWithArray:filtered];
// Output them
for (VICQuote *q in finalResults) {
VICQuoteLeg *leg = [[q.legs allObjects] firstObject];
NSLog(#"date = %#", leg.departureDate);
}
Results:
date = 2015-01-31 10:35:00 +0000
date = 2015-01-30 09:40:00 +0000
date = 2015-01-29 12:00:00 +0000
date = 2015-01-29 09:00:00 +0000
date = 2015-01-28 11:00:00 +0000
date = 2015-01-23 12:00:00 +0000
date = 2015-01-20 11:00:00 +0000

Core data, count and group by

I have an entity with 2 attributes, an NSDate and a boolean value. (this is going to be a large "table")
I need to count all YES and NO values for the boolean between two dates, grouped by days. How can i do this?
The result I'm looking for is
{
totalYes = 10,
totalNo = 5,
date = dd-mm-yyyy
},
{
totalYes = 15,
totalNo = 3,
date = dd-mm-yyyy
},
etc
Thanks
You can try this approach:
1)Get all the enteties with YES,sorted by date
2)Go trough this array and fill array with dictionaries with day value and number of yeses
3)Then, do the ame thing with noe's,adding number of no to that array of dictionaries.
NSFetchRequest *request = [[NSFetchRequest alloc]init];
request.entity = [NSEntityDescription entityForName:#"Day" inManagedObjectContext:context];
request.predicate = [NSPredicate predicateWithFormat:#"yesorno = %#",YES];
NSError *error = nil;
request.sortDescriptors =[NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"date" ascending:YES]];
//Here you get all the enteties with YES,sorted by date
NSArray *days = [context executeFetchRequest:request error:&error];
NSMutableArray *arrayOfDates = [NSMutableArray array];
int firstDay = [[[NSCalendar currentCalendar]components:NSDayCalendarUnit fromDate:[[days objectAtIndex:0]date]]day];
//Add the first day dictionary
[arrayOfDates addObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:[[days objectAtIndex:0]date],#"Day", nil];
int numberOfYes = 0;
int dayNumber = 0;
for(NSManagedObject *day in days)
{
if( [[[NSCalendar currentCalendar]components:NSDayCalendarUnit fromDate:[day date]]day]>firstDay)
{
//save number of yeses for the previous day,because we are done with it
[[arrayOfDates objectAtIndex:dayNumber]setValue:[NSNumber numberWithInt:numberOfYes] forKey:#"NumberOfYes"];
numberOfYes = 1;
dayNumber++;
firstDay = [[[NSCalendar currentCalendar]components:NSDayCalendarUnit fromDate:[day date]]day];//date with new day
[arrayOfDates addObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:[day date],#"Day", nil];//Add this day dictionary to array
}else
{
numberOfYes++;
}
}
//And somrthing similar to No

NSPredicate: filtering objects by day of NSDate property

I have a Core Data model with an NSDate property. I want to filter the database by day. I assume the solution will involve an NSPredicate, but I'm not sure how to put it all together.
I know how to compare the day of two NSDates using NSDateComponents and NSCalendar, but how do I filter it with an NSPredicate?
Perhaps I need to create a category on my NSManagedObject subclass that can return a bare date with just the year, month and day. Then I could compare that in an NSPredicate. Is this your recommendation, or is there something simpler?
Given a NSDate * startDate and endDate and a NSManagedObjectContext * moc:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(date >= %#) AND (date <= %#)", startDate, endDate];
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:[NSEntityDescription entityForName:#"EntityName" inManagedObjectContext:moc]];
[request setPredicate:predicate];
NSError *error = nil;
NSArray *results = [moc executeFetchRequest:request error:&error];
Example on how to also set up startDate and endDate to the above given answer:
...
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *components = [calendar components:(NSCalendarUnitYear | NSCalendarUnitMonth ) fromDate:[NSDate date]];
//create a date with these components
NSDate *startDate = [calendar dateFromComponents:components];
[components setMonth:1];
[components setDay:0]; //reset the other components
[components setYear:0]; //reset the other components
NSDate *endDate = [calendar dateByAddingComponents:components toDate:startDate options:0];
predicate = [NSPredicate predicateWithFormat:#"((date >= %#) AND (date < %#)) || (date = nil)",startDate,endDate];
...
Here I was searching for all entries within one month. It's worth to mention, that this example also shows how to search 'nil' date-entires.
Swift 3.0 extension for Date:
extension Date{
func makeDayPredicate() -> NSPredicate {
let calendar = Calendar.current
var components = calendar.dateComponents([.year, .month, .day, .hour, .minute, .second], from: self)
components.hour = 00
components.minute = 00
components.second = 00
let startDate = calendar.date(from: components)
components.hour = 23
components.minute = 59
components.second = 59
let endDate = calendar.date(from: components)
return NSPredicate(format: "day >= %# AND day =< %#", argumentArray: [startDate!, endDate!])
}
}
Then use like:
let fetchReq = NSFetchRequest(entityName: "MyObject")
fetchReq.predicate = myDate.makeDayPredicate()
In Swift I got something similar to:
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
let date = dateFormatter.dateFromString(predicate)
let calendar = NSCalendar(calendarIdentifier: NSGregorianCalendar)
let components = calendar!.components(
NSCalendarUnit.CalendarUnitYear |
NSCalendarUnit.CalendarUnitMonth |
NSCalendarUnit.CalendarUnitDay |
NSCalendarUnit.CalendarUnitHour |
NSCalendarUnit.CalendarUnitMinute |
NSCalendarUnit.CalendarUnitSecond, fromDate: date!)
components.hour = 00
components.minute = 00
components.second = 00
let startDate = calendar!.dateFromComponents(components)
components.hour = 23
components.minute = 59
components.second = 59
let endDate = calendar!.dateFromComponents(components)
predicate = NSPredicate(format: "day >= %# AND day =< %#", argumentArray: [startDate!, endDate!])
I had a hard time to discover that string interpolation "\(this notation)" doesn't work for comparing dates in NSPredicate.
I ported the answer from Glauco Neves to Swift 2.0 and wrapped it inside a function that receives a date and returns the NSPredicate for the corresponding day:
func predicateForDayFromDate(date: NSDate) -> NSPredicate {
let calendar = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)
let components = calendar!.components([.Year, .Month, .Day, .Hour, .Minute, .Second], fromDate: date)
components.hour = 00
components.minute = 00
components.second = 00
let startDate = calendar!.dateFromComponents(components)
components.hour = 23
components.minute = 59
components.second = 59
let endDate = calendar!.dateFromComponents(components)
return NSPredicate(format: "day >= %# AND day =< %#", argumentArray: [startDate!, endDate!])
}
Adding to Rafael's answer (incredibly useful, thank you!), porting for Swift 3.
func predicateForDayFromDate(date: Date) -> NSPredicate {
let calendar = Calendar(identifier: Calendar.Identifier.gregorian)
var components = calendar.dateComponents([.year, .month, .day, .hour, .minute, .second], from: date)
components.hour = 00
components.minute = 00
components.second = 00
let startDate = calendar.date(from: components)
components.hour = 23
components.minute = 59
components.second = 59
let endDate = calendar.date(from: components)
return NSPredicate(format: "YOUR_DATE_FIELD >= %# AND YOUR_DATE_FIELD =< %#", argumentArray: [startDate!, endDate!])
}
Building on the previous answers, an update and alternative method using Swift 5.x
func predicateForDayUsingDate(_ date: Date) -> NSPredicate {
var calendar = Calendar.current
calendar.timeZone = NSTimeZone.local
// following creates exact midnight 12:00:00:000 AM of day
let startOfDay = calendar.startOfDay(for: date)
// following creates exact midnight 12:00:00:000 AM of next day
let endOfDay = calendar.date(byAdding: .day, value: 1, to: startOfDay)!
return NSPredicate(format: "day >= %# AND day < %#", argumentArray: [startOfDay, endOfDay])
}
If you'd prefer to create the time for endOfDay as 11:59:59 PM, you can instead include...
let endOfDayLessOneSecond = endOfDay.addingTimeInterval(TimeInterval(-1))
but then you might change the NSPredicate to...
return NSPredicate(format: "day >= %# AND day <= %#", argumentArray: [startOfDay, endOfDayLessOneSecond])
...with specific note of the change from day < %# to day <= %#.
I've recently spent some time attempting to solve this same problem and add the following to the list of alternatives to prepare start and end dates (includes updated method for iOS 8 and above)...
NSDate *dateDay = nil;
NSDate *dateDayStart = nil;
NSDate *dateDayNext = nil;
dateDay = <<USER_INPUT>>;
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]]
For me this is worked.
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *components = [calendar components:(NSYearCalendarUnit | NSMonthCalendarUnit ) fromDate:[NSDate date]];
//create a date with these components
NSDate *startDate = [calendar dateFromComponents:components];
[components setMonth:0];
[components setDay:0]; //reset the other components
[components setYear:0]; //reset the other components
NSDate *endDate = [calendar dateByAddingComponents:components toDate:startDate options:0];
startDate = [NSDate date];
endDate = [startDate dateByAddingTimeInterval:-(7 * 24 * 60 * 60)];//change here
NSString *startTimeStamp = [[NSNumber numberWithInt:floor([endDate timeIntervalSince1970])] stringValue];
NSString *endTimeStamp = [[NSNumber numberWithInt:floor([startDate timeIntervalSince1970])] stringValue];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"((paidDate1 >= %#) AND (paidDate1 < %#))",startTimeStamp,endTimeStamp];
NSLog(#"predicate is %#",predicate);
totalArr = [completeArray filteredArrayUsingPredicate:predicate];
[self filterAndPopulateDataBasedonIndex];
[self.tableviewObj reloadData];
NSLog(#"result is %#",totalArr);
I have filtered array from current date to 7 days back. I mean I am getting one week data from current date. This should work.
Note: I am converting date which is coming with milli seconds by 1000, and comparing after. Let me know if you need any clarity.