最近,我發現自己需要知道我用 C 撰寫的一個小程式將兩個日期分開的天數。
我在網上搜索了一個解決方案,但沒有找到 C.
我怎么能做到?
uj5u.com熱心網友回復:
基本上有兩種方法可以做到這一點:
- 使用標準工具。使用標準庫函式
mktime構造time_t每個日期對應的值。然后減去,并將差值轉換為天數單位。 - 撰寫您自己的小臨時代碼來構造一種Julian day number形式。再次,減去。這至少有點復雜,充滿了難以正確處理的繁瑣小細節,但這是一個很棒且令人滿意的練習。(“令人滿意”,至少,如果你是個時間狂人的話。)
方法 1 如下所示:
#include <stdio.h>
#include <time.h>
#include <math.h>
int main()
{
struct tm tm1 = { 0 };
struct tm tm2 = { 0 };
/* date 1: 2022-09-25 */
tm1.tm_year = 2022 - 1900;
tm1.tm_mon = 9 - 1;
tm1.tm_mday = 25;
tm1.tm_hour = tm1.tm_min = tm1.tm_sec = 0;
tm1.tm_isdst = -1;
/* date 2: 1990-10-02 */
tm2.tm_year = 1990 - 1900;
tm2.tm_mon = 10 - 1;
tm2.tm_mday = 2;
tm2.tm_hour = tm2.tm_min = tm2.tm_sec = 0;
tm2.tm_isdst = -1;
time_t t1 = mktime(&tm1);
time_t t2 = mktime(&tm2);
double dt = difftime(t1, t2);
int days = round(dt / 86400);
printf("difference: %d days\n", days);
}
填寫一個贊struct tm,tm1有點tm2棘手。該tm_year欄位是從 1900 開始計算的,因此填寫時必須減去 1900,如圖所示。該tm_mon欄位從 0 開始,因此您必須從月份數中減去 1。你必須選擇一個時間;在這里,我任意選擇了午夜 00:00:00。最后,在該tm_isdst欄位中,您必須指定您輸入的是標準時間還是日光時間(0 或 1),或者您是否不知道或不關心(-1)。
然后該函式mktime回傳一個time_t與您指定的日期和時間相對應的值。 time_t通常是自 1970 年 1 月 1 日以來的 UTC 秒數的 Unix 計數,盡管嚴格來說 C 標準說它可以具有任何實作定義的編碼,因此減去兩個time_t值的最安全方法是使用該difftime函式,該函式回傳一個保證在幾秒鐘內。每次 nurd 都知道,一天有 86400 秒,所以將秒差除以 86400,就得到了天數。
這一切看起來都很簡單,盡管有一些微妙之處需要提防。如果您仔細查看difftime您得到的差異,您會發現它們通常不是86400 的精確倍數,如果您指定的兩個日期恰好跨越夏令時轉換。(如果是這樣,您至少有一天是 23 或 25 小時。)這就是為什么將除以 86400 的結果四舍五入至關重要的原因,如示例代碼中所示。通常填入 12 而不是 0 也是一個很好的主意tm_hour,因為使用中午(即在中午,而不是在午夜一天轉換)也可以幫助避免各種例外。
這就是方法 1。這是方法 2 的開始。我們將撰寫一個makejd計算修改后的“儒略日”數字的函式。一般來說,儒略日數是一天一天增加的,不考慮月份和日期的界限。例如,此makejd函式將計算今天 2022 年 9 月 25 日的第 154035 天。9 月 1 日是第 154011 天,而前一天 8 月 31 日是第 154010 天。出于我們將要了解的原因,這些天數一直追溯到 1601 年,其中 1 月 1 日是第 1 天。
(在現實世界中,不同的儒略日編號方案使用不同的基日。官方的儒略日編號可以追溯到公元前 4713 年;還有一個基于 1858 年的官方“修正儒略日”或 MJD 編號。)
無論如何,這里是makejd:
#define BASEYEAR 1601 /* *not* arbitrary; see explanation in text */
long int makejd(int year, int month, int day)
{
long int jdnum = 0;
jdnum = (year - BASEYEAR) * 365L;
jdnum = (year - BASEYEAR) / 4;
jdnum -= (year - BASEYEAR) / 100;
jdnum = (year - BASEYEAR) / 400;
jdnum = monthcount(month - 1, year);
jdnum = day;
return jdnum;
}
我喜歡這個函式,因為它解決的問題一開始聽起來很復雜,但函式本身看起來有一半是合理的,一旦你理解了它是如何作業的,它就會變得非常簡單。
基本上,要計算總天數,自從那個基準日期在很久以前,到我們關心的日期為止,我們需要擔心三個方面:
- 我們每一年都有 365 天。
- 然后我們將有一些天數對應于從年初到我們所在月份的整個月份。
- 最后,我們所在的月份還有幾天。
(當然還有一些閏年修正,我會在一分鐘內解釋。)
例如,如果基準年是 2020 年并且我們擔心今天的日期(2022 年 9 月 25 日),我們將有兩年乘以 365 = 730 天,再加上 243(這是 1 月到8 月)加上 25,總共 998。(這是假設的 2020 年基準年。如前所述,我們實際上將使用 1601 作為基準年。)
因此,該makejd函式僅執行這三個計算,以及各種閏??年校正。第一行,
jdnum = (year - BASEYEAR) * 365L;
從字面上看上面的第1步,計算我們關心的年份和基準年之間的差異,乘以365。第2步是線
jdnum = monthcount(month - 1, year);
它使用一個單獨的函式來計算從 1 到 N 月份的總天數,其中 N(即month - 1)是我們關心的月份的前一個月。最后,第3步非常簡單
jdnum = day;
day我們關心的日子在哪里。
然后我們來到閏年修正。每 4 年是閏年,所以這條線
jdnum = (year - BASEYEAR) / 4;
將我們關心的整年數除以 4,這就是我們需要添加的更多天數。(換句話說,我們必須為自基準年以來經過的每四年添加一天。)
但規則并不完全是每四年是閏年。我們使用的公歷的實際規則是每四年有一個閏年,除了每 100 年沒有閏年(也就是說,1900 年不是閏年),除了每 400 年有一個閏年畢竟是年(也就是說,2000 年是閏年)。所以這兩行
jdnum -= (year - BASEYEAR) / 100;
jdnum = (year - BASEYEAR) / 400;
注意減去每 100 年的非閏年,并在每 400 年的閏年中加回。
但是請注意,這些簡單的運算式僅適用于某些精心選擇的基準年值,例如 1601。如果我們使用其他基準年,就會出現一些尷尬的 ±1 虛假因子。
現在我們可以回到monthcount函式。如果我們關心“9 月 25 日”這個日期,那么我們monthcount的作業就是計算 1 月到 8 月整個月份中的所有天數。(換句話說,monthcount計算“9 月 0 日”的部分儒略日數,使我們能夠添加我們關心的確切日期,例如 25。) monthcount簡單明了:
/* Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec */
int monthlengths[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
/* return total days from January up to the end of the given month */
int monthcount(int month, int year)
{
int r = 0;
for(int i = 1; i <= month; i )
r = monthlengths[i];
if(isleap(year) && month >= 2)
r ;
return r;
}
monthlengths[]此函式使用包含每個月長度的預初始化陣列。(“30 天有九月……”)由于 C 陣列是從 0 開始的,但我們總是認為一月是第 1 個月,為了簡單起見,這個陣列“丟棄”(浪費)單元格 0,所以monthlengths[1]就是一月 31 日。
這個函式也是我們不得不擔心閏年的第二個地方。當然,在閏年,二月有 29 天。因此,如果這是閏年,并且如果我們被要求計算到 2 月或以后的月份天數,我們必須再增加一天。(這就是為什么該monthcount函式還需要傳入年份編號。)
唯一剩下的細節是在 2 月 29 日決定是否添加時使用的小isleap功能monthcount。它很簡單:
int isleap(int year)
{
return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
}
,使用C FAQ 串列中問題 20.32的公式。
......就是這樣。希望現在該makejd函式的作業原理很清楚,即使您選擇不使用類似的東西。
而且,說到那個選擇,值得一問:你應該使用哪種方式,方法 1 還是方法 2?
當然,大多數時候,如果可以的話,最好使用預先撰寫的代碼,而不是“自己動手”或重新發明輪子。當涉及到處理日期和時間的代碼時,這個建議(“使用別人的預先撰寫的代碼”)是雙重甚至三重有效的,因為日期和時間是出了名的復雜,而且它非常容易(幾乎可以保證)得到以免其中一個晦澀的細節出錯,導致細微的錯誤。
所以我幾乎總是使用方法 1。事實上,通常值得你去弄清楚如何使用標準的日期/時間函式,甚至解決一個它們不是立即明顯適合的問題。(例如,參見這個答案,我試圖用它mktime來回答這個問題,“給定月份從一周中的哪一天開始?”。)
但是,有時您可能會發現自己處于標準函式不適合的情況,在這種情況下,知道如何“自己動手”可能非常有用。請小心,并針對許多不同的日期和時間徹底測驗您的代碼!(您可能需要自己滾動的情況是,當您使用型別time_t可能無法處理的遙遠過去或遙遠未來的日期時,或者當您在沒有的嵌入式環境中作業時一個完整的 C 庫可用。)
另一種選擇方法可能是只看哪些代碼更短或更簡單。方法 1 比我想的要麻煩一些,主要是因為一個一個地填寫所有欄位是struct tm一件很麻煩的事情。方法 2 有點長,盡管實際上并沒有那么長。(這里看起來有點長,但那只是因為我用了很多冗長的解釋來包圍它。)
腳注:我說過“我喜歡這個功能”,我確實喜歡,但我承認它有一個缺陷,違反了DRY原則。公歷閏年規則被嵌入兩次,一次在行中
jdnum = (year - BASEYEAR) / 4;
jdnum -= (year - BASEYEAR) / 100;
jdnum = (year - BASEYEAR) / 400;
in makejd,然后第二次,完全分開,在isleap函式中。如果我們更改日歷,則必須有人記得更改兩個地方的規則。(我不是在開玩笑!DRY 是一個很棒的原則,我愿意盡可能地遵循它,這絕對是違反了。但在這里探索應用該原則的可能性將不得不成為另一天的話題,因為我已經在這里寫了我打算寫的 3 倍。)
附錄:我寫了類似的臨時代碼makejd“充滿了難以正確處理的繁瑣小細節”,為了證明這一點,我可以承認,盡管我認為我知道如何規避這些細節,但makejd我最初發布的功能這里正是這個問題的犧牲品。我沖出一個快速版本,在一兩個資料點上對其進行了測驗,稍作修正,然后稱它好并發布了它。但是我沒有遵循自己的建議并“徹底測驗代碼”!這項任務落到了@chux 身上,他做了我應該做的測驗,并找到了一個資料點(實際上有很多),發布的代碼給出的答案是一天之內。
所以,是的,這樣的代碼很難正確,有很多機會出現令人討厭的小錯誤,如果你要撰寫一些新代碼,你將不得不非常徹底地測驗它,如果你不想這樣做,你可能寧愿使用預先寫好的東西。
uj5u.com熱心網友回復:
我創建了一個函式,它將日期轉換為自 01/01/0001 到輸入日期以來經過的天數:
// Define a date data type.
struct date {
int day, month, year;
};
/*
* Function's limits (included):
* bottom: 01/01/0001
* top: 31/12/9999
* Input: date data type.
* Output: (int) number of days from 01/01/0001 to that date.
*/
unsigned long int convertDateToDays(struct date date){
unsigned long int totalDays;
int numLeap = 0;
int monthsAddFromYearStart[] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334};
int i;
// First, calculate the number of leap year since year one (not including date's year).
for(i = 1; i < date.year; i )
if((i % 4 == 0 && i % 100 != 0) || (i % 4 == 0 && i % 400 == 0))
numLeap ;
// If it is a leap year, as of March there has been an extra day.
if((date.year % 4 == 0 && date.year % 100 != 0) || (date.year % 4 == 0 && date.year % 400 == 0))
for(i = 2; i < 12; i )
monthsAddFromYearStart[i] ;
// (Year - 1) * 356 a day per leap year days totaling the previous months days of this month
totalDays = (date.year - 1) * 365 numLeap monthsAddFromYearStart[date.month - 1] date.day;
return totalDays;
}
通過這種方式,您可以將兩個日期轉換為天數,然后非常輕松地進行比較。這是一個例子:
struct date startDate = {28, 02, 0465};
struct date endDate = {30, 06, 2020};
unsigned long int dateDifference, dateDifferenceLastDateIncluded;
dateDifference = convertDateToDays(endDate) - convertDateToDays(startDate);
dateDifferenceLastDateIncluded = convertDateToDays(endDate) - convertDateToDays(startDate) 1;
printf("Difference in days: %lu.\n", dateDifference);
printf("Difference in days, last date included: %lu.", dateDifferenceLastDateIncluded);
/*
* Output:
Difference in days: 625053.
Difference in days, last date included: 625054.
*/
只需定義一個包含日、月和年的日期結構,并將其作為引數傳遞給 convertDateToDays() 函式。 請注意,該函式回傳一個 unsigned long int。這是因為極端情況下的天數非常大(即 9999 年 12 月 31 日)。
您現在可以將兩個日期轉換為天數并計算它們之間的差異。如果您想在操作中包含最后日期,只需添加一天,如示例所示。
希望這有幫助!
uj5u.com熱心網友回復:
要使用標準函式解決此問題:
<time.h>提供和。struct tm_mktime()difftime()
#include <limits.h>
#include <math.h>
#include <time.h>
// y1/m1/d1 - y0/m0/d0 in days
// Return LONG_MIN on error
long days_diff(int y1,int m1,int d1,int y0,int m0,int d0) {
// Important: Note other struct tm members are zero filled.
// This includes .tm_isdst to avoid daylight savings time issues.
struct tm date0 = { .tm_year = y0 - 1900, .tm_mon = m0 - 1, .tm_mday = d0 };
struct tm date1 = { .tm_year = y1 - 1900, .tm_mon = m1 - 1, .tm_mday = d1 };
time_t t0 = mktime(&date0);
time_t t1 = mktime(&date1);
if (t0 == -1 || t1 == -1) {
return LONG_MIN;
}
double diff = difftime(t1, t0); // Difference in seconds
const double secs_per_day = 24.0*60*60;
return lround(diff/secs_per_day); // Form the difference in `long` days.
}
uj5u.com熱心網友回復:
要將年、月、日公歷轉換為天數,請考慮使用修改后的儒略日作為紀元。它是從午夜開始的明確定義的天數,與從中午開始的儒略日期不同。
MJD 0.0 是當地時間 1858 年 11 月 17 日午夜。
很有可能您可以找到執行該命名計算的經過良好測驗的代碼。
除非代碼已經過測驗,否則請注意其正確性。時間函式有許多角落情況,這些情況會使看似不錯的代碼出錯。
有了這個,簡單地減去兩天的數字來得到日期之間的差異。
以下是多年前的一次嘗試。
int使用int2xmath處理年、月、日的所有值而不會溢位 - 一種具有int. 注意int可能只有 16 位。一個關鍵特性是簡化,將 3 月 1 日之前的日期轉移到前一年的幾個月,使 3 月成為第 1 個月(10 月8 個月,12 月10 個月,...)。這可以追溯到羅馬人在一年的最后一個月 - 二月添加閏日。
請注意,公歷始于 1582 年 10 月,僅在 1900 年代初期才被地球上的大部分地區使用。對于較早的日期,我們可以假設Proleptic Gregorian calendar。
#include <limits.h>
#include <stddef.h>
#include <stdint.h>
#if LONG_MAX/2/INT_MAX - 2 == INT_MAX
typedef long int2x;
#define PRId2x "ld"
#elif LLONG_MAX/2/INT_MAX - 2 == INT_MAX
typedef long long int2x;
#define PRId2x "lld"
#elif INTMAX_MAX/2/INT_MAX - 2 == INT_MAX
typedef intmax_t int2x;
#define PRId2x "jd"
#else
#error int2x not available
#endif
static const short DaysMarch1ToBeginingOfMonth[12] = { //
0, 31, 61, 92, 122, 153, 184, 214, 245, 275, 306, 337};
#ifndef INT32_C
#define INT32_C(x) ((int_least32_t)1*(x))
#endif
#define DaysPer400Years (INT32_C(365)*400 97)
#define DaysPer100Years (INT32_C(365)*100 24)
#define DaysPer4Years (365*4 1)
#define DaysPer1Year 365
#define MonthsPerYear 12
#define MonthsPer400Years (12*400)
#define MonthMarch 3
#define mjdOffset 0xA5BE1
#define mjd1900Jan1 15020
// November 17, 1858
// Example: 2015 December 31 --> ymd_to_mjd(2015, 12, 31)
int2x ymd_to_mjd(int year, int month, int day) {
int2x year2x = year;
year2x = month / MonthsPerYear;
month %= MonthsPerYear;
// Adjust for month/year to Mar ... Feb
while (month < MonthMarch) {
month = MonthsPerYear;
year2x--;
}
int2x d = (year2x / 400) * DaysPer400Years;
int y400 = (int) (year2x % 400);
d = (y400 / 100) * DaysPer100Years;
int y100 = y400 % 100;
d = (y100 / 4) * DaysPer4Years;
int y4 = y100 % 4;
d = y4 * DaysPer1Year;
d = DaysMarch1ToBeginingOfMonth[month - MonthMarch];
d = day;
// November 17, 1858 == MJD 0
d--;
d -= mjdOffset;
return d;
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/510311.html
標籤:C日期日期转换天
上一篇:顯示日期的日期選擇器格式
