I have records with date field:
class SomeRealmObject: Object {
@Persisted(primaryKey: true) var _id: ObjectId
@Persisted var date: Date
... //some other properties
}
The goal is to calculate a count of records distinct by date ("distinct" should ignore time inside date).
If I already had a list of records I could work with them like usual elements of array:
let allObjects = getAll() //SomeRealmObject array
let dates = allObjects.map { $0.startOfDay() }.reduce([]) { partialResult, date in
partialResult.contains(date) ?
partialResult : partialResult + [date]
}
let result = dates.count
But is it possible to do the same with RealmSwift
methods because I need only count of SomeRealmObject
and don't need to get its inner data? There is for example distinct(by:)
but it compares fields by their value and doesn't allow to specify additional conditions like startOfDay()
There are a few issues to overcome - first is that Realm functions don't allow us to 'dig' into dates - dates are the whole date, including the time.
Because of that, ignoring the time is not possible using Realm syntax. e.g. you can't do this
let distinctObjects = realm.objects(SomeObject.self).distinct {by: "someDate.yyyymmdd"}
bummer.
Another non-working thought is to use a predicates (Realm supports some predicates) with a block to isolate the date components
let myPredicate = NSPredicate(block: { (evaluatedDate, _) -> Bool in
//isoloate the yyyy mm dd components for evaluation
})
But alas, Realm "Only supports compound, comparison, and constant predicates"
One solution (of many) is to consider what's being stored - a Date() object is actually a very specific timestamp, which includes the time. But that's not what your distinct
needs - it needs the year, month and day
So, store the date components you want to compare as a separate property; possibly string or a date built with no time component
class SomeRealmObject: Object {
@Persisted(primaryKey: true) var _id: ObjectId
@Persisted var date: Date
@Persisted var dateString = "" //stored as yyyymmdd
//or
@Persisted var dateNoTime: Date //stored as a date with 0's as time
}
The dateNoTime
property could be built by extracting the date components you want to distict on, create a new date object and store that; for example
let components = DateComponents(year: 2023, month: 04, day: 01)
let theDate = Calendar.current.date(from: components)
There are a number of paths from there, one is to use a function or computed property that's backed by Realm properties to handle the date, so when setting the date on an object it writes out the actual timestamp version as well as the one used for distinct.
class SomeRealmObject: Object {
@Persisted(primaryKey: true) var _id: ObjectId
@Persisted var date: Date
@Persisted var dateString = "" //stored as yyyymmdd
func writeDate(withDate: Date) {
self.date = withDate
self.dateString = //break down withDate to yyyymmdd
}
}
then you can use Realms distinct
to get the results
let distictObjects = realm.objects(SomeObject.self).distict(by: ["dateString"]
*note this could also be done by loading the objects into an array and then use a Swift predicate to evaluate but that could cause memory issues and potentially be slower.