Sunday 13 November 2011

How can I store a date in a single number? Are there any standards for this? in C programming

How can I store a date in a single number? Are there any standards for this?

You might want to convert a date to a single number for several reasons, including for efficient storage or for simple comparison. Additionally, you might want to use the resultant number as part of a coding scheme. In any case, if you want to represent a date as a single number, you need to ask yourself why you need to do this and what you intend to do with the number after you have converted it. Answering these questions will help you determine which method of conversion is superior. First, look at a simple-minded

example:
#include <stdio.h>
#include <stdlib.h>
main()
{
int month , day , year;
unsigned long result;
printf( “Enter Month, Day, Year: \n” );
fflush( stdout );
scanf( “ %d %d %d” , &month , &day , &year );
result = year;
result |= month << 12;
result |= day << 14;
printf( “The result is: %ul.\n” , result );
}

This program converts three variables into a single number by bit manipulation. Here is a sample run of the
program:

Enter Month, Day, Year:
11 22 1972
The result is: 47028l.

Although this program does indeed work (you can test it by entering it into your computer), it contains
several deficiencies. It might be a good idea to try to figure out what some of the deficiencies are before proceeding to the next paragraph.

Did you think of any defects? Here are several:

The month, day, and year are not constrained. This means that the fields must be larger than is perhaps
necessary, and thus efficiency is being sacrificed. Furthermore, the user could enter arbitrarily large values that would overwrite the bounds of the bit fields, resulting in a corrupted date.

The numbers that are produced for the date are not in order; you cannot compare dates based on their
numbers. This feature might be very convenient to have

The placement of the elements into the final number is simple, if arbitrary. Extraction is, however, not so simple. (Can you see a simple way to do it?) You might want a simpler format for storing dates that will allow for simple extraction. These issues will be addressed one by one. The months need to range only from 1 to 12, and the days need to range only from 1 to 31. Years, however, are another matter. You might, depending on your purpose, choose a limit on the range of years that you need to represent in the program. This range will vary, depending on the purpose of the program. Some programs might need to represent dates in the distant past, and others might need to store dates in the distant future. However, if your program needs to store only years from 1975 to 2020, you can save quite a bit of storage. You should, of course, test all the elements of the date to ensure that they are in the proper range before inserting them into the number.

NOTE
An archaeological database might be a good example of a system that would need dates in the far past.In the C language, generally, counting (for arrays and such) begins at zero. It will help, in this case, to force all the number ranges to begin at zero. Therefore, if the earliest year you need to store is 1975, you should  subtract 1975 from all the years, causing the year series to start at zero. Take a look at the program modified

to work in this way:
#include <stdio.h>
#include <stdlib.h>
main()
{
int month , day , year;
unsigned long result;
/* prompt the user for input */
printf( “Enter Month, Day, Year: \n” );
fflush( stdout );
scanf( “ %d %d %d” , &month , &day , &year );
/* Make all of the ranges begin at zero */
--month;
--day;
year -= 1975;
/* Make sure all of the date elements are in proper range */
if (
( year < 0 || year > 127 ) || /* Keep the year in range */
( month < 0 || month > 11 ) || /* Keep the month in range */
( day < 0 || day > 31 ) /* Keep the day in range */
)
{
printf( “You entered an improper date!\n” );
exit( 1 );
}
result = year;
result |= month << 7;
result |= day << 11;
printf( “The result is: %ul.\n” , result );
}

This program doesn’t account for the fact that some months have fewer than 31 days, but this change is a minor addition. Note that by constraining the range of dates, you need to shift the month and date values by a lesser amount. The numbers produced are still unsortable, however. To fix this problem, you need to make the observation that the bits farthest to the left in the integer are more significant than the ones to the right. What you should do, therefore, is place the most significant part of the date in the leftmost bits. To do this, you should change the part of the program that places the three variables into resultant numbers to work this way:

result = day;
result |= month << 5;
result |= year << 9;
Here’s a test of this modification on some sample dates:
Enter Month, Day, Year:
11 22 1980
The result is: 11077l.
Enter Month, Day, Year:
12 23 1980
The result is: 11621l.
Enter Month, Day, Year:
8 15 1998
The result is: 7415l.

You can now store records with their date in this format, and sort them based on this number, with full
confidence that the dates will be in the proper order.

The only issues that still need to be addressed are the somewhat arbitrary nature of storage within the value, and the question of extraction. Both problems can be solved by the use of bit fields. Bit fields are explained
in Chapter X. Take a look at the code without further ado:
/* These are the definitions to aid in the conversion of
* dates to integers.
*/
typedef struct
{
unsigned int year : 7;
unsigned int month : 4;
unsigned int day : 5;
} YearBitF;
typedef union
{
YearBitF date;
unsigned long number;
} YearConverter;
/* Convert a date into an unsigned long integer. Return zero if
* the date is invalid. This uses bit fields and a union.
*/
unsigned long DateToNumber( int month , int day , int year )
{
YearConverter yc;
/* Make all of the ranges begin at zero */
--month;
--day;
year -= 1975;
/* Make sure all of the date elements are in proper range */
if (
( year < 0 || year > 127 ) || /* Keep the year in range */
( month < 0 || month > 11 ) || /* Keep the month in range */
( day < 0 || day > 31 ) /* Keep the day in range */
)
return 0;
yc.date.day = day;
yc.date.month = month;
yc.date.year = year;
return yc.number + 1;
}
/* Take a number and return the values for day, month, and year
* stored therein. Very elegant due to use of bit fields.
*/
void NumberToDate( unsigned long number , int *month ,
int *day , int *year )
{
YearConverter yc;
yc.number = number - 1;
*month = yc.date.month + 1;
*day = yc.date.day + 1;
*year = yc.date.year + 1975;
}
/*
* This tests the routine and makes sure it is OK.
*/
main()
{
unsigned long n;
int m , d , y;
n = DateToNumber( 11 , 22 , 1980 );
if ( n == 0 )
{
printf( “The date was invalid.\n” );
exit( 1 );
}
NumberToDate( n , &m , &d , &y );
printf( “The date after transformation is : %d/%d/%d.\n” ,
m , d , y );
}

There is still a certain amount of inefficiency due to the fact that some months have fewer than 31 days. Furthermore, this variance in the number of days will make it hard to increment dates and to find the difference between dates in days. The built-in C functions are best for these more complex tasks; they will save the programmer from having to rewrite a great deal of code.

Cross Reference:

XIII.2: How can I store time as a single integer? Are there any standards for this?

No comments:

Post a Comment