7

I would like to add seconds to a TDateTime variable, so that the result is the top of the minute. For example, if it's 08:30:25, I want change the TDateTime variable to store 08:31:00.

I see that TDateTime has a Decode function, which I could use. There isn't, however, an encode function to put the altered time back into a TDateTime variable.

Ken White
  • 123,280
  • 14
  • 225
  • 444
Mike Jablonski
  • 1,703
  • 6
  • 27
  • 41
  • 1
    I removed your references to an "instance of TDateTime", as `TDateTime` isn't a class or record and therefore you can't have an "instance" of it. `TDateTime` is simply a type, which is actually a `Double`. – Ken White May 21 '13 at 00:15
  • For a more general approach, have a look at [this answer](http://stackoverflow.com/a/4122475/64082) that lets you round to nearest minute, quarter of an hour, hour etc. – Svein Bringsli May 21 '13 at 07:09
  • Your rounding rules are very vague. 08:30:25 **rounded to minutes** will be 08:30, not 08:31. – OnTheFly May 21 '13 at 09:56
  • @user539484, the OP wanted to round up to next minute(top minute). So the rounding rule here is precisely defined. – LU RD May 21 '13 at 13:30
  • @LU RD, got it, check out my solution. – OnTheFly May 21 '13 at 14:08
  • 1
    Part of the problem with the `TDateTime`, they are doubles, where the fractional part is the time of the day. This means that some minutes cannot be represented as an exact number. Newer `DateUtils` library therefore always transforms the fractional part to an integer64 containing milliseconds of the day from midnight before doing calculations. Therefore the use of library functions are safer than doing explicit calculations on `TDateTime`. See [`How do I work around Delphi's inability to accurately handle datetime manipulations?`](http://stackoverflow.com/q/15031706/576719). – LU RD May 22 '13 at 16:40
  • @LURD Indeed so. This is the crux of the argument between myself and 539484. – David Heffernan May 23 '13 at 10:44
  • 1
    @DavidHeffernan, I have seen that, and knowing the history as well. I have a math function that could solve this elegantly as well. But there will always be a limitation in precision, so I refrained from publishing it. I know you have referred any math operations on `TDateTime` as an `implementation leak`, because using `TDateTime` should not utilize the fact that it is implemented as a `Double`. I agree that using the `DateUtils` and `SysUtils` is a much better alternative. There was a time when `DateUtils` was very buggy though, and many implemented their own `TDateTime` utils. – LU RD May 23 '13 at 11:00
  • @LURD Agreed on all scores. Modern date/time libraries in Delphi are greatly improved. – David Heffernan May 23 '13 at 11:06

3 Answers3

10

Using DateUtils it's possible to do it like this:

Uses
  DateUtils;

var
  Seconds : Word;    

Seconds := SecondOfTheMinute(MyTime);  // Seconds from last whole minute
// Seconds := SecondOf(MyTime); is equivalent to SecondOfTheMinute()
if (Seconds > 0) then
  MyTime := IncSecond(MyTime,60 - Seconds);
LU RD
  • 34,438
  • 5
  • 88
  • 296
5

There sure is, at least in the recent versions - see the DateUtils unit, especially all the Recode* routines and EncodeDateTime. The DateUtils unit is already available in Delphi 2010, perhaps even in earlier version.

ain
  • 22,394
  • 3
  • 54
  • 74
  • 3
    In older versions you can use EncodeDate(y, m, d) + EncodeTime(h, min, 0, 0). – Gerry Coll May 20 '13 at 22:20
  • DateUtils is old unit, since D6 or older. But it isnt really necessary to do what OP wants. – OnTheFly May 21 '13 at 14:10
  • Delphi 5 sure doesn't have it (just checked). Anyway, the [RecodeMinute](http://docwiki.embarcadero.com/Libraries/XE2/en/System.DateUtils.RecodeMinute) function in DateUtils allows to implement it in a nice readable way so why not to use it. – ain May 21 '13 at 14:24
  • Checked too, Delphi 7 does. – OnTheFly May 21 '13 at 16:36
1

Theory

The TDateTime data type represents number of days since 30 Dec 1899 as a real number. That is, the integral part of TDateTime is an amount of whole days, and the fractional part represents a time of day.

Practical

Therefore, your problem could be solved using simple arithmetics:

var
  Days: TDateTime;
  Mins: Extended;  { widen TDateTime's mantissa by 11 bits to accommodate division error } 

begin
  Days := Date + StrToTime('08:30:25');
  Writeln(DateTimeToStr(Days));


  Mins := Days * 24 * 60 ;  // compute minutes
  Mins := Math.Ceil(Mins);  // round them up
  Days := Mins / (24 * 60); // and back to days
  { or as simple and concise expression as: }
  // Days := Ceil(Days * MinsPerDay) / MinsPerDay;

  Writeln(DateTimeToStr(Days));
Community
  • 1
  • 1
OnTheFly
  • 2,059
  • 5
  • 26
  • 61
  • 1
    This code can fail when the time is not exactly representable in binary floating point. It should be easy enough to construct an example that fails. It will be a time that already has 00 seconds. But the closest representable value is greater than the true value. – David Heffernan May 21 '13 at 21:13
  • @David Heffernan, I dont think so. If it should be so easy - please come back with actual example that fails. – OnTheFly May 21 '13 at 21:39
  • Do you know about representability and binary floating point? – David Heffernan May 21 '13 at 21:53
  • 1
    @David Heffernan, please **prove your statement**. It should be easy, you said. Until then, I'm not going to feed you, sorry. – OnTheFly May 21 '13 at 22:01
  • Personally, I don't need to see an example in order to know that your code will round entire minutes in an inconsistent manner. So, my original comment is proof enough. But if you are not familiar with floating point representability issues then an example will help you understand. I put a pastebin here: http://pastebin.com/9Fjz8RX5 – David Heffernan May 22 '13 at 04:16
  • If you look at modern Delphi date handling code you'll see that they've changed a lot of it. Functions that would previously use floating point is now implemented with integer arithmetic. The date/time is converted to a 64 bit integer and arithmetic performed there. This is much more reliable. Probably this was not an option when the date/time code was first written because Delphi did not then support 64 bit integers. The only 64 bit types was `Double`. The new versions of the code are much better and much easier to reason about. – David Heffernan May 22 '13 at 05:45
  • @David Heffernan, your forged values does not fail with 11 bit mantissa expansion (guess why). As I said previously, I'm not interested in the idle chit-chat. – OnTheFly May 22 '13 at 11:04
  • You just cannot bring yourself to admit it can you? You'll argue that up is down to avoid accepting that you got something wrong. Everyone makes mistakes. Accepting that is how we learn. – David Heffernan May 22 '13 at 11:05
  • Your use of the word "forged" is also a little curious. What exactly are you inferring? And your talk about 11 bit mantissa expansion is meaningless waffle. – David Heffernan May 22 '13 at 11:15
  • @David Heffernan, your "proof" is not strong enough to convince me. Nor is your bare words (your are not royal person, right?). Forge a faulty value and we will talk. – OnTheFly May 22 '13 at 11:23
  • Up really is down in 534984 world! Try using `EncodeTime` if you don't like how I made my `TDateTime` values. But that would be a perverse world view given your advocacy for floating point arithmetical manipulations of `TDateTime` values. Until you learn about representability and understand that properly in the context of binary floating point, you are doomed not to understand what's wrong with your code in this answer. There is idle chit-chat here, but none of it comes from me. I pity you. – David Heffernan May 22 '13 at 11:29
  • @David Heffernan, thats another wall of words, while I asked you to present at least 1 (one!) number which can break non-commented code above. Until that happens you are just a prolix buffoon. – OnTheFly May 22 '13 at 14:10
  • There are lots in the pastebin. Did you run it? Do you even understand what I'm talking about? – David Heffernan May 22 '13 at 14:15
  • @David Heffernan, yes, you are being engaged into lengthy talking about your inability to find the proof :-) Rationals like `N/1440` does not break extended precision computation (explained in the technical "waffle" above, which you fail to understand). – OnTheFly May 22 '13 at 14:32
  • `Assert(EncodeTime(0, N, 0, 0)=N/1440)` I am filled with pity for somebody that is incapable of recognising when he is wrong. You'd be prepared to accept you were wrong if it was somebody other than me telling you. But the identity of the messenger does not change the veracity of the message. I'm done here. – David Heffernan May 22 '13 at 16:02
  • 1
    @David Heffernan, the message is wrong and the messenger is known for talking rubbish. You even do not know how to assess equality of FP values. Or was that a trick to confuse me? Enough wiggling out, come back with a number which can prove your point. How many times I should ask you that? Ofc, everyone else is welcome to post that number. – OnTheFly May 22 '13 at 16:26
  • 2
    It's in the pastebin. It's sad that you don't understand representability issues. Did you know that not all real numbers can be represented? Did you know that very few numbers of the form `N/1440` are exactly representable? How do you explain the behaviour of the pastebin I sent you? Or is it a matter of faith that when you and I disagree, I am always wrong? Even when the evidence says otherwise? – David Heffernan May 22 '13 at 16:30
  • @David Heffernan, there is no evidence. Neither of "lots" (ten, actually) numbers you demonstrated fails with extended precision. I told you that already. Have anything else to say? – OnTheFly May 22 '13 at 16:48
  • Why am I not surprised that you seem incapable of backing down gracefully user539484? I have been at the receiving end of that inability as well. It is a very unbecoming trait for someone who seems to have pretty good skills otherwise. What is it that makes you unable to back down and start shouting matches, even if it takes editing your answer to be able to return and keep ranting at @DavidHeffernan? I don't happen to have Delphi installed on this machine, but even if I had, I wouldn't even bother to run your code. From observing you two history heavily stacks the odds in favour of David. – Marjan Venema May 22 '13 at 17:07
  • There is no extended precision because TDateTime is double precision. And extended won't help in any way because the numbers are just as unrepresentable there. – David Heffernan May 22 '13 at 17:23
  • @Marjan Venema, and why are here then? To express your valuable opinion, right? If you don't want "bother" with my code then please do not bother me either. – OnTheFly May 22 '13 at 17:32
  • To try and get you to wisen and loosen up. In vain apparently. – Marjan Venema May 22 '13 at 17:37
  • Oh, I've just seen your edit. I would have expected you to have acknowledged that edit in the comments. Never mind, it's just as broken. Your algo is broken for all binary floating point, no matter how much precision you have. That's because, `N/1440` is not in general exactly representable in binary floating point. In which case the nearest value to `N/1440` is sometimes above, and sometimes below. Hence the unpredictability. – David Heffernan May 22 '13 at 17:39
  • I'm surprised that you assert that using `Extended` changes anything. That indicates that you don't actually understand the issue, which is fine. But it also indicates that you have not run any code, because if you had done (http://pastebin.com/seRi0NGT) then you'd realise the futility of your position. – David Heffernan May 22 '13 at 17:42
  • Finally something specific. Took a whole day and lotsa waffle from you and your little friend to come with erroneous numbers. This is fixable with epsilon-rounding, but I do not have one more day to talk about it. – OnTheFly May 22 '13 at 18:15
  • 1
    "This is fixable with epsilon-rounding." Glad that you finally faced up to the facts that it needs fixing. I don't know about epsilon. Don't know what you mean. If you must stay in floating point then the fix is this: `Math.Ceil(dt1*24*60 - 0.25) / (24*60)`. That's good to around year 6000. We'll probably still be arguing then. – David Heffernan May 22 '13 at 21:46
  • I'm going to pick you up on this: "widen TDateTime's mantissa by 11 bits to accommodate division error" That's just nonsense. The error is *representability* rather than arithmetic. – David Heffernan May 22 '13 at 21:47