刷一些算法題時總能遇到計算日期間天數的問題,每每遇到這種情況,不是打開excel就是用系統自帶的計算器。私以為這種問題及其簡單以至於不需要自己動腦子,只要會調用工具就好。直到近些天在寫一個日歷程序的時候遇到了這個問題,不調用別人的API,那就只能自己動手了。
一、概述
天數計算問題的解法大致分為兩類。一類是直接計算日期間的差值,另一類先是分別求得該日期到某一特殊時間點的差值,再兩個差值相減得到兩個日期間的差值。目前網絡上大多都是第一類解法,直接從公元元年開始循環,簡單粗暴。而另一種就較為少見了,得定義結構體等一系列繁瑣的操作,結構較為臃腫。所以,如何在盡可能偷懶的情況下又使得代碼變得優雅呢?在我試驗過了多種不同的結構體后,我還是(無奈地)選擇了<time.h>。
二、約束
此程序假定日期2018-1-1與日期2018-1-3的差值為一天。當然,為兩天也沒問題,在代碼上做少量的改動就行[滑稽]。
三、關於<time.h>
作為C語言標准庫里的時間和日期頭文件,<time.h>也集成了一些簡單、常用的類型及函數。雖然和JAVA等語言還是顯得有些簡陋,不過一個支點都能撬動地球,這點東西也夠進行復雜的操作了。
類型:time_t 表示時間的算術類型(日歷時間)
而time_t的定義:
-
#ifndef _TIME_T_DEFINED
-
#define _TIME_T_DEFINED
-
#ifdef _USE_32BIT_TIME_T
-
typedef __time32_t time_t;
-
#else
-
typedef __time64_t time_t;
-
#endif
-
#endif
__time32_t和__time64_t的定義:
-
#ifndef _TIME32_T_DEFINED
-
#define _TIME32_T_DEFINED
-
typedef long __time32_t;
-
#endif
-
-
#ifndef _TIME64_T_DEFINED
-
#define _TIME64_T_DEFINED
-
__MINGW_EXTENSION typedef __int64 __time64_t;
-
#endif
-
#define __int64 long long
所以繞來繞去,time_t就是一個long類型的變量,記錄的是從1970年1月1日00:00:00(UTC)開始,到目前為止經過的秒數,即日歷時間。
類型:struct tm 用於保存組成日歷時間各個部分的結構類型
-
日歷的各個組成部分被稱為分解時間(broken-down time)。
-
#ifndef _TM_DEFINED
-
#define _TM_DEFINED
-
struct tm {
-
int tm_sec; //分后的秒
-
int tm_min; //小時后的分
-
int tm_hour; //午夜后的小時
-
int tm_mday; //月中的天
-
int tm_mon; //一月后的月數
-
int tm_year; //1900年后的年數
-
int tm_wday; //星期日以后的天數
-
int tm_yday; //一月一日后的天數
-
int tm_isdst; //夏令時標志
-
};
-
#endif
函數:time_t mktime(struct tm *tmptr) 將tmptr指向的分解時間轉換為日歷時間
四、思路
在獲得兩組正確的時間后,分別求該日期所對應的日歷時間,再相減,除以一天的秒數,即得到天數差值。
主要模塊:
-
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 main (void)
-
{
-
char str[2][11];
-
int second = 0;
-
int day = 0;
-
do
-
{
-
for(int i = 0;i<2;i++)
-
{
-
printf("Please input a date,like xxxx xx xx:(%d/2)\n",i+1);
-
gets(str[i]);
-
}
-
}while(review((const char(*)[11])str)==false);
-
-
second = timeparse((const char(*)[11])str);
-
second = second>0?second:-second;
-
day = second/24/3600;
-
printf("day = %d\n",day-1);
-
-
return 0;
-
}
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.頭文件的導入及宏
-
#include <stdio.h>
-
#include <time.h>
-
#include <stdbool.h>
-
#define MIN_YEAR 1900
2.日期的輸入及驗證
用個一do…while()結構接受日期,直到日期正確跳出循環。review函數對輸入的日期進行驗證,年份不得小於1900,天數得在該月所有天數內。對閏月的驗證在天數驗證的模塊內。因為只需要看日期是否合法,所以並不用糾結日期內有幾個閏月。
-
int month_day[13] = {0,31,28,31,30,31,30,31,31,30,31,30,31};
-
char str[2][11];
-
-
do
-
{
-
for(int i = 0;i<2;i++)
-
{
-
printf("Please input a date,like xxxx xx xx:(%d/2)\n",i+1);
-
gets(str[i]);
-
}
-
}while(review((const char(*)[11])str)==false);
-
bool review(const char str[][11])
-
{
-
int arr[2][3];
-
int flag = 0;
-
-
for(int i = 0;i<2;i++)
-
{
-
sscanf(str[i],"%d %d %d",&arr[i][0],&arr[i][1],&arr[i][2]);
-
}
-
-
for(int i = 0;i<2;i++)
-
{
-
if(arr[i][0]<MIN_YEAR)
-
flag += error(i,0);
-
if(arr[i][1]>12 || arr[i][1]<1)
-
flag += error(i,1);
-
if(arr[i][2]>month_day[arr[i][1]])
-
{
-
if((arr[i][0]%4==0 && arr[i][0]%100!=0) || arr[i][0]%400==0)
-
if(arr[i][1]==2 && arr[i][2]==month_day[arr[i][1]]+1)
-
continue;
-
flag += error(i,2);
-
}
-
}
-
-
if(flag==0)
-
return true;
-
else
-
return false;
-
}
-
-
int error(int i,int result)
-
{
-
char arr[3][20] = {
-
"年份錯誤",
-
"月份錯誤",
-
"天數錯誤"
-
};
-
printf("第%d個日期%s\n",i+1,arr[result]);
-
-
return 1;
-
}
3.日期的計算
timeparse()得到兩個日期間相差的秒數,再對秒數做處理(轉為天數)。
-
-
int second = 0;
-
int day = 0;
-
-
second = timeparse((const char(*)[11])str);
-
second = second>0?second:-second;
-
day = second/24/3600;
-
printf("day = %d\n",day-1);
toTime_t()求得一個日期的日歷時間(s)。
-
int timeparse(const char str[][11])
-
{
-
int year,month,day;
-
int s[2];
-
for(int i = 0;i<2;i++)
-
{
-
sscanf(str[i],"%d %d %d",&year,&month,&day);
-
s[i] = (int)toTime_t(year,month,day);
-
}
-
-
return s[0]-s[1];
-
}
-
-
time_t toTime_t(int year,int month,int day)
-
{
-
struct tm ti = {0};
-
ti.tm_year = year - 1900;
-
ti.tm_mon = month -1;
-
ti.tm_mday = day;
-
-
return mktime(&ti);
-
}
六、總結
總共100行的代碼,日期檢驗的子函數就占了40行,還有很大的優化空間。並且受制於time_t的特性,該程序並不能計算1900年前的日期,不具有通用性。另外關於相鄰日期間隔幾天的不同定義,在主函數的輸出里做了處理,如果認為1-1與1-3相隔兩天的話,把-1去掉就行。