0

I want a method which compares the times, eg if Atime occurs earlier than Btime do something, I would use a CompareTime Function but my problem with this function is what is said in "Bold Brackets" (see below)

Call CompareTime to compare the two TDateTime values specified by A and B. CompareTime returns: LessThanValue if A occurs earlier in the day than B (even if A occurs on a later day than B). GreaterThanValue if A occurs later in the day than B (even if A occurs on an earlier day than B).

Ken White
  • 123,280
  • 14
  • 225
  • 444
Alec
  • 569
  • 2
  • 17
  • 27
  • 2
    Use [`CompareDateTime`](http://docwiki.embarcadero.com/Libraries/XE6/en/System.DateUtils.CompareDateTime) since [`CompareTime`](http://docwiki.embarcadero.com/Libraries/XE6/en/System.DateUtils.CompareTime) compares only time portions of given `TDateTime` values. – TLama Jun 02 '14 at 09:30
  • TDateTime is a float value with the # of days since 31-12-1899. The fraction is the time. So it you want to compare two of them you can use `CompareDateTime` or just `<` of any other comparing operator. – Ritsaert Hornstra Jun 02 '14 at 09:47
  • 1
    @RitsaertHornstra, using comparing operators directly on `TDateTime` values will rely on implementation details. Better to avoid them and use `CompareDateTime` or other available date-time functions/procedures in `SysUtils` and `DateUtils`. – LU RD Jun 02 '14 at 09:59
  • @LU RD in absolute sense you are correct, although I cannot see how there could be a new TDateTime datatype without support for operator < etc (we do have operator overloading). So It does not really depend on the implementation but on the API and since version 1 of Delphi TDAteTime has supported the operators < and >. If Embarcadero wishes to alter the TDateTime API in this manner almost all Delphi software with date times in them will break. I do consider using the operators more of the Pascal way: keep the code very readable; function calls tend to clobber up the code much more. – Ritsaert Hornstra Jun 02 '14 at 10:06
  • @RitsaertHornstra, think about situation when you'd like to compare date time as a whole and e.g. just a date portion. Would you really mix your code by using operator for the first case and e.g. `CompareDate` function for the other ? – TLama Jun 02 '14 at 10:39
  • 2
    Actually `CompareDateTime` and the floating point comparison operators are not functionally equivalent. The documentation makes this quite clear. See the final part of my answer. – David Heffernan Jun 02 '14 at 10:42
  • @LU RD: I tend to use separate variables for Dates and DateTimes (I write a lot of interest / fiscal software and mixing these different types tend to make the code unreadable (fiscal rules are already almost unreadable by itself). Here readability is prio 1, so I rather use operators instead of func.calls. Note that I usually use FILETIME for dates, times and datetime fields in my software to avoid strange round off errors. Especially when sub millisecond accuracy is needed. Given the CompareDateTime compares up to milliseconds: this can yield strange effects with sub millisecond timestamps. – Ritsaert Hornstra Jun 02 '14 at 11:42
  • Sorry guys but I think I have to change my question a little bit because my situation also changed.Thanks but CompareDateTime does not work , but here is a scenario, i want to perform an action only at a specific time only, I have 2 values, the date specified by user and the system.now date, I want an action to perform if these 2 values are the same, by considering only the hour and the minutes. So CompareDateTime wont work because TDateTime values are considered the same if they have the same value down to the millisecond. – Alec Jun 02 '14 at 12:46
  • If your situation has changed, that's really an issue for you. The question stands as originally asked. If you want to ask a different question to cover your new scenario, please do so. – David Heffernan Jun 02 '14 at 13:13
  • @RitsaertHornstra, if you want sub-millisecond resolutions, the best way would be to implement a time variable with two integers, one for days and one for time. Put them in an advanced record and define your operators and calculation rules with possible implicit and/or explicit conversions. That way code will be readable and easily maintained. – LU RD Jun 02 '14 at 13:58

1 Answers1

6

A TDateTime values can be thought of as containing two distinct parts: the date part and the time part. The CompareTime function only compares the time part and ignores the date part. The documentation says:

Indicates the relationship between the time portions of two TDateTime values.

Call CompareTime to compare the two TDateTime values specified by A and B. CompareTime returns:

  • LessThanValue if A occurs earlier in the day than B (even if A occurs on a later day than B).
  • EqualsValue if A occurs at the same time of day as B, ignoring the date portion of the two values.
  • GreaterThanValue if A occurs later in the day than B (even if A occurs on an earlier day than B).

You want to compare the entire date time value. To do that you should use CompareDateTime. One important note in the documentation for the function states:

Note: CompareDateTime differs from a direct comparison of the corresponding double precision values in that two TDateTime values are considered the same if they have the same value down to the millisecond. It is possible to create two TDateTime values that differ numerically when viewed as doubles, but which represent the same year, month, day, hour, minute, second, and millisecond.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • I see the note, but if both are valid TDateTime values, they can not differ as Doubles (they do not store anything "below" the millisecond). So a normal Double comparison operator should work too. – Rudy Velthuis Jun 02 '14 at 16:07
  • @RudyVelthuis Try comparing `0.0` against `0.49/(1000*60*60*24)`. Remember also the fact that not all real numbers are representable in IEEE754 binary floating point format. – David Heffernan Jun 02 '14 at 16:15
  • I know that not all real numbers are representable in a Delphi Double, but apparently a Double is good enough to represent a date and time. So if both doubles are valid TDateTimes, and if one TDateTime is distinguishable from any other DateTime, they can be compared using a simple comparison. – Rudy Velthuis Jun 03 '14 at 02:29
  • FWIW, I compared `0.49/(1000*60*60*24)` (i.e. `0.0000000056712962962962964`) to `0.0` and the former value is larger. As I said, if both values to be compared are valid TDateTimes, the resolution of a Double is good enough to make each of them unique, so a simple comparison will do. Try it, or well, try to prove me wrong. – Rudy Velthuis Jun 03 '14 at 02:40
  • @Rudy CompareDateTime returns 0 for these values. – David Heffernan Jun 03 '14 at 05:45
  • Yes, CompareDateTime returns 0 because `0.49 * ...`is not a valid TDateTime. The smallest unique value above `0.0` is `1.0 * ...`, i.e. 1 millisecond. As I said, if both values are **valid** TDateTimes, a simple comparison will do. – Rudy Velthuis Jun 03 '14 at 06:29
  • @Rudy Please define what you mean by valid here. I trust you understand never to use floating point arithmetic with date times. Your rule therefore appears to be to ban floating point arithmetic, but allow comparison operators. You'd also be quite happy to use = to test for equality I see. Fundamentally this all stems from the bogus use of double precision floats for what belongs in an integral type. – David Heffernan Jun 03 '14 at 06:46
  • Valid is a TDateTime that is defined by the routines that define or return a TDateTime, i.e. a time value, stored as a Double, with a resolution of 1 millisecond, what else? Not every Double is a valid TDateTime, obviously. Since the difference between two TDateTimes is a multiple of 1 millisecond, or 0, a comparison can be done. What is so hard to understand there? – Rudy Velthuis Jun 03 '14 at 07:21
  • @Rudy That is an imprecise definition. A precise one might be a date time value that is invariant to a `DecodeDateTime/EncodeDateTime` cycle. Do you understand that most date time values cannot be exactly represented. The best we can do is choose the closest floating point value. Historic versions of Delphi did not produce the same floating point value given the same inputs to `EncodeDateTime` as today's Delphi. Am I right in my understanding that your advice for equality comparison is to use `=`? – David Heffernan Jun 03 '14 at 07:30
  • It may be imprecise, but you apparently understood it. Most date time values can not be exactly represented, but they have the same resolution. They are far enough apart not to be be disturbed by any floating point problems. So prove me wrong. Give me two (valid) TDateTime values that produce a different result than a mere comparison. – Rudy Velthuis Jun 03 '14 at 07:46
  • @Rudy Precision of definition is important. You are happy with mine? Invariant under encode/decode cycles. Am I allowed to use date time values generated by previous versions of Delphi? Am I allowed to use floating point arithmetic? Please can you not ignore my questions. Please state your position on the use of `=` with date time values. – David Heffernan Jun 03 '14 at 08:02
  • Valid TDateTimes are those that are the result or return value of one of the runtime TDateTime routines. If these values are used, despite the imprecision of floating point, two TDateTimes that represent the same date and time will have the same bit pattern, and if they differ, a normal comparison will do. Prove me wrong. – Rudy Velthuis Jun 03 '14 at 09:03
  • Actually, I think this discussion goes too far for this medium. You are free to discuss it elsewhere. You know where you can find me. – Rudy Velthuis Jun 03 '14 at 09:08
  • @RudyVelthuis Modern versions of Delphi deal with the awfully poor design of `TDateTime` by using an integer based time stamp to perform operations. The conversion between `TDateTime` and the time stamp brings about a degree of order. So I believe that in modern versions of Delphi it is harder to trip up the date time code. But this is why one uses the functions from the RTL rather than floating point arithmetic as you are proposing. Anyway, why won't you answer my questions? – David Heffernan Jun 03 '14 at 09:10
  • @RudyVelthuis I can only assume that you won't answer my questions because you know that doing so will lead you to contradict yourself. Ask yourself how your statements can be consistent. You stress the importance of using "valid" date times. And then define those to be the values returned by RTL functions. But then you suggest that we don't use the RTL function `CompareDateTime` and instead prefer `<`, `>`, `=` etc. Only you won't admit to that. – David Heffernan Jun 03 '14 at 09:12
  • Calculations may be a problem, indeed. Simple *comparisons* are not. Try it. A valid TDateTime is one that is a multiple of `1/(1000*60*60*24)`. Any value inbetween (e.g. `0.49*(...)`) is obviously not a valid TDateTime. – Rudy Velthuis Jun 03 '14 at 09:13
  • @RudyVelthuis `1/(1000*60*60*24)` is not representable. So the definition is bogus. Again, why won't you answer my questions? Would you like me to list them for you clearly numbered to make it easier for you? – David Heffernan Jun 03 '14 at 09:13
  • It doesn't matter if it is exactly representable. What matters is that the same value is represented by the same bit pattern. There are no rounding errors, as the distinction between two valid values is much much larger than a few million ulps. So another value is represented by a totally different bit pattern. Two values that have the same time value will be represented by *exactly* the same bit pattern too. So you can do a simple comparison. As I said: show me two TDateTime values that are not comparable with a simple >, < or =. You can't. – Rudy Velthuis Jun 03 '14 at 10:11
  • @RudyVelthuis So your rules are: 1. Don't store date time values to disk or database, because the valid values can change between Delphi versions, and potentially processor arch. 2. Don't perform any arithmetic to generate values. 3. Only use RTL functions to operate on, and generate values. 4. You are allowed to compare using `<`, `>` and `=`. With those restrictions, I think you likely end up with robustness. All this is because `TDateTime` implemented on top of `Double` is a mistake. Should be unsigned integral milliseconds since an epoch. Then all bit patterns are valid. – David Heffernan Jun 03 '14 at 10:16
  • FWIW, I am not suggesting that you don't use RTL functions. I am merely saying that for two differing TDateTime values, you can use < and > as well. But two equal time values will have the same bit pattern, so they can be compared with = too. I am well aware of the difficulties comparing two float values, but here, that doesn't play a role. Differing time values will have different Double values, but more importantly, equal TDateTime values will have the same bit pattern. – Rudy Velthuis Jun 03 '14 at 10:18
  • Those are not my rules. You can store TDateTime values and read them back, just like you can store and read back and compare Double values. Values can only be generated by arithmetic. And to get valid TDateTime values, the RTL functions use arithmetic. – Rudy Velthuis Jun 03 '14 at 10:21
  • But you did not answer to my challenge: show me that I'm wrong. – Rudy Velthuis Jun 03 '14 at 10:21
  • @RudyVelthuis So long as you restrict yourself to the rules I listed in my latest post, then I agree with you. But only if you use modern Delphi. The versions that no longer use floating point arithmetic (e.g. `dt1 + offset`) to generate date time values. The versions that use integral based time stamps. Go back to Delphi 7, say, and see how easy it is to generate so-called valid date time values that are not invariant under encode/decode cycles. – David Heffernan Jun 03 '14 at 10:27
  • @RudyVelthuis If you allow me to use the `+` operator to generate new date time values then for sure I can show you some anomalies. If you allow me to use D7 then I can do so using pure RTL functions. – David Heffernan Jun 03 '14 at 10:28
  • @RudyVelthuis Have a read of this Q (http://stackoverflow.com/questions/15031706/how-do-i-work-around-delphis-inability-to-accurately-handle-datetime-manipulati) and in particular my answer. And better still, John Herbster's excellent QC report which inspired the changes (http://qc.embarcadero.com/wc/qcmain.aspx?d=56957). Still, the design choice of using `Double` has left the entire area a complete mess. – David Heffernan Jun 03 '14 at 10:30
  • I already said that you can use comparison operators. Of course you can't add two TDateTimes (what is the result of 20 Dec 2002 + 31 Sept. 2003?), but I never claimed you could. – Rudy Velthuis Jun 03 '14 at 12:33
  • @Rudy Yes, that's meaningless. It makes no sense for you to suggest that. I cannot imagine why you would propose that. But imagine working out the time between two values. Or incrementing a time by some amount. – David Heffernan Jun 03 '14 at 12:57
  • Not sure what you are talking about. I merely mentioned that in the case of TDateTime, a comparison operator would work. Most arithmetic *with* TDateTimes makes no sense. Arithmetic to calculate a DateTime does of course make sense. Subtracting two TDateTimes also makes sense, but that gives you a time span, not a new date time value. FWIW, **SO** keeps on telling me that this kind of extended discussion doesn't belong here. – Rudy Velthuis Jun 03 '14 at 14:56
  • @RudyVelthuis And yet you keep on posting..... *Arithmetic to calculate a DateTime does of course make sense.* And that's where I can provide the counter-example that you ask for. I think. – David Heffernan Jun 03 '14 at 14:59
  • "And yet you keep on posting". Only as a reply to your posts, after I had already said this should be moved. – Rudy Velthuis Jun 03 '14 at 15:04
  • @RudyVelthuis So here you go. Try doing this a few times in a loop: `dt := dt + 1/(1000*60*60*24)`. Then compare with the same loop but body replaced with `dt := IncMilliSecond(dt, 1);`. – David Heffernan Jun 03 '14 at 15:07
  • @RudyVelthuis And the [documentation](http://docwiki.embarcadero.com/Libraries/en/System.TDateTime) says this: *To find the fractional number of days between two dates, simply subtract the two values, unless one of the TDateTime values is negative. Similarly, to increment a date and time value by a certain fractional number of days, add the fractional number to the date and time value if the TDateTime value is positive.* Now, I would not do that. I would use the utility functions in the RTL. But the documentations give you blessing to make what you refer to as "invalid" date time values. – David Heffernan Jun 03 '14 at 15:10
  • @RudyVelthuis All this stems from an appallingly bad design choice. Storing date/time values in a data structure where not all bit patterns are valid is a huge mistake. Sadly we are stuck with it. – David Heffernan Jun 03 '14 at 15:11
  • For a month value, stored in 4 bits, not all values are valid either. Using Double is not a huge mistake, it works fine, as the values stay clear from rounding problems. And yes, you can subtract TDateTimes, but the result is not a valid TDateTime, just an approximation. The same happens with adding timespans to TDateTimes. – Rudy Velthuis Jun 03 '14 at 15:28
  • 1
    @RudyVelthuis That's OK though because the valid values are all contiguous. Anyway, I think you will have to agree with my rules now. In order to use `=` to test for equality, you cannot use `+` to make new values. Note how that contradicts the documentation. And note how older versions of Delphi are helpless because they don't have the benefit of Herbster's workarounds. Using double was a huge mistake. An appalling design decision. Simple single Int64 value gets the job done with no problems whatsoever. And no need for floating point. Foolish to model discrete quantities with floating point. – David Heffernan Jun 03 '14 at 15:32
  • @RudyVelthuis Anyway, you accept my counter-example to your argument now? – David Heffernan Jun 03 '14 at 15:32