2

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

Alan
  • 2,046
  • 2
  • 20
  • 43
  • 1
    Instead of C times, why not [C++](https://en.cppreference.com/w/cpp/chrono)? – tadman Oct 28 '19 at 17:54
  • Does `chrono` handle pre 1970 dates? Sorry if this is a stupid question, I am new to C++ and the references the 1970 date. – Alan Oct 28 '19 at 18:09
  • 1
    `chrono` is crazy versatile. [What epoch do you want to use?](https://stackoverflow.com/questions/29799293/when-is-stdchrono-epoch) – user4581301 Oct 28 '19 at 18:14
  • @user4581301 testing the code from that link leaves me with the functions `sys_dates`, `cast_time`, and `utc_time` as undefined for some reason. I am using C++17 in MSVS2019. I see the reference here: https://en.cppreference.com/w/cpp/chrono/year_month_day/operator_days but that doesn't seem to be in my library with Microsoft. Not sure if that is unix only, still working it out – Alan Oct 28 '19 at 18:32
  • 1
    My Apologies. I only meant that link as reinforcement that you can go wild with your epochs. The code at the bottom is a sort-of a preemptive strike. It demonstrates how to use a feature in a forthcoming C++ Standard revision. To play with that code now you need to use [Mr. Hinnant's Date library](https://howardhinnant.github.io/date/date.html), a prototype of sorts of the C++2a extensions to Chrono. – user4581301 Oct 28 '19 at 18:38
  • It seems odd that to accomplish what I want I would have to include a C++2a library with thousands of lines of code that will just slow down execution. Isn't there a good way to declare a date of 1700-01-01 and then add days from there without a whole new library? Or a date of 0000-01-01, and add days to get to 1700 and then increment from there? – Alan Oct 29 '19 at 14:50

2 Answers2

1

Here is a list of public domain algorithms that model Unix Time and the proleptic Gregorian calendar for millions of years backward and forward in time. They are extremely efficient (no iteration, minimal branching, minimal cache thrashing).

You can use these algorithms to write your own date library that correctly handles pre-1970 dates. These are also the same algorithms that form the foundation of Howard Hinnant's C++20 preview of the <chrono> library.

Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
0

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();
            }
        }
    }
}
Alan
  • 2,046
  • 2
  • 20
  • 43
  • The first implementation is, essentially, the same as boost's (modulo the string part). If you are strongly interested in performance, you might be interested in [this](https://github.com/cassioneri/calendar) project where I show an implementation that beats boost, libc++, glibc, .Net, OpenJDK and others (again, without the string conversion part). In addition, it also contains the inverse conversion (i.e., from date to integers) and other calendar algorithms: `is_leap_year` and `last_day_of_month`. – Cassio Neri Mar 01 '21 at 23:24