
Microsoft Year 2000 Readiness Disclosure
and Resource Center |
 |
 |
 |
Building Year-2000 Compliant Applications
with Visual Studio and Windows DNA |
 |
Executive Summary |
 |
 |
Year 2000
compliance permeates every facet of software development. Problems
occur when software is designed to store, transmit, pass or
calculate date-related information using only the last two digits
for the year. Under these circumstances, the year 2000 is
interpreted by these programs as 1900, which may result in the
failure of the software to perform as expected. Year 2000 compliance
problems generally exist in legacy applications, but they are also
introduced into new applications because developers take code
shortcuts to save space in data records and databases, as well as
improperly use date functions and data types. Developers need to be
aware of the impact of non-compliance, as well as development
techniques to insure compatibility with next millennium.
The Microsoft« Windows DNA architecture represents a
comprehensive new framework for integrating client/server and
internet software solutions on the Windows platform. Windows DNA is
based on the premise of software component integration and
interoperability. DNA combines "component based" software
development with a "service based" operating system. However,
integrated software components and modularity do not protect the
developer from introducing Year 2000 compliance problems in their
applications. Regardless of the tools used to develop software
today, a clear Year 2000 philosophy is needed to avoid spending time
reworking an application in future years.
Year 2000 compliance issues are best avoided by strict use of the
development tools' date data types and date functions when working
with date-related data. Most development tools have built in rules
for handling date arithmetic that will avoid Year 2000 compliance
problems. Developers should validate that the development tools they
are using are Year 2000 compliant and the rules and logic used by
the tools for date handling.
Year 2000 Overview |
 |
 |
The Year 2000
problem itself is fairly simple: software designed to store
date-related information using only the last two digits for the year
may not perform as expected when representing dates from the year
2000 and beyond. Since the century is assumed by these programs to
always be 19, the year 2000, for example, will be interpreted by
these programs as 1900. Non-Year 2000 compliant software will cease
to function properly because date-related calculations will not
return the expected results. For example, a program that calculates
the difference in years between 1978 and 1999 using two-digit date
notation would work correctly (99 - 78 = 21 years); whereas a
program that calculates the difference in years between 1978 and
2001 using two-digit date notation would not yield the correct
result (01 - 78 = -77).
Many applications only deal with current and historical dates.
These types of applications, when not Year 2000 compliant, will only
exhibit problems when the year 2000 comes upon us. However, many
applications in production today are already exhibiting Year 2000
problems. For example, many credit card authorization system had
trouble dealing with card expiration dates beyond 1999 (which are
generally represented in MM/YY form).
The impact of non-compliance is the failure of the software to
perform as expected. In the worst case, total application failure
can be expected. At the very least, misrepresented or corrupted data
can make its way into the corporate database. This corrupt data may
be used as the basis for making key business decisions or, for
example, may fail to generate an exception or flag a customer's
record when expected.
What exactly is Year 2000 compliance? Microsoft defines a Year
2000 compliant product as one that "will not produce errors
processing date data in connection with the year change from
December 31, 1999 to January 1, 2000 when used with accurate date
data, provided all other products (e.g., other software, firmware
and hardware) used with it properly exchange date data with the
Microsoft product."
Does this mean that if you are using all Microsoft development
tools, or all Year 2000 compliant development tools, that the
applications that you develop will automatically be Year 2000
compliant? Not at all. In fact, Microsoft issues a disclaimer with
their development tools regarding Year 2000 compliance that states
"Compliance refers to the Microsoft product as delivered by
Microsoft. The Compliance Statement does not apply to user
customizable features or third party add-on features or products,
including items such as macros and custom programming and formatting
features." What this amounts to is that while the development tool
is itself Year 2000 compliant, it is still up to the developer to
practice development techniques that insure compliance is maintained
in their custom applications.
How did the problem occur? |
 |
 |
In order to fully
understand the magnitude of the problems presented by the Year 2000,
it is helpful to understand historically how the problem occurred.
With mainframe based software development in the '70s,
conservation of expensive resources like memory was critical.
Developers were able to realize significant memory savings by
shortening the year portion of date fields from four characters to
just two. Developers today may find this difficult to understand.
Considering that each variant data type littered throughout today's
typical ASP page consumes over 22 bytes of memory, conserving those
additional two bytes seems dubious. But the practice of assuming
that the century was the 19th reportedly allowed companies to
realize millions of dollars in savings. The need to critically
scrutinize every byte of memory used, coupled with the fact that
applications were not expected to stand the test of 30 years of
time, has in essence created the Year 2000 issue for the information
technology industry. Other Year 2000 problems, such as the incorrect
calculation of a leap year, or using a "dummy" date such as "9/9/99"
as a flag are generally smaller in scope. These problems are related
to the lack of insight on the part of the developer, or, in the case
of an incorrect leap year calculation, an un-trapped error.
Mainframe-based "legacy" software written decades ago isn't the
sole contributor to the problem. Actually, software written today
continues to exhibit problems of compatibility with the next
millennium. In almost all cases, the problem can be attributed to
just two causes:
(1) Developers continue to take shortcuts by representing the
year portion of a date with just two digits.
In most parts of society, two digits are used to represent the
year portion of a date. The most common way to represent a date in
the United States is in the format "mm/dd/yy". It is a custom that
people have used in their everyday lives for decades. This custom
isn't generally a problem because humans can determine the correct
year based on the context in which it is used. Therefore, a
four-digit year isn't always necessary.
Think about it: when you sign a contract or write a check, do you
write the date's full year, or do you use mm/dd/yy notation?
Probably the latter. Unfortunately, computer code is written by
humans, and humans have difficulty overcoming their habits.
The general public often scoffs at our industry's Year 2000
problem. The general public, however, will likely face their own
personal psychological Year 2000 problems after 1999 when performing
everyday tasks like writing checks and computing people's ages.
Maybe then they will be more sympathetic to our old code!
Computers cannot make assumptions based on context but instead
must apply a single rule to interpret the correct date. The century
must be assumed either through the selection of some arbitrary
number (19) or by applying a simple rule that determines the century
based on the two digit year in question. The 32-bit Microsoft
operating systems (with current service packs installed) assume the
century is 19 if the two-digit year is 30 or greater. For two-digit
years less than 30, the century is assumed to be 20. For software
coded today using dates with two-digit years, the computer is
essentially guessing the correct date because the developer failed
to correctly utilize the tools available to them.
(2) Developers use development tools that don't properly
handle date related data, or don't use the date data type
correctly.
Many development tools today provide a special data type for
handling date-related data. In addition, many provide special
functions for performing date conversions, date comparisons, and
date math. Using these functions is necessary to prevent inaccuracy,
or loss of precision when working with date and time data. Tools
that don't provide these services force the developer to substitute
a character or string data type, or a less accurate numeric data
type. Unfortunately, date math is somewhat complex, and the
developer is forced to implement this complexity. For example,
developers are required to programmatically deal with leap years and
months that contain different numbers of days. In many situations,
developers simply ignore these exceptions or deal with them
incorrectly.
Distributed interNet Applications Architecture
|
 |
 |
The Windows
Distributed interNet Applications (DNA) architecture, introduced by
Microsoft in September 1997, represents a comprehensive new
framework for integrating client/server and internet software
solutions on the Windows platform. The goal of the DNA architecture
is to integrate the development model for applications so that they
can harness the power and advanced processing capabilities of
today's PCs, yet still take advantage of the flexibility and ease of
deployment of the Web.
Windows DNA is based on the premise of the integration and
interoperability of software components. DNA combines "component
based" software development with a "service based" operating system.
One of the ways Microsoft accomplishes the goals of DNA is by
tightly integrating the core application infrastructure into the
operating system. By building functions like transaction processing,
message queuing, security and directory services into the operating
system, Microsoft has freed the developer from spending a
significant amount of project resources creating the application
infrastructure. In addition, since these services are provided by
the operating system, all levels of the application - the user
interface, business services, data services - can use them as
needed. This means that a developer writing a web-based front-end
and another creating server-side business components both have
access to the underlying infrastructure.
Component-based software is assembled from independent pieces
that interrelate to perform a specific function. Components written
to the Windows DNA architecture have well-defined COM interfaces
that make them extendable and subscribe to or utilize services
provided by the operating system which makes them compatible. For
example, a developer writing a component that needs to implement
asynchronous messaging can use the interface published by the
operating system and know that it will be compatible with any other
messaging-enabled component written to that interface.
Windows DNA applications are fundamentally different from most of
today's corporate applications. That's because Windows DNA lets
developers take advantage of Internet, LAN-based, and client/server
computing -- enabling the applications they create to do more at a
lower cost.
Overview of the Sample Application |
 |
 |
For the remainder
of this paper, we'll look at a sample application that is built upon
the Windows DNA architecture. The application was designed to
highlight the proper development techniques for handling dates when
using Microsoft Windows DNA-based development tools. Since code
snippets from the application will be used throughout the next
sections, you may want to open the sample application for reference.
The application can be opened using Visual Studio to open the
workspace Y2k.dsw from the folder that you selected at installation
time. The technologies used include Visual Basic Scripting Edition«
(VBScript), JavaScript, Active Server Pages, Microsoft Transaction
Server (MTS), ActiveX« Data Objects and the HTML subset of DHTML.
The development tools used include Visual InterDev╘ web development
system, Visual J++ development system for Java, Visual Basic and
Visual C++« development systems from Microsoft Visual Studio«, as
well as Microsoft SQL Server╘. There is an alternate client portion
written as a Microsoft Excel 97 worksheet. Don't worry if you do not
have all of the Developer Studio development tools installed on your
system. As long as you're not going to recompile the application,
you won't need all of the tools.
Requirements for running the sample application
Running the sample application is not necessary in order to
follow this paper, but may help you to understand the context of
some of the code snippets. If you wish to run the application, make
sure you are running Windows NT« Server 4.0 Service Pack 3 or later,
with Windows NT Option Pack installed, along with SQL Server 6.5
with Service Pack 2 or later. The application needs to create a
table in the "pubs" database. If you do not plan on running the
sample application, you can elect not to create the table. The COM
components (written in Visual Basic and Visual C++) can be imported
into MTS by importing the package file Y2kPackage installed in the
folder Y2K\MTS. This step is optional; the application will still
function correctly outside of MTS.
The application tracks the accounts receivable of a fictional
company. The user enters transactions for one of three customers,
and then views an accounts receivable aging report. The report
summarizes the dollar amount outstanding for the time periods 0-30
days, 31-60 days, 61-90 days, etc. The application is very simple in
its design, and is not meant to illustrate a solution to any
particular business problem. Nor is it meant to be an example of an
ideal architecture using Windows DNA. Rather, it is intended to be
used as a mechanism for understanding the underlying techniques used
when handling date-related data, including date validation, date
formatting, and data arithmetic.
The application addresses all of the pieces of a relatively
complex Windows DNA "helix". There are two client portions to the
application: a web-based version that uses a Visual J++ applet as
the main user interface for data entry; and an Excel 97 worksheet
version that interfaces directly with the business component. Notice
how in each scenario below a date passes from
component-to-component.
For the Excel-based client:
- The user enters values directly into cells in an Excel
worksheet.
- The worksheet uses VISUAL BASIC macros to perform the
client-side data validation.
- The data is then passed to a VISUAL BASIC component running in
an MTS process on the server.
- The VISUAL BASIC component validates the data, partially
through the use of a C++ COM component also hosted in MTS.
- If the data is validated, the VISUAL BASIC component uses ADO
(via the OLEDB provider for ODBC) to store the data in the "pubs"
database on SQL Server.
For the web-based Java client:
- Javascript on the web page queries the applet for values
entered by the user and passes them to the web server for
processing via an HTTP Post.
- ASP code on the server receives the data.
- ASP passes the data to a VISUAL BASIC component running in an
MTS process on the server.
- The VISUAL BASIC component validates the data, partially
through the use of a C++ COM component also hosted in MTS.
- If the data is validated, the VISUAL BASIC component uses ADO
(via the OLEDB provider for ODBC) to store the data in the "pubs"
database on SQL Server.
The following diagram
outlines the structure of the sample application.
Figure
1 The sample application architecture.
(If your browser does not support inline frames, click here to view
Figure
1 in a separate page.)
The
application consists of the following:
File(s) |
Description |
Location |
Y2k.dsw |
Visual Studio workspace |
Y2K |
Y2kApplet.dsp |
Visual J++ project for Java applet. |
Y2K\JavaApplet |
Y2K.xls |
Excel 97 worksheet user interface |
Y2K\Excel |
Y2kSample.dsp |
Visual C++ project for C++ COM component |
Y2K\C_helper |
Y2kWeb.dsp |
Web project û ASP pages and Web files |
Y2K |
Y2k.VBP |
VISUAL BASIC Project for VISUAL BASIC
component |
Y2K\VB |
Y2kPackage.pak |
MTS Package |
Y2K\MTS |
Y2k.dat |
Database definition file |
Y2K\data |
Development Philosophy |
 |
 |
It is helpful to
establish an overall "Year 2000 compliance" philosophy when setting
out to develop an application. A philosophy can then be integrated
into the application's design strategy, or the company's development
strategy as a whole. The philosophy for achieving Year 2000
compliance is fairly straight forward:
- Always use the development tools' date data types or otherwise
insure no loss of precision.
- Always use the development tools' data math/comparison
operators, or develop operators that insure no loss of precision
(including exception handling, such as leap years).
- Make sure the development tool and environment itself do not
have Year 2000 issues (e.g. check out http://www.microsoft.com/y2k for
Microsoft's compliance). Be sure to consider all of the tools
being used throughout the system, including things like report
writers, third party add-ons and controls, etc. Also be sure to
consider the underlying infrastructure, such as the data access
drivers and network protocols.
The sample application
subscribes to and illustrates this philosophy.
DNA Integration - Working with the sample code
|
 |
 |
Client Portion: Java The sample application
presents a simple user interface using an applet written in Java on
a web page (Figure 1). JavaScript interacts with the applet to
receive the values for the Customer ID, Transaction Date, Amount,
Terms and Due Date. Before receiving the data, it first calls the
applet's CheckData() function (Listing 1).
Figure
2 The web-based user interface. (If your
browser does not support inline frames, click here to view Figure
2 in a separate page.)
Figure 2 û The web-based user
interface.
The CheckData() function first verifies that all of the required
fields have been entered. Next, it checks that the value entered for
the Transaction Date is a valid date. This is accomplished by using
the Java SimpleDateFormat class (java.text.SimpleDateFormat), which
is a subclass of the DateFormat class (java.text.DateFormat). In the
Java language, a date is represented as a specific millisecond in
time.
public String CheckData() { //Validates the data.
//Returns an error message if data is invalid. //Otherwise,
just returns an empty string.
Calendar transDate =
Calendar.getInstance(); Calendar dueDate =
Calendar.getInstance(); //Check for required fields... if
(m_cboCustomer.getSelectedItem().length() == 0) return "A
customer is required."; if (m_txtDate.getText().trim().length()
== 0) return "A transaction date is required."; if
(m_txtDate.getText().trim().length() != 10) return "The
transaction date is invalid. Proper format is 'mm/dd/yyyy'"; if
(m_txtAmount.getText().trim().length() == 0) return "A
transaction amount is required."; if
(m_cboTerms.getSelectedItem() == "Due Date") if
(m_txtDueDate.getText().trim().length() == 0) return "A due date
is required."; else if (m_txtDueDate.getText().trim().length()
!= 10) return "The due date is invalid. Proper format is
'mm/dd/yyyy'";
//Validate the dates entered using the
pattern mm/dd/yyyy. //If I try to call parse() for an invalid
date, an exception //occurs, which is caught by the exception
handler //Specify the date format that I'm expecting.
SimpleDateFormat df = new SimpleDateFormat("MM/dd/yyyy");
df.setLenient(false);
//Attempt to create a date from
the text entered by the user. try {
transDate.setTime(df.parse(m_txtDate.getText())); }
catch (Exception e) { //If the user entered an invalid date,
return an error message return "The transaction date is invalid.
Proper format is 'mm/dd/yyyy'"; }
//Also validate
the due date, if one was entered. if
(m_cboTerms.getSelectedItem() == "Due Date") { try {
dueDate.setTime(df.parse(m_txtDueDate.getText())); //make
sure due date is the same or later //than the transaction
date... if (transDate.after(dueDate)) { return "The due date
cannot be earlier than the transaction date."; } } catch
(Exception e) { return "The due date is invalid. Proper format
is 'mm/dd/yyyy'"; } }
//Make sure the amount entered
is numeric. try {
Float.valueOf(m_txtAmount.getText()).floatValue(); }
catch (Exception e) { if
(e.getClass().toString().equals("class
java.lang.NumberFormatException")) return "Invalid numeric
amount."; else return e.getClass().toString(); }
//Everything is Ok, just return an empty string. return
""; }
Listing 1 û Validation in Java |
 |
 |
The
SimpleDateFormat class in Java is used to format and parse date and
time data. It can be used to convert a string to a date, or a date
back to a string, and format it with the specified format mask. You
can specify a formatting mask to use during the constructor:
//Specify the date format that we're expecting.
SimpleDateFormat df = new SimpleDateFormat("MM/dd/yyyy");
df.setLenient(false);
Listing 2 û Using
SimpleDateFormat
This indicates that whenever we format a date using this instance
of the SimpleDateFormat class, Java will use the format
"MM/dd/yyyy". This causes the date May 31st, 1998 to be
formatted as 5/31/1998. Remember that Java is a case sensitive
language. Using the lower case letter "m" instead of upper case will
not produce the same results û the lower case letter "m" is used for
the minutes portion when specifying a formatting mask for a time.
The second line in Listing 2 tells the SimpleDateFormat class not
to be lenient when interpreting dates. It is important to tell the
SimpleDateFormat class not to be lenient when handling dates. If you
don't, Java will not verify that the data entered is actually a
valid date, rather it will only verify that it is in the proper
format. For example, the invalid date in Listing 3 will not generate
an exception because the format of the date is valid. The default
value for setLenient is true, so always change it to false when
working with the SimpleDateFormat class.
//This will not generate an exception, even though the date
is invalid. SimpleDateFormat df = new
SimpleDateFormat("MM/dd/yyyy"); Date dt = new Date(); dt =
df.parse("02/29/1997");
Listing 3
In order to verify that the dates entered by the user are valid,
we need to convert them into Java's Date data type. We can do this
by calling SimpleDateFormat's parse() function. The parse() function
takes a string and attempts to convert it into a Date data type. If
the string cannot be converted to a date, an exception will be
raised which we can trap and use to return an error message, as
displayed in Listing 3:
//Attempt to create a date from the text entered by the
user. try {
transDate.setTime(df.parse(m_txtDate.getText())); }
catch (Exception e) { //If the user entered an invalid date,
return an error message return "The Transaction Date is invalid.
Proper format is 'mm/dd/yyyy'"; }
Listing 4 û Validating the dates
This type of procedure should be followed with any other dates
entered by an end user. By attempting to convert string data stored
in the applet's TextField into JavaÆs native Date data type, we are
assured that the information entered is in fact a valid date.
Now that we know all of the date information is valid, the applet
will enforce one business rule: the due date cannot be earlier than
the transaction date. In order to do this, we need to compare two
dates. When comparing dates or performing date arithmetic, always
use the development toolÆs operators. In Java, the SimpleDateFormat
class is used for formatting and parsing only. The class used for
date arithmetic and comparisons, including time zone conversions, is
the Calendar class (java.util.Calendar). The Calendar class can be
used to extract a certain portion of a date (such as the month or
the year) or to perform comparisons on two dates, as displayed in
Listing 5.
transDate.setTime(df.parse(m_txtDate.getText()));
dueDate.setTime(df.parse(m_txtDueDate.getText()));
//make sure due date is the same or later //than the
transaction date... if (transDate.after(dueDate)) { return
"The Due Date cannot be earlier than the Transaction Date.";
Listing 5 û Validating the dates
Before using the Calendar class, we must assign it a date. We do
this with the setTime() function. This function assigns a date, a
time, or both to the Calendar. The setTime() function takes a date
as its argument. We previously parsed the contents of the TextFields
into dates for the Transaction Date and Due Date when we were
validating those fields. We can move the results into their
respective Calendars at the same time.
The CalendarÆs after() function, which also takes a Calendar is a
parameter, returns true if the date or time of the Calendar passed
as a parameter is after the date or time of this Calendar. Comparing
the two Calendar objects, then, is just a matter of calling the
after() function on the Transaction Date's Calendar and passing in
as a parameter the Due Date's Calendar. If it is false, we
will return an error message; otherwise we return an empty string
and processing continues. By using the Calendar class's built in
date comparison functionality, weÆve removed all possibility of
programmer date math bugs or lack of precision.
Once the data has been validated by calling the applet's
CheckData() function, we are ready to send it to the server to be
further processed and eventually saved to the database. We'll get
the data from the applet to the web server through the use of an
HTML POST method. The HTML POST method is used to send data from a
form tag on a web page to the server to be processed. In our
example, all of the inputs on the form are of type hidden, which
means the browser will not make them visible to the user. We can
still store values in the hidden fields, then send them to the
server by using POST.
To get the values from the applet to the HTML form, we call the
applet's Customer(),Date(), Amount(), Terms() and DueDate()
functions and copy the values returned from those functions into the
form fields. Listing 6 shows the JavaScript function add() which was
taken from the Active Server Page that gets the values from the
applet. This is the code that gets called when the user clicks the
Add button on the client page (refer to Figure 1).
function add() {
var sRet =
document.Y2kApplet.CheckData(); if (sRet != "") {
alert(sRet); return false; } else {
with(document.forms[0]) { hidCustomer.value =
Y2kApplet.Customer(); hidDate.value = Y2kApplet.TransDate();
hidAmount.value = Y2kApplet.Amount(); hidTerms.value =
Y2kApplet.Terms(); hidDueDate.value = Y2kApplet.DueDate();
submit(); } }
Listing 6 û Getting the data from the applet
|
 |
 |
Collecting the data: ASP/VBScript
The data is received by an Active Server Page that parses out the
values POSTed to it. The data is then passed to a Visual Basic (VB)
component running under MTS, which saves it to the database. The
Active Server Page uses VBScript to create an instance of the VISUAL
BASIC component. It then calls either the AddByDate method or the
AddByDay method (Listing 7), depending on whether the user specified
a due date or not.
Working with Variants
VBScript has only one data typeùVariantùwhich can contain a
representation of many other data types. In addition, each Variant
has a sub-type that further describes the underlying value it has
been assigned. The sub-types consists of the more strongly types
data types from VISUAL BASIC (String, Long, Integer, Boolean, Date,
Single, Double, etc.) When a Variant is initially assigned a value,
VISUAL BASIC automatically chooses the appropriate sub-type based on
the value being assigned. The following table illustrates how
VBScript will assign sub-types to the variable MyVariant:
MyVariant = "Hello" |
Sub-type is String |
MyVariant = 123.456 |
Sub-type is Double |
MyVariant = 15 |
Sub-type is Integer |
MyVariant = "5/5/1998" |
Sub-type is String |
Note that in the last example in the table above, VBScript
assigned the String sub-type to MyVariant, instead of the Date
sub-type. VBScript will always choose the String data type when
being assigned character data.
Remember our development philosophy, "always use the toolÆs date
data type"? When using VBScript or Variants, adhering to this rule
is a little bit tricky. Variants play fast and loose with their
sub-types, often changing them without the developer even realizing
it. Basically, we need to make sure that when storing date
information in a Variant that we always force VISUAL BASIC to
sub-type our information to the Date sub-type. That is, we need to
make sure that the information in the Variant is of the Date
sub-type.
We can do this by assigning the VariantÆs value something that it
can not interpret as anything other than a date. In the example
above, what looked to a human like a date ("5/5/1998") was
interpreted (and thus assigned) as a string sub-type. However, as we
will see in the example, we could have explicitly coerced VISUAL
BASIC into assigning the Variant a date sub-type if we had
explicitly converted the string "5/5/1998" into a date value using
the CDate() function.
Our VISUAL BASIC component expects the Transaction Date and the
Due Date parameters to be date data types (see Listing 7). To send
the values for these fields as dates to the VISUAL BASIC component,
we use the VBScript function CDate(). CDate() takes one parameter (a
variant of course) and attempts to convert it into a date. If the
parameter passed into the CDate() function cannot be converted into
a date, a Type Mismatch error will occur.
The CDate() function uses the same logic as the VBScript function
IsDate(), which returns true if its argument is a valid date It is
important to use CDate() to force variants to the Date sub-type to
prevent compliance problems. This can be illustrated in the
following example:
VariantX = "5/5/1998" |
Sub-type of VariantX is String |
VariantY = "6/31/1998" |
Sub-type of VariantY is String |
DateX = CDate(VariantX) |
Sub-type of DateX is Date |
DateY = CDate(VariantY) |
A Type Mismatch error occurs because VariantY cannot be
converted to a date. |
You can determine the sub-type of any Variant in VBScript by
using the TypeName() function. The TypeName function takes a Variant
as a parameter, and returns its sub-type. This function comes in
very handy when debugging. Passing the wrong data type to an
external function or procedure (as illustrated in the table above)
is a common mistake, and is difficult to uncover.
Another useful function is the IsDate() function. IsDate() takes
a Variant as a parameter. It returns True if the parameter can be
converted to a valid date, False if it can not. IsDate() is useful
for validating whether or not user input (or external input) is a
valid date.
Dim Y2kComponent If Request.QueryString("mode") = "add"
then Set Y2kComponent = Server.CreateObject("Y2kVB.Component")
If Request.Form("hidTerms") = 0 then Call
Y2kComponent.AddByDate(Request.Form("hidCustomer"), _
CDate(Request.Form("hidDate")), _
Csng(Request.Form("hidAmount")), _
CDate(Request.Form("hidDueDate"))) Else Call
Y2kComponent.AddByDays(Request.Form("hidCustomer"), _
CDate(Request.Form("hidDate")), _
Csng(Request.Form("hidAmount")), _
CInt(Request.Form("hidTerms"))) End If Set Y2kComponent
= Nothing End If
Listing 7 û VBScript on an Active Serve Page
calling the VISUAL BASIC component.
Alternate Client Portion: Excel
In addition to the Java/Internet client, the sample application
includes a Microsoft Excel client piece. Complex solutions can be
created using Excel's VBA capabilities. Using VBA, the Excel client
will connect directly to the VISUAL BASIC component running in MTS.
For simplicity, the Excel client mimics the logic of the Java
client. The VBA CheckData() validation function is similar to the
function that we used in Javaùthey differ only in language syntax
(see Listing 8). The syntax for handling dates in VBA is identical
to Visual Basic, which we will discuss in the next section.
Function CheckData() As String 'Used for client side
validation Dim sRet As String With ActiveSheet
'Get
the customer name m_Customer =
Worksheets("Data").Cells(ActiveSheet.Range("Customer"), 1).Value
'Make sure that a customer was chosen. If m_Customer =
"" Then CheckData = "A customer is required." Exit Function
End If
'Make sure that a transaction date was entered.
If .Range("TransDate") = "" Then CheckData = "A transaction
date is required." Exit Function
'Check the transaction
date to make sure it is valid. ElseIf Not
IsDate(.Range("TransDate")) Then CheckData = "The transaction
date is invalid. Proper format is 'mm/dd/yyyy'" Exit Function
Else m_TransDate = .Range("TransDate") End If
'Make sure that a due date was entered. If
.Range("Terms") = 5 Then m_Terms = 0 If .Range("DueDate") =
"" Then CheckData = "A due date is required." Exit Function
End If If Not IsDate(.Range("DueDate")) Then CheckData =
"The due date is invalid. Proper format is 'mm/dd/yyyy'" Exit
Function End If
m_DueDate = .Range("DueDate").Value
Else m_Terms =
Left(Worksheets("Data").Cells(.Range("Terms"), 2).Value, 2) End
If
m_Amount = Val(.Range("Amount"))
'Verify that the
amount is >= 0 If m_Amount <= 0 Then CheckData = "A
transaction amount is required to be greater than 0." Exit
Function End If
End With
End Function
Listing 8 û The Excel VBA code for client-side
validation |
 |
 |
The
business component: Visual Basic
Our VISUAL BASIC business component is invoked through a call to
either its AddByDate method or AddByDay method. Both of these
methods add a record to the database. The AddByDate method is used
when the user entered the actual due date for the receivable,
whereas the AddbyDay method is used if the user just selected a
number of days from the Terms. Listing 9 contains the code for both
of these functions.
The VISUAL BASIC component validates the data passed to it from
its consumer (either the ASP page or the Excel client). Because this
component represents a "business component", it validates all
parameters, regardless of whether or not they were previously
validated. The business component should function as a "black box",
meaning that it can be called from any type of client and should not
assume that client side validations were performed.
Public Sub AddByDays(ByVal Customer As String, ByVal
TransactionDate As Date, ByVal Amount As Single, ByVal Terms As
Integer) On Error GoTo Receivable_EH
Dim Cmd As
adodb.Command Dim dtDueDate As Date Dim DateMath As
Y2KSAMPLELib.DateMath
Set DateMath =
CreateObject("DateMath.DateMath.1") dtDueDate =
DateMath.DateAddX(Terms, TransactionDate) Set DateMath = Nothing
'Check some business rules... Call
CheckBusinessRules(Customer, TransactionDate, Amount, dtDueDate,
"AddByDays")
Set Cmd = CreateObject("ADODB.Command")
Cmd.ActiveConnection = CONNECT_STRING
Cmd.CommandType =
adCmdStoredProc Cmd.CommandText = "y2k_addReceivable"
Cmd.Parameters("@customer").Value = Customer
Cmd.Parameters("@date").Value = TransactionDate
Cmd.Parameters("@amount").Value = Amount
Cmd.Parameters("@due_date").Value = dtDueDate
Cmd.Execute Set Cmd = Nothing
If Not ctxContext
Is Nothing Then Call ctxContext.SetComplete End If
Exit Sub
Receivable_EH: Set Cmd = Nothing If
Not ctxContext Is Nothing Then Call ctxContext.SetAbort End
If
Call RaiseError("AddByDays", Err.Number, Err.Description)
End Sub
Public Sub AddByDate(ByVal Customer As
String, ByVal TransactionDate As Date, ByVal Amount As Single, ByVal
DueDate As Date)
On Error GoTo Receivable_EH
Dim Cmd
As adodb.Command
'Check some business rules... Call
CheckBusinessRules(Customer, TransactionDate, Amount, DueDate,
"AddByDate")
Set Cmd = CreateObject("ADODB.Command")
Cmd.ActiveConnection = CONNECT_STRING
Cmd.CommandType =
adCmdStoredProc Cmd.CommandText = "y2k_addReceivable"
Cmd.Parameters("@customer").Value = Customer
Cmd.Parameters("@date").Value = TransactionDate
Cmd.Parameters("@amount").Value = Amount
Cmd.Parameters("@due_date").Value = DueDate
Cmd.Execute
Set Cmd = Nothing
If Not ctxContext Is Nothing Then
Call ctxContext.SetComplete End If
Exit Sub
Receivable_EH: Set Cmd = Nothing If Not ctxContext Is
Nothing Then Call ctxContext.SetAbort End If
Call
RaiseError("AddByDate", Err.Number, Err.Description)
End Sub
Listing 9 - Functions in the VISUAL BASIC
component to add records to the database.
Continuing with our philosophy, we are using the Date data type
in VISUAL BASIC for the Transaction Date and the Due Date. This is
very convenient because in Visual Basic, a run-time error will be
raised if we attempt to pass non-date values to either of these
parameters.
In addition, VISUAL BASIC has straight forward date math
functions such as DateAdd for adding dates and DateDiff for
determining the difference between two date. For comparing dates, we
can just use the same comparison operators we would use for numeric
data ("<", "<=", ">", ">=", etc.). As long as both of
the data types we are comparing are date data types, VISUAL BASIC
will accurately compare the two values. However, using these
operators with values that are not valid dates will trigger a
run-time error.
Listing 10 shows the VISUAL BASIC code used in the function
CheckBusinessRules to verify that the Due Date is not earlier than
the Transaction Date.
'The Due Date cannot be earlier than the transaction date.
If DueDate < TransactionDate Then Call
RaiseError(ErrorSource:=Source, _ ErrorNumber:=vbObjectError +
1002, _ ErrorDescription:="Due Date cannot be earlier than
Transaction Date.") End If
Listing 10 - Using comparison operators to
compare two dates in Visual Basic.
Note that the Date data type also stores time information. So,
use caution when comparing dates in VISUAL BASIC using the
comparison operators, because if your date variables also store
times (even unknowingly), the times will be compared as well.
For example, the Debug.Print statement in Listing 11 will very
likely always return false. This is because d2 is assigned the value
of the Now function, which returns the current date and time,
whereas d1 is assigned the value of just the date. When the time is
not specified in a Date data type, it defaults to 12:00:00 AM.
Unless the Now function was also executed at this exact time, the
Debug.Print will return false.
Thus, if you are only interested in working with a date (as
opposed to a more precise date/time), make sure to not unknowingly
give it time precision as well.
Dim d1 As Date Dim d2 As Date
d1 = Date d2
= Now
Debug.Print (d1 = d2) 'will return false, except if
run at exactly midnight.
Listing 11 - Comparison operators can be
deceiving.
If you are working in date/time precision, but need to just
compare dates, consider using the DateDiff function. DateDiff can be
used to find the number of days that occur between two dates. Using
DateDiff, Listing 11 can be re-written as Listing 12. Since DateDiff
is used with the "d" parameter, only the day portion of the two
dates are compared.
Dim d1 As Date Dim d2 As Date
d1 = Date d2
= Now
Debug.Print DateDiff("d", d1, d2) 'returns 0.
Listing 12 - Use DateDiff to compare only a portion
of a Date in Visual Basic |
 |
 |
Performing Data arithmetic: C++ component
The AddByDays routine from Listing 9 is called whenever the user
did not specify the exact due date for the transaction, but instead
specified the transaction to become due after a fixed number of days
have passed. In this case, we will compute the due date for the
transaction by adding the fixed number of days to the Transaction
Date.
We could do this with the VISUAL BASIC function DateAdd, which
returns a date to which the number of the specific time interval has
been added. For example, to specify a Due Date of 30 days after the
Transaction Date, we would use DueDate =
DateAdd("d",30,TransactionDate). Instead of using the built-in
VISUAL BASIC date addition function, however, we will use a COM
component that we wrote in C++ that does essentially the same thing.
This is not necessarily a good design decision, but it allows us to
illustrate the date handling functionality of C++ within the scope
of our application.
The C++ component will run in the same MTS package as the VISUAL
BASIC component. We will create an instance of the C++ component
from within VISUAL BASIC by using the CreateObject function. The C++
component has two methods. DateDiffX functions similarly to Visual
Basic's DateDiff function, except that it only returns the
difference in days between two dates. DateAddX functions similarly
to Visual Basic's DateAdd function, except that it only adds or
subtracts days to a given date.
Refer to Listing 9 for the syntax for creating an instance of the
COM components from Visual Basic, and calling the DateAddX function.
Listing 13 displays the C++ DateAddX function.
STDMETHODIMP CDateMath::DateAddX(IN long Number, IN DATE
Date, OUT DATE * NewDate) { *NewDate = Date + Number;
return S_OK; }
Listing 13 - C++ component implementation of
DateAdd.
The COLEDateTime class encapsulates the Date data type in C++,
which is analogous to the VISUAL BASIC Date data type. It is
designed to be used with the COleVariant class used in Automation.
The COLEDateTime class expresses dates as the number of days since
December 30, 1899. Therefore, the date December 31, 1899 would be
expressed as 1. This makes the date arithmetic for the DateAddX
function very simple û we can simply add the number of days (30) to
the date using the (+) operator. Since the Date data type is
expressed in days, the result is itself a date.
To calculate the difference between two dates, we will use
similar logic. Again, since the Date data type is expressed in days,
we can simply subtract date1 from date2 to get the number of days
between the two days.
STDMETHODIMP CDateMath::DateDiffX(IN DATE Date1, IN DATE
Date2, OUT long * Difference) { *Difference = Date2 - Date1;
return S_OK; }
Listing 14 - C++ component implementation of
DateDiff.
Saving the data: ADO and SQL Server
Once the data is validated (and converted into the proper format
if necessary), we are ready to save the information into the
database using a stored procedure. The best way to ensure no loss of
precision when passing date-related data into a stored procedure is
to declare the stored procedureÆs parameters as Transact-SQLÆs
DateTime data type.
The datetime data type is stored in 8 bytes (two 4-byte
integers) -- 4 bytes for the number of days before or after the base
date of January 1, 1900, and 4 bytes for the number of milliseconds
after midnight. By using the datetime data type, the validity
of the value passed to the stored procedure is automatically
enforced. If an invalid date is passed as a parameter, Transact-SQL
will trigger a run-time error. Transact-SQL also has a less precise
smalldatetime data type stored in 4 bytes. The
smalldatetime data type consists of 2 bytes for the number of
days after January 1, 1900, and 2 bytes for the number of minutes
past midnight. Data values for smalldatetime range from
January 1, 1900, through June 6, 2079, with accuracy to the minute.
When passing dates as parameters to a stored procedure using
ActiveX Data Objects (ADO), no conversion is required between Visual
Basic's Date data type, and the ADO Parameter of type adDBDate
(Listing 9). Don't use Transact-SQL character data types for
date-related data. Not only will this be less efficient than using
the DateTime data type, it also introduces the possibility of
invalid dates getting into the database at some time (2/29/1900 for
example). By using the datetime data type, as we've seen in
all of the other examples, we are afforded some built-in protection
by the environment.
There is one limitation when using the Transact-SQL
datetime data type with Visual Basic. The datetime
data type is accurate to the millisecond, but Visual Basic's
Date data type is not. Visual Basic's Date data type
does not support milliseconds, and will not recognize a date with
millisecond precision. This means that dates returned from SQLServer
to VISUAL BASIC will be truncated. This is not an issue if you are
not concerned with millisecond precision, but what if you are?
You might want millisecond precision, for example, if you are
using a datetime column (lets call it update_date) for optimistic
concurrency support. For example, you call a function to read a
record, including the update_date. You make some changes to the
record and want to save it. Before saving the record, you want to
compare the update_date that you originally retrieved from the
database, with the current value from that record in case is was
changed sometime after you first retrieved it. In this scenario, you
would probably want millisecond precision, and therefore Visual
Basic's date data type will not work for you.
In a case like this, you would have a few choices.
- You could build your own Date class that implements any date
functions that you need, but also supports milliseconds.
- Convert T-SQLÆs datetime to use a string data type so
milliseconds can be displayed. If you choose this method, you need
to use caution when manipulating the data so precision is not
lost.
- Use a different data type for detecting concurrency
collisions. After all, even with millisecond precision, it
is still possible for a collision to go undetected.
Instead, for example, you could use T-SQLÆs timestamp data type,
which guarantees uniqueness.
Development Tool Reference |
 |
 |
The following
table summarizes the Date related capabilities of the development
tools that we have discussed throughout this paper.
Tool |
Data Type |
Arithmetic Functions |
Visual J++ |
SimpleDateFormat |
Use Calendar class. Methods include before,
after, add, roll. |
Visual Basic, VBA |
Date, Variant (Date sub-type) |
DateAdd, DateDiff, IsDate, Date, Time, Now,
DatePart, Format, TypeName, VarType,
others |
VBScript |
Variant (Date sub-type) |
DateAdd, DateDiff |
Visual C++ |
OLEDateTime |
Arithmetic operators (-,+) |
ADO |
ADOParameter type adDBDate |
N/A |
Transact SQL |
datetime, smalldatetime |
DATEADD,
DATEDIFF |
Summary |
 |
 |
Developing
applications with the Year 2000 in mind requires developer
attention, regardless of the development tools being used. Using a
compliant set of tools is not enough to insure an application's Year
2000 compliance. Developers must adopt a development philosophy to
address Year 2000 issue in order to prevent unknowingly introducing
compliance problems. This development philosophy includes:
- Always using the development toolsÆ date data types or
otherwise ensure no loss of precision.
- Always using the development toolsÆ data math/comparison
operators, or develop operators that ensure no loss of precision.
- Making sure the development tool and environment itself do not
have Year 2000 issues.
Although the specific implementation for handling dates in the
Windows DNA architecture differs slightly from tool-to-tool, the
underlying concepts are the same for all tools. Using the
development tool's built-in date data types and date operators will
minimize the impact of the new millennium.
About The Author
Jerry Brunning is a consultant with Clarity Consulting, Inc.
Clarity is a Chicago-based consulting firm that specializes in the
design and development of client/server information systems. Jerry
is co-author of the forth-coming book "Visual Studio Enterprise
Development."
He can be reached at jbrunning@claritycon.com
or on the Web at http://www.claritycon.com/.
 |
ALL COMMUNICATIONS OR CONVEYANCES OF INFORMATION TO YOU
CONCERNING MICROSOFT AND THE YEAR 2000, INCLUDING BUT NOT LIMITED TO
THIS DOCUMENT OR ANY OTHER PAST, PRESENT OR FUTURE INFORMATION
REGARDING YEAR 2000 TESTING, ASSESSMENTS, READINESS, TIME TABLES,
OBJECTIVES, OR OTHER (COLLECTIVELY THE "MICROSOFT YEAR 2000
STATEMENT"), ARE PROVIDED AS A "YEAR 2000 READINESS
DISCLOSURE" (AS DEFINED BY THE YEAR 2000 INFORMATION AND
READINESS DISCLOSURE ACT) AND CAN BE FOUND AT MICROSOFTÆS YEAR 2000
WEBSITE LOCATED AT http://www.microsoft.com/year2000/
(the "Y2K WEBSITE"). EACH MICROSOFT YEAR 2000 STATEMENT IS PROVIDED
PURSUANT TO THE TERMS HEREOF, THE TERMS OF THE Y2K WEBSITE, AND THE
YEAR 2000 INFORMATION AND READINESS DISCLOSURE ACT FOR THE SOLE
PURPOSE OF ASSISTING THE PLANNING FOR THE TRANSITION TO THE YEAR
2000. EACH MICROSOFT YEAR 2000 STATEMENT CONTAINS INFORMATION
CURRENTLY AVAILABLE AND IS UPDATED REGULARLY AND SUBJECT TO CHANGE.
MICROSOFT THEREFORE RECOMMENDS THAT YOU CHECK THE Y2K WEBSITE
REGULARLY FOR ANY CHANGES TO ANY MICROSOFT YEAR 2000 STATEMENT. EACH MICROSOFT YEAR 2000 STATEMENT IS PROVIDED "AS IS"
WITHOUT WARRANTY OF ANY KIND. CONSEQUENTLY, MICROSOFT DISCLAIMS ALL
WARRANTIES, EITHER EXPRESS OR IMPLIED, INCLUDING THE WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. MOREOVER,
MICROSOFT DOES NOT WARRANT OR MAKE ANY REPRESENTATIONS REGARDING THE
USE OR THE RESULTS OF THE USE OF ANY MICROSOFT YEAR 2000 STATEMENT
IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY, OR OTHERWISE. NO
ORAL OR WRITTEN INFORMATION OR ADVICE GIVEN BY MICROSOFT OR ITS
AUTHORIZED REPRESENTATIVES SHALL CREATE A WARRANTY OR IN ANY WAY
DECREASE THE SCOPE OF THIS WARRANTY DISCLAIMER. IN NO
EVENT SHALL MICROSOFT OR ITS SUPPLIERS BE LIABLE FOR ANY DAMAGES
WHATSOEVER REGARDING ANY MICROSOFT YEAR 2000 STATEMENT INCLUDING
DIRECT, INDIRECT, INCIDENTAL, CONSEQUENTIAL, LOSS OF BUSINESS
PROFITS, PUNITIVE OR SPECIAL DAMAGES, EVEN IF MICROSOFT OR ITS
SUPPLIERS HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
SOME STATES DO NOT ALLOW THE EXCLUSION OR LIMITATION OF LIABILITY
FOR CONSEQUENTIAL OR INCIDENTAL DAMAGES, SO THE FOREGOING LIMITATION
MAY NOT APPLY TO YOU. THE INFORMATION CONTAINED IN EACH MICROSOFT
YEAR 2000 STATEMENT IS FOUND AT THE Y2K WEBSITE AND IS INTENDED TO
BE READ IN CONJUNCTION WITH OTHER INFORMATION LOCATED AT THE Y2K
WEBSITE, INCLUDING BUT NOT LIMITED TO MICROSOFTÆS YEAR 2000
COMPLIANCE STATEMENT, THE DESCRIPTION OF THE CATEGORIES OF
COMPLIANCE INTO WHICH MICROSOFT HAS CLASSIFIED ITS PRODUCTS IN ITS
YEAR 2000 PRODUCT GUIDE, AND THE MICROSOFT YEAR 2000 TEST CRITERIA.
ANY MICROSOFT YEAR 2000 STATEMENTS MADE TO YOU IN THE COURSE OF
PROVIDING YEAR 2000 RELATED UPDATES, YEAR 2000 DIAGNOSTIC TOOLS, OR
REMEDIATION SERVICES (IF ANY) ARE SUBJECT TO THE YEAR 2000
INFORMATION AND READINESS DISCLOSURE ACT (112 STAT. 2386). IN CASE
OF A DISPUTE, THIS ACT MAY REDUCE YOUR LEGAL RIGHTS REGARDING THE
USE OF ANY SUCH STATEMENTS, UNLESS OTHERWISE SPECIFIED BY YOUR
CONTRACT OR TARIFF.
|
 |
|