I'm requesting the user to enter two dates (day, month and year).
Using the time.h header, I'm storing it in a struct tm variable like following (let's say date1):
date1.tm_mday = day;
date1.tm_mon = month;
date1.tm_year = year - 1900;
date1.tm_hour = 0;
date1.tm_min = 0;
date1.tm_sec = 0;
date1.tm_isdst = 0;
The second date (date2) is the same above but with "date2" instead of "date1" (two structures).
Next, I did the mktime of date1 and date2 to calculate the seconds of both dates:
date1TimeT = mktime(&date1);
date2TimeT = mktime(&date2);
Following, I checked whether the date1 is bigger than date2, so I placed:
if(date1TimeT > date2TimeT)
finalDateTimeT = date1TimeT - date2TimeT;
else
finalDateTimeT = date2TimeT - date1TimeT;
Now to calculate the days, months and years that have passed through this two dates:
totalDays = (int) finalDateTimeT / 86400;
remainingYears = totalDays / 365;
remainingMonths = (totalDays - remainingYears * 365) / 30;
remainingDays = (totalDays - remainingYears * 365 - remainingMonths * 30);
However, this is not 100% accurate, as this does not take care of the leap years (those who are divisible by 4).
Is there a safer way to calculate this? Keeping in mind the leap years.
How do I calculate the difference in days, months and years between two dates?
There are two totally different ways to do this. The more popular one is the way you've already tried: First convert both dates to some common, monotonic timebase, such as a Unix-style time_t value. Then subtract. This gives you a simple difference in, in this case, seconds. You can divide by 86400 to get days, but it's hard to see how to convert to months or years.
The reason it's hard is that our familiar year/month/day/hour/minute/second system of date/timekeeping is a mixed base scheme, and it's worse than that, in that different months have different numbers of days. There's no single constant days-per-month value you can divide by, the way there is for, say, inches-per-foot (or even hours-per-day).
And then time zones, daylight saving time, leap years, and leap seconds can make things even more complicated.
But in fact, those complications are one reason that the convert-to-monotonic-timebase technique is so popular, because it cuts through most of that complexity rather nicely. Assuming that library functions like mktime() and localtime() have been implemented properly, they take care of all the nasty details of time zones, daylight saving time, and leap years. Just about the only thing left for you to do is to make sure you use them correctly.
Using them correctly involves:
making sure to encode the tm_year and tm_mon fields of struct tm correctly (this is easy to get wrong, and in fact your posted code makes one of the common mistakes, forgetting that tm_mon is 0-based)
making sure to set tm_isdst to -1 if you want mktime to figure out whether DST applied for the date/time in question or not
setting tm_hour to 12 (not 0) if you're only interested in dates, not times (this helps to avoid certain anomalies that can crop up near DST transitions, although this probably wouldn't matter for your problem), and
when converting to days, remembering to round, not truncate, when dividing by 86400, since you may see an effective 23- or 25-hour day length if you're straddling a DST transition.
Leap years are generally not a problem when using this technique, and I'm not sure what you meant when you said that it was "not 100% accurate" because it "does not take care of the leap years". mktime and localtime understand leap years perfectly, and will allow you to compute a proper difference (in seconds) between two dates which straddle the February/March transition, in both leap and non-leap years. It's true that you won't get an accurate number of years if you just divide by the constant 365, but that's no worse than the way you'll often get the wrong number of months if you divide by a constant 30.
So if you've converted to a monotonic number of seconds, the answer to the question of how you convert to years, months, and days is: you don't. It's an impossible, ill-defined problem. You'll have to settle for presenting an answer in terms of just days, or perhaps divide by 7 to get weeks and days. (Or you can present a decidedly approximate answer after dividing by an average number of days per month, like 30.43.)
Now, just about everyone would agree that, informally at least, the difference between, say, February 5 and March 7 is "one month and two days". Notice that this is equally true in leap and non-leap years. If you want to implement this more "informal" or "human-centric" algorithm in a C program, you certainly can, although it's not going to involve mktime or localtime. Instead, it's going to be an implementation of mixed-base arithmetic, with a complication due to the fact that the number of days per month is not constant.
Suppose we want to find the difference between April 5, 2021 and June 10, 2022. That will look like
year month day
2022 6 10
- 2021 4 5
---- -- --
1 2 5
The answer is "One year, two months, and five days". This problem was simple, because we didn't have to borrow.
Here's a harder one: what's the difference between January 25,
2020 and March 5, 2020? That looks like
year month day
2020 3 5
- 2020 1 25
---- -- --
???? ?? ??
What are we supposed to do with this? The first date is clearly greater than the second, but of course 5 is less than 20. Informally, from January 25 to February 25 is one month, and then it's a few more days until March 5, but that "few more days" hits the worst case, because it straddles the end of February, so we have to worry about whether it's a leap year or not.
The way to work the problem is probably like this:
year month day
2020 2 34
- 2020 1 25
---- -- --
0 1 9
Here I have borrowed 1 from the month's column, and added it in to the day's column. Since 2020 was a leap year, there were 29 days in February, so the adjusted days number after the borrow is 5+29=34.
You might have to implement a similar borrow from the year's to the month's column, although that's easier, because the number of months per year is always 12. (That is, I'm pretty sure you will not have to worry about leap years at that point in the algorithm.)
I'm out of time for this too-long answer, so I'm not going to write any C code to do this mixed-base arithmetic, but with this prose outline I think you should be able to implement it straightforwardly enough.
Footnote 1: The algorithm I've presented isn't quite complete. It will not handle, for example, the interval from January 31 to March 1 properly. It's not necessarily obvious how to handle that rather problematic case, but the answer is probably to add the possibility of looping and borrowing twice, as #chux shows in his answer (see comment "Might loop twice").
Footnote 2: Remember that in our Gregorian calendar, the leap-year determination is not a simple test for divisibility by 4.
Footnote 3: For completeness and for anyone who might be curious, I'll say a little more about leap seconds. Unlike leap years, leap seconds are not just automatically taken care of for you by mktime and localtime. In fact, handling leap seconds properly is more or less impossible under the conventional mktime/localtime scheme, for the simple reason that the Unix time_t representation denies the existence of leap seconds altogether. Due to the presence of leap seconds, UTC is not the uniform, continuous, monotonic time scale that time_t assumes. Handling leap seconds properly requires thinking pretty carefully about how you want them to affect your addition and subtraction algorithms, and it requires using some data type other than time_t.
Footnote 4. Best not to worry at all about the anomalies that happened in 1752 (or whenever your country changed over to the Gregorian calendar). :-)
The idea to go through the delta days is a bad one. Once you pass the month boundary you are in trouble. Each month has 28..31 days. In your original post you assume every month to have 30 days.
In order not to loose relationship with the month's lengths we have to work directly with day/month/year. We can combine month+year in one variable as every year has 12 months, like the expression month+year*12. Here my code:
#include <time.h>
#include <stdio.h>
void PrintDelta (int StartYear, int StartMonth, int StartDay, int EndYear, int EndMonth, int EndDay)
{
//Month and Day are 1-based
int Start_MonthYear = StartYear * 12 + StartMonth-1;
int End_MonthYear = EndYear * 12 + EndMonth-1;
int Delta_MonthYear = End_MonthYear-Start_MonthYear;
int Delta_Days = EndDay-StartDay;
if (Delta_Days < 0)
{
//Like Jan 31st --> Feb 1st
//remove one month from Delta_MonthYear and add one month to Delta_Days
if (0)
{ //old buggy code used month before end date
static const int MonthDays[2][12]=
{
//MonthDays[][0] is the number of days in the month BEFORE January!!
//12 01 02 03 04 05 06 07 08 09 10 11 <--- month
{ 31,31,28,31,30,31,30,31,31,30,31,30}, //non-leap year
{ 31,31,29,31,30,31,30,31,31,30,31,30} //leap year
};
Delta_Days += MonthDays[(EndYear & 3) ? 0 : 1][EndMonth-1];
}
else
{ //better code uses month from start date
static const int MonthDays[2][12]=
{
{ 31,28,31,30,31,30,31,31,30,31,30,31}, //non-leap year
{ 31,29,31,30,31,30,31,31,30,31,30,31} //leap year
};
Delta_Days += MonthDays[(StartYear & 3) ? 0 : 1][StartMonth-1];
}
Delta_MonthYear--;
}
printf("Timespan from %d-%02d-%02d to %d-%02d-%02d is %d year(s), %d month(s), %d day(s)\n",
StartYear, StartMonth, StartDay,
EndYear, EndMonth, EndDay,
Delta_MonthYear/12, Delta_MonthYear%12, Delta_Days
);
}
int main(int argc, char **argv)
{
PrintDelta(2000,2,28, 2001,3,1); //1yr 1day, including Feb 29th
PrintDelta(2001,2,28, 2002,3,1); //1yr 1day, no Feb 29th
PrintDelta(2022,1,11, 2022,1,21); //10 days
PrintDelta(2022,1,31, 2022,2,1); //one day
PrintDelta(2022,1,31, 2022,3,1); //1 month 1 day
}
Subtract days, then months, then years. The only challenging issue is how to borrow from months
Some illustrative sample code below -simplifications exist.
#include <stdio.h>
int is_leap_year(int year) {
// Leave this for OP
}
// year: Gregorian calender
// mont: 0 = Dec, 1 = Jan, ... 12 = Dec
static int days_per_month(int year, int month) {
static const char dpm[] = {31, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
if (month == 2 && is_leap_year(year)) {
return 29;
}
return dpm[month];
}
typedef struct {
int year, month, day;
} ymd_type;
// *a - *b
// Assume: a and b elements are already in their preferred ranges and a >= b.
ymd_type date_subtract(const struct tm *a, const struct tm *b) {
ymd_type ymd = {a->tm_year + 1900, a->tm_mon + 1, a->tm_mday};
ymd.day -= b->tm_mday;
while (ymd.day < 0) { // Might loop twice
ymd.month--;
ymd.day += days_per_month(ymd.year, ymd.month);
}
ymd.month -= b->tm_mon + 1;
if (ymd.month < 0) {
ymd.year--;
ymd.month += 12; // 12 months per year
}
ymd.year -= b->tm_year + 1900;
return ymd;
}
int test_ymd(int y1, int m1, int d1, int y0, int m0, int d0) {
const struct tm t1 = {.tm_year = y1 - 1900, .tm_mon = m1 - 1, .tm_mday = d1};
const struct tm t0 = {.tm_year = y0 - 1900, .tm_mon = m0 - 1, .tm_mday = d0};
ymd_type ymd = date_subtract(&t1, &t0);
printf(" %4d/%2d/%2d - %4d/%2d/%2d = %2dy %2dm %2dd\n", y1, m1, d1, y0, m0,
d0, ymd.year, ymd.month, ymd.day);
return 0;
}
Sample
int main() {
test_ymd(2022, 2, 17, 2020, 1, 1);
test_ymd(2022, 3, 1, 2020, 12, 31);
test_ymd(2022, 3, 1, 2022, 1, 31);
test_ymd(2020, 3, 1, 2020, 1, 31);
return 0;
}
Output
2022/ 2/17 - 2020/ 1/ 1 = 2y 1m 16d
2022/ 3/ 1 - 2020/12/31 = 1y 1m 29d
2022/ 3/ 1 - 2022/ 1/31 = 0y 0m 29d
2020/ 3/ 1 - 2020/ 1/31 = 0y 0m 30d
To Do:
Handle cases when dates are in the opposite order.
Handle cases when the date is not in the primary rage.
Handle extreme case like when year is near INT_MAX.
I currently work in SAS and utilise arrays in this way:
Data Test;
input Payment2018-Payment2021;
datalines;
10 10 10 10
20 20 20 20
30 30 30 30
;
run;
In my opinion this automatically assumes a limit, either the start of the year or the end of the year (Correct me if i'm wrong please)
So, if I wanted to say that this is June data and payments are set to increase every 9 months by 50% I'm looking for a way for my code to recognise that my years go from end of June to the next end of june
For example, if I wanted to say
Data Payment_Pct;
set test;
lastpayrise = "31Jul2018";
array payment:
array Pay_Inc(2018:2021) Pay_Inc: ;
Pay_Inc2018 = 0;
Pay_Inc2019 = 2; /*2 because there are two increments in 2019*/
Pay_Inc2020 = 1;
Pay_Inc2021 = 1;
do I = 2018 to 2021;
if i = year(pay_inc) then payrise(i) * 50% * Pay_Inc(i);
end;
run;
It's all well and good for me to manually do this for one entry but for my uni project, I'll need the algorithm to work these out for themselves and I am currently reading into intck but any help would be appreciated!
P.s. It would be great to have an algorithm that creates the following
Pay_Inc2019 Pay_Inc2020 Pay_Inc2021
1 2 1
OR, it would be great to know how the SAS works in setting the array for 2018:2021 , does it assume end of year or can you set it to mid year or?
Regarding input Payment2018-Payment2021; there is no automatic assumption of yearness or calendaring. The numbers 2018 and 2021 are the bounds for a numbered range list
In a numbered range list, you can begin with any number and end with any number as long as you do not violate the rules for user-supplied names and the numbers are consecutive.
The meaning of the numbers 2018 to 2021 is up to the programmer. You state the variables correspond to the June payment in the numbered year.
You would have to iterate a date using 9-month steps and increment a counter based on the year in which the date falls.
Sample code
Dynamically adapts to the variable names that are arrayed.
data _null_;
array payments payment2018-payment2021;
array Pay_Incs pay_inc2018-pay_inc2021; * must be same range numbers as payments;
* obtain variable names of first and last element in the payments array;
lower_varname = vname(payments(1));
upper_varname = vname(payments(dim(payments)));
* determine position of the range name numbers in those variable names;
lower_year_position = prxmatch('/\d+\s*$/', lower_varname);
upper_year_position = prxmatch('/\d+\s*$/', upper_varname);
* extract range name numbers from the variable names;
lower_year = input(substr(lower_varname,lower_year_position),12.);
upper_year = input(substr(upper_varname,upper_year_position),12.);
* prepare iteration of a date over the years that should be the name range numbers;
date = mdy(06,01,lower_year); * june 1 of year corresponding to first variable in array;
format date yymmdd10.;
do _n_ = 1 by 1; * repurpose _n_ for an infinite do loop with interior leave;
* increment by 9-months;
date = intnx('month', date, 9);
year = year(date);
if year > upper_year then leave;
* increment counter for year in which iterating date falls within;
Pay_Incs( year - lower_year + 1 ) + 1;
end;
put Pay_Incs(*)=;
run;
Increment counter notes
There is a lot to unpack in this statement
Pay_Incs( year - lower_year + 1 ) + 1;
+ 1 at the end of the statement increments the addressed array element by 1, and is the syntax for the SUM Statement
variable + expression The sum statement is equivalent to using the SUM function and the RETAIN statement, as shown here:
retain variable 0;
variable=sum(variable,expression);
year - lower_year + 1 computes the array base-1 index, 1..N, that addresses the corresponding variable in the named range list pay_inc<lower_year>-pay_inc<upper_year>
Pay_Incs( <computed index> ) selects the variable of the SUM statement
This is a wonderful use case of the intnx() function. intnx() will be your best friend when it comes to aligning dates.
In the traditional calendar, the year starts on 01JAN. In your calendar, the year starts in 01JUN. The difference between these two dates is exactly 6 months. We want to shift our date so that the year starts on 01JUN. This will allow you to take the year part of the date and determine what year you are on in the new calendar.
data want;
format current_cal_year
current_new_year year4.
;
current_cal_year = intnx('year', '01JUN2018'd, 0, 'B');
current_new_year = intnx('year.6', '01JUN2018'd, 1, 'B');
run;
Note that we shifted current_new_year by one year. To illustrate why, let's see what happens if we don't shift it by one year.
data want;
format current_cal_year
current_new_year year4.
;
current_cal_year = intnx('year', '01JUN2018'd, 0, 'B');
current_new_year = intnx('year.6', '01JUN2018'd, 0, 'B');
run;
current_new_year shows 2018, but we really are in 2019. For 5 months out of the year, this value will be correct. From June-December, the year value will be incorrect. By shifting it one year, we will always have the correct year associated with this date value. Look at it with different months of the year and you will see that the year part remains correct throughout time.
data want;
format cal_month date9.
cal_year
new_year year4.
;
do i = 0 to 24;
cal_month = intnx('month', '01JAN2016'd, i, 'B');
cal_year = intnx('year', cal_month, i, 'B');
new_year = intnx('year.6', cal_month, i+1, 'B');
year_not_same = (year(cal_year) NE year(new_year) );
output;
end;
drop i;
run;
I have to write a program in C which, given the name of a day, returns all of the dates in this month that the day will be on.
For example, if the input is "Sunday" the output should be: 5,12,19,26 (which are the days Sunday will be on this month.)
Does any one have any idea how to do this? I have tried a lot.
You can use the time() function to get the current time.
Then use the localtime() function to get a struct (struct tm) with the information (year, month, day, ...) of the current time.
From the "struct tm" get the tm_mday and the tm_wday. Use these fields to determine the next or previous sunday. e.g. if tm_mday is 12, and tm_wday is 3 (wednesday), then we now that the 9th of this month is a sunday (12-3 = 9). From that number simply add or subtract 7 to get all other sundays.
You need to know it for a given year too? Or is this for only this year? If you need to know it for any given year, you can do a "days per month" enum, having one for the leap years and one for the non-leap years.
You just need to know in which day of week started a year (i.e: "Monday", "Tuesday", etc)
You will, at least, have 5 dates for any given month, so, you can have a fixed length array of ints.
You know that the gregorian calendar repeats itself each 400 years, and that if a year X started with day "Y", then, year X + 1 will start with day ("Y" + 1) % 7 if x is not a leap year, if it is a leap year, it will start with day ("Y" + 2).
that could give you the first date of any year, and knowing how many days have all the months for any given year, you can easily get what date that month starts in ("Monday", etc).
Then, all you have to do, is something like
int offset = 0;
int i;
while (myDate + offset != monthStartingDate) {
offset++;
}
i = offset + monthStartingDate;
(myDate is the number of day of week, and monthStartingDate is the number of day of week for the first day of that month)
when you go out of that loop, you will have the first ocurrence, then, you just add 7 until i is out of month bounds.
you can add each i into the array..
int res[5] = {0,0,0,0,0}
for ( ; i < daysOfMonth(month, year); i += 7) {
int res[i / 7] = i;
}
then you just return res.
Oh, I dind't know that you were able to use date functions :P I think the idea of the excercise was practicing C :P
1) Take a string (weekday name) from the input (use scanf or gets)
2) Convert it to a number (find it's index in a table of weekdays using loop and strcmp), assign 0 to Sunday, 1 for Monday ...
3) Get current time with time function and convert it to tm struct with localtime function
4) From tm struct calculate the first day in current month of a given weekday
first_mday_of_given_wday = (tm_struct.tm_mday + given_wday - tm_struct.tm_wday + 6) % 7 + 1
5) Find out how many days is in current month. To do this:
put 1 into tm_mday and 0 into tm_isdst of your tm struct
duplicate the struct
increase by 1 tm_mon in the duplicate (watch for the last month! in that case increase tm_year by 1 and set tm_mon to 1)
convert booth strutcs to time_t with mktime function and calculate difference (just subtract these time_t values), convert result from seconds to days (divide by 60*60*24)
6) Run a loop though calculated range:
for (i = first_mday_of_given_wday; i <= num_days_in_month; i += 7) printf("%d, ", i)
5th step can be omitted in a certain situations. We know that a month can have from 28 to 31 days. If any of hypothetical days 29,30,31 of current month cannot be a given weekday we can assume that current month has 28 days.
So simply - assume we have 28 days in current month if first_mday_of_given_wday is more then 3, otherwise calculate the number like shown in 5th step.
I have an embedded system that currently keeps track of seconds until an event is supposed to occur using a real-time clock driven by a watch crystal.
Now it needs to keep track of the actual date and time. So, I need to be able to calculate the day, month, year, hour, minute and second from a start date/time and offset in seconds.
Could anyone point me in the right direction for taking into account leap years, daylight savings time (DST) and other complications?
Hardware solutions are not an option as this feature is being added to an existing product. An RTC peripheral is integrated into the MCU chosen for the next generation device.
The C Snippets archive has some date and time functions. Update: unfortunately, the C snippets archive is now defunct. I have updated the link to point to the web archive of the page.
See also "Julian day", Wikipedia, which includes formulas for Julian date calculation.
A "julian date calculation" Google search should uncover more if you want to search further.
Calendar code can be a bit complex - if the C runtime library you're using doesn't have such support built-in (and some way to integrate your clock counter to it) you might consider looking at the code in P.J. Plauger's "The Standard C Library" and adapting it to your needs.
I'm bored, couldn't resist trying a solution. Here's a prototype in ruby - should be clear enough to translate to C.
Given offset and a start date stored as: Baseyear, Baseday, Basesec where day 0 = Jan1,
you can calculate the date as
#initialize outputs
year= Baseyear
day = Baseday
sec = Basesec+offset
#days & seconds remaining in the current year
is_leap = is_leap_year(year)
days_remaining = 365+(is_leap ? 1 : 0) - day
secs_remaining = SEC_PER_DAY*days_remaining
#advance by year
while (sec>=secs_remaining)
sec-=secs_remaining
year+=1
is_leap = is_leap_year(year)
days_remaining = 365+(is_leap ? 1 : 0)
secs_remaining = SEC_PER_DAY*days_remaining
day=0
end
#sec holds seconds into the current year, split into days+seconds
day += sec / SEC_PER_DAY
day = day.to_i #cast to int
sec %= SEC_PER_DAY
#lookup month
for i in (0..11)
dpm = DAYS_PER_MONTH[i] # =[31,28,31,30,...]
if (i==1 && is_leap)
dpm+=1
end
if day < dpm
month = i
break
else
day-=dpm
end
end
day+=1 #1-based
hour = sec/3600
min = (sec%3600)/60
sec = sec%60
puts "%s %d, %d # %02d:%02d:%02d" % [MONTHNAME[month],day,year, hour, min, sec]
It should be easy to add a check that the day is between the begin and end days for DST in the current locale, and adjust the hour accordingly.
The following function determines whether a given year is a leap year:
bool is_leap_year(int year)
{
return ((0 == year % 400) || ((0 == year % 4) && (0 != year % 100)));
}