Forum: Compiler & IDEs Problem bei Zeitberechnung aus Unixzeit


von Tachoron (Gast)


Lesenswert?

Hi ihr Cracks,

Ich versuche derzeit die Unixzeit richtig aufzuschlüsseln, liege 
teilweise aber immer etwas daneben. Vielleicht hat einer von euch mal 
die Zeit und Muße kurz über meinen Code zu gucken um meinen Fehler zu 
identifizieren.

Beispiele:

Winterzeit:
Wenn ich den Wert 973070045 umwandel, würde ich gerne die Lokalzeit 
01.11.2000 10:14:05 herausbekommen. Mein Ergebnis ist aber der 
02.11.2000 9:14:05. Also um einen Tag und eine Stunde falsch.

Sommerzeit:
Wenn ich den Wert 1339745796 umwandel, würde ich gerne die Lokalzeit 
15.06.2012 09:36:36 herausbekommen. Mein Ergebnis ist aber der 
16.06.2012 08:36:36. Also um einen Tag und zwei Stunden falsch.

Ich kann aber irgendwie keinen Fehler finden, vielleicht fällt euch das 
leichter.

Der Code besteht aus den folgenden 2 Funktionen:
1
/*Defines fuer die Berechnung der Unix-Zeit*/
2
#define FIRSTYEAR 1970 /*Startjahr*/
3
#define FIRSTDAY 6     /*Starttag*/

1.Umwandlung der Unixzeit
1
void si_log_UnixtimeToDate(s32 unixtime_s32,s32 * year_ps32, s32 * month_ps32, s32 * day_ps32, s32 * weekday_ps32,
2
                                            s32 * hour_ps32, s32 * minute_ps32, s32 * second_ps32)
3
{
4
   s32 day_s32, month_s32, year_s32, leap400_s32;
5
   s32 dayofyear_s32;
6
7
   /*Sekunden extrahieren*/
8
   (*second_ps32) = unixtime_s32 % (s32)60;
9
   unixtime_s32 = unixtime_s32 / (s32)60;
10
   /*Minuten extrahieren*/
11
   (*minute_ps32) = unixtime_s32 % (s32)60;
12
   unixtime_s32 = unixtime_s32 / (s32)60;
13
   /*Stunden extrahieren*/
14
   (*hour_ps32) = unixtime_s32 % (s32)24;
15
   day_s32 = unixtime_s32 / (s32)24;
16
   
17
   /*Wochentag ermitteln*/
18
   (*weekday_ps32) = ((day_s32 + (s32)FIRSTDAY) % (s32)7); 
19
   
20
   /*Jahr ermitteln*/
21
   year_s32    = (s32)(FIRSTYEAR % 100);  
22
   leap400_s32 = (s32)(4 - (((FIRSTYEAR - 1) / 100) & 3));    
23
   
24
   do
25
   {
26
      dayofyear_s32 = 365;
27
      /*Schaltjahr?*/
28
      if( (year_s32 & (s32)3) == (s32)0 )
29
      {
30
         dayofyear_s32 = 366;  
31
         /*100 Jahr Ausnahme?*/        
32
         if( (year_s32 == (s32)0) || (year_s32 == (s32)100) || (year_s32 == (s32)200) )  
33
         {
34
            /*400 Jahr Ausnahme?*/
35
            leap400_s32--;
36
            if( leap400_s32 > (s32)0 ) 
37
            {
38
               dayofyear_s32 = 365;
39
            }
40
         }     
41
      }
42
      if( day_s32 >= dayofyear_s32 )
43
      {
44
         day_s32 -= dayofyear_s32;
45
         year_s32++;          
46
      }
47
   } while(day_s32 >= dayofyear_s32);
48
   (*year_ps32) = year_s32 + (((s32)FIRSTYEAR / (s32)100) * (s32)100);  /*+Jahrhundert*/
49
   
50
   /* Kein Schaltjahr und nach dem 28.2?*/
51
   if( (dayofyear_s32 & (s32)1) && (day_s32 > (s32)58) )    
52
   {
53
      day_s32++;  /*29.2 skippen*/
54
   }   
55
56
   for( month_s32 = 1; day_s32 >= days_of_month_s32[month_s32-(s32)1]; month_s32++ )
57
   {
58
      day_s32 -= days_of_month_s32[month_s32-(s32)1];    
59
   }   
60
  
61
   (*month_ps32) = month_s32;     /*1...12*/
62
   (*day_ps32) = day_s32 + (s32)1; /*1...31*/
63
   
64
   /*ggf. Sommerzeit hinzuaddieren*/
65
   si_log_Summertime(month_ps32, day_ps32, weekday_ps32, hour_ps32);
66
   
67
} /*Ende si_log_UnixtimeToDate*/

2.Umrechnung Sommerzeit (Subroutine)
1
void si_log_Summertime(s32 * month_ps32, s32 * day_ps32, s32 * weekday_ps32, s32 * hour_ps32)
2
{
3
   u8 summertime_u8 = TRUE_C;
4
   s32 hour_s32 = (*hour_ps32);
5
   s32 day_s32 = (*day_ps32);
6
   s32 month_s32 = (*month_ps32);
7
   s32 wday_s32 = (*weekday_ps32);
8
   
9
   if( (month_s32 < (s32)3) || (month_s32 > (s32)10) )   
10
   {
11
      summertime_u8 = FALSE_C;      /*Monate Jan,Feb,Nov,Dez -> Winterzeit*/
12
   }
13
   else
14
   {
15
      if(( (day_s32 - wday_s32) >= (s32)25) && 
16
        ( ((wday_s32 > (s32)0) || (hour_s32 >= (s32)2)) ))
17
      { /* nach dem letzten Sonntag 2 Uhr */
18
         if( month_s32 == (s32)10 ) /* Oktober -> Winterzeit */
19
         {            
20
            summertime_u8 = FALSE_C;   
21
         }  
22
      }
23
      else
24
      { /* bevor den letzten Sonntag 2 Uhr */
25
         if( month_s32 == (s32)3 ) /* Maerz -> Winterzeit */
26
         {
27
            summertime_u8 = FALSE_C;   
28
         }  
29
      }
30
   }
31
   
32
   /*Sommerzeit?*/
33
   if(summertime_u8 == TRUE_C)
34
   {
35
     hour_s32++;   /*+1 Stunde*/            
36
     if( hour_s32 == (s32)24 ) 
37
     {  /*Naechster Tag*/   
38
        hour_s32 = 0;
39
        if( day_s32 == days_of_month_s32[month_s32-(s32)1] )
40
        { /*Naechster Monat*/
41
           month_s32++;
42
           day_s32 = 1;
43
        }
44
        else
45
        { 
46
           day_s32++;   
47
        }
48
        /*Naechster Wochentag*/
49
        wday_s32++;               
50
        if( wday_s32 == (s32)7 )
51
        {
52
          wday_s32 = 0;
53
        } 
54
     }  
55
     /*Werte uebernehmen*/   
56
     (*hour_ps32) = hour_s32;
57
     (*day_ps32) = day_s32;
58
     (*month_ps32) = month_s32;
59
     (*weekday_ps32) = wday_s32 ;       
60
   }   
61
}

Danke für jegliche Hilfe!

von Sven P. (Gast)


Lesenswert?

Du könntest einfach in der C-Bibliothek nachschauen, wie es da gemacht 
wird. Eine entsprechende Funktion wäre 'localtime()', die Quellen dazu 
sind frei.

Und bitte, BITTE gewöhne dir den Schwachsinn mit diesem '_s32' wieder 
ab. Bitte!
Und nein, dieses '_s32' hat absolut garnichts mit der ungarischen 
Notation zu tun, wirklich nicht. Das ist schlicht die Perversion einer 
eigentlich ganz guten Idee, nachdem Microsoft ebendiese völlig falsch 
verstanden hat...

von Klaus W. (mfgkw)


Lesenswert?

Es ist mir ehrlich gesagt etwas zu nervig, mir zu überlegen, was du mit 
so etwas meinst:
   leap400_s32 = (s32)(4 - (((FIRSTYEAR - 1) / 100) & 3));


Wenn man schon arithmetische Rechnung und Bitfummelei vermmatscht, 
könnte man wenigstens sagen, was man damit meint.
Daß man in solchem Code Fehler suchen muß, ist nicht selten.
Spaß macht es nicht, weil es mit klarer Programmierung vermeidbar wäre.

PS: Wenn es dir zu anstrengend ist, (int32_t) zu schreiben und ein 
eigenes (s32) baust, wieso castest du unnötigerweise dann jeden Kram?

von Tachoron (Gast)


Lesenswert?

Hallo,

Die Notationen entsprechen den mir vorliegenden Richtlinikatalog und 
sind keinesfalls meinen eigenem Gutdünken entsprungen. Also bitte 
tollerieren.

Das explizite Casten liegt einfach daran das ich C-Code nach Lint bzw. 
MISRA Richtlinien programmiere. Dort ist implizites casten untersagt.

Dennoch Danke für eure Rückantworten :-)

Das mit localtime schaue ich mir einmal an.

von Tachoron (Gast)


Lesenswert?

Übrigens noch ein Nachtrag: Bei der von dir zitierten Zeile handelt es 
sich um eine BErechnung für die 400Jahres-Sonderregel für Schaltjahre. 
Und ja du hast recht - hier fehlt ein Kommentar.

von Sven P. (Gast)


Lesenswert?

Tachoron schrieb:
> Hallo,
>
> Die Notationen entsprechen den mir vorliegenden Richtlinikatalog und
> sind keinesfalls meinen eigenem Gutdünken entsprungen. Also bitte
> tollerieren.
Naja, armer Kerl :-)


> Das explizite Casten liegt einfach daran das ich C-Code nach Lint bzw.
> MISRA Richtlinien programmiere. Dort ist implizites casten untersagt.
Dann solltest du das aber auch richtig umsetzen.
Die ganzen expliziten Casts nach der Form:
1
(s32)100
sind nämlich überflüssig bis falsch, wenigstens in vor-C90-Compilern.

Einfachs Beispiel für eine Maschine, bei der 'int' genau 32 Bit lang 
ist:
1
unsigned int u1 = 2147483648;
2
unsigned int u2 = (unsigned int) 2147483648;
3
if (u1 == (unsigned int) 2147483648);
Compilerausgabe:
1
test.c:287: Warnung: diese Dezimalkonstante ist nur in ISO-C90 vorzeichenlos
2
test.c:288: Warnung: diese Dezimalkonstante ist nur in ISO-C90 vorzeichenlos
3
test.c:290: Warnung: diese Dezimalkonstante ist nur in ISO-C90 vorzeichenlos

Was du vermutlich erreichen wolltest:
1
unsigned int u = 2147483648 U;

Oder, wenn man eben nicht sämtliche Typen blödsinnigerweise 
nachdefiniert:
1
#include <stdint.h>
2
uint32_t u = UINT32_C(2147483648);

Naja, vermutlich wieder typische Fehlinterpretation :-)

von Karl H. (kbuchegg)


Lesenswert?

Tu dir selbst einen Gefallen und lass so einen Quatsch bleiben
1
      if( (year_s32 & (s32)3) == (s32)0 )

wenn du Modulo meinst, dann schreib auch Modulo. Diese 'wahnsinnig 
clevere' Optimierung beherrschen Compiler seit ungefähr 40 Jahren völlig 
problemlos (und noch viele mehr, die du nicht kennst). Dafür solltest du 
lieber nachdenken, warum deine Datentypen eigentlich alle signed sind? 
Denn gerade bei signed Datentypen stimmt diese 'Optimierung' nämlich 
NICHT. Bei negativen Zahlen geht das in die Hose. Und wenn du jetzt 
einwendest, du hättest keine negativen Zahlen, dann frag ich dich: warum 
sind deine Datentypen dann nicht in erster Linie 'unsigned'?


Du addierst bzw subtrahierst hier immer wieder
1
   for( month_s32 = 1; day_s32 >= days_of_month_s32[month_s32-(s32)1]; month_s32++ )
2
   {
3
      day_s32 -= days_of_month_s32[month_s32-(s32)1];    
4
   }
eine 1.
Gewöhn dich daran, dass wir Programmierer bei 0 anfangen zu zählen.
Eine kanonische for-Schleife, die zb alle Arrayelemente durchgeht
lautet
  for( i = 0; i < Anzahl_Elemente; i++ )
    mach was mit Array[i];

* bei 0 wird zu zählen angefangen
* da steht immer ein 'kleiner' in der Bedingung.

Sieht eine for-Schleife so aus, braucht man nicht groß weiter darüber 
nachdenken. Sieht sie aber nicht so aus, dann werd ich SOFORT hellhörig 
und muss das analysieren. Und öfter als man glaubt, hat der 
Programmierer dann da tatsächlich einen Fehler eingebaut, den er mit der 
kanonischen Form vermieden hätte. Wenn, so wie bei dir, das eigentlich 
gar keine Zählschleife ist, solltest du darüber nachdenken, ob du nicht 
zu dokumentarischen Zwecken lieber eine ordinäre while einsetzen 
solltest. Das betont mehr, dass da die Abbruchbedingung eine spezielle 
Bedeutung hat.


Hmm
1
   (*day_ps32) = day_s32 + (s32)1; /*1...31*/
du addierst da noch 1 und dein tagesdatum ist um 1 zu groß. 
Zusammenhang?


Noch was. Ich akzeptiere, dass du jeden Pfurz casten musst (auch wenn 
ich persönlich das für Unsinn im MISRA halte). Aber mach dir um Gottes 
Willen wenigstens für die Konstanten ein paar #define. In deinem Code 
kann man doch vor lauter Castings nichts mehr erkennen! Das ist doch 
unsinnig, jeden Pfurz ständig zu casten (zumal es ja auch den Compiler 
nicht daran hindert, das Ergebnis hinten nach noch einmal implizit zu 
casten. Woran es ihn aber ganz sicher hindert ist: Ordentlich zu 
optimieren.)

von Karl H. (kbuchegg)


Lesenswert?

> Ich kann aber irgendwie keinen Fehler finden, vielleicht fällt
> euch das leichter.

Gehs halt mal systematisch an.
Jag die Unixzeit 0 durch und steppe im Debugger durch.
Dann für 1 Stunde mehr, 1 Tag mehr, für 1 Jahr mehr, für 1 Schaltjahr 
mehr.

Da jetzt irgendeine Zeit (=große Zahl) einzusetzen, für die du das 
Ergebnis kennst, ist meistens nicht sehr zielführend um rauszukriegen, 
welche der Berechnungen in die Hose geht.

von Klaus W. (mfgkw)


Lesenswert?

Oder (wenn das mit dem Debugger zu langweilig ist) ab 1.1.1970 
fortlaufend mit der Funktion aus der C-Lib vergleichen lassen und beim 
ersten Unterschied mit dem Debugger reingehen.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Tachoron schrieb:
> Bei der von dir zitierten Zeile handelt es
> sich um eine BErechnung für die 400Jahres-Sonderregel für Schaltjahre.

Bis ins wievielte Jahrtausend muss denn deine Software funktionieren?
Die 100- und 400-Jahres-Regeln hoben sich im Jahr 2000 auf.  Alle
Software, die bis zum Jahr 2099 in der Datenmülltonne landet (weil
man deren Datenträger bis dahin sowieso nur noch mit Mitteln der
Archäologie exhumieren kann ;) muss sich darum folglich keine weiteren
Gedanken machen und kann das Schaltjahr einfach mittels modulus 4
ermitteln.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Tachoron schrieb:
> Wenn ich den Wert 1339745796 umwandel, würde ich gerne die Lokalzeit
> 15.06.2012 09:36:36 herausbekommen. Mein Ergebnis ist aber der
> 16.06.2012 08:36:36. Also um einen Tag und zwei Stunden falsch.

Der Unterschied beträgt nur 1 Tag und 1 Stunde.

Folgende Fehler habe ich in deinem Code gefunden:

1. Die 1 Stunde Differenz kommt von daher, dass die Unix-Zeit am
   1.1.1970 um 00:00:00 UTC zu zählen beginnt. Zu diesem Zeitpunkt war
   es aber nach MEZ schon 01:00:00. Um die Ergebnisse in MEZ zu
   erhalten, musst du also 1 Stunde zum berechneten Ergebnis addieren.

2. Der eine Tag kommt von der folgende Schleife:
1
   do
2
   {
3
      dayofyear_s32 = 365;
4
      /*Schaltjahr?*/
5
      .
6
      .
7
      .
8
      if( day_s32 >= dayofyear_s32 )
9
      {
10
         day_s32 -= dayofyear_s32;
11
         year_s32++;          
12
      }
13
   } while(day_s32 >= dayofyear_s32);

  hinterlässt dayofyear_s32 mit der Anzahl der Tage des Jahres vor dem
  Abfragedatum. Du benutzt die Variable aber anschließend, um zu
  entscheiden, ob das Jahr des Abfragedatums selbst ein Schaltjahr ist.
  Deswegen ist das Ergebnis in Schaltjahren ab dem 1. März immer 1 Tag
  zu groß.

3. Der 1.1.1970 war kein Samstag (6), sondern ein Donnerstag (4).
   Dadurch sind die berechneten Wochentage falsch, was wiederum
   Auswirkungen auf die Sommerzeitberechnung hat.

4. Falls die Routine auch für die Vergangenheit funktionieren soll: Die
   Sommerzeitumstellung hat sich seit 1970 ein paarmal geändert:

     http://de.wikipedia.org/wiki/Sommerzeit#Deutschland

Möglicherweise enthält der Code noch weitere Fehler. Ich würde ihn
deswegen für alle Tage von 1970 bis 2201 jeweils mindestens für die
Uhrzeiten 01:00 und 03:00 (wegen der Sommerzeitumstellung um 02:00)
durchlaufen lassen und die Ergebnisse mit denen eines validierten
Programms (z.B. date von Unix/Linux oder eine entsprechende Biblio-
theksfunktion) vergleichen.

von Tachoron (Gast)


Lesenswert?

Hallo Yalu,

Ich komme leider erst sehr spät zum Antworten aber danke für deine sehr 
konstruktive und hilfreiche Antwort!

Das mit dem Tag hatte ich bis dato auch herausgefunden, die anderen 
Punkte aber so noch nicht.

Hat mir in jedem Fall sehr geholfen!

Gruß
Dennis

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.