Y2K Site Map | Terms of Use | Problem | Steps | Certification | Briefings | Compliance | Solutions | BIOS | Test & Evaluation | Cost
The following Y2K material has been kept available by MITRE for historical purposes only and has not been updated unless noted.
![]() | Logic Window Solutions |
The most common two-digit year formats are MMDDYY, YYMMDD, DDDYY. What happens in computations, including checking the ranges and order of dates, is that strings of these forms are converted to ordinal values that increase with the actual year. These first view the year, month and day fields in terms of their underlying ordinal representations (usually 1-12 for months and 1-31 for days) with the year becoming a problem when its input value is two digits from 00-99 or even just 01-98. A "logic window" fix for two digit year use while having a range of years represented that spans the change of the century (from 1999 to 2000) is to have a base year that serves as a pivot for the values of the underlying representation, such that two-digit values after the pivot represent 19-- and two-digit values before the pivot represent 20--. The resulting range of ordinal years represented is 1900 + pivot to 2000 + pivot -1. This can be generalized for other "base centuries" than the 20th if need be.
An example of a "Fixed Window" form of a "logic window fix" has been implemented by Oracle. There the pivot is the midcentury value of 50, which is used with six-digit formats designated as "RR" in place of the two digit year "YY" in combination with the other MM and DD fields. The primary advantage is data involving six-digit input fields does not have to be changed. To make this work over time, this has to be thought of as a "Sliding Window" where the pivot value will change occasionally by convention (or more formally). There just has to be a periodic update of the pivot parameter along with a pruning/archiving of obsolete data.
ExampleAn example of these issues is a fragment from a FORTRAN program:
C
C If the input year has only two digits convert it to a four-digit
C year.
C
IF ( IYEAR .LT. 50 ) THEN
IFOUR_YR= IYEAR + 2000
ELSE IF ( IYEAR .LE. 99 ) THEN
IFOUR_YR= IYEAR + 1900
ELSE
IFOUR_YR= IYEAR
END IF
This is an example as is of a fixed window with a pivot of 50. It could be a sliding window by considering the "50" as a parameter, to be changed as needed by agreement of all potential users some other value, such as 80. Questions are of how to do the parameterization, and how much of the fixing to do inline versus in closed functions. Whether to use closed library or utility functions is another aspect of the system construction decision. In either case, the question is how the conversion from the format with the "YY" gets to look like the "RR" in actual computational or decision logic.
Conversion IssuesIn implementing such a fix, there are of course actually two conversions for each date format:
forward_conversion (date : input_format_type) : ordinal_temp_type
backward_conversion (val : ordinal_temp_type) : input_format_type
Around each sequence of operations where date values are used in operations,
there has to be a preamble of the form:
temp1 := forward_conversion(var1);
temp2 := forward_conversion(var2);
.
.
tempn := forward_conversion(varn);
where the temps (computational temporaries) are maintained through all the
affected operations. At the end of the sequence, any of the changed temps
have to be converted back by a postlude with assignments of the form:
var-i := backward_conversion(temp-i);
A combined approach is to think of most of the conversion work being done at the interfaces, using "bridging conversions" to put all dates into a neutral intermediate representation, which can even be in some form optimized for a particular platform or application format. There are some vendors addressing this problem appropriately, with the natural emphasis as predictable on particular platform support. For the DoD, a more general move to object-oriented interface support has the same impetus.
ParameterizingThe form of the conversions is parameterizable via the range and pivot value. The forward conversions are just:
temp.yr := date.yr + (lower bound of range - pivot);
or
temp.yr := (upper bound of range - pivot) + date.yr;
(depending on which avoids underflow of the positive ordinal "date.yr - pivot")
and the backward conversions are:
date.yr := temp.yr - (lower bound of range - pivot);
(with overflow ignored)
This last operation is basically a modulus, or "mod" in many languages ("%" in C). A first optimization ignoring representation anomalies is to operate on strings exclusively, since comparisons and sorting-like operations can work with string values easily in many languages. Then the modulus is just a substring replacement using the two-digit year, which is probably the most common implementation. Similarly in the context of the Y2K issue, the plus in the first conversion is equivalent to just replacing the implied two-digit century in a format having a two-digit year with the current century or an adjacent one depending on the underflow. The current century can be either also be a parameter or can be obtained from the current date.
Continuous slidingUsing the current year as a parameter raises the possibility of doing the periodic adjustment of the pivot on a regular basis, such as each year. A good practical implementation of this was described in a Datamation letters forum in response to Peter de Jager's column. The formalization is as follows:
if C1 is less than 100 then
/* this date occurs "before" the current pivot */
if X2 is greater than C1
/* this was in the previous century */
output X4 - 100
else
output X4
else
if X2 is less than C1 - 99
/* this is in the next century */
output X4 + 100
else
output X4
Handling common situations well may be more important than making the conversions overly general. Here is some Ada code for this kind of conversion from YYMMDD format to YYYYMMDD format:
function To_YYYYMMDD(Date: in YYMMDD) return YYYYMMDD is
begin
if String(Date(1..2)) >= Pivot_String
then return YYYYMMDD("19" & Date);
else return YYYYMMDD("20" & Date);
end if;
end To_YYYYMMDD;
and the reverse conversion is just
function To_YYMMDD(Date: in YYYYMMDD) return YYMMDD is
begin
return YYMMDD(Date(3..8));
end To_YYMMDD;
More examples: C and COBOL
The equivalent code in COBOL or C requires special setup, including declarations (of separate fields for YY,MM,DD and a string pointer in C), but the effect is the same. One part of a C solution involves careful string pointer usage (where in_ptr is the input, and out_ptr the output string pointer):
char *in_ptr, *out_ptr;
int year,month;
/* Convert year section of input */
year = ((*in_ptr - ASCII_ZERO) * 10) + ((*(in_ptr + 1) - ASCII_ZERO));
if (year < 80)
{
/* Assume year is after 2000 AD */
*(out_str + YEAR_OFF) = '2';
*(out_str + YEAR_OFF + 1) = '0';
}
else
{
/* Otherwise year is in this century */
*(out_str + YEAR_OFF) = '1';
*(out_str + YEAR_OFF + 1) = '9';
}
/* Copy input year to last 2 chars of output year */
*(out_str + YEAR_OFF + 2) = *in_ptr;
*(out_str + YEAR_OFF + 3) = *(in_ptr + 1);
Here the start of the YY field is indicated by YEAR_OFF, and is 0 for the formats where YY is first.
Bridging as an optimizationUsing the same conversions at all user data input points to put all dates into a neutral intermediate representation can often be in some way optimized for a particular platform or application format. Then the read accesses (where strings are input) have to do the equivalent of the C library operation (to extract year, month, day fields from each string):
sscanf(input-string," %d %d %d ",yy,mm,dd);
yr = YEAR(yy);
This can be done by program transformation via preprocessor-like
local architecture reengineering approaches, or within intermediate
language manipulations by a compiler. In addition, accesses and
assignments have to be "rewritten" to use the above conversions
date->yy => yr, date->mm => mm, date->dd => dd in the
appropriate incarnation
and the reverse for field reassignments. For "full assignments",
only those associated with outputs have to be really converted, just
doing the reverse of input:
sprintf(output-string," %d %d %d ",INVERSE-YEAR(yr),mm,dd);
where "yr" has to be translated to a suitable format by a user-
supplied function for the target application or data base. The
value of leaving some details to the application building step,
probably in the form of libraries, is clear. A combination
of compiler temporary management and inter program link-time
optimization using parameter analysis would produce the best
solution.
Other optimization approaches
The limit of this approach (called "reversing the clock" in the previous page) requires no program changes by doing all windowing conversions at the interfaces. This requires input and output conversions to do the YEAR mapping in reverse to put all input from after the year 2000 into the 1972-2000 window, runs the normal application, and maps all output back to the post-2000 time frame.
Conversions supports calculations and sorting, however it does impact performance when displayed dates are converted into a human understandable format. Deciding on the correct approach requires a thorough analysis of how dates are used within an organizations information systems. There may be some cases where a company uses only one of these conversion approaches, or they may use all in some limited form.
One idea is that conversions within sequences of program logic and operations deal with temporaries that are correct for the existing operations, such as comparison used in range checking logic or sorting. Values in variables or more permanent storage (the real data base at the other extreme from volatile memory) should not change. This is an effective approach, but requires wrapping all potential subsystems or clients regardless of the actual needs of the computations they perform, not to mention recompiling for the changed (not so temporary any more) data types. There is some apparent preference for this method in many commercially available conversions, but there is no guarantee it is really most effective or least expensive way to approach a y2k conversion. There are potentials for performance hits, and there could even be problems introduced by compiling code with changed data types (due to algorithmic changes that could affect computations in subtle ways, or even just in encountering compiler bugs due to involving different data types subject to different optimizations).A more carefully engineered approach might be to identify local regions where introducing conversions would be just as straightforward and less likely to have unintended side effects. Compiler optimization of register use is an example of this kind of approach; there is really a continuum from register to temporary use in compiler generated code that has just the right results. Regions with high usage of converted date manipulation operations should be identifiable by normal data flow methods, and the identification of converted temporaries then used for insuring valid operations.
Using compilers to control the placement of such conversions possibly would improve the effectiveness of this kind of solution. A separate discussion describes how to pursue this line of development and includes a downloadable discussion from a recent conference.
For further information directly related to Year 2000 issues, please contact the Year 2000 hot line
Last modified: Thursday, 14-Feb-2008 09:21:05 EST
|