Zeitdifferenz-Messung starten

Hotte93

Grünschnabel
Hey Com,

ich habe ein kleines Problem mit der Zeitdifferenz.
Ich möchte eine Stempeluhr programmieren, die beim Ein-, sowie Ausstemplen die Zeit in eine Textdatei schreibt.
Hier mein bisheriger Programm Code:

C#:
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <time.h>
#include <conio.h>



int main() {
   
int eingabe=0;
int start=0;
int end  =0;


time_t start_t;
time_t end_t;
time_t t;
double diff_t;

    
    
    struct tm *ts;
    t = time(NULL);
    ts = localtime(&t);
   
   

time(&start_t);   //Zeitaufnahme starten


    FILE*fp;                       
    fp= fopen("Textdatei.txt", "w+");      //Öffnen bzw erstellen der Datei.
    fprintf(fp, "start ");
    fprintf (fp, asctime(ts));  //Aktuelle Zeit in Datei speichern.

           
   
printf("Ausstempeln fuer Zeiterfassung\n");
scanf("%d", end);        
time(&end_t);         //Zeitaufnahme stoppen
   
  
   
   

fprintf(fp, "end   ");
fprintf(fp, asctime(ts));  // Ausgabe Ende der Zeitmessung in Datei
fclose(fp);                     //Schließen der Datei



diff_t= difftime(end_t, start_t);  //Differenz berechnen beider Zeiten
printf("%.0lf sec", diff_t);




   
    return 0;
}


So wie das Programm jetzt läuft startet die Zeitaufnahme sobald das Programm gestartet wird.
Jedoch soll dies erst nach der Eingabeaufforderung "start" beginnen.
Ich habe versucht dies über ein scanf Befehl zu erreichen, jedoch läuft das Programm dann komplett durch ohne die eingabe "end" zu berücksichtigen.
Weiß jemand woran das liegt?

Mfg
Hotte
 

Technipion

Erfahrenes Mitglied
Hallo Hotte93,
ich habe dein Programm mal umgeschrieben, so wie ich glaube dass du es haben möchtest. Hier ist der Code:
C:
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <time.h>
//#include <conio.h>
/* conio.h wurde ursprünglich von Borland eingeführt und ist heute bloß
* noch auf MS-basierten System verfügbar. Es ist in der C Standard
* Library nicht aufgeführt und auch nicht vom POSIX definiert.
*/



int main() {

    int eingabe = 0;
    int start   = 0;
    int end     = 0;


    time_t start_t;
    time_t end_t;
    time_t t;
    double diff_t;


    struct tm *ts;
    t = time(NULL);
    ts = localtime(&t);


    time(&start_t); // Zeitaufnahme starten.


    FILE *fp;                    
    fp = fopen("Textdatei.txt", "w+"); // Öffnen bzw Erstellen der Datei.
  
    // ^ Wenn du die Datei hier schon öffnest, hält dein Programm über die
    // | gesamte Laufzeit die Datei für dich offen. Das wird in der Praxis
    // | ungerne gemacht, um andere Programme nicht "einzuschränken".
  
    // Besser wäre es die Werte in Variablen zu speichern und erst ganz
    // am Schluss eine Datei zu öffnen und abzuspeichern. ;)

    printf("Eingabe drücken um Zeiterfassung zu starten.");
    getchar();
  
    fprintf(fp, "start ");
    fprintf(fp, asctime(ts));          //Aktuelle Zeit in Datei speichern.
  
    printf("Zeiterfassung gestartet.\n");
    printf("Eingabe erneut drücken um Zeiterfassung zu beenden.");
  
    //scanf("%d", end);
    // ^ Wofür verlangst du vom Benutzer eine Ganzzahl?
    // | Und wofür ist int end eigentlich gut? Es wird gar nicht benutzt.
  
    // | Die Funktion getchar() wartet solange bis ein Enter kommt.
    // v
    getchar();
  
    time(&end_t);                      //Zeitaufnahme stoppen.


    fprintf(fp, "end   ");
    fprintf(fp, asctime(ts));          // Ausgabe Ende der Zeitmessung in Datei.
    fclose(fp);                        // Schließen der Datei.


    diff_t = difftime(end_t, start_t); //Differenz berechnen beider Zeiten
    printf("%.0lf sec\n", diff_t);

    return 0;
}
Das Programm hat auf meinem Ubuntu 16.04 LTS Rechner folgende Ausgabe erzeugt:
Code:
Eingabe drücken um Zeiterfassung zu starten.
Zeiterfassung gestartet.
Eingabe erneut drücken um Zeiterfassung zu beenden.
4 sec

Ich hätte an dich noch ein paar Fragen bezüglich deines Codes. Zuerst: Du hast als "Stopper" ein scanf() benutzt um aus der Konsole einen int auszulesen. Aber wofür eigentlich? Er wird später im Programm nicht mehr genutzt. Gab es da vielleicht noch andere Codestücke die du jetzt nicht gepostet hattest?
Falls es dir nur darum ging eine Eingabe des Benutzers abzuwarten, würde ich dir zu getchar() raten. Das ist eine C Standard-Funktion die es extra gibt um bis zum nächsten Enter zu blockieren.
Der Grund warum es mit deinen zwei scanf() Funktionen nicht geklappt hatte ist, dass das zweite scanf() nach der Betätigung der Eingabetaste (um das erste scanf() zu beantworten) unmittelbar durch ein Return geschlossen wird, das immer noch im Eingabepuffer rumliegt. Man kann das verhindern, indem man vor dem zweiten scanf()-Aufruf den Eingabepuffer mittels fflush(stdin); leert. Diese Stream-Geschichten unterscheiden sich aber auch von Plattform zu Plattform, ich habe es gerade unter meinem Ubuntu getestet und hier nicht gebraucht. Unter Windows habe ich den Puffer allerdings schon öfter flushen müssen. Falls bei dir das zweite getchar() nicht funktioniert, probier's also mal mit einem fflush() Befehl davor.
Noch etwas Wichtiges zur Verwendung von scanf(): scanf() erwartet Pointer! Wenn du schreibst
C:
int i;
scanf("%d", i);
ist das äußerst gefährlich! Du verletzt damit wahrscheinlich die Speicherzugriffsrechte deines Programmes. Du musst stattdessen schreiben
C:
int i;
scanf("%d", &i);
und scanf() somit einen Zeiger auf die Variable geben, in die die Eingabe geladen werden soll.
Für die Verwendung siehe http://www.cplusplus.com/reference/cstdio/scanf/.

Als Zweites hätte ich noch eine Frage bezüglich der Zeitmessung. Du schreibst beide Male die Zeit ts in die Datei. Ist das Absicht? Oder wolltest du nicht lieber start_t und end_t verwenden?

Gruß Technipion
 

Hotte93

Grünschnabel
Super danke!
Mit getchar() funktioniert es jetzt.

Mit dem scanf() wollte ich nur bewirken das wenn "end" eingelesen wird die Zeitaufnahme endet.
Klar geht das ja auch jetzt mit dem getchar().

Macht es denn einen Unterschied im Programm ab ich zweimal ts schreibe oder dann eben mit start_t und end_t?

MfG
Hotte
 

Technipion

Erfahrenes Mitglied
Hallo Hotte93,
kein Ding, dafür sind wir hier im Forum da! :)

Macht es denn einen Unterschied im Programm ab ich zweimal ts schreibe oder dann eben mit start_t und end_t?
Aber natürlich macht das einen - wenn nicht sogar DEN - Unterschied. In deinem Code steht in Zeile 25/26 so sinngemäß drin: "Nimm die aktuelle Zeit und wandle sie so um, dass ich sie in asctime() einsetzen kann."
Wenn du dein ts dann für die Startzeit und die Endzeit einsetzt, dann sind ja beide gleich.
Allerdings stoppst du ja mit start_t und end_t extra die Start- und Endzeit mit. Alles was also zu tun bleibt, ist statt ts die beiden einzusetzen.

Wenn du dann auch wieder die Sachen wegmachst, die du eigentlich gar nichts brauchst, dann sieht dein Programmcode so aus:
C:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>


int main() {

    time_t start_t;
    time_t end_t;

    double diff_t;


    printf("Eingabe drücken um Zeiterfassung zu starten.");
    getchar();
   
    time(&start_t);                            // Zeitaufnahme starten.
   
    printf("Zeiterfassung gestartet.\n");
    printf("Eingabe erneut drücken um Zeiterfassung zu beenden.");
    getchar();
   
    time(&end_t);                              // Zeitaufnahme stoppen.
   
   
    // Jetzt Datei öffnen und beschreiben:
   
    FILE *fp;                     
    fp = fopen("Textdatei.txt", "w+");         // Öffnen bzw Erstellen der Datei.
   
    fprintf(fp, "start ");
    fprintf(fp, asctime(localtime(&start_t))); // Startzeit in Datei speichern.

    fprintf(fp, "end   ");
    fprintf(fp, asctime(localtime(&end_t)));   // Endzeit in Datei speichern.
   
    fclose(fp);                                // Schließen der Datei.


    diff_t = difftime(end_t, start_t);         //Differenz beider Zeiten berechnen.
    printf("%.0lf sec\n", diff_t);

    return 0;
}

Jetzt stehen in der Ausgabedatei auch unterschiedliche Zeiten drin. :D

Gruß Technipion
 

Hotte93

Grünschnabel
Stimmt wohl :D
Jetzt habe ich noch ein kleines Problem.
Und zwar soll ich aus der Datei in der die Daten stehen die Gesamt-Differenz zwischen den Start- und Endzeiten bestimmten.
Zudem soll es auch möglich sein mehrere Start-, sowie End Zeiten hintereinander einzulesen.
Habe jetzt mal eine Schleife dazu programmiert.

C#:
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <time.h>
#include <conio.h>



int main() {
   
int eingabe    =0;




time_t start_t;
time_t end_t;
time_t t;
double diff_t;
double difftime1[200];
char start [200];
char end   [200];

    
    
struct tm *ts;
FILE*fp;                       



while(1){


   

scanf("%d", &eingabe);
getchar();

            if(eingabe==0){

                fp= fopen("Textdatei.txt", "a+");

                printf("Beginn Zeitaufnahme\n");
                time(&start_t);  //Zeitaufnahme starten


                  fprintf(fp, "start ");
                fprintf (fp, asctime(localtime(&start_t)));
   
                fclose(fp);
   
}



            if(eingabe==1){

                fp= fopen("Textdatei.txt", "a+");            
   
                printf("Ende Zeitaufnahme\n");
                time(&end_t);
   
  
   
   
                fprintf(fp, "end   ");
                fprintf(fp, asctime(localtime(&end_t)));  // Ausgabe Ende der Zeitmessung in Datei

                fclose(fp);
}

            if(eingabe==2){
                break;
}



}

fp= fopen("Textdatei.txt", "a+");

diff_t= difftime(end_t, start_t);  //Differenz berechnen beider Zeiten
printf("%f", diff_t);

fclose(fp);






   
    return 0;
}

Ich weiß nicht wie ich alle Startzeiten/ Endzeiten addiere und dann davon die Differenzzeit bilden soll.

MfG
Hotte
 

cwriter

Erfahrenes Mitglied
Hi

Und zwar soll ich aus der Datei in der die Daten stehen die Gesamt-Differenz zwischen den Start- und Endzeiten bestimmten.
Dafür müsstest du ja die Datei lesen - tust du aber in deinem Code nicht wirklich. Hier macht es Sinn, sich um die Dateistruktur Gedanken zu machen. Momentan schreibst du überraschend viel und öffnest die Dateien viel zu oft - in deinem rein sequentiellen Programm ist das kein Problem, aber alleine schon, um sich auf eine eventuelle Parallelisierung vorzubereiten, lohnt es sich, das zu überdenken.

Bspw. kannst du menschenlesbare Daten schreiben, etwa
Code:
start:<Zeit>
end:<Zeit>
diff:<Zeit>
Dann kannst du alles mittels
C:
fscanf(f, "%[^:]:%[^\n]", type, value); //"%[^:]" heisst: Lies den String, bis ein ':' auftritt (Genauer: Lies alles, was nicht (^) ':' ist). Analog für '\n'.

//Verzweigung nach type
if(strcmp(type, "start") == 0) /*...*/;
Man bemerke: Da (end - start = diff) sind die Informationen redundant: Wir haben 1 Gleichung und keine Unbekannten. Wir können das entweder ignorieren und ggf. als Prüfwert verwenden, oder aber wir können einen beliebigen Wert streichen und jeweils rekonstruieren.

Du kannst zudem überlegen, ob du die Zeiten tatsächlich als asctime speichern willst - für Menschen sind die Dinger leichter lesbar, aber die Maschine hat nix davon. Du kannst dir überlegen, ob du nicht einfach nur das time_t als 64bit-Wert direkt in die Datei speichern und jeweils für die menschenlesbare Ausgabe konvertieren willst.

Ich weiß nicht wie ich alle Startzeiten/ Endzeiten addiere und dann davon die Differenzzeit bilden soll.
Die Summe aller Startzeiten ist S_S, die Summe aller Endzeiten S_E, die Summe aller Differenzen S_D.
Also ist (S_E - S_S = S_D). Es gilt aber auch: S_D = Sum(diff) = Sum(end - start).
Du musst also nicht alle Startzeiten und alle Endzeiten addieren (das ist auch keine gute Idee; da die Zeit >> Diff (Zeit sehr viel grösser als die Differenz), sind Overflows sehr viel wahrscheinlicher), sondern du kannst auch nur alle Differenzen addieren.


Zudem soll es auch möglich sein mehrere Start-, sowie End Zeiten hintereinander einzulesen.
Es ist recht schwierig, das optimal zu gestalten. Aber damit dies überhaupt funktioniert, musst du irgendeine Art Schlüssel haben - eine ID für jede "Person", die sich ein- oder austrägt. Und jetzt funktionieren deine Dateizugriffe definitiv nicht mehr. Entweder, du speicherst bei jedem start und end die ID mit (chronologisch nach Event), oder du schreibst die Blöcke pro ID (chronologisch nach Austragung der ID). Du brauchst dafür sowas wie
C:
typedef struct {
    unsigned int ID;
    time_t start;
    time_t end;
} entry;
für jede ID und du müsstest die Startzeit speichern, bis sich jemand austrägt, und dann alle Informationen auf einen Schlag schreiben. Damit das einigermassen flott geht, bietet sich ein Hashtable (gepimpter Array), hier ggf. mit Listenerweiterung, an. Als Eingabe für scanf() ist entsprechend eine ID anzugeben.

Kurze Erläuterung, bei weiteren Fragen einfach nachfragen:
Ein Array hat n Elemente, die irgendwie gefüllt werden können. Aber wenn du n Elemente hast und ein bestimmtes Element suchst, kann es sein, dass du n Elemente vergleichen musst - das ist doof, weil langsam. In Hashtabellen beinhaltet der Index einen Teil der Information. Bspw. kann man faulerweise eine ID modulo 10 rechnen, also nur die letzte Ziffer berücksichtigen.
Bei einer Tabelle mit 10 Elementen ist die ID mit der letzten Ziffer "5" also am Index 5, in C:
C:
table[ID % 10] = /*...*/;
Das ist sehr schnell (man weiss immer, wo eine ID hinkommt und falls man eine ID sucht, weiss man, wo sie sein muss).
Dummerweise gibt es mehrere IDs an derselben Stelle, z.B. käme damit 10 an dieselbe Stelle wie 0, 20, 30, ... (abzählbar viele).
Dies nennt man eine (Hash-)kollision. Die ist doof; die mögen wir nicht. Eine einfach Lösung? Verdoppeln wir die Grösse der Tabelle! Dann haben wir x % 20 als Hashfunktion und haben nur noch halb so viele Kollisionen - die sind aber (theoretisch) noch immer unbegrenzt viele. So viel Speicher haben wir aber nicht, ergo können wir die Tabelle nicht ewig vergrössern.
Es gibt mehrere Lösungen für dieses Problem: Bei statistisch gleichmässiger Verteilung (z.B. Jeden Tag kommen 10 Leute mit beliebiger ID) ist es relativ wahrscheinlich, dass Kollisionen auftreten, aber wir haben in einer Hashtabelle der Grösse 10 sicher genug Platz, also kann man sondieren; die Position also immer gleich verschieben, bis Platz ist. Bei dieser Art von Verteilung ist linear wohl am Besten, bei anderen ist quadratisches Sondieren zu bevorzugen.
Da man aber nicht so ganz weiss, wie viele Leute tatsächlich arbeiten gehen, lohnt sich die Listenform eher.
Dann ist "in" jedem Tabelleneintrag eine Liste von Arbeitern; wir verteilen quasi die Liste aller Arbeiter auf kleinere Listen, die gewisse Eigenschaften (eben z.B. unterschiedliche letzte Ziffern) haben.
Doof dabei: Listen sind miserabel für Computer. Sie sind indirekt (erfordern Dereferenzierung) und sind nicht / nur schlecht parallelisierbar, da man für das n-te Element n Schritte machen muss. Bei Arrays kannst du einfach mit einer Zahl (dem Index) auf den Wert zugreifen, also in einem Schritt. Bei intelligent gewählten Kritieren (und Grösse) der Hashtabelle sollte es aber möglich sein, die Listen möglichst kurz zu halten, sodass dies nicht ins Gewicht fällt.

Dein Ansatz mit Arrays mit 200 Elementen kann funktionieren, aber wie schon angetönt geht das ins Unendliche (was, wenn jemand 201 Mitarbeiter hat? 202? 203? 10123159859173?). Eine Liste ist auf jeden Fall notwendig, und sei es nur, um diejenigen abzufangen, die im (schnelleren) Array keinen Platz mehr haben.

(Mir ist schon klar, dass das eine Übungsaufgabe ist und entsprechend nicht alles so genau genommen werden muss, aber es ist wohl besser, schon früh eine Aufmerksamkeit für diese Probleme zu entwickeln)

Jedenfalls: Beide Dateiformate sind gleichwertig, Konvertierungen sind also problemlos möglich; das "bessere" Format kommt ein bisschen darauf an, ob der Fokus auf Tagesablauf (chronologische Ordnung) oder Arbeitnehmer (Ordnung nach ID) liegt.

Die folgende Aufteilung ist übrigens auch nicht optimal:
C:
fprintf(fp, "end   ");
fprintf(fp, asctime(localtime(&end_t)));
//Geht als 
fprintf(fp, "end   %s", asctime(localtime(&end_t)));
//...in einem Aufruf (schneller). Analog bei der anderen Stelle.

Gruss
cwriter
 

Hotte93

Grünschnabel
Hey cwriter,

danke für den langen Text, ich versuche das mal Stück für Stück nachzuvollziehen.

Ich hatte mir vorhin noch etwas überlegt.
Also das Schreiben der Zeiten in die Datei funktioniert soweit mit dem Programm (Egal ob ich mehrere Start und Enzeiten hintereinander einlese).

Könnte ich die Sache mit der ID nicht umgehen?
Das ich einfach sage das die Datei nur das ausgewertet werden darf wenn die Anzahl der Startzeiten mit der Anzahl der Endzeiten übereinstimmt? Schließlich muss ich ja nur die Gesamtzeit bestimmten und nicht einzelne berücksichtigen.

Wenn es jetzt zB. eine Funktion in time.h gibt die mir die aktuelle Zeit als Differenz in Sekunden von Tagesbeginn angibt:

zB. 6 Uhr morgens start entspricht 21.600 Sekunden.
Diesen Wert speicher ich in einem long double.

Und dann zB. 14Uhr nachmittags end entspricht 50.400 Sekunden.

Dann rechne ich alle Endzeiten zusammen und ziehe alle Startzeiten davon ab.
Und durch eine Modulo Rechnung bringe ich dann die Sekunden in die Form hh:mm:ss

MfG
Hotte
 

cwriter

Erfahrenes Mitglied
Also das Schreiben der Zeiten in die Datei funktioniert soweit mit dem Programm (Egal ob ich mehrere Start und Enzeiten hintereinander einlese).
Natürlich - du verlierst einfach die Information, wer sich einträgt. Bei nur einer Eintragungsart ist diese Information trivial gegeben.
Könnte ich die Sache mit der ID nicht umgehen?
Doch, aber wie gesagt: Informationsverlust.
Z.B. ist die Folge EAEAEA (E=Eintragung, A = Austragung) eindeutig (Man weiss immer, wer (nämlich der letzte) sich austrägt.
Aber bei EEAA ist es nicht klar - ist es E1E2A1A2 oder E1E2A2A1?

Für die Summe alleine spielt es aber tatsächlich keine Rolle - zumindest theoretisch.
Das ich einfach sage das die Datei nur das ausgewertet werden darf wenn die Anzahl der Startzeiten mit der Anzahl der Endzeiten übereinstimmt? Schließlich muss ich ja nur die Gesamtzeit bestimmten und nicht einzelne berücksichtigen.
Und wie bestimmst du das?
Und ja, geht. Dann allerdings ohne Differenzen in der Datei direkt - und der Overflow wird wahrscheinlicher*.

* Du müsstest zwar recht viele Arbeiter haben, damit das passieren kann. Aber du willst ja auch nicht deinen ganz eigenen Milleniumsbug in deinem Programm haben, oder?

zB. 6 Uhr morgens start entspricht 21.600 Sekunden.
Diesen Wert speicher ich in einem long double.
Also wenn du ohnehin nur in Sekunden genau sein willst, sind Gleitkommaoperationen völlig Overkill. Wenn du dann auch noch einschränkst, dass jeder Arbeiter höchstens über 2 Tage hinaus arbeitet (also ggf. auch von 23 Uhr bis 6 Uhr), dann kannst du die Sekunden in 60*60*24*2=172800 Werten speichern. Dafür reicht ein uint (4Mrd. irgendwas) locker.
Modulo geht dann auch ein bisschen besser :)
Allerdings verlierst du dann ja wieder die Information, wann sich jemand eingetragen hat.
(Wie gesagt kannst du auch nicht einfach auf Sekunden im Tag runterrechnen, denn wenn jemand 24h "arbeitet", bekommt er wieder eine 0 raus, genauso wie jemand, der von 23h bis 1h arbeitet, eine Zeit von -22 Stunden erhält)

Dann rechne ich alle Endzeiten zusammen und ziehe alle Startzeiten davon ab.
Und durch eine Modulo Rechnung bringe ich dann die Sekunden in die Form hh:mm:ss
Wenn du alle Edgecases ignorierst: Ja, ginge. Allerdings ist das so, wie wenn du bei einem strassentauglichen Auto alle "unwichtigen" Teile ausbaust, weil auf der Rennstrecke Dinge wie Airbags sowieso nichts nützen, und dann doch ab und zu auf den öffentlichen Strassen fahren willst - es ist optimaler für einen sehr kleinen Bereich (schnell Fahren) und ist unbrauchbar in allen anderen Bereichen. Da nun aber wesentlich mehr öffentliche als Renn-strecken existieren, ist das eher suboptimal.
Oder, wie der Programmierer so gerne sagt: "Early optimization is the root of all evil".
Für deine Übungsaufgabe kann es performanter als der allgemeine Ansatz sein, aber es wird auch fragiler.

Gruss
cwriter