24

Anyone know what the minimum and maximum possible values are for an NSDate?

tjpaul
  • 343
  • 3
  • 16
Oliver Pearmain
  • 19,885
  • 13
  • 86
  • 90
  • Please explain in what scenario you need to know this. – abdus.me Mar 01 '13 at 11:48
  • What is the need for it??? – Melbourne Mar 01 '13 at 11:54
  • 1
    ===> WWDC 2011 Video "Session 117 - Performing Calendar Calculations" (59:11) –  Mar 01 '13 at 12:05
  • 3
    I receive epoch based date/time values from a JSON .NET service and cast these to NSDate using [NSDate dateWithTimeIntervalSince1970:]. I'm seeing an unusual output when printing out the value of 0001-01-01 (the .NET DateTime.MinValue); it incorrectly prints as 31 Dec 0001 and I was wondering if this had anything to do with a minimum restriction with NSDate. Subsequently however after some more digging I've found the issue is with NSDateFormatter. I am however still curious what the max and min values are, perhaps there aren't any feasible bounds other than its underlying type (a double?). – Oliver Pearmain Mar 01 '13 at 13:29
  • 2
    @HaggleLad Your intuition is correct. The bounds are defined by the underlying type. As for the "0001-01-01" bit... No calendar is going to be reliable when you're talking about dates more than a few hundred years old, mainly because human records are unreliable. As for why it's printing as 31 Dec, make sure you're setting the timeZone correctly. "31 Dec 0001" may actually be referring to 1 BC in (for example) North America, when it's already 1 Jan 1 AD in GMT. – Dave DeLong Mar 01 '13 at 18:28
  • Suggest you update your original question with the clarification you made in these comments? – Grimxn Dec 03 '21 at 20:29

4 Answers4

49

Use [NSDate distantFuture] and [NSDate distantPast].

Senseful
  • 86,719
  • 67
  • 308
  • 465
user3682629
  • 499
  • 4
  • 3
  • 1
    +1 Apple provide these method for this kind of tricky questions. – 9dan May 28 '14 at 07:50
  • For those who, like the OP, are converting .Net DateTime values, and want to support DateTime.MinValue and DateTime.MaxValue, then these Swift methods might be of interest: http://stackoverflow.com/a/41625877/253938 – RenniePet Jan 13 '17 at 02:50
3

EDITED Answer:

I thought that I'd discovered the minimum date value was 0001-01-01 00:00:00 + 0000 (given my code below) HOWEVER as has been pointed out in the comments below, this is only for the current era (i.e. A.D.). An NSDate value can go lower than this but what you see as a result of an NSLog may not be what you are expecting, so I would recommend looking at other answers here and ignoring my ignorance.

ORIGINAL Answer:

I believe that I've discovered the minimum date value is 0001-01-01 00:00:00 + 0000

You need to be very careful when NSLog'ing and parsing values to ensure that your timezone isn't skewing the date value slightly.

I will try and demonstrate below:

NSDateFormatter *dateFormatter1 = [[NSDateFormatter alloc] init];
[dateFormatter1 setDateFormat:@"yyyy-MM-dd hh:mm:ss z"];

NSDateFormatter *dateFormatter2 = [[NSDateFormatter alloc] init];
[dateFormatter2 setDateFormat:@"yyyy-MM-dd HH:mm:ss z"];

NSDateFormatter *dateFormatter3 = [[NSDateFormatter alloc] init];
[dateFormatter3 setDateFormat:@"yyyy-MM-dd HH:mm:ss z"];
[dateFormatter3 setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];

NSDate *date1 = [NSDate dateWithTimeIntervalSince1970:-62135596800];
NSLog(@"date1 = %@", date1);
NSLog(@"date1 via dateFormatter1 = %@", [dateFormatter1 stringFromDate:date1]);
NSLog(@"date1 via dateFormatter2 = %@", [dateFormatter2 stringFromDate:date1]);
NSLog(@"date1 via dateFormatter3 = %@", [dateFormatter3 stringFromDate:date1]);

NSDate *date2 = [NSDate dateWithTimeIntervalSince1970:-62135596800 - 100]; // The - 100 makes no difference
NSLog(@"date2 = %@", date2);
NSLog(@"date2 via dateFormatter1 = %@", [dateFormatter1 stringFromDate:date2]);
NSLog(@"date2 via dateFormatter2 = %@", [dateFormatter2 stringFromDate:date2]);
NSLog(@"date2 via dateFormatter3 = %@", [dateFormatter3 stringFromDate:date2]);

This results in the following output (on my iPad given its particular timezone):

date1 = 0001-01-01 00:00:00 +0000
date1 via dateFormatter1 = 0001-12-31 11:58:45 GMT-00:01:15
date1 via dateFormatter2 = 0001-12-31 23:58:45 GMT-00:01:15
date1 via dateFormatter3 = 0001-01-01 00:00:00 GMT
date2 = 0001-12-31 23:58:20 +0000
date2 via dateFormatter1 = 0001-12-31 11:57:05 GMT-00:01:15
date2 via dateFormatter2 = 0001-12-31 23:57:05 GMT-00:01:15
date2 via dateFormatter3 = 0001-12-31 23:58:20 GMT

Note date2 and how subtracting an additional 100 seconds did not reduce the value of the date but actually seemed to increase it. Obviously something peculiar about the way the internals of NSDate work, the time is adjusted by 100 seconds but this skews the day and month perhaps because it couldn't go any lower?

Oliver Pearmain
  • 19,885
  • 13
  • 86
  • 90
  • 1
    if you try [NSDate dateWithTimeIntervalSinceNow:- DBL_MAX] you get something like 41221-07--2147483641 00:00:00 +0000 .. – peko Mar 01 '13 at 15:05
  • -1 incorrect. This is only the minimum date for the current era, not the minimum date overall. – Dave DeLong Mar 01 '13 at 15:50
  • OK thanks for the pointer, I've updated my answer. Dates blow my mind and the NSDateFormatter really doesn't help matters. – Oliver Pearmain Mar 01 '13 at 16:06
2

maximum should be: 5828963-12-20 00:00:00 +0000 because

NSDate *dateSinceNow = [NSDate dateWithTimeIntervalSinceNow:DBL_MAX];
NSDate *dateSindeReferenceDate = [NSDate dateWithTimeIntervalSinceReferenceDate:DBL_MAX];
NSLog(@"min: %@, max: %@", dateSinceNow, dateSindeReferenceDate);

is the same.

minimum i get this is: 0001-01-01 00:00:00 +0000

cause this is what i get when i try:

NSDateFormatter *df = [[NSDateFormatter alloc] init];
[df setDateFormat:@"yyyy-MM-dd hh:mm:ss"];
[df setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
NSDate *myDate = [df dateFromString: @"0001-01-01 00:00:00"];
NSLog(@"%@", myDate);

but this is current era as pointed out by davedelong

so if you try the first example with - DBL_MAX you get: 41221-07--2147483641 00:00:00 +0000. no idea what era this is...

peko
  • 11,267
  • 4
  • 33
  • 48
  • I believe you're experiencing the same issue that caused me to ask this question. I think the minimum possible date value is 0001-01-01 00:00:00 however when you use the NSDateFormatter the date is being skewed by your timezone. Try again with the following: [df setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]]. – Oliver Pearmain Mar 01 '13 at 14:08
  • setting timezone gives me 0001-01-01 00:00:00 +0000 too – peko Mar 01 '13 at 15:03
  • 3
    This is only half correct. The max bit is correct, but your logic for the minimum is flawed. That's the minimum *for the current era* (CE). `NSDate` is perfectly capable of expressing BCE dates. What you're looking for is `[NSDate dateWithTimeIntervalSince1970:-DBL_MAX]` – Dave DeLong Mar 01 '13 at 15:49
  • @DaveDeLong is 41221-07--2147483641 00:00:00 +0000 a bce date? or do i have to an era or something else? – peko Mar 01 '13 at 15:57
  • @peko it looks like it to me. You can confirm this by `-compare:`-ing that date to the `0001-01-01` date. – Dave DeLong Mar 01 '13 at 15:59
1

The OP's real question (clarified in his comment to his own OP) is about 1/1/1. As you can see from the Playground below in my TL;DR;, Apple prints "31 Dec 0001" instead of "31 Dec 0001 BC" if you don't specify a DateFormatter with Era, so 1/1/1 AD could easily be 31/1/1 BC (the day before) if the OP's local timezone wasn't UTC.

NSDate / Date can effectively be used up to ~30m years in the past, but NSDateComponents / DateComponents operations fail with dates before 4500 BC. Read on for details...

TL;DR;

As some commentators and respondents have pointed out, [NSDate distantPast] (or Date.distantPast in Swift) is supplied by the API, however, that is not the minimum NSDate / Date - it is simply a convenience for business logic, since few applications need to do calculations into BCE (note that .distantFuture is also arbitrary - it's 1st January 4001 AD).

Other respondents (@peko) have dealt with real "distantFuture" maxima, but no one has fully explored the minimum.

So what does Date do when you actually give it dates before .distantPast? Here's a wee Playground:

// Swift 5 Playground
import Foundation

var d = Date.distantPast // "31 Dec 1 at 23:58"
// However, that's the default print, which *does not* include Era

let df = DateFormatter()
df.dateFormat = "EEEE dd MMMM yyyy G" // "G" is Era

print(df.string(from: d)) // "Friday 31 December 0001 BC\n"

// What happens when we go lower?
d = d.addingTimeInterval(-1e12) // "29 Nov 31689 at 22:12"
print(df.string(from: d)) // Thursday 29 November 31689 BC\n"

// Lower still?
d = d.addingTimeInterval(-1e14) // "18 Feb 3200497 at 12:25"
print(df.string(from: d)) // "Tuesday 18 February 3200497 BC\n"

// But it breaks here...
d = d.addingTimeInterval(-1e15) // ""
print(df.string(from: d)) // "\n"

So NSDate / Date has an effective minimum ~30m years in the past.

I have, however, had difficulty in doing calculations in much more recent times than NSDate / Date can deal with.

Specifically, Calendar's .dateComponents(unitFlags, from: date) fails (and prints an error message like below), earlier than about 4713 BC.

2021-12-03 19:15:48.885955+0000 Tempus II[61381:6027263] [general] CFAbsoluteTime -268229356861.914001 exceeds calendar calculation range.

... that "-268229356861.914001" is 6500 BC, but any trying to do DateComponent calculations on anything before 4713 BC will give a similar message, and the calculations will be wrong!

Here's another wee Playground to try that yourself:

let df = DateFormatter()
df.dateFormat = "EEEE dd MMMM yyyy G" // "G" is Era

// 500BC -> by 1000 years, which works
let dc500 = DateComponents(calendar: .autoupdatingCurrent,
                           era: 0,
                           year: 500)
var d = Calendar.autoupdatingCurrent.date(from: dc500)!
print(df.string(from: d)) // Thursday 01 January 0500 BC\n"

d = Calendar.autoupdatingCurrent.date(byAdding: delta, to: d)!
print(df.string(from: d)) // "Monday 01 January 0501 AD\n"
// *** NB 501AD is the RIGHT answer for 500BC + 1000, as there
// *** was no year 0

d = Calendar.autoupdatingCurrent.date(byAdding: delta, to: d)!
print(df.string(from: d)) // "Friday 01 January 1501 AD\n"


// 5000BC -> by 2000 years, which doesn't work
delta = DateComponents(calendar: .autoupdatingCurrent,
                       year: 2000)

let dc5000 = DateComponents(calendar: .autoupdatingCurrent,
                            era: 0,
                            year: 5000)
d = Calendar.autoupdatingCurrent.date(from: dc5000)!
print(df.string(from: d)) // "Sunday 01 January 5000 BC\n"

d = Calendar.autoupdatingCurrent.date(byAdding: delta, to: d)!
print(df.string(from: d)) // "Tuesday 01 January 2713 BC\n"
// *** WRONG!

d = Calendar.autoupdatingCurrent.date(byAdding: delta, to: d)!
print(df.string(from: d)) // "Wednesday 01 January 0713 BC\n"
// *** Back on track again

d = Calendar.autoupdatingCurrent.date(byAdding: delta, to: d)!
print(df.string(from: d)) // "Thursday 01 January 1288 AD\n"
// *** Again, correct 
// 713 -> 1BC + 1 AD -> 1288 = 1288 + 713 - 1 = 2000

p.s.

Don't use Date in business logic without understanding TimeZone, Calendar, DateComponents and DateFormatter!

Grimxn
  • 22,115
  • 10
  • 72
  • 85