Chinaunix首页 | 论坛 | 博客
  • 博客访问: 533610
  • 博文数量: 78
  • 博客积分: 1913
  • 博客等级: 上尉
  • 技术积分: 829
  • 用 户 组: 普通用户
  • 注册时间: 2008-03-14 21:29
文章分类

全部博文(78)

文章存档

2011年(27)

2010年(26)

2009年(20)

2008年(5)

我的朋友

分类: C/C++

2008-05-07 12:26:06

  大一的时候,写过一个万年历,那时候用的方法也是计算用户输入的年月相对一个固定年份和月份的偏移量,也就是计算该年该月的1日前面有多少个空格,便于打印。
   昨天突来兴趣,用一种比较好的方法来实现了万年历。 
   先谈谈思路:
   万年历,以2000年1月1日为基准,依用户输入的年(Y)和月(M)计算Y/M/01相对2000/01/01的偏移量。因为我们主要是打印出一张日历表来,所以我们只需要知道Y年M月的第一天是星期几,就OK了。
   这里涉及到的问题不多,第一个是闰年判断,很长简单的一条语句,我把它放在一个函数里(USHORT IsLeapYear()),第二个是打印函数,如果我们计算出了Y年M月1日是星期几,打印自然是很简单的事情(Print()),第三个是偏移量计算函数,这里主要谈谈如果来计算偏移量,以使得计算效率达到最优。
 
  1).Y/01/01相对2000/01/01的偏移量(我们称之为年偏移量)
     2000年是闰年,闰年有366天,2000年1月1日是星期六,那么要计算2001年的1月1日是星期几,只要计算偏移量offset=366%7=2,所以2001年1月1日就是星期一,如果要计算Y年1月1日,先假设Y>2000我们先判断(2000,Y)年这个开区间内有多少个闰年,多少个非闰年,闰年的偏移量是2,非闰年的偏移量365%7=1,所以这样一样就可以计算出Y年1月1日相对2000年1月1日的总的偏移量,闰年的判断要注意的是,我们暂时不考虑首尾(这里是2000和2004这两年),用这条语句"Range_Y/4 - Range_Y/100 + Range_Y/400"来计算2000-Y年之间的闰年个数,Range_Y=Y-2000,首先假定相对2000年每隔4年就有一个闰年,但是我们知道,闰年的定义是:“能被4整除但不能被100整除或者是能被400整除的年份”,所以当Y=2100年时,该年是不是闰年,但是能被4整除,所以我们先除去所有的X00年,认为它们都不是闰年,然后再加上X00年中的确是闰年的年份,比如2400年,这些年必定是每400年一次,所以用了上面那条语句。
   在程序中还用到了一个Flag,其取值为:Flag=(Y==2000)?0:((Y>2000 || IsLeapYear(Y))?2:1);这条语句很关键,而且也很巧,刚刚我们用的是(2000,Y)的一个开区间,如果用户输入的是2000年,这里我们将Flag置0,当然最后得到的年偏移量为0,如果用户输入的Y>2000,例如Y=2008,我们让Flag=2,其实这个2指的是2000年的偏移量,因为2000年是闰年,offset=2,但是2008是闰年,我们要不要再加上2呢?不用,因为我们只是计算到了Y/01/01,如果月份M超过了2月29,我们会在后面进行再加1操作,所以后面会有一个对月份判断的操作。如果用户输入的Y<2000,比如Y=1996年,自然,1996年为闰年,所以Flag也为2,这个2指的就是1996年的偏移量,这里同样要关联到对月份判断的操作,如果用户输入的Y=1995年,那么Flag=1,这个1指的是1995年的偏移量。
   现在得到了年偏移量,如果Y<2000,我们取负的年偏移量,否则就取正的.
   2).Y/01/01到Y/M/01的偏移量(我们也可以称之为月偏移量)
      这里跟Y是否大于小于2000年已经没关系了,月偏移量始终取正值,如果Y是闰年且月份>=3月份,总的偏移量要加1.
 
下面是原代码,在GCC下编译通过,但是在Turbo C下出现一个令我百思不得其解的问题,猜想可能是编译器的原因,详细看代码中的注释.
 
 
年份用的是unsigned long型,所以只要Y不超过0xFFFFFFFF,都能计算出来.
 

/*
 * Based on the year 2000/01/01,when is Saturday to finger out the Y/M/01 's weekday,if we know this,we
 * can print all that month's weekdays.
 */


#include "stdio.h"

#define USHORT unsigned short
#define ULONG unsigned long

USHORT DaysOfEachMonth[12] = {31,28,31,30,31,30,31,31,30,31,30,31};/* the common days of each month,not the leap year */
USHORT Total[12] = {0,31,59,90,120,151,181,212,243,273,304,334};
USHORT M = 0;/* month */
ULONG Y = 0;/* Year */

USHORT IsLeapYear(ULONG year); /* Judge the year is a leep year(return 1) or not(return 0) */
USHORT WeakDayOfYM1();/* Count the weakday of Y/M/1 */
void Print(USHORT space);

unsigned short IsLeapYear(ULONG year)
{
    return((year%4 == 0 && year%100) || year%400 == 0)?1:0;
}

USHORT WeakDayOfYM1()
{
    ULONG Range_Y = 0, Sub_N = 0;
    USHORT Div_Y = 0, Div_M = 0, Space = 0, TotalDays = 0, Flag = 0;
    short Div = 0;/* use unsigned short Div may < 0 */

    Flag=(Y==2000)?0:((Y>2000 || IsLeapYear(Y))?2:1);/* we donot Judge the year Y, if Y=2000,we Flag=0
                                                      * if the year>2000 or the year is a leep year
                                                      * Flag=2,else Flag=1
                                                      * this is the key!! */

    Range_Y = (Y>2000)?(Y-2001):((Y<2000)?(2000-Y-1):0); /* not include the year Y */
    Sub_N = Range_Y/4 - Range_Y/100 + Range_Y/400;/* num of leap year */
    Div_Y = 2*Sub_N%7 + (Range_Y - Sub_N + Flag)%7;/* offset between 2000/01/01 and Y/01/01,
                                                    * eacn leap year has 366 days the offset is 366%7=2,
                                                    * so we use 2*Sub_N, and the other years which are not
                                                    * leap year, the offset is 365%7=1,so we use Range_Y-Sub_N
                                                    * do not use Div_Y = (2*Sub_N + Range_Y - Sub_N + Flag)%7;
                                                    * defend overflow.
                                                    */

    TotalDays=Total[M-1];/* the total days from January to Month M */
    
    if(IsLeapYear(Y) && M>=3)
        TotalDays++; /* Yet we donot Judge the year Y, now we do it,if Y is a leap year and M is big
                               * than 29,Februray, we increase the totaldays,because the default days of Feb is
                               * 28 */

    Div_M=TotalDays%7;/* offset between Y/01/01 and Y/M/01 */
    Div=(Y>2000)?(Div_M+Div_Y)%7:(Div_M-Div_Y)%7; /* offet between 2000/01/01 and Y/01/01
                                                 * there is a puzzle puzzled me so long,when
                                                 * Y=1900,M=1 or 2 or 3 ...,u will see Div_M=0,Div_Y=5,but
                                                 * the result of Div=4!!! this happend in Turbo C 2.0
                                                 * but in gcc and Dev-C++ and VS 2005 that's OK,so surprised thing!!!
                                                 */

    
    Space=(6 + Div)%7;/* the number 6 is the weekday of 2000/01/01-Saturday,also is the init offset */
    return Space;/* return the result offset we do want */
}

void Print(USHORT space)
{
    USHORT days=DaysOfEachMonth[M-1],i=0,j=0;

    printf("\t\t********==== Calendar ====********\n");
    printf("\t\t*************F.U.Moon*************\n");

    printf("\t\t\t %lu/%-2hu \n\n",Y,M);
    printf("\tSun\tMon\tTue\tWen\tThu\tFri\tSat\n");
    for(i=0;i<space;i++)
        printf("\t");
    if(IsLeapYear(Y) && M == 2)
        days=29;    
        for(j=1;j<=days;j++)
        {
            printf("\t%3d",j);
            if((space + j)%7 == 0)
                printf("\n");
        }
}


int main(int argc, char* argv[])
{
    USHORT space = 0;
    printf("Input Year and Month:");
    scanf("%lu%hu",&Y,&M);/* we do not check the input */
    space = WeakDayOfYM1();
    Print(space);
    fflush(stdin);/* flush the stdin,if not do this some compile need two getchar(); */
    getchar();
    return 0;
}

 

文件: calendar.rar
大小: 6KB
下载: 下载

阅读(1855) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~