Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Software-Projekte der Mitglieder (https://www.delphipraxis.net/26-software-projekte-der-mitglieder/)
-   -   Zeitenberechnungs-Unit, bitte um Tests (https://www.delphipraxis.net/92997-zeitenberechnungs-unit-bitte-um-tests.html)

BingoBongo 30. Mai 2007 03:02


Zeitenberechnungs-Unit, bitte um Tests
 
Liste der Anhänge anzeigen (Anzahl: 1)
Hallo allerseits,

viele kennen sicherlich das saublöde Problem, dass die Delphi-eigenen Zeitkonvertierungsfunktionen in den meisten Fällen bei Zeiten > 24 Stunden den Dienst verweigern und dann behaupten, es wäre keine gültige Eingabe oder ähnliches.
Ich habe mich bei der Programmierung eines Tools über diese Beschränkung maßlos ärgern müssen und habe mir deshalb eine kleine Unit kreiert, die dieses Manko ausbügeln soll.
Und genau um diese geht es hier.

Hier erstmal die allgemeinen Infos:

function timestring: addiert die übergebenen dezimal dargestellten Zeiten ('1,25' oder '2,3654') und gibt die Summe in der Form 13:15:56 als String zurück

function decintime5: wandelt eine Dezimalzahl in eine Zeit der Form 23:56 um und gibt sie als String zurück

function decintime8: wandelt eine Dezimalzahl in eine Zeit der Form 23:56:24 um und gibt sie als String zurück

function timeindec: wandelt einen als String übergebenen Time-Wert vom Format '13:50:34' in einen Dezimalwert um

function decodetime5: Diese Function zerlegt einen String der Art '25:15' in seine Einzelbestandteile h, min und das unabhängig von der 24 Stunden Grenze der Delphi-eigenen Routinen. Zusätzlich ist result der Funktion der real-Wert der zerlegten Zeit.

function decodetime8: Diese Function zerlegt einen String der Art '25:15:26' in seine Einzelbestandteile h, min, sek und das unabhängig von der 24 Stunden Grenze der Delphi-eigenen Routinen. Zusätzlich ist result der Funktion der real-Wert der zerlegten Zeit.


Ich möchte euch bitten, diese Unit einmal zu testen, da ich trotz eigener Tests freilich bislang nicht wirklich sicher bin, alle Fehlerquellen ausgeschlossen zu haben.
Bitte beachtet auch die Bemerkungstexte an den jeweiligen Funktionen, da manchmal noch ein zusätzlicher Hinweis drin steht, auf was geachtet werden muß.

Ich bitte um Rückmeldungen.

Bingo

SirTwist 30. Mai 2007 12:14

Re: Zeitenberechnungs-Unit, bitte um Tests
 
Hallo BingoBongo,

eine schöne Sourcecode-Orgie hast Du da erzeugt ;-) Um ehrlich zu sein, wird mir nicht klar, welche Werte diese Dezimalzahlen annehmen können. Also wenn Du schreibst
Zitat:

function decintime5: wandelt eine Dezimalzahl in eine Zeit der Form 23:56 um und gibt sie als String zurück
was ist dann mit "eine Dezimalzahl" gemeint? wird 23,5 zu "23:30:00"? oder wird 23,5 zu "00:23:30"?

Um ehrlich zu sein, reicht dafür folgender Fünfzeiler aus:
Delphi-Quellcode:
var h, m, s: Integer;
   zeit: TTime;
begin
  h := Trunc(indec);
  m := Trunc(Frac(indec) * 60);
  s := Trunc(Frac(indec) * 3600) mod 60;
  zeit := EncodeTime(h, m, s);
  result := FormatDateTime('hh:nn:ss', zeit);
exit;
(runtergetippert, ungetestet).

Deine erste Funktion timestring() hat 30 Übergabeparameter ohne Default-Wert, d.h. man muss wirklich bei jedem Aufruf alle 30 Parameter angeben. Wenn man schon so eine Funktion braucht, dann wäre es sinniger, die Parameter als offenes Array angeben zu können:
Delphi-Quellcode:
function AddTimes(arr: array of String): String;
var i: Integer;
   zeit: Double;
begin
  zeit := 0;
  for i:=Low(arr) to High(arr) do
    zeit := zeit + StrToFloat(arr[i]);
  result := decintime8(zeit);
end;
Der Aufruf erfolgt dann mit AddTimes(["1,5", "2,5", "3,5"]);

In der Funktion decodetime verwendest du einen konstanten String ziffern und testest, ob ein einzelnes Zeichen Deiner Eingabe in diesem String enthalten ist. Schöner wäre es, Ziffern als Menge von Chars zu definieren (ziffern = ['0'..'9']; ) und dann mit "if timestring[i] in ziffern then..." zu testen. Ich weiß zwar nicht, ob das performanter ist, aber es sieht im Quellcode schöner aus ;-)

Anstelle von
Delphi-Quellcode:
result:= hour * H + minute * M + sekunde * S;
solltest Du besser
Delphi-Quellcode:
result := EncodeTime(hour, minute, sekunde);
verwenden, da dies genau die dafür vorgesehene Funktion ist.

So, ich hoffe, das hilft Dir ein wenig.

mkinzler 30. Mai 2007 12:23

Re: Zeitenberechnungs-Unit, bitte um Tests
 
Und schau dir außerdem mal die Unit DateUtils an.

BingoBongo 30. Mai 2007 13:29

Re: Zeitenberechnungs-Unit, bitte um Tests
 
Diese Funktionen sind hauptsächlich dazu gedacht, die Zeitformatierungsbegrenzung, die wohl Delphi-eigen ist, zu umgehen. Oder hast du schonmal versucht, 2 Zeiten, z.Bsp. 12:30:35 und 15:17:56 zu addieren? Selbstverständlich das ganze in hh:mm:ss. Diese 5-stellige Ausgabe ist dann als hh:mm zu verstehen.

Die Konvertierungsfunktionen von Delphi sind nämlich bedauerlicherweise scheinbar auf maximal < 24 Stunden begrenzt.
Diese Funktionen entstanden, weil ich an einem kleinen Proggi werkle, bei dem massig Zeiten addiert werden müssen. Und da bin ich auf diese ärgerliche Hürde gestoßen.

Und was die Unit DateUtils betrifft, habe ich in der Hilfe nichts gefunden, was diese Rechengrenze umgeht. Falls ich mich darin irre, so gebt mir bitte einen Tipp, wonach ich genau! suchen sollte.

Auch diese irrsinnige 30-parametrige Funktion hat für meine Zwecke einen Sinn. Es für mich tatsächlich nötig, 30 Zeiten zusammenzurechnen, und da kommen leicht mal über 100 Stunden als Summe raus, bei denen dann die DateUtils wieder streiken würden.

Bingo

SirTwist 30. Mai 2007 13:58

Re: Zeitenberechnungs-Unit, bitte um Tests
 
Wenn Du mit Zeiten rechnen willst, würde ich von Dezimalzahlen Abstand nehmen! Diese sind zu ungenau.

Ich habe mal eine Zeiterfassungs-Anwendung geschrieben und dabei alles in Minuten umgerechnet und diese als Integer-Wert abgespeichert. Alternativ kann man das auch mit Sekunden so machen.

Dann brauchte ich noch vier Funktionen:
Delphi-Quellcode:
function MinToTime(min: Integer): TTime;
function TimeToMin(t: TTime): Integer;
function StrToMin(str: String): Integer;
function MinToStr(min: Integer): String;
Das reicht eigentlich schon aus.

Beispiel:
Delphi-Quellcode:
function MinToTime(min: Integer): TTime;
begin
  result := EncodeTime(min div 60, min mod 60, 0);
end;

function TimeToMin(t: TTime): Integer;
var h, m, s, ms: Word;
begin
  DecodeTime(t, h, m, s, ms);
  result := h * 60 + m;
end;

function MinToStr(min: Integer): String;
begin
  result := Format('%d:%2.2d', [min div 60, min mod 60]);
end;

shmia 30. Mai 2007 14:12

Re: Zeitenberechnungs-Unit, bitte um Tests
 
Hallo, habe mir deine Unit angeschaut und meine langjährige Erfahrung sagt mir, dass du völlig auf dem Holzweg bist.
Grundsätzlich gilt:
Alle Datums- und Zeitwerte werden zuerst in das interne Format TDateTime umgewandelt.
Erst dann werden damit Berechnungen durchgeführt.
Mit Strings werden direkt keine Zeitberechnungen durchgeführt.

Wenn deine Zeitangaben > als 24 Stunden sind, dann muss man eben Tage hinzunehmen.
Du brauchst also nur 2 Funktionen:
1.) einen String (z.B. 50:30:00) nach TDateTime wandeln
2.) einen TDateTime-Wert in einen String wandeln (z.B. 4d 23:50:00 = 4Tage, 23 Std,50 Min)


die Funktion zu 1.) könnte so aussehen:
Delphi-Quellcode:
function StrToTimeSpan(timestring: string):TDateTime;
// diese Function parst einen String der Art '25:15:36'
// ohne 24 Stunden Grenze der Delphi-eigenen Routinen.
var
  sstd, smin, ssek: string;
begin
   sstd := StrToken(timestr, ':');
   smin := StrToken(timestr, ':');
   ssek := StrToken(timestr, ':');

   hour:= strtointDef(sstd, 0);
   minute:= strtointDef(smin, 0);
   sekunde:= strtointDef(ssek, 0);

   result := (((sekunde / 60.0) + minute) / 60.0 + hour) / 24.0;
end;
Delphi-Quellcode:
// kopiert aus der JCL
function StrToken(var S: string; Separator: Char): string;
var
  I: Integer;
begin
  I := Pos(Separator, S);
  if I <> 0 then
  begin
    Result := Copy(S, 1, I - 1);
    Delete(S, 1, I);
  end
  else
  begin
    Result := S;
    S := '';
  end;
end;
zu 2.) das bekommst du selbst hin.
Hinweis: du musst vorher nur die ganzen Tage abtrennen (function frac() und int()), dann kannst du TimeToStr verwenden.

BingoBongo 30. Mai 2007 14:26

Re: Zeitenberechnungs-Unit, bitte um Tests
 
Diese ganzen Gedankengänge hatte ich leider zu meist schon selbst angestellt.
Leider muß ich mit Dezimalzahlen als Zeiten rechnen, da ich teilweise Eingabewerte aus einer bestehenden Exceltabelle auslese und das sind dann leider Dezimalzahlen. Diese rechne ich dann ja so genau es eben möglich ist in einen Zeitwert der Form hh:mm:ss oder hh:mm um, je nachdem wie ich es dann brauche.
Auch den Gedanken mit der Tagesabtrennung mußte ich verwerfen, da es sich auf dem 'Lohnzettel' seltsam macht, wenn dann steht: Arbeitszeit: 22d 7h 15min.
Deshalb muß alles in Stunden ausgegeben werden, auch wenn es von der Menge her mehrere Tage sind.

Ich vermute bisher sehr zu meiner Freude, dass diese Funktionen im allgemeinen scheinbar keine Berechnungsfehler beinhalten, denn sonst hätte das schon einer von euch geschrieben.
Mir ist auch klar, dass es vermutlich auch andere, vielleicht elegantere Wege gibt, die für mich erforderlichen Konvertierungen und Berechnungen durchzuführen.
Den Vorschlag mit dem Array werde ich noch einmal überdenken, vielleicht spart mir das etwas Schreibarbeit.


Bingo

SirTwist 30. Mai 2007 14:34

Re: Zeitenberechnungs-Unit, bitte um Tests
 
Gerade wenn es sich um Lohnzettel handelt, solltest Du mit Integerwerten rechnen. Die Darstellung als String hhh:mm:ss ist wirklich nur für die Enddarstesllung in der GUI notwendig, alles andere mit Integer rechnen. Dezimalzahlen sind dabei viel zu ungenau (das geht an shmia).

Glaub mir, ich habe das schon alles fertig ;-)

Klaus01 30. Mai 2007 14:38

Re: Zeitenberechnungs-Unit, bitte um Tests
 
Wenn hier von Dezimalzahl geredet wird, ist dann Dezimalbruch gemeint?
Info DezimalSystem

Grüße
Klaus

shmia 30. Mai 2007 14:53

Re: Zeitenberechnungs-Unit, bitte um Tests
 
Zitat:

Zitat von BingoBongo
Auch den Gedanken mit der Tagesabtrennung mußte ich verwerfen, da es sich auf dem 'Lohnzettel' seltsam macht, wenn dann steht: Arbeitszeit: 22d 7h 15min.
Deshalb muß alles in Stunden ausgegeben werden, auch wenn es von der Menge her mehrere Tage sind.

Auch das ist kein Problem:
Delphi-Quellcode:
function TimeSpanToStr(timespan:TDateTime);
var
   Hour, Min, Sec, MSec: Word
begin
   DecodeTime(timespan, Hour, Min, Sec, MSec);
   
   // jetzt noch die vollen Tage draufaddieren
   Hour := Hour + Int(timespan)*24.0;
   result := Format('%.2d:%.2d:%.2d', [Hour, Min, Sec]);
end;

BingoBongo 30. Mai 2007 15:00

Re: Zeitenberechnungs-Unit, bitte um Tests
 
Wenn ich hier Dezimalzahl sage, dann meine ich eine Zahl, bei der irgend eine Ziffernkombination links vor dem Komma steht, und einige Ziffern rechts nach dem Komma. Z.Bsp.: 1,2356974 oder 21,459

Und was den Rat mit den Integerwerten angeht: ich stimme dir zu, habe aber leider keinen Einfluß auf das Zahlenformat aus der Exceltabelle. Da sind eben nicht nur Integerwerte gespeichert, sondern oftmals auch 'krumme' Zahlen. Und die wollen nun auch mal 'behandelt' werden.

Bingo

SirTwist 30. Mai 2007 15:36

Re: Zeitenberechnungs-Unit, bitte um Tests
 
Zitat:

Zitat von BingoBongo
WDa sind eben nicht nur Integerwerte gespeichert, sondern oftmals auch 'krumme' Zahlen. Und die wollen nun auch mal 'behandelt' werden.

Ja, dann konvertiert man sie genau einmal beim Einlesen nach Integr (multiplizierne, runden, feddich) und rechnet danach mit Integerwerten weiter. Ich habe anfangs mit TTime-Werten gerechnet, was ja eigentlich nur Dezimalwerte sind und habe mir richtig herbe Ungenauigkeiten eingehandelt.

Die Darstellung an den Schnittstellen ist egal, ob als String, als Dezimalwert. Und als Schnittstelle gilt sowohl die GUI für den Anwender, als auch irgendwelche Import/Export-Funktionen. Wichtig ist die Darstellung innerhalb des Programms, die sollte möglichst genau sein und einfache Berechnungen erlauben. Das bekommst Du halt am einfachsten mit Integer-Werten hin.

juergen 30. Mai 2007 19:00

Re: Zeitenberechnungs-Unit, bitte um Tests
 
Hallo Bingo,
etwas schmunzelnn musste ich schon, als ich mir deine Unit angeschaut habe :wink:
Ich hatte mich auch mal mit Zeitberechnungen beschäftigt und hatte mich dabei zum Anfang genauso in eine "Rechnungsorgie" verloren,
wo ich anschließend -genau wie du- nicht mehr ganz sicher war, was da alles wie berechnet wird.

Dann hatte ich alles -wie schon angemerkt- umgestellt auf Umwandlung in reine Minutenwerte als Integerwert. (17:59 Std. = 1118 Minuten)
(Zahlen aus Excel umwandeln in Minutenwerte als Integerwert)
Die Ergebnisse brauchst du nur noch durch 60 teilen und entsptrechend aufbereiten.

Damit kann man wirklich alles auch NACHVOLLZIEHBAR rechnen.

Ich kann dir auch nur anraten diese Umstellung vorzumnehmen (allein der Supportbarkeit wegen).

shmia 31. Mai 2007 10:20

Re: Zeitenberechnungs-Unit, bitte um Tests
 
Zitat:

Zitat von SirTwist
Dezimalzahlen sind dabei viel zu ungenau (das geht an shmia).

Der Datums-/Zeittyp von Delphi ist TDateTime (intern ein Double-Wert mit 64bit=8Byte)
Ein Double-Wert hat 15 bis 16 signifikante Dezimal-Stellen.
Für den Datumsanteil werden 5 Stellen benötigt; bleiben also noch 10 Nachkommastellen für den Zeitanteil übrig.
Eine Millisekunde entspricht 1.157E-8 eines Tages.
Addiert man mehrere Double-Werte kann man mit einem Rechenfehler von wenigen Millisekunden rechnen.
Wenn man diese Ungenauigkeiten im Millisekundenbereich sich nicht erlauben kann, dann muss man tatsächlich auf einen anderen Zeitmassstab ausweichen.
Für 98% aller Anwendungen ist TDateTime aber vollkommen ausreichend. Vorallem, wenn man Arbeitszeiten aufaddiert und dann auf vollen Minuten rundet.

Ein kleiner Fallstrick lauert allerdings noch:
Angenommen man nimmt den 31.5.2005 (entsprechend 39233.0 Tage seit dem 30.12.1899) und addiert munter
Stunden drauf.
Durch Rundungsfehler kommt man dann vielleicht auf:
39245.999999994 = 12.06.2007 23:59:59
obwohl man den 13.6. erwartet hätte. Es fehlt vielleicht nur eine Millisekunde.
Aber das kann man mit folgender Funktion beheben:
Delphi-Quellcode:
function RoundToFullSeconds(dt:TDateTime):TDateTime;
const
   secondsperday = 24.0*60.0*60.0;
begin
   Result := Round(dt*secondsperday) / secondsperday;
end;


Alle Zeitangaben in WEZ +1. Es ist jetzt 14:10 Uhr.

Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz