Search code examples
c++dategregorian-calendar

C++ Int to Any Date Without External Library


I need to convert some integers to date. First off I do know of the Boost::Gregorian library, but I can't use it because it won't compile with Clang, which is where my application gets it best performance.

I am parsing the raw database files and so performance will be important since the conversions will happen hundreds of thousands of times to represent birthdates, timestamps, appointment times and so on.

I have a few different origin dates depending on which database I am parsing. The origin dates I am using are:

System 1: 1706-02-24
System 2: 1840-01-01

I tried it this way, but I get an error that timeinfo2 is null when I try printing it out:

time_t rawtime;
struct tm* timeinfo;
time(&rawtime);
timeinfo = localtime(&rawtime);
timeinfo->tm_year = 1706 - 1900;
timeinfo->tm_mon = 2 - 1;
timeinfo->tm_mday = 24;
timeinfo->tm_mday += 98040; // days since origin

time_t newtime;

struct tm* timeinfo2;
newtime = mktime(timeinfo);
timeinfo2 = localtime(&newtime);

Result should be: 1968-08-12


Solution

  • I have a lot of respect for what Howard has put together, but I needed something that would execute as fast as possible and worried about including the entire date library when all I needed was an int to date.

    Here is what I came up with:

    string GetDateFromDaysSincePointInTime(int days)
    {
        int a, b, c, d, e, m, dd, mm, yyyy;
        a = days + 2374475;
    
        b = (4 * a + 3) / 146097;
        c = -b * 146097 / 4 + a;
        d = (4 * c + 3) / 1461;
        e = -1461 * d / 4 + c;
        m = (5 * e + 2) / 153;
        dd = -(153 * m + 2) / 5 + e + 1;
        mm = -m / 10 * 12 + m + 3;
        yyyy = b * 100 + d - 4800 + m / 10;
        return  to_string(yyyy) + "-" + to_string(mm) + '-' + to_string(dd);
    }
    

    To use this, simply call it with an int GetDateFromDaysSincePointInTime(113908). That will give you a date. Assuming my starting point is different from your starting point, go to the site https://www.timeanddate.com/date/dateadd.html and add/subtract your desired date from the date that was output. Then alter the int value on variable a by that amount and run again to get your corrected date.

    From there, it should be able to be easily altered to have leading zeros if desired:

    std::ostringstream month;
    month << std::setw(2) << std::setfill('0') << mm;
    std::ostringstream day;
    day << std::setw(2) << std::setfill('0') << dd;
    return  to_string(yyyy) + "-" + month.str() + '-' + day.str()
    

    Alternate way

    Here is another way that is more readable, and there doesn't seem to be any real performance hit on 30,000 records:

     string GetDateFromInt(int days)
     {
        int startYear = 1600;
        int year = days / 365.2421875;
        float peryear = 365.2421875;
        int remainder = fmod(days, peryear); // here you could add what day of the year to get a date in the middle of the year
        bool leapyear= ((year & 3) == 0  && (year % 100 != 0));
        int leapYearIndex = leapyear ? 1 : 0;
        int daysInYear = leapYearIndex ? 366 : 365;
        const unsigned short int __mon_yday[2][13] =
        {
            /* Normal years.  */
            { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 },
            /* Leap years.  */
            { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 }
        };
        int dayOfYear = remainder;
    
        if (dayOfYear >= 1 && dayOfYear <= daysInYear) {
            for (int mon = 0; mon < 12; mon++) {
                if (dayOfYear <= __mon_yday[leapYearIndex][mon + 1]) {
                    int month = mon + 1;
                    int dayOfMonth = dayOfYear - __mon_yday[leapYearIndex][mon];
                    std::ostringstream months;
                    months << std::setw(2) << std::setfill('0') << month;
                    std::ostringstream day;
                    day << std::setw(2) << std::setfill('0') << dayOfMonth;
    
                    return  to_string(startYear + year) + "-" + months.str() + '-' + day.str();
                }
            }
        }
    }