logo To Foot
© J R Stockton, ≥ 2010-11-03

JavaScript Date and Time 1 :
Date Arithmetic
.

No-Frame * Framed Index * Frame This
Links within this site :-

See "About This JavaScript Site" in JavaScript Index and Introduction.

See "General Date/Time Introduction" in JavaScript Date and Time Introduction.

Date/Time Arithmetic

See also in Field Rollover.

Efficiency

Date methods can be slow, so be careful not to repeatedly recalculate anything that can be remembered.

Using UTC

I find that the JavaScript UTC methods can be somewhat faster than the corresponding non-UTC ones. In hindsight, that should be expected. Unless actual date/times are needed, for repetitive work such as calendar display it should be noticeably better to use the UTC methods.

This shows generation of a Date Object set by UTC literals and generation of a Date Object representing UTC YMDhms from a Date Object value representing the same civil YMDhms :-

Using Day Counts

It can be quicker to use, instead of Date Objects, algorithms using efficient Day Count conversion routines; this is done in some of Week Number Calculation.

Creating a Date Object

Normally, a Date Object will always be created with a particular value required : "now", or given by parameters.

Otherwise, for N being numeric, new Date(N) should be faster than new Date() since, while both create an Object, the former only needs to load it with N while the latter must consult the system clock and maybe the locality settings. Moreover, with the former, the value is reproducible. So, unless the current date/time is needed, the former is better.

Obj = new Date()	// if "now" is needed
Obj = new Date(0)	// otherwise better

To load UTC YMD into a Date Object, use, as above :-

Obj = new Date(Date.UTC(Y, M-1, D)) // also h m s ms

Copying a Date Object

To copy or clone a Date Object D1 consider :-

D2 = new Date(+D1)	or	D2 = new Date(D1.valueOf())

in which the + calls for the transfer of a Number rather than allowing a String (properties other than the date/time will not be copied).

Seasonal Clock Changes

Remember to allow for possible Summer Time and Time Zone changes, except when using GMT/UTC. Much code presented elsewhere ignores these, and is therefore in error part of the time. If UTC functions can be used, the overheads and errors can be avoided.

Otherwise, if Days are specified, then work directly with Days, rather than with milliseconds from 1970, as civil days are not always 24 hours long, but can be 23 or 25 hours. Likewise, both Months and Years have varying numbers of Days. Always test over a Summer Time change.

Anything containing 1000*60*60*24 or 86400000 or 864e5 is suspect, unless it is used in conjunction with UTC functions or there is appropriate rounding or offset adjustment. Note that in general it will be the user's settings that matter; on the Web, Summer Time remains significant even for Polar and Equatorial authors.

Where that number is used, it is better written as 864e5 since that form is less prone to error.

Clock Change Demonstration

The addition of 24 hours of actual time has advanced the EU civil time by only 23 hours; the inverse occurs in Spring.

Opera 10.51 has a peculiar problem.

Day Counts

SEE ALSO my Date and Day Count which is largely developed from Zeller's work.

JavaScript and UNIX Day 0 was GMT 1970-01-01 Thu; MJD 0 was GMT 1858-11-17 Wed; the difference is 40587 days. Day 0 for Delphi 2+ and compatible spreadsheets, etc., was Civil 1899-12-30, 25569 days before UNIX Day 0. See Date Miscellany I and Date and Time Scales.

Remember that a browser's local time is likely not to be GMT/UTC; so getTimezoneOffset may be needed.

Parts of this uses similar methods to those of The Calendrical Works of Rektor Chr. Zeller : The Day-of-Week and Easter and Date and Day Count.

Conversion to DayCount

Date Object to True UNIX day number

Given a Date Object, divide by 864e5 and take the integer part.

Date Object to Civil UNIX day number

Given a Date Object (or string), go to 0000h GMT and divide; or work with the internal value of a Date Object.

Note that new Date(D) where D is a Date Object is not reliable for years before A.D. 100. If D is known to be a Date Object, use +D which will also often be quicker.

For differencing, Standard Time Zone (but not offset change) can be ignored. The UNIX Epoch date is a Thursday; adding three to put the base at the start of a week can be useful.

Y M D to MJD without using a Date Object

Tests - all days in :     (slow)   Use Back to return.
Lines should end 0 0 1.

Conversion from DayCount

True UNIX day number to Date Object of Start

Civil UNIX day number to Date Object

CMJD to Y M D without using a Date Object

Date/Time Incrementing/Decrementing

Again, remember Summer Time and Time Zone; days are not all the same length. Do not attempt date shifting in time units, or month/year shifting in days; direct functions are provided.

Any attempt to deal with civil days by using milliseconds is pure folly - though JavaScript GMT/UTC days should be constant in length (being specified (ECMA-262 3rd Edn Sec 15.9.1.1) as having no Leap Seconds).

Note : The clock change, Spring and Autumn, can occur on the 31st of the month; so, for example, 86400 seconds before April 1st can be late in March 30th (e.g. EU, one year in 7) instead of being the last day of March.

Set as many components as possible in a single method call (the optional parameters were introduced in JavaScript 1.3)

  setFullYear(yearValue[, monthValue, dayValue])
  setMonth(monthValue[, dayValue])
  setDate(dayValue)
  setHours(hoursValue[, minutesValue, secondsValue, msValue])
  setMinutes(minutesValue[, secondsValue, msValue])
  setSeconds(secondsValue[, msValue])
  setMilliseconds(msValue)

Days

Use such as

which always give the present civil time tomorrow (strictly, there is one hour in Spring during which this is not possible, for places with Summer Time); as usual, month and year will be changed if necessary.

For tomorrow (maybe to see if today is the end of the month) :-

Months

Note that incrementing/decrementing in Months can, in the first instance, get to the 29th, 30th or 31st of a month that does not have that date; in that case, the Object enters the following month, which may or may not be appropriate (see also 0: Date Object Information).

Starting with January 31st of successive years, that correctly gives for leap years Feb 29th, else Feb 28th. Similarly for altering the Year and avoiding a non-existent Feb 29th.

To step continuously along month-ends, step the beginnings of each following month, and go back one day each time.

Date/Time Differencing

Once more, remember Summer Time and Time Zone.

One must decide whether the difference is to use days each of 24 hours, or is to use days of local time.

Mere comparison is easy (apart from the autumnal ambiguous hour); compare the fields in order of diminishing significance, and return at the first difference; or use Date objects and compare their values.

To determine whether one date is within a given number of days of another, it may be easier or safer to increment/decrement one of the dates and then compare the result with the other date than it is to work by subtraction.

If Date Objects are provided, one must consider possible effects of their time components on comparisons.

Note : In Opera 9.2x, new Date("Y/M/D") gives NaN for Y after 9999.

Auxiliary Functions

See in datefmts for ISO 8601.

The above function is used in the code below.

Difference in Days

For the difference of dates in days, set the local dates into date objects, subtract the values, divide by 864e5, and then round to integer; this should accommodate Summer Time transitions.

Alternatively, set the dates as GMT into date objects or use Date.UTC.

Depending on your definition of "between", perhaps increment or decrement the result.

ISO 8601 Gregorian Days Difference Demo

Input : ISO 8601 dates; 100-01-01 to 275760-09-13; used in two ways :-
-

  Results : Error Message or difference in Days.

A difference in days, whether or not accompanied by months and/or years, can readily be converted to weeks and days :- W = (D-(D%=7))/7 ; think about needs for D<0 . To reverse, D += 7*W .

Difference in Years, Months and Days

Difference in Years, Months and Days is somewhat arbitrary, as years and months vary in length.

See also Thirty-Day Months in JavaScript Date and Time 3 : Input and Lengths and Date Miscellany II.

General

If avoidable (it may be needed for formal administrative purposes), do not attempt to subtract date/time in Y-M-D h:m:s; all that borrowing is tiresome and error-prone. Think about the exact requirements; check management expectations and needs. For people's ages, however, it seems often necessary to work in Y-M-D, with care.

Since there are always 12 months in a year, one can for simplicity work with M' = 12×Y+M; and for subtraction one can ignore month numbers being 1..12 rather than 0..11. Similarly, hh:mm:ss can be converted to seconds, if Summer Time transitions are ignored.

Frequently, either the initial or the final date will be fixed, and the other will be the current date.

Difficulties

A sequence of differences is obtained by keeping one date fixed and stepping the other date by one day at a time.

A sequence of differences should always look plausible. Requirements include :-

I doubt whether all of those can be achieved at once!

Consider
 Apr 4 to May 6 must be one month and two days; also May 4 to Jun 6.
 Step the start daily to May 4; 30 steps, as Apr has 30 days.
 Step the close daily to Jun 6; 31 steps, as May has 31 days.
 Thus there cannot be a 1:1 correspondence between the two sets of
 intermediate differences; there must be an omission or a repeat.
Consider
  Apr 4 to      Jun 3; 1 month gets to May 4, then -4+31+3 = 30 days.
  Jun 3 back to Apr 4; 1 month gets to May 3, then +3+30-4 = 29 days.
In the first case the "borrow" is of May, in the second it is of April.

Detail

First convert BC dates to astronomer's notation, in which years go ... -2 -1 0 +1 +2 ... ; and, if necessary, consider past calendar reforms. The following uses only Gregorian AD dates.

Convert the years and months to just months, and subtract. Subtract the dates; if negative, first "borrow" the length in days of an appropriate month, with allowance for its year if it is February. Convert months back to years and months.

Increasing Interval - Borrow from the month before the moving date, as Feb 01 == Jan 32.
Decreasing Interval - Borrow from the month with the moving date, as Feb 00 == Jan 31.

ISO 8601 Gregorian Administrative Date Difference Demo
  Use DIM throughout, for test
Input : ISO 8601 dates; 0-01-01 to 275760-09-13 :-
  ≥    
  DiffDateB : Decreasing Interval
  DiffDateC : Increasing Interval
Result : Error Message or difference Yr, Mo, Dy.   UNDERTESTED.
New Trials - D1 ≥ D2, Range :-
      B C     All

Note that with inputs of 2001-03-01 & 2001-01-30 the result that DiffDateC gives is 0,1,-1 and one may wish to put a lower bound of 0 on the Days, or borrow two months.

Average-Length Method

Another way would be to determine the difference in days, divide by 365.25/12 or 365.2425/12 and round down to get integer months, after which the rest is simple.

  to  

 

However, that makes the effects of varying month- and year- lengths rather conspicuous.

Difference in Years and Days

This has some of the problems of Difference in Years, Months and Days.

It is tricky, in respect of February 29th :-

ISO 8601 Gregorian Administrative Y D Difference Demo

Input : ISO 8601 dates; 0-01-01 to 275760-09-13 :-
  ≥      
UNDERTESTED.   Result : Error Message or difference Yr, Dy.
diffYearAndDate :   Always wrong for Y<100 (Y2k)
Fails for Y>9999 in Opera 9.2x

Difference in Years

For the difference in years alone :-

ISO 8601 Y Difference Triple Demo

Input : ISO 8601 dates; 0-01-01 to 275760-09-13 :-
  ≥  

A:   B:   C:
UNDERTESTED.   Result : Error Message or difference Yr.

Difference in Years and Fractions

Non-optimal code :-

Remember, though, Summer Time, and those born on February 29th.

Difference in Time

Again, remember Summer Time and location.

One can subtract seconds; if negative, borrow a minute; then subtract minutes, etc.

If days, hours, minutes, seconds are needed, then set the start and finish date/times into a date object (new Date() creates an object for the current date/time), and take valueOf() for each, and subtract these to get the difference in milliseconds. If only times are given, use a dummy date, e.g. T = "12:34:56" ; D = new Date("1/1/70 "+T) , possibly appending UTC.

Or, for start and finish, multiply days by 24, add hours, multiply by 60, add minutes, multiply by 60, add seconds; and subtract.

After subtracting, use Mod & Div to convert to days .. seconds :-

  a mod b = a % b
  a div b = Math.floor(a/b)
          = (a-(a%b))/b // may need Math.round

  Sec  = ms  div 1000 // ms  = ms  mod 1000
  Min  = Sec div   60 ;  Sec = Sec mod   60
  Hr   = Min div   60 ;  Min = Min mod   60
  Days = Hr  div   24 ;  Hr  = Hr  mod   24

It normally does not make a great deal of sense to convert time differences from days to months and years, which are of variable lengths.

JavaScript Date and Time Index
Home Page
Mail: no HTML
© Dr J R Stockton, near London, UK.
All Rights Reserved.
These pages are tested mainly with Firefox and W3's Tidy.
This site, http://www.merlyn.demon.co.uk/, is maintained by me.
Head.