搜索 CoreData 以匹配 NSDate - Swift

Posted

技术标签:

【中文标题】搜索 CoreData 以匹配 NSDate - Swift【英文标题】:Search CoreData for Matching NSDate - Swift 【发布时间】:2014-08-29 14:46:27 【问题描述】:

我想搜索所有现有对象,看看CoreData中是否有任何匹配的日期对象:

日期当前通过 start_date 属性保存在我的 CoreData 中,格式如下:2013-08-29 14:27:47 +0000。

然后我让用户从 UIDatePicker 中选择一个日期并将 .date() 分配给变量 date

例如我选择的日期 = 2013-08-29 17:34:23 +0000。

以下是我如何使用谓词搜索 CoreData。

let predicate = NSPredicate(format: "start_date contains[search] %@", date)
let request:NSFetchRequest = NSFetchRequest(entityName: "Project")
let sortDescriptor:NSSortDescriptor = NSSortDescriptor(key: "number", ascending: true)
let sortDescriptorsAry:NSArray = [sortDescriptor]
request.sortDescriptors = sortDescriptorsAry
request.predicate = predicate

return request

但是我没有得到任何结果。我假设这是因为两个属性都不匹配,因为时间:

开始日期 = 2013-08-29 14:27:47 +0000

日期 = 2013-08-29 17:34:23 +0000

我该如何告诉 CoreData 忽略“177:34:23 +0000”位,或者有更好的方法吗?

编辑:

我确实可以选择更改最初存储日期格式的方式:

我试过这个:

    var now:NSDate = self.startDatePicker.date
    var calendar:NSCalendar = NSCalendar(calendarIdentifier: NSGregorianCalendar)
    var components:NSDateComponents = calendar.components(NSCalendarUnit.CalendarUnitYear | NSCalendarUnit.CalendarUnitMonth | NSCalendarUnit.CalendarUnitDay, fromDate: now)
    components.hour = 00
    components.minute = 00
    components.second = 00
    var newDate:NSDate = calendar.dateFromComponents(components)!

但是在某些日子里,我的时间被设置为前一天。 例如:

选择8月30日,转换后我得到2014-08-29 23:00:00 +0000

【问题讨论】:

问题:您是否需要在您的核心数据start_date 属性中保存NSDate 这种格式2013-08-29 17:34:23 +0000 或者是否可以更改您的模型并使用这种格式保存NSDate2013-08-29 00:00:00 +0000? @POB 我可以更改日期格式最初保存在核心数据中的方式。 我有一个疑问。我怎样才能翻译你的谓词?是:“检索所有具有与date 完全匹配的start_date 的managedObjects”还是“检索所有具有start_date 类型为“day”并且包含/包含date 的managedObjects?”?跨度> @POB 它应该检索所有与当天 start_date 匹配的 managedObjects。因此,例如,我想 - 检索所有具有 start_date 01/01/2014 的对象 - 它的完成时间应该无关紧要 【参考方案1】:

在Core Data 中保存start_date 属性之前,您需要确保它们的时间设置为12:00 AM:

//Get "Aug 29, 2014, 12:00 AM" from "Aug 29, 2014, 10:07 PM"
let newDate = NSDate() //or any other date
let calendar = NSCalendar.currentCalendar()
calendar.timeZone = NSTimeZone.systemTimeZone()
var startDate: NSDate?
var duration: NSTimeInterval = 0
calendar.rangeOfUnit(.DayCalendarUnit, startDate: &startDate, interval: &duration, forDate: newDate)

//Create, set and save a new managedObject
//Records, here, is the name of your NSManagedObject subclass
let record = NSEntityDescription.insertNewObjectForEntityForName("Records", inManagedObjectContext: managedObjectContext) as Records
record.start_date = startDate
/* set other attributes here */

var error: NSError?
if !managedObjectContext.save(&error) 
    // Replace this implementation with code to handle the error appropriately.
    // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
    println("Unresolved error \(error), \(error!.userInfo)")
    abort()

然后,您将能够以这种方式使用您想要的谓词在 Core Data 中获取日期:

//Set your datePicker date to 12:00 AM
let pickerDate = dateFromMyDatePicker //as NSDate
let calendar = NSCalendar.currentCalendar()
calendar.timeZone = NSTimeZone.systemTimeZone()
var predicateDate: NSDate?
var duration: NSTimeInterval = 0
calendar.rangeOfUnit(.DayCalendarUnit, startDate: &predicateDate, interval: &duration, forDate: pickerDate)

//Create your fetchRequest
/* ... */
//Set your predicate
let predicate = NSPredicate(format: "start_date == %@", predicateDate)

编辑

作为rangeOfUnit:startDate:interval:forDate: 的替代方案,您可以使用您提供的代码。但在这两种情况下,不要忘记添加以下行:

calendar.timeZone = NSTimeZone.systemTimeZone()

【讨论】:

我试过了,但是有些日期读错了。例如如果我选择 8 月 30 日,它将转换为日期 2014-08-29 23:00:00 +0000 注意:2014-08-29 22:00:00 +0000 显示在调试器中,Aug 30, 2014, 12:00 AM 显示在 Playground 中,30/08/2014 00:00:00 UTC+2 使用 NSDateFormatter 显示(dateStyle = .ShortStyle,timeStyle = .LongStyle)可以表示相同的 NSDate (法国巴黎 UTC+2)。【参考方案2】:

如果您仍然必须选择更改存储数据的方式,我会将其作为三个属性存储在您的项目实体中:一个 Integer 代表年份,一个 Integer 代表月份和一个 Integer 代表天。编码会更容易(您可以创建如下谓词:

NSInteger year = ....
NSInteger month = ....
NSInteger day = ....    
[NSPredicate predicateWithFormat: @"day == %@  AND month == %@ and year == %@", @(day), @(month), @(year)];

),这可能比使用字符串更快。如果您想使用字符串(注意多余的空格或其他标点符号!),您可以在代码中构造一个格式为 'yyyy-mm-dd' 的字符串(使用 NSString stringWithFormat:),然后使用 NSPredicate 如下:

NSInteger year = ....
NSInteger month = ....
NSInteger day = ....    
NSString * dateString = [NSStringWithFormat: @"%4i-%2i-%2i", year, month, day];
[NSPredicate predicateWithFormat: @"dateString contains %@", dateString];

但是,这仅在您构建了您存储的字符串而不是 NSDate.description() 时才有效。如果您存储实际的 NSDate,请参阅我的第一个原始答案:)。

但实际上,如果您还有选择权,请不要将其存储为字符串。确实会有问题,如果不是现在,那么以后(我可以从经验中看出)。

【讨论】:

我通常使用一个整数:10000*year + 100*month + day。所以,今天是 20140830。易于比较并随时间严格递增。 啊,这很好。它可能有一些含义,但我喜欢它。【参考方案3】:
<edit>

在我看来,您的第二种做事方式是不错的选择。存储年、月和日的整数值,并用它创建一个谓词。您对 NSDateComponents 和日历的使用非常棒,因此这应该是最简单的方法。非常重要:不要使用 00 的时间,因为午夜是一个棘手的时间(世界部分地区有夏令时,存在一个问题,即午夜是否正好是第二天前一天的一部分)。只需输入 12 作为时间,尽可能远离午夜。 另外,我建议您观看 2011 年的 WWDC 'Performing Calendar Calculations, Session 117。它谈到了为什么午夜很棘手(如果不是,那么在 2013 年的会议中解释:))。

</edit>

NSDate 具有亚秒级精度。它的描述方法(当你 NSLog 一个 NSDate 时被调用)只显示秒精度。 因此,要在 NSPredicate 中使用 NSDate,请始终指定“大于/小于或等于”运算符。

例如:

NSDate * lastWeek = // ... created using NSCalendar and NSDateComponents.
NSDate * now = [NSDate date];

NSPredicate * entitiesFromAfterLastWeekAndBeforeNow = [NSPredicate predicateWithFormat: @"start_date >= %@ AND start_date <= %@", lastWeek, now];

我建议不要将数据存储在本机 NSDate 的其他内容中,因为您可能会存储由于转换而不正确的日期。此外,将日期存储为字符串会使查询速度变慢(因为必须解析字符串,而 NSDate 只是一个数字)。但那是另一个讨论,你可能想要发布,所以你必须做你认为最好的事情。

编辑:对不起,现在才注意到您正在编写 Swift。但我认为我的例子很清楚,它可以转换为 Swift,对吧?

【讨论】:

谢谢,虽然我特别想搜索具有匹配日、月、年的实体。但是,由于时间 (h:m:s) 与用户选择的日期(来自 UIDatePicker.date())不匹配 - 以及创建实体时的 .date() - 我没有得到任何结果。跨度> 我更新了我的答案。我认为您的第二种方法是一个不错的方法。 谢谢,所以现在选择 8 月 30 日给了我:2014-08-30 11:00:00 +0000。因此,当我从 DatePicker 中选择 8 月 30 日进行搜索时,假设从现在起 5 个月后,它会一直是 2014-08-30 11:00:00 +0000 还是最终会是 2014-08-30 12:00:00 +0000... 在这种情况下,这不会破坏搜索吗?我认为它不会,只是仔细检查:) 是的,从现在起 5 个月后,您可能会以 11:00 或 13:00 而不是 12:00 结束。但是,如果您在谓词中省略了时间,并且仅使用年、月和日构建查询,那么它将返回正确的结果,因为当时间在中午前后波动几个小时时,这一天不会改变。 (除非在异国情调的情况下,例如在萨摩亚,几年前一整天都被跳过了:))。 另外:不要忘记,当你 NSLog 一个 NSDate 时,它​​会打印出日期,就好像你当时在伦敦没有夏令时一样。这就是为什么您应该通过具有相同区域设置用于存储和查询的 NSCalendar 将其转换为 NSDateComponents。显示日期时,您可以创建另一个日历或 NSDateFormatter,以不同方式处理日期。但是一旦你使用特定的 NSCalendar+locale 组合存储日期,就永远坚持下去。

以上是关于搜索 CoreData 以匹配 NSDate - Swift的主要内容,如果未能解决你的问题,请参考以下文章

为啥 NSDate() 在 CoreData 中存储一个奇怪的值

NSDate 在 CoreData 中被保存为时间戳

CoreData 中的 NSDate 是不是封装了时区?

CoreData 按 NSDate 排序不起作用?

检索未包含在 CoreData 请求结果中的第二天

首先对 CoreData NSFetchRequest 完全匹配进行排序