Microsoft Y2K  
Microsoft
 This static CD-based web site is representative of the www.microsoft.com/y2k site as of October 15, 1999.

Microsoft Year 2000 Readiness Disclosure & Resource Center
Preparing Office Solutions for the Year 2000
How Microsoft Products Assume Centuries

The most important of handling the Year 2000 problem for desktop development is to ensure that you understand how your development environment interprets short years. If you type 12/31/97 into your application, what year is actually stored? What about 12/31/00? How do you figure out the rules? The better understand this issue, you need to take a brief look at some historical issues.

In the world of 16-bit programming, most applications have their own internal routines for working with dates. For the most part, a two-digit year is always assumed to be in the 20th century. So if you enter a date of 12/31/97, the application stores the value of 12/31/1997. The obvious problem with this approach is that when your application meets the new millennium, a date entered as 12/31/00 defaults to 12/31/1900, not to 12/31/2000.

Obviously, a better solution was obviously neededùwhy have each application define its own rules about how dates are managed? And was automatic conversion of short years to the 20th century the best approach? The new solution was to make the rules involving dates to be handled by the operating system and to default to the current century. This solution was developed during the development of Microsoft Visual Basic version 4, and Microsoft Office 95 (including Microsoft Access 95, version 7) and involved two things:

  • Moving the handling of date rules from the control of applications to the domain of the operating system (through the OLE Automation file OLEAUT32.DLL).
  • A change in the assumption rule: two-digit years would default to the current century as defined by the system's clock.

Later, it was decided that defaulting to the current century was not the best approach, so the OLE Automation library was updated to introduce a new rule called the "sliding window". Two-digit years between 00 and 29 are interpreted as belong in the 21st century, all others default to the 20th century.

As you can see, there are now three separate rules governing the interpretation of short years. The problem is further compounded by the fact that even within the same version of an application development tool, you cannot assume that you automatically know which rule will be used. This is because the rule is defined in the OLE Automation libraries for 32-bit operating systems.

For example, Microsoft Access 95 ships with the older OLE Automation library that uses the rule of always defaulting to the current century for two digit years. After Microsoft Access 95 shipped, the OLE Automation library was updated to use the new rule. Just by installing a program that uses the new library, you change how your application interprets years.

The new sliding window rule was introduced into the OLE Automation library that shipped with a multitude of products. These include Microsoft Internet Explorer version 3.0 and later, Microsoft Windows NT 3.51 Service Pack 5, Microsoft Windows NT 4.0, Microsoft Windows 95 OSR 2, Microsoft Office 97, Microsoft Visual Basic 5.0 and other products. The bottom line is the OLEAUT32.DLL versions 2.20.4054 and later use the new sliding window technique, while previous versions use the current century rule.

Microsoft Excel on the other hand, does not use the OLE Automation Library in any version for dealing with dates--it has its own rules that depend on the version you are using. Excel embeds Visual Basic for Applications (VBA), however, which does use the OLE Automation Library.

Determining Which Rule Your Application Is Using

So how do all these rules and libraries affect Microsoft's desktop products? And how do you determine which one is in effect for a given instance of your application? The following table shows how each version of these applications interpret 2-digit years:

Product Rule Comments
  • Microsoft Access, version 2
  • Microsoft Visual Basic, version 3
All two-digit years are automatically and unchangeably assigned to the 20th century. The logic and rules for dealing with dates is built into each program's private core executables and dynamic link libraries.
  • Microsoft Access 95, version 7
Uses the OLE Automation library. The rule used depends on which version of the library you have installed.
  • Microsoft Excel, version 5
  • Microsoft Excel 95, version 7
A two-digit year less than 20 is assigned to the 21st century. A two-digit year greater than or equal to 20 is assigned to the 20th century. For example, a value of "12/31/19" is stored as 12/31/1919, where a value of "12/13/20" is stored as 12/13/2020.
  • Microsoft Excel 97, version 8
A two-digit year less than 30 is assigned to the 21st century. A two-digit year greater than or equal to 30 is assigned to the 20th century. For example, a value of "12/31/25" is stored as 12/31/2025, where a value of "12/13/30" is stored as 12/13/1935.
  • Microsoft Visual Basic, version 4, 16-bit
VB 4/16 uses the 16-bit version of the OLE Automation library. This version has never been updated, so the built-in sliding window solution is not available to VB 4/16 applications. Assumes that all two-digit years belong in the current century as defined by the system clock.
  • Microsoft Access 97, version 8
  • Microsoft Visual Basic, version, 4, 32-bit
  • Microsoft Visual Basic, version 5
Uses the OLE Automation library. The rule used depends on which version of the library you have installed.
  • Microsoft Outlook 97
When dealing with dates in tasks, events, and meeting events, Outlook uses a window that defined as 30 years back from the current date and 70 years forward. Birthdays recorded in the Contacts portion of Outlook use a different rule because a greater bias towards the past is needed. In this case, two-digit years are assumed to belong in a window between ninety-five years back and five years forward from the current date as defined by the system clock. You must update your OUTTLIB.DLL file to version 8.03 or events that span the century boundary will not be recorded correctly.
  • Microsoft Word, version 6
  • Microsoft Word 95, version 7
The two digit year "00" is interpreted as the year 2000, all other two digit years are interpreted as being in the 20th century. This rule is used by Microsoft Word when performing a table sort, and when using Date formatting (picture switch) of Quote Fields. Issues may exist with user-input dates when sorting tables. Additionally, data imported from other programs is represented as text, and should therefore be examined.
  • Microsoft Word 97, version 8
Two digit years from 00 to 29 are assumed to be in the 21st century, all others are assumed to be in the 20th century. A date window of 1930-2029 is used when interpreting two digit year when using the Date formatting (picture switch) of Quote fields. Issues may exist with user-input dates when sorting tables. Additionally, data imported from other programs is represented as text, and should therefore be examined.
Table 1   Rules Used for Century Assumption

If you are using a product listed above as using the OLE Automation Library, you need to check the version of the OLEAUT32.DLL file on your system to determine which rule is in place. You can check this version manually, or use the supplied code to programmatically check the version.

To verify the version manually, open Windows Explorer and go to the Windows\System directory. Locate the file named OLEAUT32.DLL and right-click on it. From the context menu, select Properties. When the Properties dialog appears, select the Version tab and click on 'Product Version' to see the version number for the file. If it is 2.20.4049 or later, the sliding window algorithm is being used. If the version is earlier than this, all two-digit years are assumed to be in the current century as defined by the system clock.

Programmatically Checking for the Version

You can also check the version of the OLE Automation Library using VBA program code. Place the following VBA code in a new module and run the IsOLELibNewer() procedure to see if the newer version (sliding window behavior) of OLEAUT32.DLL is installed.

' API Calls for getting a file's version information
Private Type FileVersion
   FileVersion As String   ' Full file version as a string
   FileVersionMSl As Integer   ' File version MSB Low
   FileVersionMSh As Integer   ' File version MSB High
   FileVersionLSl As Integer   ' File version LSB Low
   FileVersionLSh As Integer   ' File version LSB High
   ProductVersion As String   ' File product version as a string
   ProductVersionMSl As Integer   ' Product version MSB low
   ProductVersionMSh As Integer   ' Product version MSB high
   ProductVersionLSl As Integer   ' Product version LSB low
   ProductVersionLSh As Integer ' Product version LSB high
End Type

Private Type VS_FIXEDFILEINFO
    dwSignature As Long
    dwStrucVersionl As Integer
    dwStrucVersionh As Integer
    dwFileVersionMSl As Integer
    dwFileVersionMSh As Integer
    dwFileVersionLSl As Integer
    dwFileVersionLSh As Integer
    dwProductVersionMSl As Integer
    dwProductVersionMSh As Integer
    dwProductVersionLSl As Integer
    dwProductVersionLSh As Integer
    dwFileFlagsMask As Long
    dwFileFlags As Long
    dwFileOS As Long
    dwFileType As Long
    dwFileSubtype As Long
    dwFileDateMS As Long
    dwFileDateLS As Long
End Type

Private Declare Function GetFileVersionInfo _
    Lib "Version.dll" _
    Alias "GetFileVersionInfoA" _
    (ByVal lptstrFilename As String, _
    ByVal dwHandle As Long, _
    ByVal dwLen As Long, _
    lpData As Any) _
    As Long

Private Declare Function GetFileVersionInfoSize _
    Lib "Version.dll" _
    Alias "GetFileVersionInfoSizeA" _
    (ByVal lptstrFilename As String, _
    lpdwHandle As Long) _
    As Long

Private Declare Function VerQueryValue _
    Lib "Version.dll" _
    Alias "VerQueryValueA" _
    (pBlock As Any, _
    ByVal lpSubBlock As String, _
    lplpBuffer As Any, _
    puLen As Long) _
    As Long

Private Declare Sub MoveMemory _
    Lib "kernel32" _
    Alias "RtlMoveMemory" _
    (dest As Any, _
    ByVal Source As Long, _
    ByVal length As Long)

Private Sub GetResourceVersion( _
    strFileName As String, _
    recFileVer As FileVersion)
    ' Comments : Returns file version information
    ' Parameters: strFileName - Name of the file
    '    recFileVer - FILEVERSION type
    ' Returns : Nothing
    '
    Dim lngRC As Long
    Dim lngDummy As Long
    Dim abytBuffer() As Byte
    Dim lngBufferLen As Long
    Dim lngVerPointer As Long
    Dim udtVerBuffer As VS_FIXEDFILEINFO
    Dim lngVerbufferLen As Long
   
    On Error GoTo PROC_ERR
   
    ' Get the size
    lngBufferLen = GetFileVersionInfoSize(strFileName, lngDummy)
    If lngBufferLen < 1 Then
       Exit Sub
    End If

   ' Set up the byte array
    ReDim abytBuffer(lngBufferLen)
   
    ' Get the file version information
    lngRC = GetFileVersionInfo(strFileName, 0&, lngBufferLen, abytBuffer(0))
    lngRC = VerQueryValue(abytBuffer(0), "\", lngVerPointer, lngVerbufferLen)
   
    ' Manipulate the bits
    MoveMemory udtVerBuffer, lngVerPointer, Len(udtVerBuffer)

    ' Build the file version string
    recFileVer.FileVersion = _
      Format$(udtVerBuffer.dwFileVersionMSh) & "." & _
      Format$(udtVerBuffer.dwFileVersionMSl) & "." & _
      Format$(udtVerBuffer.dwFileVersionLSh) & "." & _
      Format$(udtVerBuffer.dwFileVersionLSl)
   
    recFileVer.FileVersionLSh = udtVerBuffer.dwFileVersionLSh
    recFileVer.FileVersionLSl = udtVerBuffer.dwFileVersionLSl
    recFileVer.FileVersionMSh = udtVerBuffer.dwFileVersionMSh
    recFileVer.FileVersionMSl = udtVerBuffer.dwFileVersionMSl
   
    ' Build the product version string
    recFileVer.ProductVersion = _
      Format$(udtVerBuffer.dwProductVersionMSh) & "." & _
      Format$(udtVerBuffer.dwProductVersionMSl) & "." & _
      Format$(udtVerBuffer.dwProductVersionLSh) & "." & _
      Format$(udtVerBuffer.dwProductVersionLSl)
   
    recFileVer.ProductVersionLSh = udtVerBuffer.dwProductVersionLSh
    recFileVer.ProductVersionLSl = udtVerBuffer.dwProductVersionLSl
    recFileVer.ProductVersionMSh = udtVerBuffer.dwProductVersionMSh
    recFileVer.ProductVersionMSl = udtVerBuffer.dwProductVersionMSl
   
PROC_EXIT:
    Exit Sub
   
PROC_ERR:
    MsgBox "Error: " & Err.Number & ". " & Err.Description, , _
      "GetResourceVersion"
    Resume PROC_EXIT
   
End Sub

Public Function IsOLELibNewer() As Boolean
    ' Comments : Determines if the installed OLE Automation library
    '    (OLEAUT32.DLL) has the sliding window algorithm
    '    in place.
    ' Parameters: None
    ' Returns : True if the newer version supporting the sliding
    '    window algorithm is installed, False otherwise or
    '    if an internal error occurs.
    '
    Dim strFile As String
    Dim recFile As FileVersion
   
    On Error GoTo PROC_ERR
   
    strFile = "OLEAUT32.DLL"
   
    GetResourceVersion strFile, recFile
   
    Select Case recFile.FileVersionLSh
      Case 0
         ' Old version
         IsOLELibNewer = False
      Case 4044
         ' Old algoritm
         IsOLELibNewer = False
      Case Is >= 4049
         ' New algorithm
         IsOLELibNewer = True
      Case Else
         ' Some unknown version, assume failure
         MsgBox "Unknown OLEAUT32.DLL version."
         IsOLELibNewer = False
   
    End Select
   
PROC_EXIT:
    Exit Function
   
PROC_ERR:
    MsgBox "Error: " & Err.Number & ". " & Err.Description, , _
      "IsOLELibNewer"
    Resume PROC_EXIT
   
End Function

Code Listing 1   Determining Which OLE Library Is Installed

Custom Sliding Window Solutions

If your application is based on a 16-bit program such as Microsoft Access, version 2 or Microsoft Visual Basic, version 3, or and application that uses one of the older versions of the OLE Automation Library, the sliding window algorithm does not exist. In this case, you have to create your own code-based algorithm. Additionally, even when you are using a product that employs the sliding window algorithm, you must decide if the 00-29 range is right for your application. If not, you need to implement your own routine.

The following code shows a sample sliding window procedure. Note that this version always throws the century information away and supplies the century according the limits set:

Function SafeCentury16 (varDateIn As Variant, intPivot As Integer) As Variant
' Comments : Returns the passed date with the century modified
'    as determined by the specified pivot year, using
'    the following rule:
'    Last Two Digits Century
'    Of Input Year Assumed
'    ---------------- ---------
'    <= intPivot 21st
'    > intPivot 20th
'
'    Note that any century information supplied in the
'    varDateIn parameter is thrown away. This is because
'    by the time a date gets to this procedure, it has
'    most likely undergone the Access assumption rule
'    and is therefore suspect.
'
'    This version is for 16-bit hosts such as Access 2.0
'    and Visual Basic 3.0
'
' Parameters: varDateIn - variant containing a date. This can be
'    a string or date, but must be one of the following
'    formats:
'        mm/dd/yy
'        dd/mm/yy
'        mm/dd/yyyy
'        dd/mm/yyyy
'    intPivot - year to pivot to the current century.
' Returns : variant of type 7 (date) containing the transformed
'    date or Null if the passed value cannot be evaluated
'    as a date or an error occurs.
' Example : SafeCentury16 ("12/13/12", 29) returns 12/12/2012
'    SafeCentury16 ("12/13/1912", 29) returns 12/12/2012
'    SafeCentury16 ("12/13/29", 29) returns 12/13/2029
'    SafeCentury16 ("12/13/30", 29) returns 12/13/1930
'
Dim intYear As Integer
Dim fIsDate As Integer
Dim fOk As Integer

On Error GoTo PROC_ERR

' Assume failure
fOk = False

' Determine if the passed value can
Select Case VarType(varDateIn)

Case V_DATE
   ' Its a date, simply work on the century
   fOk = True

Case V_STRING
   ' Its a string so we use IsDate to determine if the value is
   ' valid as a date. Note that IsDate() uses the date settings
   ' in the Windows Control Panel to determine validity. Therefore
   ' certain date formats (such as "Monday, June 3rd, 1995") will
   ' not be considered a valid date by this procedure. See the
   ' procedure comments for valid date formats for use with this
   ' procedure.
   On Error Resume Next
   fIsDate = IsDate(varDateIn)
   fOk = (Err = 0)
   On Error GoTo PROC_ERR

Case V_LONG
   ' Check to see if it is in the serial date range
   fOk = (varDateIn > -657434 And varDateIn < 2958465)

Case Else
   ' Can't figure out what it is

End Select

If fOk Then
   ' Get the right-most two digits of the year. Note the use of
   ' Format function to ensure that the string we search in
   ' always has four digits.
   intYear = CInt(Right$(Format(varDateIn, "yyyy"), 2))

   ' Pivot the century. Note the use of explicit, hard-coded
   ' centuries. While this may appear to be non-Year 2000
   ' compliant (don't hardcode century data), it does
   ' specify the full four digits of the year. And since
   ' we need to rely on this procedure to make exactly
   ' the assumption we want, the hard-coded values are correct.
   If intYear > intPivot Then
     ' If the supplied two-digit year is greater than then
     ' pivot number, the returned value is in the 20th
     ' century.
     intYear = 1900 + intYear
    Else
     ' If the supplied two-digit year is less than or equal
     ' to the pivot number, the returned value is in the
     ' 21st century.
     intYear = 2000 + intYear
    End If

   ' Return the variant. Note that we cast it explicitly as a date.
   ' This code explicitly creates the date in the format mm/dd/yyyy.
   ' If your date format is different, change the order of
   ' concatenation.
   SafeCentury16 = CVDate(Month(varDateIn) & "/" & Day(varDateIn) & "/" & intYear)
Else
   SafeCentury16 = Null
End If

PROC_EXIT:
   Exit Function

PROC_ERR:
   SafeCentury16 = Null
   Resume PROC_EXIT

End Function

Code Listing 2   The Sliding Window Procedure for 16-bit Basic (Access 2.0 and VB 3)

Public Function SafeCenturyVBA( _
varDateIn As Variant, _
intPivot As Integer) _
As Variant
' Comments : Returns the passed date with the century modified
'    as determined by the specified pivot year. If the
'    last two digits of the supplied date are less than
'    or equal to the intPivot value, the 21st century is
'    used. Otherwise, the 20th century is used.
'    Note that any century information supplied in the
'    varDateIn parameter is thrown away. This is because
'    by the time a date gets to this procedure, VB has most
'    likely already assumed the century and the date is
'    therefore suspect.
' Parameters: varDateIn - variant containing a date. This can be
'    a string or date, but must be one of the following
'    formats:
'        mm/dd/yy
'       dd/mm/yy
'        mm/dd/yyyy
'        dd/mm/yyyy
'    intPivot - year to pivot to the current century.
' Returns : variant of type 7 (date) containing the transformed
'    date or Null if the passed value cannot be evaluated
'    as a date or an error occurs.
'
Dim intYear As Integer
Dim fIsDate As Boolean
Dim fOk As Boolean

On Error GoTo PROC_ERR

' Assume failure
   fOk = False

' Determine if the passed value can
   Select Case VarType(varDateIn)

Case vbDate
   ' Its a date, simply work on the century
   fOk = True

Case vbString
   ' Its a string so we use IsDate to determine if the value is
   ' valid as a date. Note that IsDate() uses the date settings
   ' in the Windows Control Panel to determine validity. Therefore
   ' certain date formats (such as "Monday, June 3rd, 1995") will
   ' not be considered a valid date by this procedure. See the
   ' procedure comments for valid date formats for use with this
   ' procedure.
   On Error Resume Next
   fIsDate = IsDate(varDateIn)
   fOk = (Err = 0)
   On Error GoTo PROC_ERR

Case vbLong
   ' Check to see if it is in the serial date range
   fOk = (varDateIn > -657434 And varDateIn < 2958465)

Case Else
   ' Can't figure out what it is

End Select

If fOk Then
   ' Get the right-most two digits of the year. Note the use of
   ' Format function to ensure that the string we search in
   ' always has four digits.
   intYear = CInt(Right$(Format(varDateIn, "yyyy"), 2))

   ' Pivot the century. Note the use of explicit, hard-coded
   ' centuries. While this may appear to be non-Year 2000
   ' compliant (don't hardcode century data), it does
   ' specify the full four digits of the year. And since
   ' we need to rely on this procedure to make exactly
   ' the assumption we want, the hard-coded values are correct.
   If intYear > intPivot Then
     ' If the supplied two-digit year is greater than then
     ' pivot number, the returned value is in the 20th
     ' century.
     intYear = 1900 + intYear
Else
     ' If the supplied two-digit year is less than or equal
     ' to the pivot number, the returned value is in the
     ' 21st century.
     intYear = 2000 + intYear
End If

   ' Return the variant. Note that we we cast it explicitly as a date.
   SafeCenturyVBA = CVDate(Month(varDateIn) & _
        "/" & Day(varDateIn) & _
        "/" & intYear)
Else
   SafeCenturyVBA = Null
   End If

PROC_EXIT:
   Exit Function

PROC_ERR:
   MsgBox "Error: " & Err.Number & ". " & Err.Description, , _
   "SafeCenturyVBA"

   SafeCenturyVBA = Null

   Resume PROC_EXIT

End Function

Code Listing 3   The Sliding Window Procedure for VBA

<< 1 2 3 4 5 6 7 8 9 10 11 12 13 14 >>


Send This To a Friend


 

Tuesday, March 16, 1999
1998 Microsoft Corporation. All rights reserved. Terms of use.

This site is being designated as a Year 2000 Readiness Disclosure and the information contained herein is provided pursuant to the terms hereof and the Year 2000 Information and Readiness Disclosure Act.