在日常生活中,我們常常遇到要知道某一天是星期幾的問題。有時候,我們還想知道歷史上某一天是星期幾。比如:
“你出生的那一天是星期幾啊?”
“明年五一是不是星期天?我去找你玩?”
通常,解決這個問題的最簡單辦法就是看日歷,但是我們總不會隨時隨身帶着日歷,更不可能隨時隨身帶着幾千年的萬年歷。老師告訴我們,學習C語言,就是為了用它來幫助我們解決實際問題的,那么,既然我們通過《C程序設計伴侶》學了C語言,如何用C語言寫個程序來推算出自己出生的那天是星期幾呢?
答案當然是肯定的(要不然,我也不會在這里啰嗦)。要計算日期所對應的星期,有一個著名的泰勒公式:
w = [ c/4 ] – 2c + y + [y/4] + [13 * (m+1) / 5] + d – 1
其中,c是年份的前兩位,y是年份的后兩位,m是月份,d是日期,這里需要注意的是,如果是1月和2月,c和y需要按照上一年來取值。比如,我們要 計算2013年1月28日,那么,c=20,y=12(因為是1月,按照上一年取值),m=1,d=28。將這些按照日期獲得的數據帶入公式,得到的w除 以7,得到的結果是幾就是星期幾,如果是0則是星期日。另外還需要說明的是,公式中的中括號[…]表示取其中計算結果的整數部分。
按照上面的公式算法,加上我們從《C程序設計伴侶》中學到的關於函數和日期處理的知識,我們可以將這個公式的算法實現為:
// whatday.c 根據泰勒公式推算日期對應的星期 #include <stdio.h> #include <string.h> #include <time.h> // 根據日期推算星期 int whatday(int year,int mon,int day) { int m = mon; int d = day; // 根據月份對年份和月份進行調整 if(m <= 2) { year -= 1; m += 12; } int c = year / 100; // 取得年份前兩位 int y = year % 100; // 取得年份后兩位 // 根據泰勒公式計算星期 int w = (int)(c/4) - 2*c + y + (int)(y/4) + (int)(13*(m+1)/5) + d - 1; return w%7; // 返回星期 } // 將數字轉換成字符串 char* convertday(int w,char* str) { if(w<0 || w>6 || NULL == str) return NULL; char* days[7]; days[0] = "Sunday"; days[1] = "Monday"; days[2] = "Tuesday"; days[3] = "Wednesday"; days[4] = "Thursday"; days[5] = "Friday"; days[6] = "Saturday"; // 轉換 strcpy(str,days[w]); return str; } int main(int argc,char* argv[]) { // 檢查參數 if(argc != 1 && argc != 4) { puts("usage: whatday or whatday 2013 1 28"); return 1; } int year = 0; int mon = 0; int day = 0; // 根據不同參數,獲取日期 if(1 == argc) { // 如果只有一個參數,以當前日期作為查詢日期 time_t t = time(NULL); struct tm* cur = localtime(&t); year = cur->tm_year + 1900; // 年份 mon = cur->tm_mon + 1; // 月份 day = cur->tm_mday; // 日期 } else if(4 == argc) { // 將參數轉換成日期 year = atoi(argv[1]); mon = atoi(argv[2]); day = atoi(argv[3]); } else { puts("usage: whatday.exe 1981 9 22"); return 1; } // 根據日期計算星期 int w = whatday(year,mon,day); // w 有可能是負數,轉換為正 if(w < 0) { w += 7; } char daystr[16] = ""; // 將數字表示的星期轉換為字符串 if(NULL != convertday(w,daystr)) { // 輸出推算結果 printf("%d-%d-%d is %s.",year,mon,day,daystr); } return 0; }
編譯這個程序得到whatday.exe應用程序,使用它,我們就可以方便地推算出自己出生的那一天是星期幾了:
F:\code>gcc -o whatday.exe whatday.c
F:\code>whatday 1981 9 22
1981-9-22 is Tuesday.F:\code>
根據程序的推算結果,我現在終於知道了原來我是周二出生的啊。感謝泰勒老師,感謝他發現的泰勒公式(太偉大了),感謝C語言,感謝《C程序設計伴侶》,還有CCTV,MTV,感謝。。。還有人和我一樣是周二出生的嗎?
最后,雖然這種方法簡單是簡單,但是正如sw老師在平論中提到的那樣,它可能會遇到日期被人為調整的特殊情況,在這些特殊情況下,就可能有問題了。那么,要處理這些特殊情況,又該怎么辦呢?
在C語言中,解決問題的辦法永遠不止一個
我們應當時刻記住這句話。同樣,要推算某個特定日期對應的星期,也肯定不止泰勒公式這唯一的一種方法。正如sw老師在評論中提到的那樣,我們可以利用C標准庫中的時間函數mktime()和localtime()來達到同樣的目的。
首先,我們可以用分解時間(struct tm)表示我們要推測的時間點(比如,1981年9月22日),然后mktime()函數可以把用分解時間表示的這個固定的時間點轉換為日歷時劇(用time_t表示),然后再用localtime()函數將這個日歷時間轉換為分解時間,我們就可以得到這個日期對應的星期數(分解時間的tm_wday成員)。雖然整個過程稍微麻煩了一點,但是其正確性可以得到保證(即使出了問題,也該標准委員會的那些大佬們負責)。你可以按照這個思路自己實現,也可以參考下面的實現。
// whatday.c 使用C標准庫中的mktime()函數推算日期對應的星期 #include <string.h> #include <time.h> #include <stdio.h> // 根據日期的年月日得到星期 int whatday(int year,int mon,int day) { struct tm t; memset(&t,0,sizeof(t)); // 用年月日填充分解時間t t.tm_year = year - 1900; // 減去起始年份
t.tm_mon = mon - 1; // 起始月份 t.tm_mday = day; // 將分解時間t轉換為日歷時間ct time_t ct = mktime(&t); if(-1 == ct) // 日期錯誤 { return -1; } else { // 用localtime()函數獲取日歷時間ct對應的 // 分解時間,其tm_wday成員就是我們需要的星期數 struct tm* bt = localtime(&ct); return bt->tm_wday; } } // 將數字轉換成字符串 char* convertday(int w,char* str) { if(w<0 || w>6 || NULL == str) return NULL; char* days[7]; days[0] = "Sunday"; days[1] = "Monday"; days[2] = "Tuesday"; days[3] = "Wednesday"; days[4] = "Thursday"; days[5] = "Friday"; days[6] = "Saturday"; // 轉換 strcpy(str,days[w]); return str; } int main(int argc,char* argv[]) { // 檢查參數 if(argc != 1 && argc != 4) { puts("usage: whatday or whatday 2013 1 28"); return 1; } int year = 0; int mon = 0; int day = 0; // 根據不同參數,獲取日期的年月日 if(1 == argc) { // 如果只有一個參數,以當前日期作為查詢日期 time_t t = time(NULL); struct tm* today = localtime(&t); year = today->tm_year + 1900; mon = today->tm_mon + 1; day = today->tm_mday; } else { // 將參數轉換成日期 year = atoi(argv[1]) ; mon = atoi(argv[2]); day = atoi(argv[3]); } // 根據年月日查詢星期幾 int w = whatday(year,mon,day); char daystr[16] = ""; // 將數字表示的星期轉換為字符串 convertday(w,daystr); // 輸出推算結果 printf("%d-%d-%d is %s.",year,mon,day,daystr); return 0; }
對比上次的代碼我們會發現,我們只是改變了whatday()函數的實現,其它代碼並沒有發生太大變化。如果你學過《C程序設計伴侶》中提到的字符串處理函數(strcha()和strcpy()以及ctime()函數),還可以將這個程序進一步簡化。到底如何進行,看過書了解這些函數后就知道了,你一定可以的。
當然,利用mktime()和localtime()函數配合使用的應用遠遠不止這些,比如,接下來我們就會用他們來計算每個月的第一個星期一所對應的日期。