title image


Smiley Nochmal Datumsdifferenzen und Rückrechnung skalarer Daten in ein Datum
Hallo Ines,



also nochmal zum mitschreiben. Die hier gezeigte Lösung unterscheidet sich ganz wesentlich von Deiner, denn die Schritte für die Jahresberechnung sind deterministisch - Da muss man nix rumprobieren. Wir sitzen schliesslich vor nem Rechner und nicht vor nem Rumprobierer.

Lediglich beim Monat muss ich raten. Dabei ist aber sichergestellt, dass wenn der geratene Monat falsch ist, er um genau 1 zu gross geschätzt wurde. Natürlich wäre bei der Monatsschätzung auch eine Division durch 31 möglich gewesen. Dabei würde es sich aber um eine echte Division handeln, während die Division durch 32 durch eine einfache Schiebeoperation ersetzt werden kann (d>>5).

Die Offsettabelle lege ich statisch in den Read-Only Bereich, dadurch entfällt das unnötige Umkopieren beim Funktionsstart.

Ich hab's nun nicht ausprobiert, aber diese Lösung dürfte schätzungsweise um ein mehrfaches schneller sein als Deine.

Von der Grösse her geb ich Dir teilweise Recht, meine Version braucht im Textsegment tatsächlich etwas mehr Platz als Deine, dafür wird aber der Stack nicht so zugemüllt.



Zur Codierungsfunktion foo() muss ich eigentlich nicht viel sagen. Da wird mit 365*Jahr einfach eine erste Abschätzung gemacht, die weggelassenen Schalttage sowie Monatsoffset und Tag addiert.

Wir zählen dabei die vergangenen Tage seit dem 1. März 0000. Der Grund für genau dieses (etwas seltsame) Basisdatum wird hoffentlich gleich klar werden.



Bei der Decodierungsfunktion bar() ist's etwas kniffliger.

Ein Abschnitt von 400 Jahren ist immer exakt 146097 Tage lang - Damit haben wir erst mal keine Probleme.

Die Probleme kommen aber bei Jahrhunderten, denn ein Jahrhundert kann entweder 36524 oder 36525 Tage lang sein.

Bei 4 Jahresabschnitten sieht's ähnlich aus, denn die können 1460 oder 1461 Tage lang sein.

Bei Jahren ist's das gleiche, denn die können 366 oder 365 Tage lang sein.



Wenn man nun "normal" denkt und bei 0000-01-01 (1. Januar 0000) anfängt, dann hat man das Problem, dass die Ausnahmen immer am Anfang des betrachteten Zeitabschnitts liegen und man auch noch berücksichtigen muss, ob man ein Datum vor oder nach Beginn des März hat.



Die Fallunterscheidungen werden (fast) unnötig, wenn wir ab dem Datum 0000-03-01 (1. März 0000) rechnen. Man dividiert einfach durch die Länge des Normalfalls. Lediglich Bei Jahren und Jahrhunderten hauen wir am jeweils letzten Tag des betrachteten Abschnitts um 1 daneben und das ist leicht zu erkennen bzw. zu korrigieren.



Ok, machen wir's mal Schrittweise

Zunächst betrachten wir Abschnitte von 400 Jahren und die sind alle, wie schon gesagt, exakt 146097 Tage Lang. Wir dividieren also durch 146097 und erhalten so in x die Anzahl der 400 Jahresabschnitte. Weil wir im nächsten Schritt nur mit Jahrhunderten rechnen wollen, ziehen wir auch diese Tage (x*146097) ab. Im gleichen Zug erhalten wir schon eine bis auf 400 Jahre genaue Abschätzung für das Jahr (y=400*x).

Nun betrachten wir Abschnitte von 100 Jahren. Die ersten drei Jahrhunderte sind 36524 Tage lang, das letzte ist 36525 Tage lang. Dividieren wir nun die verbliebenen Tage durch 36524, dann haben wir fast schon das richtige Jahrhundert. Lediglich am letzten Tag des letzten Jahrhunderts haben wir um einen 1 zu grossen Wert. Das korrigieren wir mit unserer Tabelle (tt[]). Im nächsten Schritt werden wir Abschnitte von 4 Jahren betrachten, deshalb ziehen wir die entsprechende Anzahl von Tagen ab (d-=36524*x). Die vergangenen Jahrhunderte müssen wir natürlich unserem Jahr zuschlagen (y+=x*100).

Nun zu den 4 jährigen Zeiträumen. Die sind üblicherweise alle 1461 Tage lang, lediglich der letzte Abschnitt kann um einen Tag kürzer sein. Die Division durch 1461 liefert also immer das korrekte Ergebnis. Wie immer ziehen wir die vergangenen Tage ab (d-=1461*x) und korrigieren unser Jahr (y+=x*4).

Nun betrachten wir die Jahre eine 4 Jahresabschnitts. Die ersten 3 sind immer 365 Tage lang, das vierte kann um einen Tag länger sein. Genau wie bei den Jahrhunderten können wir mit unserer Division am letzten Tag danebenhauen und korrigieren das mit unserer Tabelle (tt[]). Nun wieder die hier ermittelten Tage abziehen (d-=x*365) und das Jahr korrigieren (y+=x).

Im Prinzip haben wir nun fast schon das richtige Jahr, wir müssen aber noch den Monat betrachten. Wir schätzen den Monat mit m=d/32+1 ab. Bei dieser Abschätzung können wir um maximal einen Monat zu weit liegen was wir mit unserer Offsettabelle über prüfen und ggf. einen Monat abziehen. Nun wieder die vergangenen Tage abziehen (d-=moff[m]).

Wir haben nun die Information, wieviele Jahre und Monate seit dem 1.März 0000 vergangen sind. Wenn wir feststellen, dass der Monat nach Dezember liegt, dann befinden wir uns nach unseren "normalen" Denkweise schon im nächsten Jahr und müssen das korrigieren (y+=1). Die Monate sind auch noch etwas verdreht und wir korrigieren das im gleichen Schritt. Man könnte das mit ner Modulo Operation zu machen, aber mit if geht's schneller.

Was wir nun noch an Tagen übrig haben, ist natürlich der Tag des Monats.



Wir haben nun Jahr, Monat und Tag (auch wieder 0 basiert) - Fertig!



Wie schon gesagt, es macht keinen Sinn mit Daten vor 1582 zu rechnen, obwohl der Algo ab dem 1. März 0000 eigentlich korrekt funktioniert.



Für den Wochentag kannst Du das modulo-basiert machen, einfach.



wd=(foo(<year>, <month>, <day>)+3)%7;



(Amerikanische Zählweise, also Sonntag=0, Montag=1, ... Samstag=6)



Die Feiertage kannst Du, genau wie von schon Pacer100 beschrieben, mit der klassischen Osterberechnung von Gauss machen.

Ich rate aber dringend davon ab, die Feiertagsdaten fest reinzucodieren. Die Feiertage sind von Land zu Land verschieden. Du kannst dann den ganzen Scheiss neu compilieren, sobald es in ein anderes Land (oder auch nur Bundesland) kommt.



Bei den Kalenderwochen musst Du die DIN 1355 und die amerikanische Methode unterscheiden (Google lässt grüssen).



Viele Grüsse,



Uli



Nochmal der (etwas veränderte) Code:





#include <stdio.h>

#include <stdlib.h>



typedef unsigned long date_t;



#define is_leap(y) ((y)%4==0 && ((y)%100!=0 || (y)%400==0))



/* Encode date (> 0000-03-01) to date_t

y: >=0

m: 0...11

d: 0...[27,28,29,30] (depends on length of month)

*/

date_t foo(int y, int m, int d)

{

static unsigned const moff[12] =

/* Month: Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec */

/* Days: 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 */

{ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334};

date_t ret;



/* calculate plain offset */

ret = (date_t)y*365 + moff[m] + d;



/* add leap days */

if( m<2 ) y-=1;

return ret + (date_t)y/4 - y/100 + y/400 - 59;

}



void bar(int *py, int *pm, int *pd, date_t d)

{

register unsigned x;

unsigned y, m;

static unsigned char const tt[5] = { 0, 1, 2, 3, 3};

static unsigned const moff[13]=

/* Month: Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec, Jan, Feb - */

/* Days: 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 31, 29, - */

{ 0, 31, 61, 92, 122, 153, 184, 214, 245, 275, 306, 337, 366 };



x=d/146097; d-=x*146097; y =x*400; /* 400 years (Epoch) */

x=tt[d/36524]; d-=x*36524; y+=x*100; /* 100 years of ^(Century) */

x=d/1461; d-=x*1461; y+=x*4; /* 4 years of ^ (Quad) */

x=tt[d/365]; d-=x*365; y+=x; /* 1 year of ^ (Year) */



if(moff[m=(d>>5)+1] > d) m-=1; /* Month */

d-=moff[m];

if(m>9) { m-=10; y+=1;} else m+=2;



*py = y; *pm = m; *pd = d; /* Deliver */

}





/* Start debugging stuff */



int check(

date_t (*foo)(int,int,int),

void (*bar)(int*, int*, int*, date_t)

)

{

int y=0, m=2, d=0, dm; /* We start at 0000-03-01 */

date_t r=0;

int y1, m1, d1; /* Targets */

static int const mdays[12]=

/* Month: Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec, - */

{ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

do { /* year */

do { /* month */

dm=mdays[m];

if(m==1 && is_leap(y)) dm+=1;

do { /* day */

if(r!=foo(y,m,d)) {

printf("encode error: %04d-%02d-%02d:date2num: %lu - r:%lu\n",

y, m, d, foo(y,m,d), r);

return 1;

}



bar(&y1, &m1, &d1, r);

if(y1!=y || m1!=m || d1!=d) {

printf("decode error: %04d-%02d-%02d (r=%lu) -> %04d-%02d-%02d\n",

y, m, d, r, y1, m1, d1);

return 1;

}

r+=1;

} while((++d)<dm);

d=0;

} while((++m)<12);

m=0;

} while((++y)<6000);



return 0;

}



int main(void)

{

int i;



for(i=10; i--;) check(foo, bar);

printf("foo/bar - done\n");



system("pause");



return 0;

}



(Kleiner Tip am Rande: Wer den Code ausprobieren will, der sollte ihn erst in ein leeres Word-Dokument kopieren, dort nochmal kopieren und dann erst in den Editor einfügen - Sonst gehen die Zeilenumbrüche hops)



Die Funktionen check() und main() sind reiner Debugkram und sollen lediglich den Umgang mit foo() und bar() klar machen.



Den Datentyp date_t (und andere) habe ich deshalb auf unsigned geändert, weil der Code mit vorzeichenlosen Zahlen etwas effizienter sein kann.



Das Ganze geht sogar noch (ein klein wenig) krasser - Aber da sollst Du selbst draufkommen. Bei foo() wäre es auch wünschenswert, wenn die Monatsangabe kleiner als Null oder grösser als 11 (Dezember) sein dürfte - Aber das überlass ich Dir als Hausaufgabe



Viel Spass,



Uli



Edit: Kommentare im Quelltext korrigiert, Links hinzugefügt und den Text etwas umformuliert.



























































geschrieben von

Login

E-Mail:
  

Passwort:
  

Beitrag anfügen

Symbol:
 
 
 
 
 
 
 
 
 
 
 
 
 

Überschrift: