核心演算法思路
將兩個日期分別轉換為「從某個基準日(如 1970-01-01 或 0000-01-01)起的總天數」,然後相減取絕對值。關鍵難點在於**閏年判斷**和**每月天數差異**的正確處理。
閏年判斷規則
// 格里高利曆閏年規則
bool is_leap_year(int year) {
return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
}
每月天數表
int days_in_month[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
// 閏年2月為29天
方案一:累加法(直觀易懂,適合教學)
#include <stdio.h>
#include <stdlib.h>
int days_from_epoch(int year, int month, int day) {
int total = 0;
// 1. 完整年份天數
for (int y = 1970; y < year; y++) {
total += is_leap_year(y) ? 366 : 365;
}
// 2. 完整月份天數
for (int m = 1; m < month; m++) {
if (m == 2) {
total += is_leap_year(year) ? 29 : 28;
} else {
total += days_in_month[m-1];
}
}
// 3. 當月天數
total += day - 1; // 1號算第0天
return total;
}
int main() {
int y1, m1, d1, y2, m2, d2;
printf("輸入日期1 (年 月 日): ");
scanf("%d %d %d", &y1, &m1, &d1);
printf("輸入日期2 (年 月 日): ");
scanf("%d %d %d", &y2, &m2, &d2);
int days1 = days_from_epoch(y1, m1, d1);
int days2 = days_from_epoch(y2, m2, d2);
int diff = abs(days1 - days2);
printf("天數差: %d 天\n", diff);
return 0;
}
方案二:儒略日數公式(O(1) 高效,工程首選)
// 將日期轉換為儒略日數 (Julian Day Number)
// 算法來源:Fliegel & Van Flandern (1968) / 《天文算法》
long date_to_jdn(int year, int month, int day) {
int a = (14 - month) / 12;
int y = year + 4800 - a;
int m = month + 12*a - 3;
// JDN = day + (153*m + 2)/5 + 365*y + y/4 - y/100 + y/400 - 32045
return day + (153*m + 2)/5 + 365*y + y/4 - y/100 + y/400 - 32045;
}
int main() {
int y1, m1, d1, y2, m2, d2;
scanf("%d%d%d%d%d%d", &y1,&m1,&d1,&y2,&m2,&d2);
long jdn1 = date_to_jdn(y1, m1, d1);
long jdn2 = date_to_jdn(y2, m2, d2);
printf("天數差: %ld\n", labs(jdn1 - jdn2));
return 0;
}
方案三:使用標準庫 <time.h> (C89 標準,跨平台)
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
time_t date_to_timestamp(int year, int month, int day) {
struct tm t = {0};
t.tm_year = year - 1900; // 年份從1900開始
t.tm_mon = month - 1; // 月份 0-11
t.tm_mday = day;
t.tm_hour = 12; // 設為中午避免夏令時邊界問題
return mktime(&t);
}
int main() {
int y1, m1, d1, y2, m2, d2;
scanf("%d%d%d%d%d%d", &y1,&m1,&d1,&y2,&m2,&d2);
time_t t1 = date_to_timestamp(y1, m1, d1);
time_t t2 = date_to_timestamp(y2, m2, d2);
if (t1 == -1 || t2 == -1) {
printf("日期超出 time_t 範圍 (通常 1970-2038 或 1900-2099)\n");
return 1;
}
double diff_days = difftime(t1, t2) / 86400.0;
printf("天數差: %.0f 天\n", fabs(diff_days));
return 0;
}
三種方案對比
| 方案 | 優點 | 缺點 | 適用場景 |
| 累加法 | 邏輯清晰、易於理解調試、無庫依賴 | O(Y) 時間、年份範圍受限於循環 | 教學、小範圍年份、嵌入式無標準庫 |
| 儒略日數 | O(1) 常數時間、支持極大年份範圍、無運行時依賴 | 公式較難記憶驗證 | 高性能、歷史天文、跨世紀計算 |
| time.h | 標準庫、自動處理閏年/時區、代碼最少 | 受限於 time_t 範圍 (32位系統 1970-2038)、依賴系統時區設置 | 現代應用、業務邏輯、快速開發 |
常見陷阱與測試用例
| 陷阱 | 測試用例 | 預期結果 |
| 閏年邊界 | 2000-02-28 到 2000-03-01 | 2 天 (2000是閏年) |
| 世紀非閏年 | 1900-02-28 到 1900-03-01 | 1 天 (1900非閏年) |
| 跨世紀 | 1999-12-31 到 2000-01-01 | 1 天 |
| 同一天 | 2024-01-01 到 2024-01-01 | 0 天 |
| 日期順序 | 2024-12-31 到 2024-01-01 | 365 天 (取絕對值) |
| 大年份差 | 1900-01-01 到 2100-01-01 | 73049 天 (儒略日數可算) |
輸入驗證建議
bool is_valid_date(int y, int m, int d) {
if (y < 1 || m < 1 || m > 12 || d < 1) return false;
int dim = days_in_month[m-1];
if (m == 2 && is_leap_year(y)) dim = 29;
return d <= dim;
}