Days between dates & our perception of time

NSCalendarDate and NSDate make it generally trivial to work with dates, times, time zones, and all that rot. At least, they make it easier than just about any other date/time classes I have run into.

Still, there are a handful of operations that are non-obvious. At least, non-obvious to me.

One, in particular, is answering the question of “how many days between this day and that day?”. The obvious answer is to grab the time interval between the two dates and divide by the number of seconds in a day. The problem is that a date object stores a date and time. If the two dates represent, say, morning on one date and afternoon on the other, the difference in days between will be off by one.

The math is correct, but our perception of “days between” has nothing to do with the time of day between the two days. Capturing perception in an API is hard.

For example, most people consider tomorrow one day away even if it is 8pm today.

My first pass at implementing a solution was to derive calendar dates locked to midnight for the two dates and do the math. And it doesn’t work with generic NSDate instances.

Instead, a bit of basic math solves the problem nicely. The following is for calculating the # of days between a “due date” and “today”.

    int dueDateTimeInterval = (int) [[self valueForKey:@"dueDate"] timeIntervalSinceReferenceDate];
    int nowTimeInterval = (int) [[NSDate date] timeIntervalSinceReferenceDate];
    
    int clampedDueDate = dueDateTimeInterval - (dueDateTimeInterval % SECONDS_IN_A_DAY);
    int clampedNowTimeInterval = nowTimeInterval - (nowTimeInterval % SECONDS_IN_A_DAY);
    
    return (clampedDueDate - clampedNowTimeInterval) / SECONDS_IN_A_DAY;

Works for me for the cases I test. If I have made a silly mistake, I’m sure it will be made known shortly after this post hits the weblog.


Deprecated: link_pages is deprecated since version 2.1.0! Use wp_link_pages() instead. in /srv/www/friday/bbum/wp-includes/functions.php on line 4713


12 Responses to “Days between dates & our perception of time”

  1. ssp says:

    A couple of points:

    1. Amen for the comments on handling dates.
    2. While NSDates are perfect for handling data, I tend to consider them a pain when having to read/write dates to and from files or the UI. Particularly when wanting a date format that’s not pre-defined by the system _and_ wanting to respect the user’s preferred date formats as well as localisation this does require a bit of care.
    3. This may be different from person to person, but when having an ‘appointment’ at 0:40 or at 1:15 in the night or so, I tend to consider it being ‘today’ rather than ‘tomorrow’ the evening before. I’d put the date change around 4 or 5 in the morning for intuitively correct notes. My perception is that new days start while I’m sleeping and not at 0:00.

    4. What are your test cases? Do they contain any dates where the due date is in the evening and the current time is at mid day? As the reference time is given in GMT, your time zone should be GMT-8 or something and your code doesn’t use the time zone you’re in, I’d expect such examples to give the wrong result, i.e. ‘tomorrow’ when it should be ‘today’ as those times are on different days in London but not in California.

  2. Bob Ippolito says:

    Of course, the correct way to do it is to use NSCalendarDate..


    NSCalendarDate *clampToDay(NSDate *d) {
    // might want to allow for timezone fudge here?
    NSCalendarDate *c = [NSCalendarDate dateWithTimeIntervalSinceReferenceDate:[d timeIntervalSinceReferenceDate]];
    return [c dateByAddingYears:0 months:0 days:0 hours:-[c hourOfDay] minutes:-[c minuteOfHour] seconds:-[secondsOfMinute]];
    }

    int stupidHumanDateDifference(NSDate *a, NSDate *b) {
    return [clampToDay(b) dayOfCommonEra] - [clampToDay(a) dayOfCommonEra];
    }

  3. bbum says:

    Why is that any more correct than what I did?

    And it ends up producing numerous temporary objects that are later tossed.

  4. Bob Ippolito says:

    Actually, you probably don’t need the clamping.. dayOfCommonEra is probably enough. It’s early here.

  5. Bob Ippolito says:

    It’s more correct because SECONDS_IN_DAY is going to have a little bit of drift (not really in the time spans we’re talking about, but it’s still not technically correct), and it accounts for time zones. Your example only works for GMT.

    Throwing out clampToDay and instead just converting to NSCalendarDate and doing a direct calculation with dayOfCommonEra should be perfectly fine, which gets rid most of the messages sent. If you were using NSCalendarDate instead of NSDate to begin with (which you probably should, given how you intend to use them), then there are no intermediate objects at all.

  6. Pierce Wetter says:

    I’ve had a lot of experience with this problem.

    First off, when you want to represent a “day”, not a timestamp, NSDate and NSCalendarDate are actually the wrong solution to the problem because they represent a point in time, not an entire day. You really want to calculate some sort of day offset. If you use 1/1/2001 as zero, that corresponds to NSCalendarDate, which makes conversion easy.

    If you must use NSCalendarDate (because its easy, because EOF/CoreData supports it, etc.) you have to use Noon, not midnight or DST or timezone issues will cause your date to shift inappropraitely. So day 0 is 1/1/2001 12:00 not 1/1/2001 00:00. If you get a non-noon time, then you’ll have to round it to the nearest day, but ‘ware midnight if you get times from both EST and PST…

    In our case, the market closes at 4pm EST, so midnight is far away, but there’s no good answer for what to do at 10pm PST…

    So of all the solutions, Bob’s is best because he is independently rounding each date to a day, this is good because this part of the code is the most application dependent. If you’re work day focused, and you’re working with programmers, anything before 7am EST is probably the “previous day’s work”. If you’re doing something else, perhaps midnight makes more sense. Rounding each date separately makes sense, because if you are using midnight, but have dates from different time zones, the round to day code will work correctly in then 1am EST will round to tomorrow, while 10pm PST will round to yesterday.

    The real solution is that if you really need days, you need an NSDay class, but I’ve been bitching about that for years (along with the inability of either EOF or CoreData to map a class to a simple data type like integer…)

  7. John C. Randolph says:

    This reminds me of something I started many years ago, which was a set of classes to represent spans of time, and selections of time. The span was a pair of dates, which could include NSDistantFuture and NSDistantPast. The TimeSelection class was a set of spans, that automatically merged any spans added to it that overlapped existing spans.

    -jcr

  8. Malte Tancred says:

    As has been pointed out, an NSDay class would be nice. In my own efforts to solve the problem of representing whole days without getting caught in time zone hell, I stumbled over The Calendar FAQ, in which one can learn about Julian Day numbers and how to calculate the week number (as defined in ISO standard 8601 and which we use in Sweden). The algorithms presented are easily implemented in just about any language. I’ve written code in Java and Objective-C (there’re some hacks here if you’re interested).

    Oh, in ruby, all you have to do is require 'date'. I wouldn’t be surprised if python has a similar API.

  9. Brian says:

    and then there is the issue of what day it really is… because people use the language so ambiguously, you might go out with some friends at 10pm, and talk about what you are doing tomorow. then, at 2am, or even 4am, everyone says let’s call it a night, and then what do they say? ‘See you tomorrow!’ – meaning see you later today. So, to an extent, you would have to put context into the class, because if i had been to bed and gotten up at 4am and seen them, i would say ‘see you later today.’

    best wishes in the classes. can think of a few other ways to do it, but all end up doing essentially the same things already posted.

  10. Alexander Lamb says:

    That is something I really wanted for a long time: a NSCalendar class (I remember suggesting this in Redwood city in 1996:-)
    Calculating days between dates is important, but what about calculating working houres between dates! This becomes especially important in project management software. You need to first merge two or three NSCalendar objects (the one for the company with the regular working hours, the one for the person for whom we are tracking the tasks which may add some vacations or different working hours and maybe even a project calendar).

    Then, we need to be able to ask: “At what date and time shall we be if we add X working hours to a given date and time considering calendar A and B”…

    Of course, I had that kind of class in a pet project of mine. But if Apple could bring some basic NSCalendar it would be a great help (it would have to be core data compatible since we would want to store custom calendars).

    Alexander Lamb

  11. MMaI says:

    I think the whole premise of this is wrong. First you say “days between” and and then you say you want tomorrow to be considered one day away. Those are two different things, because there are zero days *between* today and tomorrow. I think ssp made the point best when they noted that a “relative” day starts when you get up, and that varies from person to person. So it’s really more of a time zone or perhaps localization issue.

  12. bob says:

    [[NSDate date] timeIntervalSinceReferenceDate] can be written as [NSDate timeIntervalSinceReferenceDate]

Leave a Reply

Line and paragraph breaks automatic.
XHTML allowed: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>