Why Is an Nsdate in a Core Data Managed Object Converted to Nstimeinterval

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 as
NSTimeInterval 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



Leave a reply



Submit