11

I have one struct tm.
And I need to add some fixed interval (given in xx years, xx months, xx days) to the tm struct.
Is there any standard function to do this?

The compiler I use is MSVC 2005 on Windows XP.

Sᴀᴍ Onᴇᴌᴀ
  • 8,218
  • 8
  • 36
  • 58
StNickolay
  • 950
  • 2
  • 9
  • 21
  • Please tell us which compiler and platform you use (and which standards it support if you know). This is to clarify why `mk_time` do not work for you. Even if you are already found a your solution it will help others. – Vovanium Nov 18 '10 at 13:40
  • Windows XP, MSVC 2005, I try copy-pasted code from pmg, It doesn't work with or without mktime – StNickolay Nov 18 '10 at 14:23
  • Paste your full testing code here, please! – Vovanium Nov 18 '10 at 22:15

4 Answers4

12

There are two functions which convert time formats:

  1. mktime() which converts struct tm (representing local time) to time_t.
  2. localtime() which converts time_t to local time in struct tm.

Interesing is the first one, which accepts out-of-range struct member values and as a side-product of conversion set them (and all others) appropriately. This may be used to correct field data values after arithmetic operations. However type of fields is int, so there may be an overflow (on 16 bit system), if e. g. you add number of seconds in the year.

So if you want to have actual dates this code would help (modified copy of answer from @pmg):

struct tm addinterval(struct tm x, int y, int m, int d) {
    x.tm_year += y;
    x.tm_mon += m;
    x.tm_mday += d;
    mktime(&x);
    return x;
}

Also note about tm_isdst member, care about it. Its value may cause time to shift forth and back when you jump over daylight time switch dates.

Vovanium
  • 3,798
  • 17
  • 23
  • I wrote before, that correction did't work. mktime(&x) return -1 – StNickolay Nov 18 '10 at 13:08
  • I think (hope) DST does not change the results because we're dealing with days, not hours. – pmg Nov 18 '10 at 13:21
  • Sorry, i haven't seen you comment when wrote my. But `mktime` works with my GCC. Your code may fail if your compiler does not support c99 standard. – Vovanium Nov 18 '10 at 13:28
  • @pmg: If mk time sees time `13:17 Dec, 7` with `tm_isdst` is set to 1, it will try to set correct this flag, and this will cause change in time (and possibly date). If you set `tm_isdst` to -1 before `mktime()`, change will not occur. _I tested this._ – Vovanium Nov 18 '10 at 13:34
  • `mktime()` has always had the normalising behaviour in standard C, it does not require C99. – caf Nov 18 '10 at 21:31
11

The standard addition operator works.

struct tm x;
/* add 2 years and 3 days to x */
x.tm_year += 2;
x.tm_mday += 3;

Edit: you can easily make a function

struct tm addinterval(struct tm x, int y, int m, int d) {
    x.tm_year += y;
    x.tm_mon += m;
    x.tm_mday += d;
    mktime(&x); /* normalize result */
    return x;
}

EDIT: added mktime to normalize result

pmg
  • 106,608
  • 13
  • 126
  • 198
  • 1
    +1 but beware of overflows, e.g. adding 3 months to 10 months doesn't give 13 months, it gives 1 year and 1 month – SmacL Nov 18 '10 at 11:57
  • 1
    Yep ... care is mandatory. "2012-02-29 + 1year is 2013-03-01" – pmg Nov 18 '10 at 11:59
  • thanks alot , simple addition take care about leap уears and differance in months length? For example, if I will add month with 31 day to month with 28 days, this increase month on 1 and day field on 3? – StNickolay Nov 18 '10 at 12:12
  • Seems this Is not what I need, I need intelligent addition, that automatically take into year and month overflows. – StNickolay Nov 18 '10 at 12:34
  • 6
    @StNickolay: If you call `mktime(&x)` after applying the additions, it will normalise the structure (so eg. 40 October becomes 9 November). – caf Nov 18 '10 at 12:37
  • 1
    I found correct solution http://stackoverflow.com/questions/423961/easy-way-to-add-1-month-to-a-time-t-in-c-c Thanks everybody. Topic is closed. – StNickolay Nov 18 '10 at 12:40
  • @caf: Wrong, mktime(&x) returns -1 – StNickolay Nov 18 '10 at 12:50
  • @StNickolay: No, a conforming `mktime()` only returns `-1` if the requested time is outside the range of `time_t`. – caf Nov 18 '10 at 21:16
0

I suggest to convert the date at hand to a number of days first. Adding an interval is trivial then. Afterwards, convert the number back to a date.

You can find algorithms for convernig a date to a number of days and back at e.g. http://alcor.concordia.ca/~gpkatch/gdate-algorithm.html

Frerich Raabe
  • 90,689
  • 19
  • 115
  • 207
0

The other answers lead to highly unstable results depending on how your system initializes struct tm and whether mid-day time values were initialized properly.

If all you are interested in is changes in the date, while the time remains the same, then set tm_isdst, tm_hour, tm_sec all to 0 before passing to mktime. Better yet, grab their values before and reset them after for consistency (and if they were inconsistent before, they'll consistently remain so). Reusing code from other answers:

tm addinterval(tm t, int y, int m, int d)
{
    auto hour = t.tm_hour;
    auto min = t.tm_min;
    auto sec = t.tm_sec;

    // First we discover the DST Flag. By setting hour to 12
    //   we can assure the mktime does not shift the date
    //   because it will shift the hour!
    t.tm_isdst = 0;
    t.tm_hour = 12;
    t.tm_min = 0;
    t.tm_sec = 0;
    mktime(&t);

    // Now we can add the interval
    t.tm_year += y;
    t.tm_mon += m;
    t.tm_mday += d;
    mktime(&t);

    // Now reset the mid-day time values
    t.tm_hour = hour;
    t.tm_min = min;
    t.tm_sec = sec;

    // Return struct tm while keeping mid-day time the same
    //   while the only values that changed are the date and perhaps isdst.
    return t;
}

I wish it was simpler, but that's just how it has to be.

AOK
  • 493
  • 5
  • 16