© J R Stockton, ≥ 2010-09-07

JavaScript : Object DATE2.

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.

CAVEAT
All of this code was written presuming that new Date(Y,M,D) would always set 0h local of the date. But see Opera Summer Time; Opera was corrected between versions 9.61 & 9.64.


The page with DATE1 is superseded, abandoned, and removed.

The Need for a Better DATE Object

There are deficiencies in the native JavaScript Date Object of ECMA-262 3rd Edition (2000-03-24) and ISO/IEC 16262 2nd Edition (2002-06-01). It lacks general direct support for reading and writing ISO 8601:2004 formats and for European numeric date strings. It does not directly handle ISO Week Number or Ordinal Date. It converts years given as 0-99 to 1900-1999 or 2000-2099. The output of Date.toString is overly browser-dependent; and, in some browsers, it has parenthesized trailing rubbish.

One can add new methods and properties to the native Date Object, as in inc-date.js (this page does not use that file). Or one can write a completely new DATE Object, using the native Date Object only for access to the current date/time and the local Time Offset Rules; that means implementing the Gregorian Calendar arithmetically in JavaScript. Or, as here, following a suggestion by LRHN, one can write a new DATE Object, using an internal native Date Object but not necessarily directly exposing its methods and properties.

† The 5th Edition (no 4th) of ECMA-262 has very limited support for ISO 8601, apparently intended just for use in JSON.

Object DATE2

Object DATE2 is intended principally to provide a set of date methods, without necessarily showing how a new object type should be implemented.

Moderately tested; a test area for readers is below. See comment in code. Test before use.

A DATE2 Object has a property Q which holds a native Date Object which holds the Object's value. Most of the work uses methods of Q. There should be no need to access Q directly.

The code is reasonably modular, so it should be easy enough to comment out and remove any parts not required. It uses only what is in ECMA 3.

A DATE2 Object as here so far coded may not have all the "generic" features of built-in Object types.

Most code using Date objects can be converted to use DATE2 objects by just changing to the corresponding DATE2 methods, and using where convenient the additional methods and properties. Method toString now takes an optional format argument, and should provide all necessary output formatting.

Local Civil Time and GMT/UTC

System implementations would use existing LCT code, with the "find the offset from UTC" part skipped if DATE2.GMT was true.

For each of the UTC methods of the Date Object, each of which corresponds to a LCT (Local Civil Time) method, the DATE2 Object has X methods (even provided for seconds and milliseconds, in case it matters when setting across offset changes). They simply use the corresponding LCT or UTC Date methods, for example method getXDate uses either getDate or getUTCDate, depending on whether the static DATE2.GMT currently amounts to false or true. The same applies to toString(), which is aliased to toXString().

The same applies to new DATE2() when it is given a second argument which is not undefined or object (RegExp) - when numbers are given for year, month, and perhaps more.

Routines containing DATE2 Object methods can thus be switched between LCT and UTC merely by setting DATE2.GMT, and do not need to be written in two versions.

The original methods are not directly exposed, but are available as methods of the Q property.

Offsets should only be used in GMT mode.

Efficiency

GMT work is faster than LCT work; LCT should be used only when needed. That could be encouraged by making the default value of DATE2.GMT be true.

Determining any of year, month, date requires work largely sufficient to determine the other two; and commonly all three are needed together. Therefore, where practical, results should be more-or-less cached. The same applies to year, week-number, day - method toXString caches, to a limited extent, Y W D.

Consider function return of object types for {Y M D} {Y O} {Y W D} {h m s ms}.

Constructor

Constructor new DATE2 is patterned on new Date, but accepts many more string formats, directly or using a Pattern String RegExp(?).

Also, DATE2 Object method getXEasterSunday and static methods DATE2.ifXisOK and DATE2.giveXEasterSunday can create a new DATE2 Object.

ISO 8601

The DATE2 Object constructor and DATE2.parse accept ISO 8601 date field separators, and the Object can write in ISO formats with selectable date/time separator and optional Offset field, using Extended (with separators) or Basic (without separators) Format. ISO yyyy-mm-dd, yyyy-Www-d and yyyy-ddd are handled both for input and for output.

A simple Week Object {Y:yn, W:wn, D:dn} is used for function return to represent ISO 8601 week numbering.

JSON

A JSON date/time string, as given by method toJSON in some browsers, can be generated as (S) below. It can be read as (D) by new DATE2(string), including any reasonable number of digits for fractional seconds.

Object to String

The toString method of the native Date Object gives a browser-dependent and often inappropriate string, as shown :-

Results of Date.toString(), etc. :-
 MS IE 7       : Sat Mar 7 13:13:14 UTC 2009
 MS IE 7       : Wed Jul 1 00:00:00 UTC+0100 2009
 Firefox 3.0.7 : Sat Mar 07 2009 13:11:55 GMT+0000 (GMT Standard Time)
 Opera 9.27    : Sat, 07 Mar 2009 13:14:33 GMT+0000
 Opera 9.64    : Sat Mar 07 2009 00:00:00 GMT+0000
 Safari 3.2.2  : Sat Mar 07 2009 13:15:12 GMT+0000 (GMT Standard Time)
 Chrome 1      : Sat Mar 07 2009 13:16:02 GMT+0000 (GMT Standard Time)

 Netscape      : Mon Sep 28 14:36:22 GMT-0700 (Pacific Daylight Time) 1998

Default results of DATE2.toString() :-
		Sat, 2009 Mar 07, 13:16:46 GMT+0000 // DATE2.GMT false
		Sat, 2009 Mar 07, 13:16:46 GMT      // DATE2.GMT true
		N.B. Day and Month abbreviations can be altered

A new method toXString is provided. Its default result format is given by the current DATE2.Fmt string, and is fixed-length and browser-independent, with fields in a more logical order. Method toXString can be given a Format String argument.

The Format String is vaguely mnemonic and is interpreted letter by letter - note that XVC are each one letter before YWD - non-specific characters, including T, W and Z, are copied, and $ is the escape character. Like other X methods, it uses either local time with offset, or GMT, depending on global DATE2.GMT.

FORMAT STRING CHARACTERS

 $  Escape character; the next character is taken literally
 Y  Year using at least four digits
 y  Two-digit year (not for Y<0)
 M  Two-digit month
 o  Month as in array DATE2.Mnth, default "Jan" - "Dec"
 D  Two-digit day-of-month
 d  1/2-digit day-of-month
 x  Suffix 'st' 'nd' 'rd' 'th' for day-of-month
 O  Three-digit Ordinal Date (day-of-year)
 a  Day as in array DATE2.DoWk, default "Sun" - "Sat"
 h  Two-digit hours, 00 - 23
 p  US hours, 1 - 12
 P  postfix a.m. or p.m.
 m  Two-digit minutes, 00 - 59
 s  Two-digit seconds, 00 - 59
 i  Three-digit milliseconds, 000 - 999
 S  As for native Date.toString()
 G  String "GMT"
 U  String "UTC"
 F  Offset as ±hh:mm
 f  Offset as ±hhmm
 X  Week-numbering year using at least four digits
 V  Week-numbering two-digit week, 01 to 52 or 53
 C  Week-numbering one-digit day, 1 to 7
 R  Month as single character Ⅰ to Ⅻ
 r  Month as single character ⅰ to ⅻ

Any other character stands for itself.

N.B. DATE2.toXString() is aliased to DATE2.toString().

String to Object

DATE2.parse and new DATE2 can read dates with British, Continental, or ISO date field separators. Numeric fields are not validated. For input of European D M Y string dates, a leading tilde transposes the first two one-or-two-digit numeric fields, so dealing with JavaScript's mm/dd/yyyy FFF habit. The Month can be a single-character Roman numeral (Ⅰ-Ⅻ ⅰ-ⅻ, \u2160-\u216B \u2170-\u217B; not necessarily constant-width).

Negative years, and years after 9999, should work throughout. The 12-hour clock is tolerated in some input.

In string input, if the beginning or whitespace is followed by two zeroes and two digits, the field is presumed to represent a year. The second zero is replaced by a four (adding 400 years), and the corresponding number of milliseconds is later subtracted. That is to enable use of four-digit year strings for dates in the first century A.D., and should do no collateral damage. A similar fix deals with negative years in strings.

Any string S which can be read by new Date(S) can certainly be read as the same value into a DATE2 Object by new DATE2(+new Date(S)). Any such string should be accepted by using new DATE2(S), perhaps as a better value.

For input with "foreign" alphabetic Months, define array DATE2.Mnth; each language needs a dummy entry before its January. CAVEAT: in date string input, my Opera 9.64 ignored any mis-spelt month, substituting the current month.

Input Validation

To what extent should date string input validate the exact compliance with a defined format? Input should be, generally, liberal; but is a good place to offer rigour. There are two aspects of validity - the pattern of digits and other characters, and the values of the numbers. Validation requires some knowledge of the pattern. The new Object supports both liberal and rigorous input.

For validated date string input, new DATE2(Str, Rgxp, Ordr) behaves as DATE2.ifXisOK(Str, Rgxp, Ordr) except that failure returns a DATE2 Object of value NaN.

DATE2 Object Properties

A DATE2 Object has almost none of the exact method names particular to a Date Objects; but for each missing Date method there is an obviously-corresponding DATE2 method. DATE2 methods are indicated in List of Methods.

DATE2 has its own setTime, getTime, toString and valueOf methods.

The internal Date object can be accessed as the Q property, which it should not be necessary to use.

Range and values are as for the native Date Object, 1970-01-01.0 GMT ± 1e8 days. Gregorian Easter, as imported, is limited to non-negative years; otherwise, everything should be full-range.

Static DATE2 Properties

Static properties of DATE2 are created by function SetStaticDATE2 (a function is used only for convenient display on this page).

Given a complete ISO Basic Format, DATE2.enlargeISO(BF) returns an enlarged form of it, which new DATE2() and DATE2.parse() can read.

DATE2.ifXisOK(Str, Rgxp, Ordr) reads and validates a numeric string date (no time or offset) from Str by matching it to the RegExp Rgxp, using the matches in order given by string Ordr to provide arguments Y M D to new DATE2(,,), and checking the Month. Therefore, Rgxp should ensure that the date field is smaller than about 330. DATE2.ifXisOK returns an Object with a property Bad, and, if the value of that is zero, a property Objwhich is a new DATE2 Object. Non-zero indicates whether pattern, logic, or value failed validation.

DATE2.readPtrn(St, Ptrn) reads a numeric string date/time/offset from St by matching it to the pattern Ptrn, using the matches to assign digits to fields, returning a DATE2 Object. Unlisted characters are ignored (could change to must match???); fields default to zero.

DATE2.readPtrn Patterns
	Date	Y year	M month	D date
	Time	h hours	m mins	s secs	f msecs
	Offset	i sign	h hours	k mins
	Example	'YYYY-MM-dd hh:mm'

The Present DATE2 Code

Here, the additional Date Object methods of inc-date.js are not included. Two utility routines (LZ ToNd) from inc-cmmn.js are used. Once the Object is debugged, LZ can be simplified or implemented with ToNd.

In general, the code is being optimised for minimum lexical repetition rather than for speed.

The set and inc routines all return this, so they can be chained. That seems better than what the ECMA 3 routines do.

Known Bugs

In the expanded representation of a date, which can handle years outside 0000 to 9999, ISO 8601 requires either a plus or a minus before the year. That probably needs to be added for output here.

Changes

2009-10-01 : Name expandISO changed to enlargeISO to avoid possible confusion with the meaning of "expand" in ISO 8601.

Code Shown

Testing

During development, the code has been tested intermittently in Firefox, MS IE, Opera, Safari, Chrome, using Windows XP sp3.

Count

User Testing

JavaScript Tester : Put code in the main box, and press Eval. F.Result shows the final value.
DATE2.GMT :
F.X0 :

A superior tester, but without DATE2, is at JavaScript/HTML/VBS Quick Trials.

Date Lister

The first control requires four fields - two optionally-signed year numbers, one unsigned number, and a character y m or d.

List sequential dates in chosen formats
Year range, step, unit   |   Format string            
 
DATE2   .GMT

List of Methods and Properties

  CONSTRUCTOR :

new DATE2()			Arguments as for new Date()

  OBJECT METHODS :

 Unlike for the Date object :

toString()			Now as Wed, 2009 Mar 04, 16:19:29 GMT+0000
toString(Fmt) 			Default Fmt = DATE2.Fmt : see DATE2.GMT

 Like for the Date object :

valueOf()			Still gives milliseconds from epoch.
getTime()			〃
setTime()			Still sets milliseconds from epoch.
toLocalString()			As for Date.toLocaleString

 New :

toXString()			Alias of toString()
toXString(Fmt)

getTimeOffset()			As Date.getTimezoneOffset()

getXPartYear()			Two-digit string, LCT or GMT, year % 100
setXPartYear()			Preserves Y/100|0
getXFullYear()			As with Date, but LCT or GMT
setXFullYear(Y4, M, D)		〃
getXMonth()			〃
setXMonth(M, D)			〃
getXDate()			〃
setXDate(D)			〃
getXHours()			〃
setXHours(h, m, s, ms)		〃
getXMinutes()			〃
setXMinutes(m, s, ms)		〃
getXSeconds()			〃
setXSeconds(s, ms)		〃
getXMilliseconds()		〃
setXMilliseconds(ms)		〃
getXDay()			〃
setXDay(d)			if d in 0-6, same week 0-6

getMJD()			MJD 0.0 = +1858-11-17 00:00:00 GMT, Wed
setMJD(MJD)			Use MJD with DATE2.GMT true
getCJD()			CJD 0.0 = -4713-11-24 00:00:00 LCT, Mon
setCJD(CJD)			Use CJD with DATE2.GMT false

getXPosInMonth()		Return 1-5 for 1st-5th Thisday of month
setXPosInMonth(K, D)		Set K'th D-day of month; 5 = last

getXDayOfYear()			Get Ordinal Date 1..366
setXDayOfYear()			Set Ordinal Date
getXDayCount()			1970-01-01 = 0
setXDayCount()			〃

toISO8601Xofst(BF)		BF = Basic Format e.g. date YYYYMMDD
toISO8601Xtime(BF)
toISO8601Xdate(GOW, BF)		GOW = Greg, Ord, Week
toISO8601XYWDstr(BF)		yyyy-Www-d
toISO8601Xstr(T, GOW, ofs) 	T = Separator & BF, ofs = offset

getISO8601XDay()		Mon=1 to Sun=7
setISO8601XDay(d)		1-7, same ISO Week; but d unbounded
getISO8601XYWD()		{Y:y,W:w,D:d}
setISO8601XYWD({Y:y,W:w,D:d})	Y W D all required

incXFullYear(dY, dM, dD)	Consider moving into a short month
incXMonth(dM, dD)
incXDate(dD)

getXEasterSunday()		Return new Object, same Y, new M D
setXEasterSunday()		Same Y, new M D


  STATIC METHODS :

 Like for the Date object :

DATE2.now()			Return current instant, ms from Epoch
DATE2.UTC(...)			Similar to Date.UTC(...), but YY OK
DATE2.parse(String)		Similar to Date.parse(String), 〃

 Unlike for the Date object :

DATE2.enlargeISO(String)	Return expansion of ISO Basic formats
DATE2.countDown(ms)		Return { ± D h m s }
DATE2.giveXEasterSunday(YR)	Return DATE2 Object, if Easter available
DATE2.ifXisOK(Str, Rgxp, Ordr)	Return {Bad:#[, Obj:DATE2]}
DATE2.readPtrn(St, Ptrn)	Return DATE2 Object; Ptrn as "DD MM YYYY"

  STATIC PROPERTIES :

DATE2.GMT			Controls X methods : true is GMT/UTC
DATE2.DoWk			Array(8) ["Sun","Mon", ..., "Sun"]
DATE2.Mnth			Array(13n) ['***','Jan', ..., 'Dez']
DATE2.Fmt			Default "a, Y o D, h:m:s Gf"; else
 Y=YYYY y=YY M=MM o='Mth' D=DD d=D+ x O=DDD a='Day', X=YNum V=WN C=D;
 R r months in Roman (single-character);
 h=hh m=mm s=ss i=m/s; F=off:set f=offset ; S = Date default ; $ = escape.

Possible Additions

Get Longitude, Season, Hemisphere; get clock change dates, code. Increment time.

Check whether consistency in format string letters can be improved.

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, , is maintained by me.
Head.