Administrators occasionally need unambiguous datestamps and timestamps usable
in file and folder names, but how to create
them isn’t immediately obvious. VBScript’s
date-manipulation tools generate dates and
times that are formatted according to locale
and that usually include characters that you
can’t use in filenames.
The ISO 8601 standard is a good
choice for a date- and timestamp format—I’ll explain why and then show you how to
generate ISO 8601 stamps easily, even on
pre–Windows XP systems that don’t provide some convenient stamping tools.
The ISO 8601 Format
If you want to insert a date or time into a log
file, you can use almost any format you
want. However, you have more restrictions
if you want to use a date- or timestamp as
a name or part of a name for a file; the ubiquitous forward slash and colon are both
reserved characters, and removing them
can make a date or time that doesn’t include
leading zeroes ambiguous.
The simplest solution I’ve found
that generally works is to represent
dates and times in the widely adopted
ISO 8601 standard’s basic format for
displaying calendar dates.
The ISO 8601 standard provides
guidance for representing dates and
times in an unambiguous form. The
basic format is all-numeric; there is
also an extended format in which elements are separated with delimiting
characters.
A datestamp in the basic format is
eight numeric digits in the pattern
yyyymmdd, where yyyy is the four-digit year, mm is the two-digit month,
and dd is the two-digit day of the
month. You must pad any month or
day that has only one digit with a leading 0. In this scheme, February 1,
2005 is 20050201.
A timestamp that uses the ISO
8601 basic format is six digits in the
form hhmmss, where hh is the two-digit hour of the day in 24-hour format,
mm is the two-digit minute, and ss is
the two-digit second. Using this format, 9:05 A.M. is 090500; 9:05 P.M. is
210500.
Why this particular format? The
most crucial issue is that we settle on
some format. We can’t generally use
raw numeric dates and expect them
to be useful. Suppose we just chop
punctuation out of a date string to get
a stamp such as 1122005. In the United States, that might refer to
either January 12, 2005 or November
2, 2005.
If you’re in a multinational organization, things are even more confusing. Most of the world uses day/month/year ordering; thus, 1122005
could also refer to December 1, 2005
or February 11, 2005. Even with the
missing leading 0 in place in this
stamp, this format is ambiguous in
multinational organizations because
01122005 could still refer to either
January 12 or December 1.
The ISO 8601 basic format, in
contrast, has a variety of simple
advantages that add up:
- The format is friendly with most
file systems. All the characters are
allowed in file and folder names
and don’t need to be escaped. If
you’re using stamps as the complete folder names or file base
names, there’s no risk of them
being truncated if the files are
copied to a volume that supports
only 8.3 filenames.
- The meaning of the format is difficult to misinterpret. This is one of
the important features of the ISO
8601 standard. Dates are generally
very ambiguous because some
regions use month first and others
use day first for common display.
However, the first four characters
of an ISO 8601 date string are
always clearly the year portion of a
date, and every format that begins
with the year uses month/day order
following it. Technically oriented
people often find the format instinctive because the year/month/day
order is similar to the greater-to-lesser order used in numbers.
- If you sort ISO 8601 date- or timestamps alphabetically, they’re also
sorted chronologically. For example, you might use the stamps
20060501 and 20061130 as names
for folders. Regardless of when the
folders were created or modified
last, if you sort the folder names
alphabetically, they’ll be ordered by
the date reference in the stamp.
- The individual date and time elements are short enough to be apparent even without punctuation.
- The format might be familiar even
to those who don’t know about ISO
8601. Windows Management
Instrumentation (WMI) uses a
similar datestamp format that’s
based on ISO 8601. The standard
is also widely accepted and used
informally, even by people who
have no clue that it’s a standard.
If we want to use this ISO 8601 format, the next question is: How do we
actually generate a stamp that complies with it?
Using WMI
Generating an ISO 8601 date- or
timestamp is easy on XP and later
Windows versions. WMI’s SWbemDateTime class wraps up a bunch of
functionality for easily translating
dates and times between what Microsoft calls an OLE DateTime or VarDate—the kind of date and time
object that VBScript uses—and other
significant forms, including WMI’s
own time string format. Bobby Malik
previously discussed some aspects of
this class in “WMI Time Bewilderment” (September 2002, InstantDoc
ID 26030). I’ll show you how to do
one simple thing: put in a VBScript
date and get back a WMI date/time
string.
Listing 1 shows the code for this
task. Note that at callout A in Listing
1, the first line of code, which would
normally generate a script date and
time, is commented out. For testing
purposes, the second line of code
supplies a VBScript date corresponding to 3:26:58 P.M. on August 18,
2005.
SWbemDateTime doesn’t have a
date or time when you set it up; its
role is really just that of a translator
between different time formats. To initialize it, the code at callout B in Listing 1 passes it the referenceTime
value by using the SetVarDate
method. Then the code can get the
date and time back as a WMI
date/time string by reading the value
named Value in this class.
On my system, the echo statement
in callout B shows this:
20050818152658.000000-300
On your particular system, the last four
characters might be different because
they correspond to the number of
minutes offset from Coordinated Universal Time (UTC), but everything
before that should be identical.
The beauty of using SWbemDateTime is that we get our stamps by just
cutting the string contained in Value
into pieces. As shown at callout C in
Listing 1, we get a datestamp by
extracting the first eight characters of
the string and a timestamp by extracting the six-character sequence that
starts with the ninth character in the
string.
Unfortunately, for the substantial
portion of working PCs that have a
pre-XP OS, this technique won’t work.
We can get everything we need by
using pure VBScript, though; it just
takes a little bit of work.
Using Pure VBScript
As you may have noticed at callout A in Listing 1, VBScript is comfortable with dates and times expressed as numbers. VBScript contains native functions that can extract any component of a date/time value as a number—these are the appropriately named Year, Month, Day, Hour, Minute, and Second functions. Because these functions are present in all versions of VBScript no matter what the OS version, you can use them to help create a date- or timestamp in any version of Windows.
The VBScript date and time functions are not a solution by themselves, but if we look at the ISO 8601 standard, it becomes clear that we just need to do some formatting to get a stamp. Each element of the stamp must be a specific number of characters long: The year is four characters, and every other date and time element must be two characters long. If an element is fewer than the necessary number of characters, we add 0s to the left of it until it’s the correct length.
Listing 2 shows the entire process. Although several lines of code are required, the lines are simple and repetitive.
The code assumes that the year portion of the string is four characters long. VBScript always returns a complete year, not an abbreviated, two-digit year, so the only VBScript-generated date range the code won’t produce an accurate date/time stamp for is the interval from A.D. 100 through A.D. 999. Arguably, the code could be “fixed” to handle this range, but you likely won’t encounter dates in this range in your day-to-day work.
Callout A in Listing 2 shows how we get the other two date elements in proper form. Each element will always be a number represented with one or two characters. If it’s only one character, we preface it with a 0.
Once we have the elements padded, we just join them together, as shown at callout B in Listing 2. The WScript.Echo statements will then display the following output:
datestamp: 20050815
timestamp: 155658
Although this code works, rewriting it every time you need a date- or timestamp isn’t so much fun. The best way to simplify generating date- and timestamps is to wrap the code up in functions that you can drop into your own scripts. Listing 3 shows the code in Listing 2 reworked as two drop-in functions, ToDateStamp and ToTimeStamp. Both functions generate strings from a VBScript date because this is the most flexible way to handle stamps. Most code that needs to generate a stamp bases it on the current date and/or time, and you can use VBScript’s Now function to get a complete date and time usable in both functions.
Dating Advice
Generally, you should avoid the VBScript functions Date and Time. Although they generate a valid current date and time respectively, the Date function doesn’t retain time information, so if you pass a date generated from Date to the ToTimeStamp function, it will always return the string “000000”. VBScript’s Time function returns only the current time of day with the actual date chopped out, so although it technically still contains date data, using it with ToDateString will always return the string “18991230”. This peculiar result is because VBScript’s dates are actually a count of days from December 30, 1899.
How about two lines that can be pasted in where needed:
wDate = Year(Now) & Right("00" & Month(Now), 2) & Right("00" & Day(Now), 2)
wTime = Right("00" & Hour(Now), 2) & Right("00" & Minute(Now), 2)
Sorry - correction:
wDate = Year(Now) & Right("00" & Month(Now), 2) & Right("00" & Day(Now), 2)
wTime = Right("00" & Hour(Now), 2) & Right("00" & Minute(Now), 2) & Right("00" & Second(Now), 2)
Without knowing it had a definition, I've been using ISO 8601 format for some time for just the advantages you listed, in particular that this format is easy to understand and read, and that it sorts very nicely.
From a CMD file, you can create an ISO 8601 compatible string for the date by using this:
FOR /F "tokens=2-4 delims=/ " %%f in ("%date%") do set ISO9601Date=%%h%%f%%g
Note that I'm using two delimiters here, a forward slash AND a space, the space is necessary to lop off the day of the week from the front of the date variable.
and for time, use
FOR /F "tokens=1-3 delims=:." %%f in ("%time: =0%") do set ISO9601Time=%%f%%g%%h
The %time: =0% (after the colon it is 'space equals 0' changes all the blanks in the time variable to 0, taking care of the single digit hour issue
(These were prepared on Windows XP Pro using English-US regional settings. Your configuration may require this code to be tweaked)
Jim
what about an even easier approach?
wDate = CStr(Year(Now) * 10000 + Month(Now) * 100 + Day(Now))
wTime = Right(CStr(1000000 + Hour(Now) * 10000 + Minute(Now) * 100 + Second(Now)), 6)
to get an ISO date in a batch file just put the mathematical part of the aforementioned expressions (ie without the string functions) in a vbscript file and return the result with WScript.Quit, like
WScript.Quit(Year(Now) * 10000 + Month(dtNow) * 100 + Day(Now))
and retrieve the value by using %ErrorLevel%
Jim -
These look like they work on Vista with default US date/time settings as well. Unfortunately, I found the cmd approach doesn't work very well if the settings are already in an ISO8601 form (although it _should_ be possible to make a batch file that uses reg.exe to check the registry for the current date settings format and base parsing on that).
OlliK -
The approach you show is definitely more compact, but there actually is a case where it could be a problem due to the discrete amount of time it takes for each evaluation of Now().
If you're generating a date/time stamp right around the time when the year, month, day, hour, or minute is about to increment, you can get a bad value generated. It's going to be very rare of course, but it could be bizarre in the worst possible cases. As an example, suppose the code is running at approximately midnight on 2008 December 31. By evaluating Now() once, you get a specific value that may turn out to be
20081231 235959
or, if run milliseconds later,
20090101 000000
The first one is just before the beginning of the year, the second is just after the beginning of the year.
However, with multiple evaluations of Now(), wDate might be evaluated before midnight as 20081231 and wTime right after midnight, giving you
20081231 000000
which is 24 hours earlier! It could even happen within a line of code; VBScript evaluates right to left, so if the date changes immediately after the day is evaluated, then you would get
20091231 000000
which is almost a year off.
The code runs _very_ fast on modern machines so I would be very surprised to see this happen, but the mere possibility would qualify as a bug. That's why I use the longer way that evaluates Now() once.