7

Well I think the title sums it up. Suppose I have an object of type double which I obtained from running std::difftime on two time_t objects, and now I want to add the resulting number of seconds back to a time_t object. I don't mind losing fractions of seconds.

Note that the number of seconds might be large (ie. larger than the 60 seconds allowed in struct tm, but always lower than any integer primitive used to represent seconds on the respective machine / implementation, and never larger than the order of 1 year, although preferably I would not like this to be a restriction).

How would I go about doing this portably (ie. as per C standard)?

I am hoping not to have to divide into months, days, hours, minutes etc. and add them manually to struct tm object. Surely there's a better way!?

quant
  • 21,507
  • 32
  • 115
  • 211
  • epoch is your friends when it comes to portability. – ddoor Sep 21 '13 at 12:54
  • `time_t` is an arithmetic type (integer or floating point), So adding a `double` should be fine. – Mats Petersson Sep 21 '13 at 12:57
  • Can you use C++11's ``? – millimoose Sep 21 '13 at 12:58
  • @MatsPetersson That is relying on implementation details though, thus not at all portable. `time_t` is **not** guaranteed to be seconds since epoch, or even seconds in the first place, while the return value of `difftime` is. – millimoose Sep 21 '13 at 12:59
  • 1
    @MatsPetersson - but `time_t` is not required to represent seconds; it's just "capable of representing times". – Pete Becker Sep 21 '13 at 12:59
  • Ok, so the standard doesn't require that `time_t` is seconds, yet, I know of no implementation of `time` that doesn't return seconds in some form or another (in fact, I'm not aware of one that doesn't also have 1 Jan 1970 as the start date). I'm sure there ARE systems that deosn't do this. So then the question becomes: Is it really necessary to support these systems in your software? – Mats Petersson Sep 21 '13 at 13:05
  • @MatsPetersson The question can easily become: why would you intentionally write bug-prone unportable code? – millimoose Sep 21 '13 at 13:07
  • How about `ìf (difftime(my_time + LARGE_INT, my_time) == LARGE_INT) ...`; if it's not, then apparently my_time uses internally some _scaling factor_, which is simply the ratio of the return value and the delta. – Aki Suihkonen Sep 21 '13 at 13:13
  • 2
    @millimoose: Or why should we write code that is intentionally made utterly complicated, just so that it is able to support architectures that nobody actually uses. – Mats Petersson Sep 21 '13 at 13:13
  • Aaanyway. It seems that standard C's time calculation facilities are pretty anemic. I'd consider either ``, or the datetime manipulation functions of a library like [GLib](https://developer.gnome.org/glib/stable/glib-Date-and-Time-Functions.html) or [APR](https://apr.apache.org/docs/apr/1.4/group__apr__time.html) or a standalone time manipulation library. – millimoose Sep 21 '13 at 13:17
  • Well, depending on how large. If the integral part of the double will fit into an `int`, I believe that just adding it to `struct_tm.tm_sec` and roundtripping that through `mktime()` and `localtime()` should work - although it's still relying on `mktime()` being able to reliably normalise arbitrarily large `struct_tm` field values. – millimoose Sep 21 '13 at 13:29
  • So, just to make things amusing - even epoch time is not, in fact, the number of seconds since midnight Jan 1st 1970. Because of leap seconds, it should be 25 seconds **behind** that number. – millimoose Sep 21 '13 at 13:47
  • @millimoose yes, I can use . Post has been adjusted to include c++11 tag for clarity. – quant Sep 21 '13 at 23:39

3 Answers3

3

Use localtime to convert your time_t value into a struct tm, which holds the time broken down into hours, minutes, seconds, etc. Adjust the number of seconds appropriately, then use mktime to convert the values in the struct into a time_t.

Pete Becker
  • 74,985
  • 8
  • 76
  • 165
  • Is `mktime` guaranteed to support `tm_sec += 12131898;` - it does mention that values may be out of range, but "how much" is not not clear from what I can see. I suppose one could divide by 86400 and add that to `tm_mday`, and then divide the rest up into `hours`, `minutes` and days, perhaps. – Mats Petersson Sep 21 '13 at 13:11
  • @MatsPetersson Doing that manually is what the OP is trying to avoid. – millimoose Sep 21 '13 at 13:14
  • @MatsPetersson Also not every day has 86400 seconds - i.e. the time elapsed between the same wall clock times on two consecutive days is not the same, because of DST and leap seconds and who knows what else. (Correct time calculations are **not** easy. See http://infiniteundo.com/post/25326999628/falsehoods-programmers-believe-about-time and http://infiniteundo.com/post/25509354022/more-falsehoods-programmers-believe-about-time-wisdom) – millimoose Sep 21 '13 at 13:20
  • @millimoose: Now I'm not following at all. Are you suggesting that the above suggestion works, or doesn't work? And if so, is it OK to just add an arbitrary (and large) number of seconds to `tm_sec` that comes out of `localtime`, and pass it back to `mktime`? [Obviously, if the difference is more than 68 years, this method will not work, since `tm_sec` will overflow, but I expect OP isn't interested in 60+ years in the future, so let's ignore that for now]. – Mats Petersson Sep 21 '13 at 13:29
  • @MatsPetersson It *seems to* work **only** if the OP's `double` value (+60) will not overflow `int`. Whether it will work reliably is not stated in the docs. Whether it works better or not than adding it to `time_t` I'm not sure. Your suggestion is prone to the issue that the value of `time_t` decreases during a leap second for instance - so adding three seconds to it might not give you a time that's actually three seconds later than the original. I guess my greater point here is: when doing time maths, *do not trust your intuition on anything*, it will probably hit an edge case somewhere. – millimoose Sep 21 '13 at 13:35
  • @MatsPetersson To illustrate the issue with messing with `time_t` - on a leap second day, the times `23:59:60` and `00:00:00` on the following day will have the same `time_t` value. So if you start with `time_t` in the second before the first instant, and add `1`, you will end up with the timestamp of the instant **two** seconds later. (CBA to test whether roundtripping through `struct tm` is correct right now.) – millimoose Sep 21 '13 at 13:43
  • @millimoose: _seems_ to work is rather weak. C11 draft N1570 for `mktime` doesn't seem to say much more than "values will be adjusted to within the valid ranges". – Mats Petersson Sep 21 '13 at 13:55
  • @MatsPetersson Yes. Which is why I suggested using a robust date-time handling library if you actually care about correctness. – millimoose Sep 21 '13 at 14:03
  • I'm inclined to just use this; I've adjusted my question to clarify that the number of seconds will not cause the tm.tm_sec type to overrun, nor do I expect the struct tm object to hold a number of years exceeding its capacity on whatever implementation I am using it on. With that in mind, am I correct in understanding that adding a larger than 59 number of seconds to tm_sec and then passing the struct tm through mktime will result in a coherent result as per the C standard, all peculiarities with respect to leap seconds, etc, aside? – quant Sep 21 '13 at 23:01
3

You are allowed to perform arithmetic with time_t values.

You can set up two struct tm objects one second apart (choose two times in the middle of a minute somewhere, where leap seconds are not allowed to occur, and at a local time where DST does not change), and call mktime() on them both to get two time_t values. Subtract the earlier of of those time_t values from the later, and this will give you a second in the same number of units in which time_t is measured on your system. Multiply this by the number of seconds you want to add, and add the result to your original time_t value. You can check the result by again calling difftime() on the original and new time_tvalues to see if the seconds difference was what you were expecting.

Note that even this isn't guaranteed to be portable, since technically, time_t isn't required to even have a resolution capable of distinguishing seconds, but it would be a pretty odd implementation that did not. Also, if time_t is a floating point value (which is unusual) and you wanted to add a really, really large number of seconds, you might run into floating point precision issues. In this case you could try adding hours for instance, instead of seconds, and set up your two struct tm structs accordingly. You might conceivably run into overflow issues with a large enough number of seconds, which you just can't do much about.

Crowman
  • 25,242
  • 5
  • 48
  • 56
  • With the caveat that you must not care about imprecision caused by leap seconds or other oddities that cause `time_t` to not increase monotonically. (Since `time_t` isn't the same as the underlying system clock.) – millimoose Sep 21 '13 at 14:02
  • @millimoose: Yes, you obviously have to be satisfied with the time implementation on your system. If your implementation supports leap seconds, then incrementing `time_t` by a fixed number of seconds will give you the result you want. If it doesn't, then obviously you just can't deal with leap seconds correctly, without doing a lot of manual intervention. – Crowman Sep 21 '13 at 14:03
  • @millimoose: i.e. if you set two `struct tm`s up a minute apart, when that minute includes a leap second, then calling `mktime()` and calculating the difference ought to give you a result equivalent to 61 seconds (or 59 seconds) in `time_t` units, if your implementation handles leap seconds correctly. – Crowman Sep 21 '13 at 14:06
  • Other way around. You will get a time difference of 60 seconds, despite the fact that the two instants were 61 seconds apart. To be fair not even ICU cares enough to support leap seconds correctly, so I'm inclined to believe nothing does, and my remarks about them are academical more than anything else. – millimoose Sep 21 '13 at 14:07
  • @millimoose: I think you are not correct, `time_t` ought to represent time since *some* kind of epoch, and if leap seconds are supported, it'll give you the right number of seconds. If you just added a fixed number of seconds to a `struct tm` for instance, and ignored the leap second issue, you'd be right. If this were not so, then it would be impossible for `mktime()` to work on a system that supported leap seconds, it would go out of sync with things like `gmtime()` every time a leap second happened. – Crowman Sep 21 '13 at 14:09
  • Nope: https://news.ycombinator.com/item?id=4130447 - POSIX time requires there be 86400 seconds between the same time on consecutive days, which means it has to go backwards because of leap seconds. I don't believe those are supported by the OS at all - system time simply goes backwards during the NTP time sync when the leap second occurs. (Or however this is handled at the low level, I believe somewhere there has to be a strictly monotonically increasing tick counter and "system time" includes a moveable offset from it.) – millimoose Sep 21 '13 at 14:12
  • I think now is the right time to read through this: http://www.ntp.org/ntpfaq/NTP-s-algo.htm – millimoose Sep 21 '13 at 14:15
  • @millimoose: But this is POSIX time, the C standard doesn't require that. The `tm_sec` member of `struct tm` in standard C can hold a value from 0 to 60 inclusive to handle leap seconds, this could never be filled in by `localtime()` or `gmtime()` if `time_t` was disallowed from reflecting leap seconds. Obviously you are correct that leap seconds cannot be computationally determined, so any sane system would either ignore them, or only reflect those that had historically happened when the implementation was created. – Crowman Sep 21 '13 at 14:16
  • I honestly have no idea how to resolve that contradiction, I'm going here off of information I stumbled upon researching this right now. That says, without a reference or citation, that the return value of `time()` does go backwards. How `localtime()` fills in leap seconds and whether or not it does so is unclear. One explanation might be that since `time_t` may be something else than POSIX time, an implementation could exist that handles leap seconds correctly, where a straight conversion from POSIX time to `struct tm` cannot do so. – millimoose Sep 21 '13 at 14:20
  • @millimoose: Yes, in pretty much any system that anybody uses, what you say is overwhelmingly likely to be correct, but ISO C doesn't not prevent an implementation from reflecting leap seconds in `time_t` and `struct tm`. Even if it does, you're probably going to get weird results occasionally, since leap seconds - among other things - just make dealing with times a fundamentally hard problem for computers. If you go back far enough, you're going to start getting whole days deleted from calendars, for instance. – Crowman Sep 21 '13 at 14:22
-3

Seems like a duplicate of this.

"The C date/time type time_t is implemented as the number of seconds since a certain date, so to add seconds to it you simply use normal arithmetic."

Even if it's in C, there should be no difference to C++ :)

Community
  • 1
  • 1
Chaos
  • 374
  • 2
  • 11