Reputation: 93
I need to create a basic date calculator in C that will have the user input a date in YYYY-MM-DD
format. I have the basics of it down. Although I don't need to, I want to go the extra little bit and factor in for leap years. The program runs fine; however, it doesn't calculate leap years correctly. When I enter a date of 2016-02-26
, I should have an expected result of 2016-03-04
but I get the result of 2016-03-03
. I'd reckon that if I used an if else statement to the effect of the following using modulo.
if (month == 2 && year % 4) days = 29;
else days = 28;
Here's my full code...
//Does not require <stdlib.h>
#include <stdio.h>
// Set variables
int newDay, newMonth, newYear, daysInMonth, daysRemain;
// Set structure for day month year
struct date {
int day, month, year;
};
// set structure for date
struct date d1;
int main (void) {
//Intro
printf("Date calculation program by Keith A. Russell");
//Asks for user input
printf("\n\nPlease enter the year in four digit format (YYYY) ");
scanf("%i", &d1.year);
printf("\nEnter the month in two digit format (MM) ");
scanf("%i", &d1.month);
printf("\nEnter the day in two digit format (DD) ");
scanf("%i", &d1.day);
//Runs calculations to increase the date by a week
newDay = d1.day + 7;
newMonth = d1.month;
newYear = d1.year;
daysRemain = 0;
//For if the next week is going to be greater than the next month
if (newDay > 28)
checkMonth(); //Runs checkMonth Function
//Prints the dates
printf("\nThe new date is %i-%i-%i: \n", newYear, newMonth, newDay);
}
checkMonth() {
if (d1.month == 1 || 3 || 5 || 7 || 8 || 10 || 12)
daysInMonth = 31; //For months with 31 days
if (d1.month == 2 && d1.year % 4) //Attempt to calculate leap year
daysInMonth = 29;
else {
daysInMonth = 28; //All other years
}
if (d1.month == 4 || 6 || 9 || 11) //For months with 30 days
daysInMonth = 30;
//Sets up to advance the year if approaching the end of year
if (newDay > daysInMonth) {
daysRemain = newDay - daysInMonth;
newDay = daysRemain;
newMonth++;
checkYear();
}
}
//Runs function to advance to the next year
checkYear() {
if (d1.month == 12)
if (daysRemain > 0) {
newYear++;
newMonth = 1;
}
}
If there are more elegant ways of calculating the leap year and including it in this program, I'd appreciate that greatly. Thank you.
Upvotes: 1
Views: 1073
Reputation: 43
Not satisfied with most dates routines ended up making my own, tested it in a Sharp Basic pocket computer, here is pascal version:
{Dates by isidroco. It's fast and small, nDays offset matches excel
(without it's 1900 bug), so it matches from 61=1900/3/1 onwards.
Algorithm is valid for all Gregorian dates. Last routine wraps up
everything: Validates input and also returns nDays. (although in
pascal it's easy to port }
const
excelOffset = -693900; {will make 1900/3/1=61 as excel}
{aux function}
function yearToNDays( y: real): longInt;
begin { multiply by 0.01 is faster than divide by 100 }
yearToNDays=int(y*365.25)-int(y*0.01)+int(y*0.0025)
end;
{nDays since 1899/12/30=0; excel after: 1901/3/1=61 }
function ymdToNDays( y, m, d: integer): longInt;
var x: integer;
begin
x:=0; if m<3 then x:=1; {x=0 or 1 if ene-feb}
ymdToNDays:= yearToNDays( y-x)+ int(( m+1+ 12*x)* 30.6001)- 122
+ d + excelOffset;
end;
{day of week (0..6), 0 is sunday}
function dow( nDays: longInt): integer;
begin
dow:=(nDays-excelOffset+2) mod 7;
end;
{finds y,m,d date from ndays}
procedure nDaysToYmd(nDays: longInt; var y, m, d: integer);
var
x, z : longInt;
begin
z:=nDays-excelOffset;
y:=int( (z+0.5)/365.2425 );
repeat {loop usually once, rarely twice}
x:=yearToNDays( y );
m=int( (z-x+122)/ 30.6001 )
if m<4 then dec(y);
until m>=4;
d=z-x-int(b*30.6001)+122;
inc(m);
if m>12 then
begin
dec(m,12);
inc(y);
end;
end;
{true if valid date, also calculate nDays}
function validYmd( y, m, d: integer; var nDays: longInt): boolean;
var
y0, m0, d0 : integer;
begin
nDays:=ymdToNDays( y, m, d)
nDaysToYmd( nDays, y0, m0, d0);
validYmd:= (y=y0) and (m=m0) and (d=d0);
end;
Upvotes: 0
Reputation: 144695
There are multiple problems in your code:
The test for leap years is incorrect: (d1.month == 2 && d1.year % 4)
indicates a normal year between 1901 and 2099 which is non-leap. The correct test is this:
if (d1.month == 2 && (d1.year % 4) == 0) //Attempt to calculate leap year
daysInMonth = 29;
Note however that according to the Gregorian reformed calendar, years multiples of 100 that are not also multiples of 400 are not leap years, so the complete test is this:
if (d1.month == 2) {
daysInMonth = (d1.year % 4 || (!(d1.year % 100) && (d1.year % 400)) ? 28 : 29;
}
Your tests for month values are incorrect: instead of if (d1.month == 1 || 3 || 5 || 7 || 8 || 10 || 12)
, you should write:
if (d1.month == 1 || d1.month == 3 || d1.month == 5 || d1.month == 7 ||
d1.month == 8 || d1.month == 10 || d1.month == 12)
To make the code more readable, you could use a switch
statement. If you first check that d1.month
is between 1 and 12, you can condense the above test into a more compact single test:
if ((1 << d1.month) & ((1 << 1) | (1 << 3) | (1 << 5) | (1 << 7) | (1 << 8) | (1 << 10) | (1 << 12))) {
daysInMonth = 31;
}
Upvotes: 1
Reputation: 223699
This doesn't do what you think it does:
if (d1.month == 1 || 3 || 5 || 7 || 8 || 10 || 12)
You can't compare one value to a list of values like this. What you're actually doing is this:
if ((d1.month == 1) || 3 || 5 || 7 || 8 || 10 || 12)
You're comparing d1.month
against the value 1, but then you take that boolean result and do a logical OR with several other numbers. Since all of these numbers is non-zero, this expression will always evaluate to true.
The same goes for this:
if (d1.month == 4 || 6 || 9 || 11)
You need to explicitly compare against each value:
if ((d1.month == 1) || (d1.month == 3) || (d1.month == 5) ...
You could actually do this more cleanly with a switch
with fallthrough cases:
switch (d1.month) {
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12:
daysInMonth = 31;
break;
case 4:
case 6:
case 9:
case 11:
daysInMonth = 30;
break;
case 2:
// years divisible by 100 are not leap years, unless they are also divisible by 400
daysInMonth = (d1.year % 400 == 0) ? 29 :
(d1.year % 100 == 0) ? 28 :
(d1.year % 4 == 0) ? 29 : 28;
break;
}
Upvotes: 2
Reputation: 50110
well for a start this is wrong
if (d1.month == 1 || 3 || 5 || 7 || 8 || 10 || 12)
It will always be true. You need
if (d1.month == 1 || d1.month == 3 || d1.month == 5 ....)
Upvotes: 2