© J R Stockton, ≥ 2011-09-14

VBScript Date and Time 1.

Links within this site :-

! ! This page expects VBScript ! !

It seems unlikely that code on this page will work in browsers other than Microsoft Internet Explorer.

Its methods can be used in Windows Script Host and in other Microsoft applications, such as Excel.

VBScript is invoked (with Option Explicit) as the first scripting on this page → ←.

Analogous code, and more, can be found via JavaScript Date and Time.

Introduction

VBScript Date and Time are Gregorian and Local; Summer Time is disregarded. The date range is a daycount from 0100-01-01 to 9999-12-31 inclusive (-657434 to 2958465 days from CDate(0) = Saturday 1899-12-30 00:00 = CMJD 15018.0); WeekDay default is Sun=1 to Sat=7 (an optional second parameter controls week start). The CDate of a day is 5 decimal digits from 1927-05-18 to 2173-10-13.

Your system is set to display a VBScript CDate as

That should be remembered when reading on; the format is I believe set by Windows Control Panel, Regional Settings. Here, some computed dates are presented thus, while others are coded explicitly in ISO 8601 form, as 1616-04-23 18:30:00.

Anything involving VBScript built-in conversions between String and CDate may be affected by OS localisation.

Interpretation of the fractional part of a negative CDate is quaint on my system; it may depend on version :-

document.write CDate(+1.1), " ; ", CDate(-1.1)
gave me, in IE4, "1899-12-31 02:24:00 ; 1899-12-29 02:24:00"

Delphi behaves similarly.

Current Date and Time

Default Formats

For the current local date and time, seemingly in the user's selected Windows format :-

ISO 8601 Formats

For the current local date and time (using GNow = Now although the method is general), in strict ISO 8601 formats, independently of the user's selected Windows format, variously :-

Note how easily arithmetic deals with most leading zeroes, where separators are not needed. Trailing zero-value fields are correctly handled; hours before 10:00 can be allowed for. The method is shorter and faster than handling the fields individually with LdgZ(...) or Right("0"+..., 2).

To read a known non-native numerical date format, separate the numeric parts of the date and use DateSerial ; the time part can probably be read en bloc in safety.

Date strings in ISO 8601 can be compared and sorted directly, as strings; other formats must be converted to ISO strings or to CDate.

General Tests

Date-to-integer conversion needs to be tested both before and after noon. Conversions from some strings are localisation-dependent.

So, truncated to Number, dates are a day-count from local 1899-12-30 = 0, as in Delphi 2+. Before that date, time runs backward within each day.

Functions Now / Time have (in my system) a resolution of one second.

Timer

Seconds of day :-

The value of function Timer increases, in a Windows 98 system, at intervals of 54.9 ms; and about 15 ms in a Windows XP system. In each case, it is rounded to one hundredth of a second. The interval is system-dependent, often 10 ms or 54.9 ms; it may be affected by other processes.

Common Programming Errors

Accuracy

Since Date-Times are held as IEEE Doubles, which are binary floats, most times of day and most short intervals cannot be held accurately, Often this does not matter; sometimes it does. Comparing for exact equality seems unreliable.

Date Format Assumptions

Conversion in either direction between a String and a Date appears dependent on Windows settings. But input of "yyyy/mm/dd" may be safe everywhere.

Here I expect the first to be taken as D/M/Y, the second as M/D/Y, and the third either as your local preference or as US format. The date I get is shown [thus].

Beware!

Time Keeps Changing

A common fault appears to be to call Now / Date / Time more than once within a routine. These are not constants; and, sometimes, it may matter that the value can change. For example if the following were to be initiated at the end of 10:59, it could give 10:00 :-

  LdgZ(Hour(Now)) & ":" & LdgZ(Minute(Now))

In this page, Now is called only once, in GNow = Now , and GNow is used thereafter.

Div & Mod of Negative Values

As usual, Div & Mod, when given a negative left argument, do not do what is likely to be wanted for date and time; so, add a suitable constant. The operator Div is coded as \ ; Mod as mod .

Some code here is written on the assumption that it will only be used for dates after 1899, so that the arguments will be positive.

String to/from Date and Time

In such as document.write CDate("1/2/3 12:00p") , which gives me 2001-02-03 12:00:00 , the interpretation differs from JavaScript. The missing century is windowed into 1930-2029 (OS setting?), the field order and separator seem to default to those set in Windows (but see LastModified), else USA, and the allowable one-letter suffixes are just A & P for AM & PM, not the "military" letters. These could be system-dependent.

When the date is 1899-12-30, it is omitted from the output string. When the time is midnight (set by 00:00 or 12:00p), it is omitted, provided that the date has not been omitted.

User-input strings should usually be validated.

In VBScript code, date/time literals can be written as #2005-05-06 21:35:22# & #2005/05/06 21:35:22#; but beware localisation and adhere to that field order.

Reported 2008-07-11 in news:microsoft.public.scripting.vbscript that in WSH 5.7 (unlike 5.6), installed with XP sp3, in a machine set to DD/MM/YYYY, CStr(Date ()) gives M/D/YYYY. But I DO NOT FIND THIS. Presumably it was corrected by "Update for Windows XP (KB951978)" which I received soon after I loaded sp3.

Check for literal input of ISO 8601 form :-

DateSerial(Y, M, D)

The widely useful function DateSerial(Y, M, D) returns a Date and accepts out-of-range parameters M D (as in DateSerial Function); it can be used when the input is not assuredly in the right format forCDate(<string>). TimeSerial(h, m, s) is similar.

In MSIE4, parameters seemed to be 16-bit integer, and if large enough to drive the year out of range they could cause unexpected results. They are still 16-bit in MSIE8.

Example :-

S = "20030627T12:00:00-08:00" ' Local date/time and offset from GMT
D = DateSerial(Mid(S,1,4), Mid(S,5,2), Mid(S,7,2))
T = CDate(Mid(S,10,8)) : Q = Mid(S,18,1) : O = CDate(Mid(S,19,5))
if Q="+" then Q=+1 else if Q="-" then Q=-1
document.writeln "D = ", D, ", T = ", T, ", Q = ", Q, ", O = ", O
X = D + T - Q*O : document.writeln "<br>X = ", X, " GMT"

The sign of Q needs checking above.

For a similar format (the /1440 term may not be needed),

S = "20040122164949.000000-480"
T = _
  DateSerial(Mid(S, 1, 4), Mid(S, 5, 2), Mid(S, 7, 2)) + _
  TimeSerial(Mid(S, 9, 2), Mid(S, 11, 2), Mid(S, 13, 2)) + _
  Cdbl("0"&Mid(S, 15, 7))/86400 + Cdbl(Mid(S, 22, 5))/1440
document.write T

Leading Zero

The normal conversion to string is not always what is wanted. To build a date or time string with field separators, a leading zero function should be used, to avoid code bloat.

Numeric month, day, hour, minute, second fields should all generally be two-digit. For these, it is unlikely that error will provide a number outside 00..99, or a non-integer. But a leading zero function for general use should display the right value of a wrong number; to do otherwise could be deceptive.

function LdgZ(ByVal N) '' for general use
  if (N>=0) and (N<10) then LdgZ = "0" & N else LdgZ = "" & N
  '' or LdgZ = Right("0" & N, 2)  if N>=0 & N<100 is certain
  '' or if (Len(N)=1) then LdgZ = "0" & N else LdgZ = "" & N
  end function
'' the second is <10% faster, and unsafe for bad input
'' the third is 10% slower

function DD(N) : DD = Right(100+N, 2) : End Function '' if 0<=N<=99

Consider :-

SEC = Right("0" & SEC, 2) ' requires string concatenation
SEC = Right(100 + SEC, 2) ' requires instead just an addition

Addition may be faster.

Seconds to hh:mm:ss

The following should give a time in the locally- preferred format, if VB understands that (gives me '17:01:50'; gives an American '5:01:50 PM') :-

const cSEC = 61310
document.write CDate(cSEC/86400)

I assume cSEC<86400; otherwise, take the integer part within CDate().

One can, of course get hours minutes and seconds as numbers by div & mod operations, just the same as with pencil and paper; there is then no 24-h limit.

m = s div 60 ; s = s mod 60   ;   h = m div 60 ; m = m mod 60

Then output with a Leading Zero function.

CDate to YYYYMMDD or hhmmss

YMD = ( Year(D)*100 + Month(D) )*100 + Day(D)
hms = Right((Hour(T)*100+Minute(T))*100+Second(T)+1e7, 6)

That gives a number, but it will convert automatically to an appropriate string.

WMIDateStringToDate

The following is often quoted.

WMIDateStringToDate = CDate(Mid(dtmInstallDate, 5, 2) & "/" & _
  Mid(dtmInstallDate, 7, 2) & "/" & Left(dtmInstallDate, 4) _
  & " " & Mid (dtmInstallDate, 9, 2) & ":" & _
  Mid(dtmInstallDate, 11, 2) & ":" & Mid(dtmInstallDate, 13, 2))

Clearly, dtmInstallDate begins with the international form "YYYYMMDDhhmmss" compliant with ISO 8601. But the result is CDate("MM/DD/YYYY hh:mm:ss") which is purely American, and the code will fail if the system is localised for non-American preference.

Consider

WMIDateStringToDate = _
  DateSerial(Left(dtmInstallDate, 4), _
    Mid(dtmInstallDate,  5, 2), Mid(dtmInstallDate,  7, 2) ) + _
  TimeSerial(Mid(dtmInstallDate,  9, 2),  _
    Mid(dtmInstallDate, 11, 2), Mid(dtmInstallDate, 13, 2) )

That should work anywhere; it avoids concatenating and then parsing a string. If DateTimeSerial exists (it should!), then use it instead.

In general, string operations should be used only when necessary, and should be avoided (except for direct I/O) where the format may be localisable.

Full WMI Format

The full WMI format is indicated by this example using CEST = GMT+2 :-

20060804185239.000000+120 → 2006-08-04 16:52:39 GMT

The boxed expression should give GMT if -Mid(dtmInstallDate, 23) is included in the Minutes argument of TimeSerial .

To include fractional seconds in either case, finally +CDbl(Mid(dtmInstallDate, 15, 7))/86400 .

Date and Time Validation

Time can be validated as for date, or in a simpler manner.

Exact String

One can use IsDate() to validate as being some date and/or time; but it can accept both 03/17/2004 and 17/03/2004, as well as 2004/03/17, as well as 16A and 23P. Something like

OK = isDate(S)	'' check next line safe
if OK then OK = ( CStr(CDate(S))=S )

can be used to validate S as being a good date and/or time in exactly the current output format including separators.

     

Numeric

Given a date in a known format, one can get Y M D as numbers, and validate them, using DateSerial to avoid error; a time can be treated similarly. It is only necessary to check two fields.

function DateValid(Y, M, D)
  dim CD
  if (Y<100) or (Y>9999) then CD = D = 0 else CD = DateSerial(Y, M, D)
  DateValid = ( Month(CD) = M ) and ( Day(CD) = D )
  end function

ONLY 100 ≤ Y ≤ 9999 ARE VALID - For deprecated 2-digit years, omit the 100 test.

       

To split a string into Y M D, consider using a RegExp. If the value of the Day field is known to be in 0..99, it's probably only necessary to check that Month(CD) = M .

Eight-Digit String

function OKdate(S) '' eight digits YYYYMMDD
  on error resume next
  OKdate = CDate(Left(S, 4) & "/" & Mid(S, 5, 2) & "/" & Right(S, 2))
  OKdate = (err=0)
  end function
   

That uses the error return of CDate("yyyy/mm/dd") ; it does not explicitly check the number of digits.

String using a RegExp

This demonstrates the use of the RegExp execute method to check the pattern of a numeric date string and extract the year, month, and day components for checking by using DateSerial.

For the RegExp provided, the string should be of the form YYYY MM DD with any or no separators. The characters in "YMD order" are digits referring to the capturing parentheses in "RegExp". A back-reference can be used to ensure that the second separator matches the first one. "RegExp" and "YMD order" will normally be hard-coded.

Note that the valid CDate year range is 100 to 9999, and DateSerial remaps years 0 to 99. A Runtime Error would occur if out-of-range month/day were to map the implied date to an out-of-range year; to avoid this, the first digit of YYYY is limited here to 1-8.

 
     
- in your set format

General RegExp links are via JavaScript RegExps and Validation.

Date Comparison

Strings other than YYYYMMDD or similar must be converted for intercomparison. Generally it will be better to convert strings explicitly to CDate or to numeric YMD strings.

Code calling for the direct comparison of a Date with a String is, I think, implemented by first converting the Date to a String in locally-set format (which, on the Web, is unpredictable).

Windows File Datestamp

Windows uses, for file datestamps, a count of 100 ns units from Gregorian 1601-01-01 00:00:00 GMT. This will convert such a number to a CDate and show it.

N = 127629004619258000
D = DateSerial(1601,1,1) + N/1e7/86400
document.write D '' gives me 2005-06-10 18:07:42 GMT

Note that the above gives GMT. Windows may give local time, with a dubious Summer Time effect.

Day Counts

CMJD

Chronological Modified Julian Date (CMJD) is the daycount from zero being 1858-11-17 local time.

       

Day-of-Year

For Day-of-Year to Date, use DateSerial(Y, 1, DoY) .

Independent Conversions

One can convert between Y M D and CMJD, by arithmetic independent of VBScript date functions (partly deriving from the work of Zeller); the algorithms here are from Date and Day Count.

The date range here is not limited by that of VBScript CDate (except for testing); conversion to the Julian Calendar is feasible.

Offsets from GMT

Time Zone

I have as yet seen no way in VBScript proper to determine the offset between Local Time and GMT; there should be one. The information can be read from the Registry, when in WSH.

JavaScript can determine it, for any date by current rules. Thus the present, January, and July offsets from GMT can be obtained by JavaScript and passed to VBScript.

var nD = new Date(), JSDO = new Date(+nD) // copy added 2005-12-10
 document.write(nD) // ***
  NowOffset = nD.getTimezoneOffset() // ; nD.setHours(0,0,0,0)
  nD.setMonth(0, 1) ; JanOffset = nD.getTimezoneOffset()
  nD.setMonth(6, 1) ; JulOffset = nD.getTimezoneOffset()

This is output by the piece of JavaScript here :-

This shows using VBScript the offset values [current, January 1, July 1] originating in JavaScript and meaning (GMT-local) in minutes.

Therefore, at your location, shown by VBScript,

In UK winter, I see [ 0 0 -60 ] ; in UK summer, I see [ -60 0 -60 ] .

A JavaScript Date Object can be shown as a string by VBScript :-

Summer Time Dates

See my Summer Time.

The dates here are computed from the known algorithms, and do not reflect OS settings.

UK/EU

Changes are at 01:00 GMT/UTC. Russia used the same dates, but different times, until Summer 2011.

US

This includes the rule change, effective from 2007-03-01.

UTC by JavaScript

This shows VBScript calculating a CDate and passing it as a CDbl to a JavaScript library-type finction (here inlined) which returns a String result which is then displayed by VBScript.

In the JavaScript, CD has a value representing a VBScript CDate which is a Double of local days fron 1899-12-30.0; CD is converted to days and milliseconds and then to a Date Object D, which is then converted to an ISO 8601 UTC string.

VBScript Date and Time Index, VBScript Date and Time 2,
VBScript General and Maths, with VBS Pages Introduction
JavaScript Date and Time
Home Page
Mail: no HTML
© Dr J R Stockton, near London, UK.
All Rights Reserved.
These pages are tested mainly with MS IE 8 and Firefox 3.0 and W3's Tidy.
This site, , is maintained by me.
Head.