C语言中,怎样把日期存储到单个数字中?有这方面的标准吗?
有好几个原因使你想把日期转换成单个数字,包括为了节省存储空间或进行简单的比较。此外,你也许想用转换所得的数字作为编程结构中的一部分。无论如何,如果你想用单个数字表示日期,你就需要问一下自己为什么要这样做,以及你想怎样处理转换所得的数字。回答这些问题将有助于你确定哪种转换方法最好。首先,请看一个简单的例子:
# 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 );
}
这个程序通过位操作把三个变量转换为单个数字,以下是它的一种运行示例:
Enter Month,Day,Year:
11 22 1972
The result is:470281.
尽管这个程序确实能工作(你可以把它输入计算机测试一下),但它还有一些缺陷。在进一步讨论之前,最好还是先指出其中囊恍┤毕莘直鹗鞘裁础?BR> 你想到其中的某些缺陷了吗?以下是其中的几种缺陷:
下面我们逐个分析这些问题。
月份的范围应该从1到12,日期的范围应该从1到31,然而年份却与众不同。你可以根据你的目的把程序中要使用的年份限制在一个范围内。这个范围是可以变化的,具体视程序的目的而定。有些程序需要使用的日期可能在遥远的过去,而另一些程序需要使用的日期可能在遥远的将来。然而,如果你的程序只需要使用1975到2020之间的年份,你就能节省一位存储空间。显然,在把日期转换成数字之前,你应该先检查日期的各成员,以确保它们都在正确的范围之内。
注意:一个考古学数据库就是一个需要使用远古日期的很好的例子。
在C语言中,通常从零开始计算(如数组等)。在这种情况下,强制使所有数字的范围都从零开始是有好处的。因此,如果你要存储的最早的年份是1975年,你应该从所有的年份中减去1975,以使年份的序列从零开始。请看改为以这种方式工作的程序:
# 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>ll) || / * 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) ;
}
这个程序并没有考虑到有些月份不到31天的情况,但只需作一点小小的改进就可弥补这一缺陷。注意,当你限制了日期的范围后,在对月值和日值进行移位时,要少移几位。
上述程序所生成的数字仍然不能用来对日期进行排序。为了解决这个问题,你需要注意到这个数字最左边的几位是高于最右边的几位的有效位。因此,你应该把日期中最高的有效部分存入最左边的几位中。为此,上述程序中把二个变量安置到数字中的那部分代码应该改为如下形式:
result = day;
result | = month<<5;
result | = year<<9;
以下是用几个示例日期测试上述修改的结果:
Enter Month, Day, Year :
11 22 1980
The result is : 110771.
Enter Month, Day, Year:
12 23 1980
The result is : 116211.
Enter Month, Day, Year;
8 15 1998
The result is : 74151.
现在,你可以存储记录,而记录的日期可以存成上述格式,并且可以根据转换所得的数字对日期进行排序,而对排序结果的正确性可以充满信心。
最后还需要提到一点是这种存储方式在某种程度上的随机性和抽取日期的问题。这些问题都可以通过使用位域来解决。位域已经在第9章中介绍过了。请看下面这个已经比较完善的程序:
/ * 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) ;
}
由于有些月份不足31天,因此上述程序的某些部分效率仍然不高。此外,每月天数的不同将给日期的增值运算和差值运算带来困难。好在C语言中有些现成的函数能非常出色地完成这些更为复杂的任务,它们可以使程序员少写很多代码。
# 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 );
}
这个程序通过位操作把三个变量转换为单个数字,以下是它的一种运行示例:
Enter Month,Day,Year:
11 22 1972
The result is:470281.
尽管这个程序确实能工作(你可以把它输入计算机测试一下),但它还有一些缺陷。在进一步讨论之前,最好还是先指出其中囊恍┤毕莘直鹗鞘裁础?BR> 你想到其中的某些缺陷了吗?以下是其中的几种缺陷:
- 月份、日和年份是不受限制的,这意味着它们的存储区域必须比实际需要的大,而这将牺牲效率。此外,用户可以输入一个任意大的数值,以致超出位域的边界,从而导致一个完全错误的日期。
- 由日期转换而来的数字不是有序的,你不能根据这些数字对日期进行比较,而这种功能却能带来很大的方便。
- 各成员在转换所得的数字中的安置是简单的,甚至是随意的,然而把它们抽取出来却不那么简单了(你能想出一个简单的办法吗?)。你真正需要的可能是一种存储日期和抽取日期都比较简单的格式。
下面我们逐个分析这些问题。
月份的范围应该从1到12,日期的范围应该从1到31,然而年份却与众不同。你可以根据你的目的把程序中要使用的年份限制在一个范围内。这个范围是可以变化的,具体视程序的目的而定。有些程序需要使用的日期可能在遥远的过去,而另一些程序需要使用的日期可能在遥远的将来。然而,如果你的程序只需要使用1975到2020之间的年份,你就能节省一位存储空间。显然,在把日期转换成数字之前,你应该先检查日期的各成员,以确保它们都在正确的范围之内。
注意:一个考古学数据库就是一个需要使用远古日期的很好的例子。
在C语言中,通常从零开始计算(如数组等)。在这种情况下,强制使所有数字的范围都从零开始是有好处的。因此,如果你要存储的最早的年份是1975年,你应该从所有的年份中减去1975,以使年份的序列从零开始。请看改为以这种方式工作的程序:
# 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>ll) || / * 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) ;
}
这个程序并没有考虑到有些月份不到31天的情况,但只需作一点小小的改进就可弥补这一缺陷。注意,当你限制了日期的范围后,在对月值和日值进行移位时,要少移几位。
上述程序所生成的数字仍然不能用来对日期进行排序。为了解决这个问题,你需要注意到这个数字最左边的几位是高于最右边的几位的有效位。因此,你应该把日期中最高的有效部分存入最左边的几位中。为此,上述程序中把二个变量安置到数字中的那部分代码应该改为如下形式:
result = day;
result | = month<<5;
result | = year<<9;
以下是用几个示例日期测试上述修改的结果:
Enter Month, Day, Year :
11 22 1980
The result is : 110771.
Enter Month, Day, Year:
12 23 1980
The result is : 116211.
Enter Month, Day, Year;
8 15 1998
The result is : 74151.
现在,你可以存储记录,而记录的日期可以存成上述格式,并且可以根据转换所得的数字对日期进行排序,而对排序结果的正确性可以充满信心。
最后还需要提到一点是这种存储方式在某种程度上的随机性和抽取日期的问题。这些问题都可以通过使用位域来解决。位域已经在第9章中介绍过了。请看下面这个已经比较完善的程序:
/ * 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) ;
}
由于有些月份不足31天,因此上述程序的某些部分效率仍然不高。此外,每月天数的不同将给日期的增值运算和差值运算带来困难。好在C语言中有些现成的函数能非常出色地完成这些更为复杂的任务,它们可以使程序员少写很多代码。