Performing Simple Calculations

VBA supplies two functions, DateAdd and DateDiff, that allow you to add and subtract date and time intervals. Of course, as mentioned above, if you’re just working with days, you don’t need these functions—you can just add and subtract the date values themselves. The following sections describe each of these important functions in detail.

Adding Intervals to a Date

The DateAdd function allows you to add any number of intervals of any size to a date/time value. For example, you can calculate the date 100 days from now or the time 35 minutes ago. The function accepts three required parameters, as shown in Table 2.4. Table 2.5 lists the possible values for the Interval parameter.

Table 2.4: Parameters for the DateAdd Function

Parameter Description
Interval A string expression indicating the interval of time to add
Number Number of intervals to add. It can be positive (to get dates in the future) or negative (to get dates in the past)
Date Date to which the interval is added

Table 2.5: Possible Interval Settings for DateAdd

Setting Description
yyyy Year
q Quarter
m Month
y Day of year
d Day
w Weekday
ww Week
h Hour
n Minute
s Second

For example, to find the date one year from the current date, you could use an expression like this:

DateAdd("yyyy", 1, Date)

rather than add 365 days to the current date (a common, although incorrect, solution). What about calculating the time two hours from now? That’s easy, too:

DateAdd("h", 2, Now)

DateAdd will never return an invalid date, but if you try to add a value that would cause the return date to be before 1/1/100 or after 12/31/9999, VBA triggers a run-time error.

Watch out! The abbreviation for adding minutes to a date/time value is “n”, not “m”, as you might guess. (VBA uses “m” for months.) Many VBA developers have used “m” inadvertently and not noticed until the program was in use.

Subtracting Dates

If you need to find the number of intervals between two dates (where the interval can be any item from Table 2.5), use the DateDiff function. Table 2.6 lists the parameters for this function.

Table 2.6: Parameters for the DateDiff Function

Parameter Required? Datatype Description
Interval Yes String Interval of time used to calculate the difference between Date1 and Date2
Date1, Date2 Yes Date The two dates used in the calculation
FirstDayOfWeek No Integer constant The first day of the week. If not specified, Sunday is assumed
FirstWeekOfYear No Integer constant The first week of the year. If not specified, the first week is assumed to be the week in which January 1 occurs

For example, to calculate the number of hours that occurred between two date variables, dtmValue1 and dtmValue2, you could write an expression like this:

DateDiff("h", dtmValue1, dtmValue2)

DateDiff’s return value can be confusing. In general, it performs no rounding at all, but the meaning of the difference varies for different interval types. For example,

DateDiff("h", #10:00#, #12:59:59#)

returns 2 because only two full hours have elapsed between the two times.

When working with months or years, DateDiff returns the number of month or year borders that have been crossed between the dates. For example, you might expect the following expression to return 0 (no full months have been traversed), yet the function returns 1 because a single month border has been crossed:

DateDiff("m", #11/15/97#, #12/1/97#)

The same goes for the following expression, which returns 1 even though only a single day has transpired:

DateDiff("yyyy", #12/31/97#, #1/1/98#)

When working with weeks, DateDiff becomes, well, strange. VBA treats the “w” (weekday) and “ww” (week) intervals differently, but both return (in some sense) the number of weeks between the two dates. If you use “w” for the interval, VBA counts the number of the day on which Date1 falls until it hits Date2. It counts Date2 but not Date1. (This explanation requires visual aids, so consult Figure 2.1 for an example to work with.) For example,

DateDiff("w", #11/5/97#, #11/18/97#)

returns 1 because there’s only one Wednesday following 11/5/97 before stopping at 11/18. On the other hand,

DateDiff("w", #11/5/97#, #11/19/97#)

returns 2 because there are two Wednesdays (11/12 and 11/19) in the range.

Using “ww” for the range, DateDiff counts calendar weeks. (That is, every time it hits a Sunday, it bumps the count.) Therefore, the previous two examples both return 2, using the “ww” interval; in both cases, there are two Sundays between the two dates. Just as with the “w” interval, VBA counts the end date if it falls on a Sunday, but it never includes the starting date, even if it is a Sunday. Given that caveat, DateDiff should return the same answer for either the “w” or “ww” interval if Date1 is a Sunday.

Figure 2.1: A visual aid for DateDiff calculations

If you use date literal values (like #5/1/97#), VBA uses the exact date in its calculations. If, on the other hand, you use a string that contains only the month and date (like “5/1”), VBA inserts the current year when it runs the code. This allows you to write code that works no matter what the year. Of course, this makes it difficult to compare dates from two different years because there’s no way to indicate any year except the current one. But if you need to perform a calculation comparing dates within the current year, this technique can save you time.

Converting Text to Date/Time Format

Sometimes your code needs to work with date values that are stored as strings. Perhaps you’ve received data from some outside source and need to convert it to date format, or perhaps the user has entered a value somewhere and you now need to work with it as a date. VBA provides three functions to help you make the necessary conversions: DateValue, TimeValue, and CDate. Each of these functions accomplishes a slightly different task, and their differences aren’t apparent from the online help.

DateValue and TimeValue each accept a single argument (usually a string expression) and convert that value into either a date or a time. (As mentioned earlier in this chapter, you can also use these functions to extract just the time or date portion of a combined date/time value.) DateValue can convert any string that matches the internal date formats and any recognizable text month names as well. If the value you send it includes a time portion, DateValue just removes that information from the output value.

For example, all of the following expressions return the same value (assuming the variable intDate contains the value 30):

DateValue("12 30 97")
DateValue("December 30 1997")
DateValue("December " & intDate & " 1997")
DateValue("12/30/97 5:00 PM")
DateValue("30/12/97")

The final example returns December 30 no matter where you are, of course, only because the date is unambiguous. Try that with a date like “12/1/97”, and you’ll get the date as defined in your international settings (December 1 in the United States, January 12 in most of the rest of the world).

The TimeValue function works similarly to the DateValue function. You can send it a string containing any valid expression, and it returns a time value. If you send TimeValue a string containing date information, it disregards that information as it creates the output value.

For example, all of the following return the same time value:

TimeValue("5:15 PM")
TimeValue("17:15")
TimeValue("12/30/97 5:15 PM")

The CDate function coerces any value it can get its hands on into a date/time value, if it can. Unlike the TimeValue and DateValue functions, it returns a full date/time value, with all the information it was sent intact. In addition, it can convert numeric values into dates. For example, all of the following examples return the same value. (The last example is redundant, of course, but it works.)

CDate("12/30/97 5:15 PM")
CDate(35794.71875)
CDate(#12/30/97 5:15 PM#)

Most often, you’ll use CDate to convert text into a full date/time value, and you’ll use DateValue and TimeValue to convert text into a date or a time value only.

Putting the Pieces Together

What if, rather than text, you’ve got the pieces of a date or a time as individual numeric values? In that case, although you could use any of the functions in the previous section to perform the conversion (building up a complex string expression and then calling the function), you’re better off using the DateSerial and TimeSerial functions in this case. Each of these functions accepts three values—DateSerial takes year, month, and day, in that order; TimeSerial takes hour, minutes, and seconds, in that order—and returns a date or a time value, much like the DateValue and TimeValue functions did a single expression as input. Many of the functions presented in the remainder of this chapter use the DateSerial or TimeSerial function to create a date from the three required pieces.

For example, what if you need to know the first day of the current month? The simplest solution is to write a function that uses an expression like this:

dhFirstDayInMonth = DateSerial(Year(dtmDate), Month(dtmDate), 1)

As you’ll see, this is exactly the technique the dhFirstDayInMonth function, discussed later in this chapter, uses. By creating a new date that takes the year portion of the current date, the month portion of the current date, and a day value of 1, the function returns a new date that corresponds to the first day in the current month.

The TimeSerial function works just the same way. You pass it hour, minutes, and seconds values, and it creates the appropriate time value for you. You’ll use both functions together to build a full date/time value if you’ve got six values containing the year, month, day, hour, minutes, and seconds. That is, you might find yourself with an expression like this:

DateSerial(intYear, intMonth, intDay) + _
 TimeSerial(intHour, intMinutes, intSeconds)

Because a date/time value is simply the sum of a whole number representing days and a fraction representing time, you can use both functions together to create a full date/time value.

One useful feature of VBA’s built-in date functions is that they never return an invalid date. For example, asking for DateSerial(1997, 2, 35), which certainly describes a date that doesn’t exist, politely returns 3/7/97. We’ll actually use this feature to our benefit, as you’ll see in the section “Is This a Leap Year?” later in this chapter.

Displaying Values the Way You Want

In your applications, you most likely will want to display dates in a variety of formats. VBA supplies the Format function, which you can use to format date values just the way you need. (You can also use the Format function to format numeric values, and string values as well. See the VBA online help for more information.)

When you use the Format function, you supply an expression to be formatted (a date/time value, in this case) and a string expression containing a format specifier. Optionally, you can also supply both a constant representing the first day of the week you want to use and a constant representing the manner in which you want to calculate the first week of the year. (For more information on these two parameters, see Table 2.6 earlier in this chapter.)

The format specifier can be either a built-in, supplied string or one you make up yourself. Table 2.7 lists the built-in date/time formats.

Table 2.7: Named Date/Time formats for the Format Function

Format Name Description Use Local Settings
General Date Displays a date and/or time, depending on the value in the first parameter, using your system’s Short Date style and the system’s Long Time style Yes
Long Date Displays a date (no time portion) according to your system’s Long Date format Yes
Medium Date Displays a date (no time portion) using the Medium Date format appropriate for the language version of the host application No
Short Date Displays a date (no time portion) using your system’s Short Date format Yes
Long Time Displays a time (no date portion) using your system’s Long Time format; includes hours, minutes, seconds Yes
Medium Time Displays time (no date portion) in 12-hour format using hours and minutes and the AM/PM designator Yes
Short Time Displays a time (no date portion) using the 24-hour format; for example, 17:45 Yes

To test out these formats, we took a field trip to a fictional country. The region’s time settings for Windows are displayed in Figure 2.2, and their date settings are shown in Figure 2.3. The screen in Figure 2.4 shows some tests, using the Format function, with the various date and time formats.

Figure 2.2: Regional settings for times in a fictitious environment

Figure 2.3: Regional settings for dates in the same fictitious environment

Figure 2.4: Test of regional date formats in the Microsoft Access Debug window

If you’re feeling creative, or hampered by the limitations of the named time and date formats, you can create your own formats using the options shown in Table 2.8. If you build a string containing combinations of these characters, you can format a date/time value any way you like. Figure 2.5 demonstrates a few of the formats you can create yourself, using the characters listed in Table 2.8.

Figure 2.5: Use the Format function with user-defined formats for complete control.

Table 2.8: User-Defined Time/Date Formats for the Format Function

Character Description Use Regional Settings? Comments
(:) Time separator. Separates hours, minutes, and seconds when time values are formatted Yes In some locales, this character may have been translated and may not be a colon (:). Output value is determined by local settings
(/) Date separator. Separates the day, month, and year when date values are formatted Yes In some locales, this character may have been translated and may not be a slash (/). Output value is determined by local settings
c Displays the date as ddddd and displays the time as ttttt, in that order Yes Same as the named General Date format
d Displays the day as a number without a leading 0 (1–31) No
dd Displays the day as a number with a leading 0 (01–31) No
ddd Displays the day as an abbreviation (Sun–Sat) Yes
dddd Displays the day as a full name (Sunday–Saturday) Yes
ddddd Displays the date as a complete date (including day, month, and year) Yes Same as the named Short Date format
dddddd Displays a date as a complete date (including day, month, and year) Yes Same as the named Long Date format
w Displays the day of the week as a number (1 for Sunday through 7 for Saturday) No Output depends on the setting of the FirstDayOfWeek parameter
ww Displays the week of the year as a number (1–54) No Output depends on the FirstWeekOfYear parameter
m Displays the month as a number without a leading 0 (1–12) No If “m” follows “h” or “hh”, displays minutes instead
mm Displays the month as a number with a leading 0 (01–12) No If “mm” follows “h” or “hh”, displays minutes instead
mmm Displays the month as an abbreviation (Jan–Dec) Yes
mmmm Displays the month as a full month name (January–December) Yes
q Displays the quarter of the year as a number (1–4) No
y Displays the day of the year as a number (1–366) No
yy Displays the year as a two-digit number (00–99) No
yyyy Displays the full year (100–9999) No
h Displays the hour as a number without leading zeros (0–23) No
hh Displays the hour as a number with leading zeros (00–23) No
n Displays the minute as a number without leading zeros (0–59) No
nn Displays the minute as a number with leading zeros (00–59) No
s Displays the second as a number without leading zeros (0–59) No
ss Displays the second as a number with leading zeros (00–59) No
ttttt Displays a time as a complete time (including hour, minute, and second) Yes Same as the named Long Time format
AM/PM Uses the 12-hour clock No Use “AM” for times before noon and “PM” for times between noon and 11:59 p.m.
am/pm Uses the 12-hour clock No Use “am” for times before noon and “pm” for times between noon and 11:59 p.m.
A/P Uses the 12-hour clock No Use “a” for times before noon and “p” for times between noon and 11:59 p.m.
a/p Uses the 12-hour clock No Use “A” for times before noon and “P” for times between noon and 11:59 p.m.
AMPM Uses the 12-hour clock and displays the AM/PM string literal as defined by your system Yes The case of the AM/PM string  is determined by system settings

If you want to include literal text in your format string, you have two choices. You can do either of the following:

The first method becomes quite tedious and difficult to read if you have more than a few characters. The second method requires you to embed a quote inside a quoted string, and that takes some doing on its own.

For example, if you want to display a date/time value like this:

May 22, 1997 at 12:01 AM

you have two choices. With the first method, you could use a format string including \ characters:

Format(#5/22/97 12:01 AM#, "mmm dd, yyyy \a\t h:mm AM/PM")

Using the second method, you must embed quotes enclosing the word “at” into the format string. To do that, you must use two quotes where you want one in the output. VBA sees the two embedded quotes as a single literal quote character and does the right thing:

Format(#5/22/97 12:01 AM#, "mmm dd, yyyy ""at"" h:mm AM/PM")

Either way, the output is identical.

The Turn of the Century Approacheth

How does VBA handle the year 2000 issue? Actually, quite gracefully. Normally, users are accustomed to entering two-digit year values, and this, of course, is what has caused the great, late 20th century computer controversy. VBA interprets two-digit years in a somewhat rational manner: if you enter a date value with a two-digit year between 1/1/00 and 12/31/29, VBA interprets that as a date in the 21st century. If you enter a date with a two-digit year between 1/1/30 and 12/31/99, VBA interprets that as being a date in the 20th century. Although it would be nice if the interpretation had a user-configurable component, this fixed solution is much better than nothing at all.

The following list summarizes how VBA treats date values entered with a two-digit year value:

© 1997 by SYBEX Inc. All rights reserved.