calendarInterval()

A handler that returns the calendar difference between two AppleScript dates in years, months, days, hours, minutes, and seconds. The result is in the form of a string, but this can be changed to a list or record if required. The convention used for the interpretation of a “month” is explained in the comments.

OS version: Any

-- calendarInterval() by Nigel Garvey, 29th/30th September 2004
-- Returns the calendar difference between two dates
-- in years, months, days, hours, minutes, seconds.

-- A "month" is understood as the interval between the same day and time in
-- adjacent calendar months. If the last complete "month" between two dates ends
-- in a calendar month that doesn't contain enough days for this interpretation,
-- the "month" is then taken to end on the last day of that calendar month.

on calendarInterval(earlierDate, laterDate)
  if (earlierDate comes after laterDate) then
    set {laterDate, earlierDate} to {earlierDate, laterDate}
    set sign to "-"
  else
    set sign to ""
  end if
  
  -- Subtract the year, day, time, and month numbers individually.
  set {y, d, t} to {(laterDate's year) - (earlierDate's year), (laterDate's day) - (earlierDate's day), (laterDate's time) - (earlierDate's time)}
  copy {laterDate, earlierDate} to {b, c}
  set {b's month, c's month} to {January, January}
  set m to (b - 2500000 - laterDate) div -2500000 - (c - 2500000 - earlierDate) div -2500000
  
  -- Perform any necessary borrows.
  if (t < 0) then set {d, t} to {d - 1, t + days} -- borrow a day, add to time.
  if (d < 0) then -- borrow the calendar month before laterDate, add to days.
    set m to m - 1
    set daysInMonth to (laterDate - (laterDate's day) * days)'s day
    -- If the month has fewer days than earlierDate's day number, use the
    -- earlierDate number instead. This is equivalent to treating the last day
    -- of the calendar month as the last day of the "months" period. Trust me. :-)
    if (daysInMonth < earlierDate's day) then set daysInMonth to earlierDate's day
    set d to d + daysInMonth
  end if
  if (m < 0) then set {y, m} to {y - 1, m + 12} -- borrow a year, add to months.
  
  -- Extract the indvidual time units.
  set {h, min, s} to {t div hours, t mod hours div minutes, t mod minutes}
  
  return sign & y & " yr " & m & " mth " & d & " dy " & h & " hr " & min & " min " & s & " sec"
end calendarInterval

-- Demo:
calendarInterval(date "Saturday, 5 June 1999 00:12:30", date "Friday, 4 June 2004 00:12:30")
--> "4 yr 11 mth 30 dy 0 hr 0 min 0 sec"