RLuck
RLuck

Reputation: 93

Creating a date calculator in C

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

Answers (4)

isidroco
isidroco

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

chqrlie
chqrlie

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

dbush
dbush

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

pm100
pm100

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

Related Questions