[C語言]日期間天數差值的計算


    刷一些算法題時總能遇到計算日期間天數的問題,每每遇到這種情況,不是打開excel就是用系統自帶的計算器。私以為這種問題及其簡單以至於不需要自己動腦子,只要會調用工具就好。直到近些天在寫一個日歷程序的時候遇到了這個問題,不調用別人的API,那就只能自己動手了。

一、概述

    天數計算問題的解法大致分為兩類。一類是直接計算日期間的差值,另一類先是分別求得該日期到某一特殊時間點的差值,再兩個差值相減得到兩個日期間的差值。目前網絡上大多都是第一類解法,直接從公元元年開始循環,簡單粗暴。而另一種就較為少見了,得定義結構體等一系列繁瑣的操作,結構較為臃腫。所以,如何在盡可能偷懶的情況下又使得代碼變得優雅呢?在我試驗過了多種不同的結構體后,我還是(無奈地)選擇了<time.h>。

二、約束

    此程序假定日期2018-1-1與日期2018-1-3的差值為一天。當然,為兩天也沒問題,在代碼上做少量的改動就行[滑稽]。

三、關於<time.h>

    作為C語言標准庫里的時間和日期頭文件,<time.h>也集成了一些簡單、常用的類型及函數。雖然和JAVA等語言還是顯得有些簡陋,不過一個支點都能撬動地球,這點東西也夠進行復雜的操作了。

    類型:time_t    表示時間的算術類型(日歷時間)

    而time_t的定義:

  1.      #ifndef _TIME_T_DEFINED  
  2. #define _TIME_T_DEFINED  
  3. #ifdef _USE_32BIT_TIME_T  
  4.   typedef __time32_t time_t;  
  5. #else  
  6.   typedef __time64_t time_t;  
  7. #endif  
  8. #endif  

__time32_t和__time64_t的定義:

  1. #ifndef _TIME32_T_DEFINED  
  2. #define _TIME32_T_DEFINED  
  3.   typedef long __time32_t;  
  4. #endif  
  5.     
  6. #ifndef _TIME64_T_DEFINED  
  7. #define _TIME64_T_DEFINED  
  8.   __MINGW_EXTENSION typedef __int64 __time64_t;  
  9. #endif  
    1. #define __int64 long long

      所以繞來繞去,time_t就是一個long類型的變量,記錄的是從1970年1月1日00:00:00(UTC)開始,到目前為止經過的秒數,即日歷時間。

      類型:struct tm    用於保存組成日歷時間各個部分的結構類型

    日歷的各個組成部分被稱為分解時間(broken-down time)。

  1. #ifndef _TM_DEFINED  
  2. #define _TM_DEFINED  
  3.   struct tm {  
  4.     int tm_sec;     //分后的秒   
  5.     int tm_min;     //小時后的分   
  6.     int tm_hour;    //午夜后的小時   
  7.     int tm_mday;    //月中的天   
  8.     int tm_mon;     //一月后的月數   
  9.     int tm_year;    //1900年后的年數   
  10.     int tm_wday;    //星期日以后的天數   
  11.     int tm_yday;    //一月一日后的天數   
  12.     int tm_isdst;   //夏令時標志   
  13.   };  
  14. #endif  

    函數:time_t mktime(struct tm *tmptr)    將tmptr指向的分解時間轉換為日歷時間

四、思路

    在獲得兩組正確的時間后,分別求該日期所對應的日歷時間,再相減,除以一天的秒數,即得到天數差值。

主要模塊:

  1. bool review(const char str[][11]);  
  2. int error(int i,int result);  
  3. int timeparse(const char str[][11]);  
  4. time_t toTime_t(int year,int month,int day);  
  5.     
  6. int main (void)  
  7. {  
  8.     char str[2][11];  
  9.     int second = 0;  
  10.     int day = 0;  
  11.     do  
  12.     {  
  13.         for(int i = 0;i<2;i++)  
  14.         {  
  15.             printf("Please input a date,like xxxx xx xx:(%d/2)\n",i+1);  
  16.             gets(str[i]);  
  17.         }  
  18.     }while(review((const char(*)[11])str)==false);  
  19.         
  20.     second = timeparse((const char(*)[11])str);  
  21.     second = second>0?second:-second;  
  22.     day = second/24/3600;  
  23.     printf("day = %d\n",day-1);  
  24.         
  25.     return 0;     
  26. }  
bool review(const char str[][11]);     //對輸入日期進行驗證
int error(int i,int result);            //日期錯誤信息提示
int timeparse(const char str[][11]);    //對輸入日期進行解析,為運算的主要函數
time_t toTime_t(int year,int month,int day);     //被int timeparse()調用,得到具體日歷時間的子函數

五、實現

1.頭文件的導入及宏

  1. #include <stdio.h>  
  2. #include <time.h>   
  3. #include <stdbool.h>  
  4. #define MIN_YEAR 1900  

    2.日期的輸入及驗證

    用個一do…while()結構接受日期,直到日期正確跳出循環。review函數對輸入的日期進行驗證,年份不得小於1900,天數得在該月所有天數內。對閏月的驗證在天數驗證的模塊內。因為只需要看日期是否合法,所以並不用糾結日期內有幾個閏月。

  1. int month_day[13] = {0,31,28,31,30,31,30,31,31,30,31,30,31};  
  2. char str[2][11];  
  3.     
  4. do  
  5. {  
  6.     for(int i = 0;i<2;i++)  
  7.     {  
  8.         printf("Please input a date,like xxxx xx xx:(%d/2)\n",i+1);  
  9.         gets(str[i]);  
  10.     }  
  11. }while(review((const char(*)[11])str)==false);  
    1. bool review(const char str[][11])  
    2. {  
    3.     int arr[2][3];  
    4.     int flag = 0;  
    5.         
    6.     for(int i = 0;i<2;i++)  
    7.     {  
    8.         sscanf(str[i],"%d %d %d",&arr[i][0],&arr[i][1],&arr[i][2]);  
    9.     }  
    10.         
    11.     for(int i = 0;i<2;i++)  
    12.     {  
    13.         if(arr[i][0]<MIN_YEAR)  
    14.             flag += error(i,0);  
    15.         if(arr[i][1]>12 || arr[i][1]<1)  
    16.             flag += error(i,1);  
    17.         if(arr[i][2]>month_day[arr[i][1]])  
    18.         {  
    19.             if((arr[i][0]%4==0 && arr[i][0]%100!=0) || arr[i][0]%400==0)  
    20.                 if(arr[i][1]==2 && arr[i][2]==month_day[arr[i][1]]+1)  
    21.                     continue;  
    22.             flag += error(i,2);  
    23.         }  
    24.     }  
    25.     
    26.     if(flag==0)   
    27.         return true;  
    28.     else  
    29.         return false;  
    30. }  
    31.     
    32. int error(int i,int result)  
    33. {  
    34.     char arr[3][20] = {  
    35.         "年份錯誤",  
    36.         "月份錯誤",  
    37.         "天數錯誤"  
    38.     };  
    39.     printf("%d個日期%s\n",i+1,arr[result]);  
    40.         
    41.     return 1;  
    42. }  

      3.日期的計算

      timeparse()得到兩個日期間相差的秒數,再對秒數做處理(轉為天數)。

  12. int second = 0;  
  13. int day = 0;  
  14.     
  15. second = timeparse((const char(*)[11])str);  
  16. second = second>0?second:-second;  
  17. day = second/24/3600;  
  18. printf("day = %d\n",day-1);  

    toTime_t()求得一個日期的日歷時間(s)。

  19. int timeparse(const char str[][11])  
  20. {  
  21.     int year,month,day;  
  22.     int s[2];  
  23.     for(int i = 0;i<2;i++)  
  24.     {  
  25.         sscanf(str[i],"%d %d %d",&year,&month,&day);  
  26.         s[i] = (int)toTime_t(year,month,day);  
  27.     }  
  28.         
  29.     return s[0]-s[1];  
  30. }  
  31.     
  32. time_t toTime_t(int year,int month,int day)  
  33. {  
  34.     struct tm ti = {0};  
  35.     ti.tm_year = year - 1900;  
  36.     ti.tm_mon = month -1;  
  37.     ti.tm_mday = day;  
  38.         
  39.     return mktime(&ti);  
  40. }  

六、總結

    總共100行的代碼,日期檢驗的子函數就占了40行,還有很大的優化空間。並且受制於time_t的特性,該程序並不能計算1900年前的日期,不具有通用性。另外關於相鄰日期間隔幾天的不同定義,在主函數的輸出里做了處理,如果認為1-1與1-3相隔兩天的話,把-1去掉就行。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM