Home > Software engineering >  Subtract a number of days from given date (from scratch) in C
Subtract a number of days from given date (from scratch) in C

Time:03-16

I have a function below used to add a number of days to a given date (format: yyyy/mm/dd addDays). It does not work for leap years (there was a rushed failed attempt to make leap years work.) How can I edit this so I may subtract days from a given date? I imagine I will need a for loop which also subtracts days from addDays_ but with a decrement and reset. I'd also like to make it calculate leap year dates successfully.

int year;
int month;
int day;
int changeDays;

void DaysToDate::add(int addDays_) {
    //Calculate the date add the changeDays.
    // Do NOT input values greater than 730 days.

    int monthdays[] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
    int daysInYear = 365;
    
    if (isLeapYear(year) || isLeapYear(year 1) == true) {
        monthdays[2] = 29;
        daysInYear = 366;
        
    }

    if (addDays_ >= daysInYear) {
        int addyears = addDays_ / daysInYear;
        year  = addyears;

        if (isLeapYear(year) == true) {
            
            monthdays[2] = 29;
            daysInYear = 366;
        }
    }


    int currentDays = day;
    int i = month;
    if (addDays_ >= monthdays[month]) {
        while (addDays_ >= monthdays[i]) {

            if (i   1 >= 12) {
                month = 1;
                i = 1;
            }
            else 
                month  ;

            addDays_ -= monthdays[i];

            //cout << "monthdays[i] "<< monthdays[i] << "\n";
            if (addDays_   day < monthdays[month]) {
                day  = addDays_;
            }
            i  ;
        }
    }
    if (addDays_   day < monthdays[month]) {
        day  = addDays_;
    }
    if ((day == monthdays[month - 1]) && (month < 12))
    {
        day = 1;
        month  ;
    }
    if ((day == monthdays[month - 1]) && (month == 12))
    {
        day = 1;
        month = 1;
        year  ;
    }
    else {
        
    }

    cout << year << " " << month << " " << day << " " << "\n";

}

bool DaysToDate::isLeapYear(int year_) {
    if (year % 400 == 0) {
        return true;
    }

    if (year % 100 == 0) {
        return false;
    }

    if (year % 4 == 0) {
        return true;
    }

    else {
        return false;
    }
    
}

Input 2009/04/05 7 - output 2009 4 12

CodePudding user response:

For fixing the code to work on leap years:

#include <iostream>

int year = 2009;
int month = 02;
int day = 1;
int changeDays;

enum Months
{
    None,
    January,
    February,
    March,
    April,
    May,
    June,
    July,
    August,
    September,
    October,
    November,
    December
};

bool isLeapYear(int year_)
{
    if (year % 400 == 0)
    {
        return true;
    }

    if (year % 100 == 0)
    {
        return false;
    }

    if (year % 4 == 0)
    {
        return true;
    }

    else
    {
        return false;
    }
}

void add(int addDays_)
{
    // Calculate the date add the changeDays.
    //  Do NOT input values greater than 730 days.

    int monthdays[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
    int daysInYear = 365;

    if (isLeapYear(year))
    {
        monthdays[Months::February] = 29;
        daysInYear = 366;
    }

    int currentDays = day;
    int i = month;

    while (addDays_ >= monthdays[i])
    {
        addDays_ -= monthdays[i];
        
        if (i   1 > Months::December)
        {
            i = Months::January;
            year  ;
            if (isLeapYear(year))
            {
                monthdays[Months::February] = 29;
                daysInYear = 366;
            } else {
                monthdays[Months::February] = 28;
                daysInYear = 365;
            }
        }
        else
        {
            i  ;
        }

    }
    month = i;

    if (addDays_   day <= monthdays[month])
    {
        day  = addDays_;
    }
    else if (month < Months::December) {
        addDays_ -= monthdays[month] - day;
        day = addDays_;
        month  ;
    } else {
        addDays_ -= monthdays[month] - day;
        day = addDays_;
        month = Months::January;
        year   ;
        if (isLeapYear(year))
        {
            monthdays[Months::February] = 29;
            daysInYear = 366;
        } else {
            monthdays[Months::February] = 28;
            daysInYear = 365;
        }
    }

    std::cout << year << " " << month << " " << day << " "
         << "\n";
}


int main(int argc, char const *argv[])
{
    add(7);
    add(365);
    add(365);
    add(365);
    add(365);
    return 0;
}

Surely some improvements can be done to my suggestion but I think it is easy to follow, as it has a very similar structure to yours.

As a general tip, you had a lot of redundant if statements and some of them would cause errors, such as

if (isLeapYear(year) || isLeapYear(year 1) == true) {
    monthdays[2] = 29;
    daysInYear = 366;  
}

checking isLeapYear(year 1) will get you a wrong number of days if you start before march

CodePudding user response:

You could use ranges for this. The operation of adding (or subtracting) a number of days n to a given date ymd may be then thought as:

  • Generate a view of the current date ymd plus the following dates (a maximum of 730).
  • From that view, drop the first n elements.
  • Then take the first element of the remaining.

The ranges::views::generate_n code will contain the logic for adding (or subtracting) a day to a given date:

  • This logic will make use of the days_per_month function.
  • The days_per_month function will make use of the is_leap_year function.

The code below:

  • Defines a struct ymd to hold the values of a given date.
  • Implements the addition and subtraction of days to a given date as non member operators of the struct ymd.
  • Uses Eric Niebler's Range-v3 library for the range operations.

[Demo]

#include <cassert>  // assert
#include <iomanip>  // setw
#include <iostream>  // cout
#include <fmt/core.h>
#include <range/v3/all.hpp>
#include <utility>
#include <vector>

struct ymd {
    int year{};
    int month{};
    int day{};
};

auto is_leap_year(int y) {
    return (y % 4 == 0) and ((y % 100 != 0) or (y % 400 == 0));
}

auto days_per_month(int y, int m) {
    static int dpm[] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
    if (m == 2 and is_leap_year(y)) { return 29; }
    else { return dpm[m]; }
}

auto operator (const ymd& d, int days_to_add) {
    static const int max_days_to_add{730};
    assert(days_to_add <= max_days_to_add);
    auto ret_v =
        ranges::views::generate_n([dd=d]() mutable -> ymd {
            auto tmp{dd};
            dd.day  = 1;
            if (dd.day > days_per_month(dd.year, dd.month)) { dd.day = 1; dd.month  = 1; }
            if (dd.month > 12) { dd.month = 1; dd.year  = 1; }
            return tmp;
        }, max_days_to_add   1)
        | ranges::views::drop(days_to_add)
        | ranges::views::take(1)
        | ranges::to<std::vector>();
    return ret_v[0];
}

auto operator (int days_to_add, const ymd& d) {
    return d   days_to_add;
}

auto operator-(const ymd& d, int days_to_subtract) {
    static const int max_days_to_subtract{730};
    assert(days_to_subtract <= max_days_to_subtract);
    auto ret_v =
        ranges::views::generate_n([dd=d]() mutable -> ymd {
            auto tmp{dd};
            dd.day -= 1;
            if (dd.day < 1) {
                dd.month -= 1;
                if (dd.month < 1) { dd.month = 12; dd.year -= 1; }
                dd.day = days_per_month(dd.year, dd.month);
            }
            return tmp;
        }, max_days_to_subtract   1)
        | ranges::views::drop(days_to_subtract)
        | ranges::views::take(1)
        | ranges::to<std::vector>();
    return ret_v[0];
}

std::ostream& operator<<(std::ostream& os, const ymd& d) {
    return os << fmt::format("{:04d}/{:02d}/{:02d}", d.year, d.month, d.day);
}

int main() {
    std::vector<std::pair<ymd,int>> input{
        {{2009,4,5}, 7},
        {{2009,4,5}, 730},
        {{2020,2,20}, 30}
    };
    for (const auto& [d, number_of_days] : input){
        auto dd{ d   number_of_days };
        std::cout << d << "   " << std::setw(3) << number_of_days << " = " << dd << ",\t";
        std::cout << dd << " - " << std::setw(3) << number_of_days << " = " << dd - number_of_days << "\n";
    }
}

// Outputs:
//   2009/04/05     7 = 2009/04/12, 2009/04/12 -   7 = 2009/04/05
//   2009/04/05   730 = 2011/04/05, 2011/04/05 - 730 = 2009/04/05
//   2020/02/20    30 = 2020/03/21, 2020/03/21 -  30 = 2020/02/20
  • Related