Die Funktion MinutesBetween aus DateUtils rechnet merkwürdig

Für Fragen zur Programmiersprache auf welcher Lazarus aufbaut
Benutzeravatar
willi4willi
Lazarusforum e. V.
Beiträge: 155
Registriert: Sa 1. Nov 2008, 18:06
OS, Lazarus, FPC: Windows, Linux (debian) / Lazarus 3.2 / FPC 3.2.2
CPU-Target: i386, win64, arm

Die Funktion MinutesBetween aus DateUtils rechnet merkwürdig

Beitrag von willi4willi »

Hallo allerseits!

Bei der Verwendung von MinutesBetween aus der unit DateUtils habe ich folgende Merkwürdigkeit:

Sie gibt mir zwischen dem "01.01.2017 00:00:00" und "01.01.2017 01:00:00" sind 59 Minuten zurück. Nehme ich aber den "01.01.2017 01:00:00" und "01.01.2017 02:00:00" sind es 60 Minuten.

Ich dachte immer, jede Stunde des Tages hat 60 Minuten.

Wenn ich einmal nur eine Zeitdifferenz nach dem Jahr 1900 berücksichtigen möchte und mir folgende Funktion schreibe:

Code: Alles auswählen

Function MinutesBetween(const ANow, AThen: TDateTime): Int64;
var Tage : double;
begin
  Tage:=AThen-ANow;
  Result:= TRUNC(Tage*1440);
end;


passiert das gleiche.

Habt ihr eine Idee, wie man die richtige Zeitdifferenz herausbekommt??
 

Viele Grüße

Willi4Willi

------------

Mathias
Beiträge: 6160
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

Re: Die Funktion MinutesBetween aus DateUtils rechnet merkwü

Beitrag von Mathias »

Folgender Code gibt bei mir wie erwartet 60min .

Code: Alles auswählen

procedure TForm1.Button1Click(Sender: TObject);
var
  a, b: TDateTime;
begin
  a := StrToDateTime('01-01-2017 00:00:00');
  b := StrToDateTime('01-01-2017 01:00:00');
 
  Caption := IntToStr(MinutesBetween(a, b));
end;



Hier ist noch die Funktion von FPC 3.1.1 , sieht diese bei dir gleich aus ?

Code: Alles auswählen

Function MonthsBetween(const ANow, AThen: TDateTime; AExact : Boolean = False): Integer;
 
var
  y, m, d: Word;
 
begin
  if AExact and (ANow >= -DateDelta) and (AThen >= -DateDelta) and
     (ANow <= MaxDateTime) and (AThen <= MaxDateTime) then
    begin
    PeriodBetween(ANow, AThen, y, m, d);
    Result := y*12 + m;
    end
  else
    Result:=Trunc((Abs(DateTimeDiff(ANow,AThen))+HalfMilliSecond)/ApproxDaysPerMonth);
end
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

wp_xyz
Beiträge: 4869
Registriert: Fr 8. Apr 2011, 09:01

Re: Die Funktion MinutesBetween aus DateUtils rechnet merkwü

Beitrag von wp_xyz »

Es gab dazu vor einiger Zeit einige Bugfixes - die sind im fpc-Trunk (und kommen evt in fpc 3.04), weshalb Mathias als notorischer trunc-User das richtige Ergebnis erhält.

Das Problem ist das alte Problem mit den Gleitkommazahlen. TDateTime ist intern nichts anderes als Double und hat daher wie jedes andere Gleitkommaformat nur eine bestimmte Anzahl von Nachkommastellen. Da die Einheiten von TDateTime ganze Tage sind, sind Minuten unendlich lange Dezimalzahlen (Divisor 3) und können nicht exakt durch eine Double-Zahl ausgedrückt werden. Durch die Subtraktion kommt nochmals ein Rundungsfehler dazu.

Schau dir das folgende Programm an. Als Differenz, in Minuten ausgedrückt, erhält man 59.999999996507540. Trunc() macht daraus 59. Zwei Stunden früher machen die Rundungsfehler daraus etwas wie 60.000000006984919, wo das Trunc() den richtigen Wert ergibt.

Die Lösung ist immer, die halbe Einheit, auf die man runden möchte, also 0.5, dazuzuaddieren, und erst dann trunc zu nehmen. Damit wird aus 59.999999996507540 der Wert 60.499999..., was nach Trunc() zu den korrekten 60 wird; und der Wert von zwei Stunden früher, 60.000000006984919, wird zu 60.500000..., was nach Trunc() immer noch 60 bleibt. Also:

Code: Alles auswählen

program MinutesBetween_Test;
 
uses
  SysUtils,
  DateUtils;
 
procedure Test(d1, d2: TDateTime);
begin
  WriteLn('d1: ', FormatDateTime('ddddd hh:nn:ss', d1), ' = ', d1);
  WriteLn('d2: ', FormatDateTime('ddddd hh:nn:ss', d2), ' = ', d2);
  WriteLn('MinutesBetween(d1, d2): ', MinutesBetween(d1, d2));
  WriteLn('Differenz der Double-Werte als Minuten: ', (d2 - d1)*60*24);
  WriteLn('Differenz nach Korrektur: ', (d2 - d1)*60*24 + 0.5);
  WriteLn('Differenz nach Korrektur (gerundet): ', trunc((d2 - d1)*60*24 + 0.5));
  WriteLn;
end;
 
begin
  Test(EncodeDateTime(2017, 1, 1, 0, 0, 0, 0), EncodeDateTime(2017, 1, 1, 1, 0, 0, 0));
  Test(EncodeDateTime(2016, 12, 31, 22, 0, 0, 0), EncodeDateTime(2016, 12, 31, 23, 0, 0, 0));
  ReadLn;
end.

Mathias
Beiträge: 6160
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

Re: Die Funktion MinutesBetween aus DateUtils rechnet merkwü

Beitrag von Mathias »

Somit hat willi4willi zufällig sehr dummer Werte erwischt. :wink:

Was noch interessant wäre, wen willi4willi anstelle von 2017 mal 2016 probieren würde.
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

wp_xyz
Beiträge: 4869
Registriert: Fr 8. Apr 2011, 09:01

Re: Die Funktion MinutesBetween aus DateUtils rechnet merkwü

Beitrag von wp_xyz »

Ist egal, Es wird immer wieder Werte geben, die falsch ausfallen. Man muss einfach bei der Anwendung von Trunc() wissen, dass das Ergebnis einen Hauch kleiner sein kann als der nächste Integer und damit falsch wird. Die vorgeschlagene Korrektur, 0.5 zu addieren, hilft dagegen.

Benutzeravatar
willi4willi
Lazarusforum e. V.
Beiträge: 155
Registriert: Sa 1. Nov 2008, 18:06
OS, Lazarus, FPC: Windows, Linux (debian) / Lazarus 3.2 / FPC 3.2.2
CPU-Target: i386, win64, arm

Re: Die Funktion MinutesBetween aus DateUtils rechnet merkwü

Beitrag von willi4willi »

Ja, das ist tatsächlich egal. Auch bei 2016 kommt ein falscher Wert. Das Problem ist die Uhrzeit.
Ich werde mir mal die neuste FPC-Version besorgen und es damit probieren.
Danke für die Tipps!
 

Viele Grüße

Willi4Willi

------------

Mathias
Beiträge: 6160
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

Re: Die Funktion MinutesBetween aus DateUtils rechnet merkwü

Beitrag von Mathias »

Was ich generell merkwürdig finde, wieso wird die Uhrzeit als Float-Typ verarbeitet und nicht als Integer ?
Ein Integer wäre mit irgendwie logischer, welcher einfach Mirkosekunden zählt.
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

Benutzeravatar
willi4willi
Lazarusforum e. V.
Beiträge: 155
Registriert: Sa 1. Nov 2008, 18:06
OS, Lazarus, FPC: Windows, Linux (debian) / Lazarus 3.2 / FPC 3.2.2
CPU-Target: i386, win64, arm

Re: Die Funktion MinutesBetween aus DateUtils rechnet merkwü

Beitrag von willi4willi »

Habe es nun mit der FPC 3.1.1 übersetzt und - Wunderbar! Es funktioniert jetzt richtig.

Danke nochmals.
 

Viele Grüße

Willi4Willi

------------

wp_xyz
Beiträge: 4869
Registriert: Fr 8. Apr 2011, 09:01

Re: Die Funktion MinutesBetween aus DateUtils rechnet merkwü

Beitrag von wp_xyz »

Mathias hat geschrieben:Was ich generell merkwürdig finde, wieso wird die Uhrzeit als Float-Typ verarbeitet und nicht als Integer ?
Ein Integer wäre mit irgendwie logischer, welcher einfach Mirkosekunden zählt.

Damit bräuchte man aber zwei verschiedene Variablen für Datum und Uhrzeit. TDateTime benutzt dagegen den ganzzahligen Anteil als Tage ab einem bestimmten Stichtag und den Gleitkommaanteil als Uhrzeit innerhalb dieses Tages: 0.0 = Mitternacht, 0.25 = 1/4 Tag = 6 Uhr, 0.5 = 1/2 Tag = 12 Uhr Mittag, etc. Damit kann man mit Datums/Zeit-Angaben rechnen wie mit anderen Zahlen.

Benutzeravatar
m.fuchs
Lazarusforum e. V.
Beiträge: 2636
Registriert: Fr 22. Sep 2006, 19:32
OS, Lazarus, FPC: Winux (Lazarus 2.0.10, FPC 3.2.0)
CPU-Target: x86, x64, arm
Wohnort: Berlin
Kontaktdaten:

Re: Die Funktion MinutesBetween aus DateUtils rechnet merkwü

Beitrag von m.fuchs »

wp_xyz hat geschrieben:
Mathias hat geschrieben:Was ich generell merkwürdig finde, wieso wird die Uhrzeit als Float-Typ verarbeitet und nicht als Integer?

Damit bräuchte man aber zwei verschiedene Variablen für Datum und Uhrzeit.


Nicht unbedingt: Unixtime bzw. Ticks.
Software, Bibliotheken, Vorträge und mehr: https://www.ypa-software.de

Mathias
Beiträge: 6160
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

Re: Die Funktion MinutesBetween aus DateUtils rechnet merkwü

Beitrag von Mathias »

Nicht unbedingt: Unixtime bzw. Ticks.

Ich wusste doch, das es sowas gibt. Als ich Anfangs mit Lazarus und Now etwas machen wollte, wurde ich nicht schlau, bis ich merkte, das es ein Float-Typ ist. :roll:
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

wp_xyz
Beiträge: 4869
Registriert: Fr 8. Apr 2011, 09:01

Re: Die Funktion MinutesBetween aus DateUtils rechnet merkwü

Beitrag von wp_xyz »

m.fuchs hat geschrieben:Nicht unbedingt: Unixtime bzw. Ticks.

Natürlich. Meine Antwort bezog sich darauf, dass Mathias von "Uhrzeit" gesprochen hat.

Ticks kannte ich noch nicht. Aber das demonstriert, wie groß Int64 sein kann: 10000 Jahre mit 100 Nanosekunden Auflösung, und dann ist noch ein Faktor 3 Reserve!

marcov
Beiträge: 1100
Registriert: Di 5. Aug 2008, 09:37
OS, Lazarus, FPC: Windows ,Linux,FreeBSD,Dos (L trunk FPC trunk)
CPU-Target: 32/64,PPC(+64), ARM
Wohnort: Eindhoven (Niederlande)

Re: Die Funktion MinutesBetween aus DateUtils rechnet merkwü

Beitrag von marcov »

m.fuchs hat geschrieben:
wp_xyz hat geschrieben:
Mathias hat geschrieben:Was ich generell merkwürdig finde, wieso wird die Uhrzeit als Float-Typ verarbeitet und nicht als Integer?

Damit bräuchte man aber zwei verschiedene Variablen für Datum und Uhrzeit.


Nicht unbedingt: Unixtime bzw. Ticks.


Unixtime hat aber eine feste 1s Präzision, was etwas ungenau ist für viele Applikationen.

Timm Thaler
Beiträge: 1224
Registriert: So 20. Mär 2016, 22:14
OS, Lazarus, FPC: Win7-64bit Laz1.9.0 FPC3.1.1 für Win, RPi, AVR embedded
CPU-Target: Raspberry Pi 3

Re: Die Funktion MinutesBetween aus DateUtils rechnet merkwü

Beitrag von Timm Thaler »

Dafür ist eine Sekunde auch eine Sekunde...

Das heisst aber auch, dass mit DateTime die üblichen Probleme mit Float-Vergleichen auftreten. Etwas wie if Now = wert then sollte man vermeiden, weil es sein kann, dass Now nie gleich wert ist.

Mathias
Beiträge: 6160
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

Re: Die Funktion MinutesBetween aus DateUtils rechnet merkwü

Beitrag von Mathias »

Etwas wie if Now = wert then sollte man vermeiden, weil es sein kann, dass Now nie gleich wert ist.

Bei Float-Vergleichen mache ich immer ein <= oder >= .
Bei Ganzzahlen dies auch ein Vorteil sein.
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

Antworten