Recently, I found myself in the need to know the number of days separating two dates for a little program I was writing in C.
I searched online for a solution but found nothing for C.
How could I do it?
CodePudding user response:
There are basically two ways of doing this:
- Use standard tools. Use the standard library function
mktime
to construct atime_t
value corresponding to each date. Then subtract, and convert the difference to units of days. - Write your own little ad-hoc code to construct a form of Julian day number. Again, subtract. This is at least a little bit complicated, full of fussy little details that are difficult to get right, but it's a great and satisfying exercise. ("Satisfying", at least, if you're a time nurd.)
Method number 1 looks like this:
#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);
}
Filling out a struct tm
like tm1
and tm2
is a little tricky. The tm_year
field is counted from 1900, so you have to subtract 1900 when filling it in, as shown. The tm_mon
field is 0-based, so you have to subtract 1 from the month number. You have to pick a time; here I've arbitrarily picked 00:00:00 midnight. Finally, in the tm_isdst
field you have to specify whether you're inputting a standard or daylight time (0 or 1), or if you don't know or care (-1).
Then the function mktime
returns a time_t
value corresponding to the date and time you specified. time_t
is usually a Unix count of UTC seconds since January 1, 1970, although strictly speaking the C Standard says it can have any implementation-defined encoding, so the safest way to subtract two time_t
values is to use the difftime
function, which returns a difference guaranteed to be in seconds. As every time nurd knows, there are 86400 seconds in a day, so divide the difference-in-seconds by 86400, and you've got a number of days.
This all looks pretty straightforward, although there are a couple of subtleties to beware of. If you look carefully at the difftime
differences you get, you will find that they are often not an exact multiple of 86400, if the two dates you specified happen to span a daylight saving time changeover. (If so, you had at least one day that was either 23 or 25 hours long.) That's why it's vital to round the result of dividing by 86400, as shown in the example code. Usually it's also a very good idea to fill in tm_hour
as 12, not 0, since using noon (i.e in the middle of day, not right at the midnight day changeover) can also help to avoid various anomalies.
So that's method 1. Here's the beginning of method 2. We're going to write a function makejd
that computes a modified "Julian day" number. In general, a Julian day number is one that increases from day to day, without regard to month and day boundaries. For example, this makejd
function is going to compute day number 154400 for today, September 25, 2022. September 1 was day number 154376, and the day before that, August 31, was day number 154375. For reasons we'll get to, these day numbers go all the way back to the year 1600, where January 1 was day number 1.
(In the real world, different Julian day numbering schemes use different base days. The official Julian Day numbers go back to 4713 BC; there's also an official "Modified Julian Day" or MJD number that's based on 1858.)
Anyway, here's makejd
. [Disclaimer: As @chux has discovered and mentioned in a comment below, this code unfortunately has some significant off-by-1 errors. So please don't use it for anything serious until I track the problem down.]
#define BASEYEAR 1600 /* must be a multiple of 400 */
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;
}
I love this function, because the problem that it solves sounds very complicated at first, but the function itself looks halfway reasonable, and once you understand how it works it's downright simple.
Basically, to count up the total number of days, since that base date in the far past, up to the date we care about, we have three pieces to worry about:
- We'll have 365 days for every full year.
- Then we'll have some number of days corresponding to full months that have gone by from the start of the year, to the month we're in.
- Finally, we have some number of days into the month we're in.
(And then of course there are some leap year corrections, which I'll explain in a minute.)
For example, if the base year was 2020 and we were worried about today's date (September 25, 2022), we'd have two years times 365 = 730 days, plus 243 (which is the sum of the lengths of the months January through August), plus 25, for a total of 998. (That'd be for a hypothetical base year of 2020. As mentioned, we're actually going to use 1600 as our base year.)
So the makejd
function simply performs those three calculations, plus the various leap year corrections. The first line,
jdnum = (year - BASEYEAR) * 365L;
literally does step 1 above, computing the difference between the year we care about, and the base year, times 365. Step 2 is the line
jdnum = monthcount(month - 1, year);
which uses a separate function to compute the total number of days in the months 1 through N, where N (that is, month - 1
) is the month before the one we care about. Finally, step 3 is the very simple
jdnum = day;
where day
is the day we care about.
And then we come to the leap year corrections. Every 4 years is a leap year, so the line
jdnum = (year - BASEYEAR) / 4;
takes the number of full years we care about, divided by 4, and that's a few more days we need to add in. (In other words, we have to add in one day for every four years that elapsed since our base year.)
But the rule is not quite that every fourth year is a leap year. The actual rule for the Gregorian calendar we use is that every four years there's a leap year, except that every 100 years there's not a leap year (that is, 1900 was not a leap year), except that every 400 years there is a leap year, after all (that is, 2000 was a leap year). So those two lines
jdnum -= (year - BASEYEAR) / 100;
jdnum = (year - BASEYEAR) / 400;
take care of subtracting out the every-100-years non leap years, and adding back in the every-400-years leap years.
Note that those simple expressions work if the base year is an exact multiple of 400. If we'd used some other base year (like 1850 or 1900 or 1970), there would have been a certain number of awkward ±1 fudge factors.
Now we can go back to the monthcount
function. If we cared about the date "September 25", it's monthcount
's job to count up all the days in the full months January through August. (Stated another way, monthcount
computes the partial Julian day number of "September 0", setting us up to be able to add in the exact day we care about, like 25.) monthcount
is simple and straightforward:
/* 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;
}
This function uses a pre-initialized array monthlengths[]
containing the lengths of each month. ("Thirty days hath September...") Since C arrays are 0-based, but we always think of January as being month number 1, to keep things simple this array "throws away" (wastes) cell 0, so that monthlengths[1]
is 31 for January.
This function is also a second place we have to worry about leap years. In leap years, of course, February has 29 days. So if this is a leap year, and if we're being asked to compute the count of days in months through February or beyond, we have to add in one more day. (That's why the monthcount
function needs to have the year number passed in also.)
The only remaining detail is that little isleap
function that monthcount
uses as part of its decision of whether to add in February 29. It's simply:
int isleap(int year)
{
return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
}
, using the formulation from question 20.32 of the C FAQ list.
...And that's it. Hopefully now the workings of the makejd
function are clear, even if you choose not to use anything like it.
And, speaking of that choice, it's worth asking: which way should you use, method 1 or method 2?
Most of the time, of course, it's better to use prewritten code if you can, rather than "rolling your own" or reinventing the wheel. And when it comes to code for dealing with dates and times, that advice ("use somebody else's prewritten code") is doubly or even triply valid, because dates and times are notoriously complex, and it's ridiculously easy (almost guaranteed) to get at lest one of the obscure details wrong, resulting in subtle bugs.
So I will almost always use method 1. In fact, it's usually worth going out of your way to figure out how to use the standard date/time functions, even to solve a problem that they're not an immediately obvious fit for. (See, for example, this answer, where I contrive to use mktime
to answer the question, "On what day of the week does a given month begin?".)
Once in a while, though, you may find yourself in a situation where the standard functions aren't suitable, and in that case, knowing how to "roll your own" can be extremely useful. Just be careful, and test your code thoroughly, for lots of different dates and times! (Situations where you might need to roll your own are when you're working with far-past or far-future dates that type time_t
might not be able to handle, or when you're working in an embedded environment where you don't have a full C library available.)
The other way to choose might be to just look at which code is shorter or simpler. Method 1 is a bit more cumbersome than I'd like, mostly because filling in all the fields of a struct tm
one by one is a plain nuisance. Method 2 is a bit longer, although not actually all that much longer. (It looks quite a bit longer here, but that's just because I've surrounded it with so much longwinded explanation.)
Footnote: I said "I love this function", and I do, but I confess it's got one blemish, a violation of the DRY principle. The Gregorian leap year rules are embedded twice, once in the lines
jdnum = (year - BASEYEAR) / 4;
jdnum -= (year - BASEYEAR) / 100;
jdnum = (year - BASEYEAR) / 400;
in makejd
, and then a second time, completely separately, in the isleap
function. If we ever change calendars, someone will have to remember to change the rules in both places. (I'm not joking! DRY is a splendid principle, and I do like to follow it when I can, and this is definitely a violation. But exploring the possibility of applying the principle here will have to be a topic for another day, as I've already written about 3× as much here as I intended to, and it's time to go get the furnace ready for the winter.)
CodePudding user response:
I have created a function which converts a date to the number of days that have passed since 01/01/0001 until the input date:
// 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;
}
This way you can convert two dates to days and then compare them very easily. Here is an example:
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.
*/
Just define a date struct including day, month and year, and pass it to the convertDateToDays() function as a parameter. Notice that the function returns an unsigned long int. That's because the day count in extreme cases is huge (i.e. 31/12/9999).
You can convert now two dates to days and calculate the difference between them. If you want to include the last date in the operation just add one day, as the example shows.
Hope this was helpful!
CodePudding user response:
To solve this using standard functions:
<time.h>
offers struct tm
, mktime()
and 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.
}
CodePudding user response:
To convert year, month, day Gregorian calendar to a day number, consider using the Modified Julian Day as the epoch. It is a well defined count of days from midnight, unlike the Julian date which starts at noon.
MJD 0.0 is November 17, 1858 midnight - local time.
Chances are good you can find well tested code that performs that named calculation.
Unless code has been tested, be wary of its correctness. Time functions have many corners cases that trip up seemingly good code.
Armed with that, simple subtract two day numbers to get the difference between dates.
Below is from an attempt from years ago.
Handles all
int
values of year, month, day without overflow by usingint2x
math - a type with twice the bits ofint
. Noteint
may only be 16-bit.A key features is simplification that shifts dates before March 1, to months of the prior year, making March the 1st month (October the 8th month, December the 10th month, ...). This harkens back to the Romans adding a leap day in the last month of the year - February.
Note that the Gregorian calendar started in October 1582 and was only used by the majority of the planet sometimes in the early 1900s. For earlier dates, we could assume the 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;
}