Why is an NSDate in a Core Data managed Object converted to NSTimeInterval?
I get this if I check the "Use scalar properties for primitive data types" checkbox when I'm generating my files.
This is because NSTimeInterval is a double in disguise, whereas NSDate is a class that inherits from NSObject.
Default NSTimeInterval used by CoreData
As stated in the documentation and in comments, the internal NSDate
reference date is 1 January 2001, which is what is used by Core Data internally when serializing NSDate
objects to the database representation.
See Wikipedia's article on epoch. NSDate
and CFDate
both use the Cocoa epoch time (which dates at least to 1994, in the OpenStep specifications).
How to store NSDate in Core Data?
iOS's reference date counts from : 1 January 2001, GMT.
Unix Time Stamp reference date counts from : Jan 01 1970, UTC
If this is a completely offline app you can use the timeIntervalSinceReferenceDate and store the value directly.
But, if you are going to sync up with a server then generally, it is preferable to send the data to server as a unix time stamp. Because a desktop uses unix time Stamp as default.
So, in the second case you can either choose to save the date directly as a UnixTimeStamp and convert it for local use using a getter in Model Class (refer : @VladZ) or You can save it as iOS's reference date and convert it to UnixTimeStamp each time you send it to server.
Setting date in core data with KVC
If you pass an NSNumber to setValue:forKey:
, it is automatically unboxed if the property is a primitive type. So you should be able to do [someClass setValue:@([[NSDate date] timeIntervalSince1970]) forKey:@"updateDate"]
.
Setting NSTimeInterval from NSDate
timeIntervalSince1970
returns a NSTimeInterval
and represents the number of seconds that have elapsed since the first time instant of 1 January 1970, GMT.
When you store an NSDate
it actually stores the number of seconds since the first instant of 1 January 2001, GMT.
You are asking the date object to give you a representation with one reference date, then you are assigning that value to another date object (most likely a core data object that has been told to use native values when generating the subclass code). It takes the time interval, and ASSUMES it is using the proper reference date.
There are 31 years between those two dates, and not so coincidentally, there are 31 years between the times in your date object, and the date in the core data instance.
If you truly want to use 1970 as your basis, you should be creating a date with dateWithTimeIntervalSince1970:
.
Use Swift's Date with CoreData
Yes, but you might not like it. If you declare the property using a Core Data "Date" type and let Xcode generate the NSManagedObject
subclass for you, the property will be an @NSManaged
property of type NSDate
. As you've figured out, you'd then have to deal with Date
vs. NSDate
yourself.
If you don't let Xcode generate the subclass for you (in Xcode 8 set "Codegen" to "Manual/None"), you can declare the Core Data "date" property as something like
@NSManaged public var timestamp: Date?
It'll just work. You can read and write Date
values, and Core Data will do the right thing. But you become completely responsible for the code in the NSManagedObject
subclass. You'll have to create the whole class. If you update the Core Data model, you'll have to update the class as well. Whether this seems worthwhile is up to you but it's the only solution that seems to exist right now.
Update: In Xcode 9, generated code uses Date
, so this shouldn't be necessary any more.
How to use a predicate on NSTimeInterval?
You chose the "Use scalar properties for primitive data types" option when
creating the managed object subclass, so that the recordDate is represented asNSTimeInterval
in the FTRecord
class.
But in a predicate like "recordDate >= 123.45"
, the left-hand side is stored
as a NSKeyPathExpression
, and that uses valueForKeyPath:@"recordDate"
to access the property, which returns an NSDate
object.
The right-hand side of that predicate is stored as NSConstantValueExpression
with a reference to an NSNumber
object.
Therefore an NSDate
is compared with an NSNumber
, which leads exactly to the
exception that you got.
To fix the problem, you have to compare the property with an NSDate
(which is what @rmaddy initially suggested):
[parr addObject:[NSPredicate predicateWithFormat:@"recordDate >= %@", beginningOfCurrentMonthDate]];
[parr addObject:[NSPredicate predicateWithFormat:@"recordDate < %@", beginningOfNextMonthDate]];
NSPredicate *predicate = [NSCompoundPredicate andPredicateWithSubpredicates:parr];
I tested this and it seems to produce the expected result.
Note however that Core Data uses timeIntervalSinceReferenceDate
and dateWithTimeIntervalSinceReferenceDate
to convert between the scalar
values and NSDate
, not timeIntervalSince1970
.
Core Data NSPredicate not returning records between two dates
I assume that the problem is where you assign the date value to the managed object. If measurementDate
is defined as scalar property
@property (nonatomic) NSTimeInterval measurementDate;
then you assign an NSDate
to it with
NSDate *theDate;
obj.measurementDate = [theDate timeIntervalSinceReferenceDate];
Related Topics
How to Convert Image into Binary Format in iOS
Changing Uipageviewcontroller's Page Programmatically Doesn't Update the Uipagecontrol
How to Register Undomanager in Swift
How to Load Multiple Storyboard Files Depending on iOS Version? (5 and 6)
How to Hide a Bar Button Item for Certain Users
How to Determine File Size on Disk of a Video Phasset in iOS8
Implementing Autocomplete in iOS
How to Resolve iOS Link Errors with Opencv
Breaking on Unrecognized Selector
How to Change Device Orientation Programmatically in Swift
Youtube Video Autoplay Inside Uiwebview
Cordova Xcode Build Failed "Permission Denied"
Swift - Add Gesture Recognizer to Object in Table Cell
Sprite Kit Sprites Loading Performance Improvement
How to Detect When User Used Password Autofill on a Uitextfield
Page Based "Reloadrootcontrollerswithnames:" on Launch Loop
Custom Uitableviewcell with Progress Bar Download Update
Rails: Redirect_To 'Myapp://' to Call iOS App from Mobile Safari