/* 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[],包含每个月的长度。(“Thirty days has September...”)由于C数组是从0开始的,但是我们总是认为一月是月份号1,为了简单起见,这个数组“丢弃”(浪费)了单元格0,所以monthlengths[1]是31代表一月。 这个函数也是第二个需要考虑闰年的地方。当然,在闰年中,二月有29天。因此,如果这是一个闰年,并且如果我们被要求计算到二月或以后的月份中的天数,我们必须再加上一天。(这就是为什么monthcount函数也需要传入年份号的原因。) 剩下的唯一细节是isleap的一个小函数,monthcount在2月29日决定是否添加时使用该函数。
int isleap(int year)
{
return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
}
使用来自X1 E4 F1 X的X1 E5 F1 X的公式。 ...就是这样。希望现在makejd函数的工作原理已经很清楚了,即使您选择不使用类似的东西。 说到这个选择,我们有必要问:您应该使用哪种方法,方法1还是方法2? 当然,大多数时候,如果可能的话,最好使用预先编写的代码,而不是“自己动手”或重新发明轮子。当涉及到处理日期和时间的代码时,建议(“使用别人预先编写的代码”)是双倍甚至三倍有效的,因为日期和时间是出了名的复杂,而且很容易(几乎可以保证)至少弄错一个模糊的细节,导致细微的bug。 因此,我几乎总是使用方法1。实际上,通常值得花点心思弄清楚如何使用标准日期/时间函数,即使是为了解决一个它们并不立即适合的问题。(例如,请参见下面的答案,在这里我设法使用mktime来回答问题:“给定月份从一周中的哪一天开始?") 但是,有时候,您可能会发现自己处于标准函数不适用的情况下,在这种情况下,知道如何“滚动自己的”可能会非常有用。只要小心,并彻底测试您的代码,在不同的日期和时间!(您可能需要自己滚动的情况是,当您处理time_t类型可能无法处理的遥远过去或遥远未来的日期时,或者在没有完整C库的嵌入式环境中工作时。) 另一种选择方法可能是只看哪种代码更短或更简单。方法1比我想的要麻烦一点,主要是因为一个接一个地填充struct tm的所有字段是一件很麻烦的事情。方法2稍微长一点,尽管实际上并没有长那么多。(这里看起来有点长,但那只是因为我用了太多冗长的解释。) 脚注:我说过“我喜欢这个函数”,我确实喜欢,但我承认它有一个缺陷,违反了DRY原则。公历闰年规则嵌入了两次,一次在行中
// 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。这是因为在极端情况下,日计数非常大(例如31/12/9999)。* 现在,您可以将两个日期转换为天数,并计算它们之间的差值。如果您希望在操作中包括最后一个日期,只需添加一天,如示例所示。 希望这对你有帮助!
4条答案
按热度按时间eaf3rand1#
基本上有两种方法可以做到这一点:
1.使用标准工具,使用标准库函数
mktime
构造每个日期对应的time_t
值,然后相减,并将差值转换为天的单位。1.编写你自己的小代码来构造Julian day number的形式。同样,减法。这至少有点复杂,充满了很难正确处理的繁琐细节,但这是一个很好的、令人满意的练习。(至少,如果你是一个时间迷,这是“令人满意的”。)
方法1如下所示:
像
tm1
和tm2
这样的struct tm
的填写有点棘手,tm_year
字段是从1900开始计数的,所以填写时要减去1900,如图所示;tm_mon
字段是从0开始的,所以要从月份数中减去1;在这里,我随意选择了午夜00:00:00,最后,在tm_isdst
字段中,您必须指定输入的是标准时间还是夏令时(0或1),或者是您不知道或不关心的时间(-1)。然后,函数
mktime
返回一个与您指定的日期和时间对应的time_t
值。自1970年1月1日以来,time_t
通常是Unix count of UTC seconds,尽管严格地说C标准规定它可以具有任何实现定义的编码,所以减去两个time_t
值的最安全方法是使用difftime
函数,它返回一个以秒为单位的差值,正如每个书呆子都知道的,一天有86400秒,所以用秒为单位的差值除以86400,你就得到了天数。这一切看起来都很简单,尽管有一些微妙之处需要注意。如果你仔细观察你得到的
difftime
的差异,你会发现它们通常不是86400的整数倍,如果你指定的两个日期碰巧跨越了夏令时转换。(如果是这样,那么您至少有一天是23或25小时长的。)这就是为什么对除以86400的结果进行四舍五入是至关重要的,如示例代码所示。通常,将tm_hour
填充为12而不是0也是一个非常好的主意。因为使用中午(即,在一天的“中间”,而不是正好在午夜转换时)也可以帮助避免各种异常。这就是方法1,这里是方法2的开始,我们将编写一个函数
makejd
,它计算一个修改后的“Julian day“数,一般来说,儒略日数是一个从一天到另一天递增的数,不考虑月份和日期的界限,例如,这个makejd
函数将计算今天的日期154035,2022年9月25日,9月1日是第154011天,再前一天,8月31日,是第154010天,出于我们将要讨论的原因,这些日期可以追溯到1601年,当时1月1日是第1天。(In在真实的世界中,不同的儒略日编号方案使用不同的基准日。官方的儒略日编号可以追溯到公元前4713年;还有一个官方的“修正儒略历日”(Modified Julian Day,简称MJD),是基于1858年的。)
不管怎样,这里是
makejd
:我喜欢这个函数,因为它解决的问题一开始听起来很复杂,但函数本身看起来有一半的合理性,一旦你理解了它是如何工作的,它就非常简单了。
基本上,为了计算总天数,从遥远过去的基准日期到我们关心的日期,我们有三个部分要担心:
1.我们一整年有365天。
1.然后我们会有一些天数对应于从年初到我们所在月份的完整月份。
1.最后,我们在一个月里有了一些天。
(And当然还有一些闰年的修正,我稍后会解释。)
例如,如果基准年是2020年,而我们担心的是今天的日期(2022年9月25日),我们有两年乘以365 = 730天,加上243天(即一月到八月的长度总和),加上25,总计为998。(假设的基准年是2020年,如前所述,我们实际上将使用1601年作为基准年。)
所以
makejd
函数只执行这三个计算,加上各种闰年校正。实际上就是上面的第一步,计算我们关心的年份和基准年之间的差值乘以365。第二步是
它使用一个单独的函数来计算月份1到N中的总天数,其中N(即
month - 1
)是我们所关心的月份的前一个月。其中
day
是我们关心的日子。接下来是闰年修正。每4年是一个闰年,所以这条线
用我们关心的整年数除以4,我们还需要加上几天(换句话说,我们必须为基准年之后的每四年加上一天)。
但是规则并不是每四年就有一个闰年,我们使用的Gregorian calendar的实际规则是每四年有一个闰年,除了每一百年没有闰年(也就是说,1900年不是闰年),除了每400年有一个闰年,毕竟(也就是说,2000年是闰年)。
注意减去每隔100年的非闰年,并加上每隔400年的闰年。
然而,请注意,这些简单的表达式 * 仅 * 适用于某些精心选择的基准年值,如1601年。如果我们使用其他基准年,就会有一定数量的尴尬的±1个蒙混因素。
现在我们可以回到
monthcount
函数,如果我们关心日期“September 25”,那么monthcount
的工作就是计算从1月到8月的所有天数。(换句话说,X1 M23 N1 X计算“九月0日”的部分儒略日数,设置我们能够添加我们关心的确切日期,如25。)monthcount
简单明了:这个函数使用一个预先初始化的数组
monthlengths[]
,包含每个月的长度。(“Thirty days has September...”)由于C数组是从0开始的,但是我们总是认为一月是月份号1,为了简单起见,这个数组“丢弃”(浪费)了单元格0,所以monthlengths[1]
是31代表一月。这个函数也是第二个需要考虑闰年的地方。当然,在闰年中,二月有29天。因此,如果这是一个闰年,并且如果我们被要求计算到二月或以后的月份中的天数,我们必须再加上一天。(这就是为什么
monthcount
函数也需要传入年份号的原因。)剩下的唯一细节是
isleap
的一个小函数,monthcount
在2月29日决定是否添加时使用该函数。使用来自X1 E4 F1 X的X1 E5 F1 X的公式。
...就是这样。希望现在
makejd
函数的工作原理已经很清楚了,即使您选择不使用类似的东西。说到这个选择,我们有必要问:您应该使用哪种方法,方法1还是方法2?
当然,大多数时候,如果可能的话,最好使用预先编写的代码,而不是“自己动手”或重新发明轮子。当涉及到处理日期和时间的代码时,建议(“使用别人预先编写的代码”)是双倍甚至三倍有效的,因为日期和时间是出了名的复杂,而且很容易(几乎可以保证)至少弄错一个模糊的细节,导致细微的bug。
因此,我几乎总是使用方法1。实际上,通常值得花点心思弄清楚如何使用标准日期/时间函数,即使是为了解决一个它们并不立即适合的问题。(例如,请参见下面的答案,在这里我设法使用
mktime
来回答问题:“给定月份从一周中的哪一天开始?")但是,有时候,您可能会发现自己处于标准函数不适用的情况下,在这种情况下,知道如何“滚动自己的”可能会非常有用。只要小心,并彻底测试您的代码,在不同的日期和时间!(您可能需要自己滚动的情况是,当您处理
time_t
类型可能无法处理的遥远过去或遥远未来的日期时,或者在没有完整C库的嵌入式环境中工作时。)另一种选择方法可能是只看哪种代码更短或更简单。方法1比我想的要麻烦一点,主要是因为一个接一个地填充
struct tm
的所有字段是一件很麻烦的事情。方法2稍微长一点,尽管实际上并没有长那么多。(这里看起来有点长,但那只是因为我用了太多冗长的解释。)脚注:我说过“我喜欢这个函数”,我确实喜欢,但我承认它有一个缺陷,违反了DRY原则。公历闰年规则嵌入了两次,一次在行中
在
makejd
中,然后第二次,完全单独地,在isleap
函数中。如果我们改变日历,某人将不得不记住改变这两个地方的规则。(我不是在开玩笑!“干”是一个极好的原则,我确实喜欢尽可能地遵守它。这肯定是违反了,但探讨在这里应用这一原理的可能性将是另一天的主题,因为我在这里已经写了我打算写的大约3倍。)增编:我曾写道,像
makejd
这样的临时代码“充满了繁琐的小细节,很难正确处理”,为了证明这一点,我可以承认,尽管我认为我知道如何绕过这些细节,但我最初发布的makejd
函数 * 正是 * 这个问题的牺牲品。我做了一个小小的修正,然后说它很好并发布了它。但是我 * 没有 * 遵循我自己的建议“彻底测试代码”!这项任务落在了@chux身上,他做了我应该做的测试,发现了一个数据点(实际上有很多),发布的代码给出的答案偏离了一天。所以,是的,像这样的代码是很难做对的,有很多机会出现讨厌的小错误,如果你要写一些新代码,你必须非常彻底地测试它,如果你不想这样做,你可能宁愿使用预先写好的东西。
pgx2nnw82#
要使用标准函数解决此问题:
<time.h>
提供了struct tm
、mktime()
和difftime()
。azpvetkf3#
我创建了一个函数,它可以将日期转换为从01/01/0001到输入日期所经过的天数:
这样你就可以把两个日期转换成天,然后很容易地比较它们。下面是一个例子:
只需定义一个日期结构体,包括日、月和年,并将其作为参数传递给convertDateToDays()函数。* 注意,该函数返回一个unsigned long int。这是因为在极端情况下,日计数非常大(例如31/12/9999)。*
现在,您可以将两个日期转换为天数,并计算它们之间的差值。如果您希望在操作中包括最后一个日期,只需添加一天,如示例所示。
希望这对你有帮助!
toiithl64#
要将年、月、日Gregorian calendar转换为 * 日数 *,请考虑使用Modified Julian Day作为历元。它是从午夜开始的明确定义的天数计数,不像 Julian date 从中午开始。
MJD 0.0是当地时间1858年11月17日午夜。
您很有可能找到经过良好测试的代码来执行指定的计算。
除非代码已经过测试,否则要警惕它的正确性。时间函数有许多看似好的代码会出错的情况。
有了这些,简单地减去两个日期数字就可以得到日期之间的差值。
以下是几年前的一次尝试。
int2x
math(位数是int
的两倍)处理所有int
的年、月、日值而不溢出。注意int
只能是16位。请注意,公历始于1582年10月,只有在20世纪初的某些时候才被地球上的大多数人使用,对于更早的日期,我们可以假设Proleptic Gregorian calendar。