178

There's two ways of storing an NSDate in NSUserDefaults that I've come across.

Option 1 - setObject:forKey:

// Set
NSDate *myDate = [NSDate date];
[[NSUserDefaults standardUserDefaults] setObject:myDate forKey:@"myDateKey"];

// Get
NSDate *myDate = (NSDate *)[[NSUserDefaults standardUserDefaults] objectForKey:@"myDateKey"];

Option 2 - timeIntervalSince1970

// Set
NSDate *myDate = [NSDate date];
NSTimeInterval myDateTimeInterval = [myDate timeIntervalSince1970];
[[NSUserDefaults standardUserDefaults] setFloat:myDateTimeInterval forKey:@"myDateKey"];

// Get
NSTimeInterval myDateTimeInterval = [[NSUserDefaults standardUserDefaults] floatForKey:@"myDateKey"];
NSDate *myDate = [NSDate dateWithTimeIntervalSince1970:myDateTimeInterval];

Pros and Cons

Option 1

This seems to be compact and logical. However, I've got worries about this going wrong because of Date Formatter bugs.

Option 2

This seems to be clumsy. I'm also unsure of the accuracy of it - in one test I did, when I retrieved the date back it was out by 48 seconds, despite the Apple Docs saying that NSTimeInterval has "subsecond accuracy".

Requirements

Whatever method I choose, it must be:

  1. Precise to within a second.

  2. Readable and reliable.

My Question

Is the inaccuracy with Option 2 because I'm doing something wrong?

Which out of these two options would you use?

Is there another option that I'm not aware of?

Thanks!

Pål Brattberg
  • 4,568
  • 29
  • 40
John Gallagher
  • 6,208
  • 6
  • 40
  • 75

4 Answers4

386

You are needlessly complicating things. Why are you converting the date to a time interval (then the time interval to a different primitive)? Just [sharedDefaults setObject:theDate forKey:@"theDateKey"] and be done with it. NSDate is one of the "main types" supported by the PLIST format (dates, numbers, strings, data, dictionaries, and arrays), so you can just store it directly.

See the documentation for proof.

Just store and retrieve the date directly and watch it Do The Right Thing (including time zones, precision, etc). There is no formatter involved, as others have said.

Joshua Nozzi
  • 60,946
  • 14
  • 140
  • 135
  • 8
    Joshua, thanks for your answer. The reason I tried the silly convoluted float approach was simply because I'd seen another great developer do it and I thought he'd done it for some good reason. Obviously not. I should have more confidence in my own instinct, which was to use setObject:forKey: and have done with it. – John Gallagher Jan 06 '10 at 21:33
  • 7
    I'm outright violent toward the very thought of needless complication - a good trait for developers and generally lazy people. :-) – Joshua Nozzi Jan 06 '10 at 21:46
  • The cases where such approach is used is mostly when NSUserDefaults is used as a storage implementation of user preferences for a generic middleware... – Coyote Nov 06 '11 at 04:19
  • @Coyote: In which case it's still being accessed through NSUserDefaults or parsed from a property list file, so the same access or parsing should yield a proper NSDate object, which can be converted as needed. – Joshua Nozzi Nov 23 '11 at 15:12
  • So to pull out just use objectForKey and cast to NSDate* ? – jjxtra Jul 06 '12 at 18:01
  • Yep. Though if you're using Cocoa Bindings (NSWhateverController), make sure you use NSUserDefaultsController, rather than NSUserDefaults. – Joshua Nozzi Jul 07 '12 at 13:20
  • 3
    @JohnGallagher **[sidebar]** Do not mistake someone's specific implementation for a poor one. Your original sentiment "thought he'd done it for some good reason...Obviously not" could only be valid if you understood the entire scope of said dev's requirements. It is presumptuous and close-minded to blindly label his approach as "obviously no good reason to do it this way". That being said, yes I would agree with Joshua's approach to keep it simple if you have no reason to do otherwise. – dooleyo Sep 07 '13 at 20:14
  • @dooleyo: In this case I think it's safe to say it's a demonstrably *bad* idea in the case of a date to reduce it to a mere unsigned integer that represents the number of seconds from some arbitrary epoch (that COULD change, which would offset the represented date). Especially when you consider time zones, daylight saving, and other local adjustments. Even cross-platform arguments invalidate this approach, given different epochs (*not* an industry standard). I'll go out on a limb and say outright the original developer was wrong to do it. – Joshua Nozzi Sep 18 '13 at 13:12
14

For option #1, I don't believe a date formatter is involved. Possibly under the hood, but I imagine it's not broken. The date is stored in ISO 8601 form.

For option #2, use -setDouble:forKey: and -doubleForKey instead of the float-based versions. That might be the cause of your precision errors.

John Calsbeek
  • 35,947
  • 7
  • 94
  • 101
  • Wow, John. Thanks for your very speedy answer. Which would you personally use? – John Gallagher Jan 06 '10 at 15:21
  • 8
    Use the date directly, not the time interval. I doubt that such a basic API is broken when so many apps rely on it. – John Calsbeek Jan 06 '10 at 15:22
  • Excellent. That was my instinct, but I'd seen third party code by a respected dev that used the float method so I thought there would be some good reason he'd used it. Obviously not. Thanks again for your answer! – John Gallagher Jan 06 '10 at 15:24
  • See my answer below - why on earth would you convert the date to something else when you can store it directly? :-) – Joshua Nozzi Jan 06 '10 at 15:28
  • No problem with storing NSDate directly, but float versus double isn't the issue here. The number is in seconds, so the difference between float and double accuracy is way down in the noise. If you were seeing an error of 48s, you were almost certainly writing the number when you didn't expect to. This amount of error isn't rounding, and doesn't sound like data-mismatch. I'd put NSLog() statements around the read and write and make sure you're really reading and writing what you think you are. That said, I'd also store NSDate as NSDate. – Rob Napier Jan 06 '10 at 15:35
  • 1
    It's possible that that code you saw was the dev not remembering which types can be stored directly in a property list. – John Calsbeek Jan 06 '10 at 15:36
  • I find the 48 second mismatch that I observed also very puzzling. As soon as I switched to storing NSDates directly, this mismatch went away. I stepped through my code with the debugger and everything was working exactly as expected, except when it retrieved the float the conversion was messed up. – John Gallagher Jan 06 '10 at 22:29
  • My theory is that making it a double would have probably prevented this error - a massive number (x10^9) was being stored and so some digits will be been chopped off with a float thus resulting in the error. I think. :) I've got far too much to do to investigate any further (although strangely, I still have time to type this message...!) – John Gallagher Jan 06 '10 at 22:32
  • NSDate's internal storage (and its reference date) is an implementation detail. While it's unlikely to change, it's an outright bad idea to assume anything about this. NSDate is one of the basic types supported by property serialization / PLISTs. Just store the thing as an NSDate and you never have to worry. – Joshua Nozzi Nov 04 '10 at 18:08
  • 2
    For the record - 32 bit floats have only 24 bits for accuracy, so 1970 to now is 40 years, which is 40*365*86400 seconds, and (40 * 365 * 86 400) / (2 ** 24) = 75second error. Double precision is what a DateTime interval, and its RAW precision is now better than a millionth of a second. – Tom Andersen May 10 '12 at 15:38
  • For some reason some people use "float" instead of "double" all the time and then they get bitten by bugs like this one. Totally unnecessary. Rule: Use double _unless you can give a very good and convincing reason that float is better_. – gnasher729 Aug 13 '15 at 09:59
5

Use NSUserDefaults; dates are stored in Zulu time, so there are no time zone issues to worry about. Store it in your time zone, pull it out in another time zone, you'll be fine, the system handles the conversion (no date formatter to worry about).

Ben Gottlieb
  • 85,404
  • 22
  • 176
  • 172
1

If you are saving the expiration date from the Facebook Graph API, I would use *Option 2*.

Option two can be easily converted to a string (using stringWithFormat). Most importantly, it works for the Graph API.

Plus, you don't have to worry about the format of your date. Not dealing with NSDateFormatter is worth the possibility of a 48 second error.

Nate Symer
  • 2,185
  • 1
  • 20
  • 27