Solved // Datum von gestern ohne Library berechnen

Hi!

Für mein aktuelles Projekt benötige ich das Datum von gestern. Die Uhrzeit und das Datum lese ich von einer RTC (DS1307) direkt aus. Also ohne irgendeine Libary. Wie kann ich also aus dem aktuellen Datum das Datum von gestern oder vor einer Woche OHNE eine Libary berechnen?
Ich das jetzt irgendwie mit dem UnixTimestamp versucht. Mein Onkel Google hat mir leider kein Tipp gegeben...

Gruß...

-Jan-:
Für mein aktuelles Projekt benötige ich das Datum von gestern. Die Uhrzeit und das Datum lese ich von einer RTC (DS1307) direkt aus. Also ohne irgendeine Libary. Wie kann ich also aus dem aktuellen Datum das Datum von gestern oder vor einer Woche OHNE eine Libary berechnen?

Mit zwei Funktionen. Eine Funktion, die Dir das Datum von heute in eine fortlaufende Tageszahl umwandelt. Von der Zahl ziehst Du dann 1 ab. Und dann brauchst Du noch eine gegensätzliche Funktion, die Dir die Tageszahl wieder in das Datum zurückverwandelt.

Pseudocode für die erste der beiden Funktionen findest Du sogar bei Wikipedia:

So geht es mit Unix Time. Aus der Time Library geklaut und etwas angepasst:

#define LEAP_YEAR(Y) ( ((1970+Y)>0) && !((1970+Y)%4) && ( ((1970+Y)%100) || !((1970+Y)%400) ) )
#define SECS_PER_MIN  (60UL)
#define SECS_PER_HOUR (3600UL)
#define SECS_PER_DAY  (SECS_PER_HOUR * 24UL)

const uint8_t monthDays[] = {31,28,31,30,31,30,31,31,30,31,30,31};

struct tm_t
{ 
	uint8_t Second; 
	uint8_t Minute; 
	uint8_t Hour; 
	uint8_t Wday;
	uint8_t Day;
	uint8_t Month; 
	uint16_t Year;
};

void unixToDate(uint32_t unixtime, struct tm_t& tm)
{
	uint8_t year, month, monthLength;
	uint32_t days;

	tm.Second = unixtime % 60;
	unixtime /= 60;
	tm.Minute =  unixtime % 60;
	unixtime /= 60;
	tm.Hour = unixtime % 24;
	unixtime /= 24;
	tm.Wday = ((unixtime + 4) % 7) + 1;

	year = 0;  
	days = 0;
	while((unsigned)(days += (LEAP_YEAR(year) ? 366 : 365)) <= unixtime) {
		year++;
	}
	tm.Year = year;

	days -= LEAP_YEAR(year) ? 366 : 365;
	unixtime -= days;

	days=0;
	month=0;
	monthLength=0;
	for (month=0; month<12; month++) {
		if (month==1) {
			if (LEAP_YEAR(year)) {
				monthLength=29;
			} else {
				monthLength=28;
			}
		} else {
			monthLength = monthDays[month];
		}

		if (unixtime >= monthLength) {
			unixtime -= monthLength;
		} else {
			break;
		}
	}
	tm.Year += 1970;
	tm.Month = month + 1;
	tm.Day = unixtime + 1;
} 

uint32_t dateToUnix(struct tm_t& tm)
{   
	unsigned int i;
	uint32_t seconds;

	tm.Year -= 1970;
	seconds = tm.Year*(SECS_PER_DAY * 365);
	for (i = 0; i < tm.Year; i++) {
		if (LEAP_YEAR(i)) {
			seconds +=  SECS_PER_DAY;
		}
	}

	for (i = 1; i < tm.Month; i++) {
		if ( (i == 2) && LEAP_YEAR(tm.Year)) { 
			seconds += SECS_PER_DAY * 29;
		} else {
			seconds += SECS_PER_DAY * monthDays[i-1];
		}
	}
	seconds+= (tm.Day-1) * SECS_PER_DAY;
	seconds+= tm.Hour * SECS_PER_HOUR;
	seconds+= tm.Minute * SECS_PER_MIN;
	seconds+= tm.Second;
	return seconds; 
}

void printDate(struct tm_t& tm)
{
	Serial.print(tm.Day); Serial.print('/'); Serial.print(tm.Month); Serial.print('/'); Serial.println(tm.Year);
	Serial.print(tm.Hour); Serial.print(':'); Serial.print(tm.Minute); Serial.print(':'); Serial.println(tm.Second);
}

void setup()
{
	Serial.begin(9600);
}

void loop()
{
	tm_t tm;
	tm.Day = 27;
	tm.Month = 6;
	tm.Year = 1971;
	tm.Hour = 12;
	tm.Minute = 31;
	tm.Second = 25;

	printDate(tm);
	unsigned long time = dateToUnix(tm);
	Serial.println(time);
	Serial.println();

	time = time - 7 * SECS_PER_DAY;
	Serial.println(time);

	unixToDate(time, tm);
	printDate(tm);
	Serial.println();

	delay(5000);
}

Es gibt auch einfachere Algorithmen, aber nicht alles was man im Netz (vor allem in Foren) findet geht auch :slight_smile:

Hallo Ihr beiden,

vielen Dank für Eure Antworten. :top:
Die Lösung mit dem Unix Timestamp ist aber ein ganz schöner Brocken...

Da sieht der Algorithmus mit dem Julianischen Datum auf Wikipedia deutlich einfacher aus. Scheinbar bin ich aber zu blöd den dort gezeigten Pseudocode in Arduino-Code umzuwandeln. Mein Julianisches Datum stimmt nämlich hinten und vorne nicht. An die zweite Funktion habe ich mich schon gar nicht gewagt. :~
Wäre super wenn mir da jemand helfen könnte!

Ja, julianisches Datum ist wesentlicher einfacher. Unix-Zeit sind die Sekunden seit 1.1.1970. Beim JD werden dagegen nur die Tage gezählt.

Es gibt wie gesagt auch für Unix-Zeit einfachere Algorithmen. z.B. hier in Ruby:

Da ich aber kein Ruby spreche, habe ich es gelassen dass zu portieren

Das hier sieht auch ziemlich kompakt aus und ist C:
http://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&p=439731#439731
Damit hat man aber nur unix -> tm struct

Hallo,
ich gehe mal davon aus, dass der Arduino länger laufen soll.
Vorschlag: Datum merken und erst wechseln bei Datumswechsel

also:
aktuelles Datum: 27.07.2014
gemerktes Datum: 26.07.2014
wenn sich das aktuelle Datum ändert, das gemerkte Datum auf das vorherige aktuelle Datum setzen

Nachteil: funktioniert erst nach einem Tag automatisch und das nach jeder Programaktualisierung / Reset
müsste im Programmcode korrigiert werden - Datum von gestern händisch eingeben
Speicherung der Werte im EEPROM wäre gut

Gruss
Kurti

-Jan-:
Da sieht der Algorithmus mit dem Julianischen Datum auf Wikipedia deutlich einfacher aus. Scheinbar bin ich aber zu blöd den dort gezeigten Pseudocode in Arduino-Code umzuwandeln. Mein Julianisches Datum stimmt nämlich hinten und vorne nicht.

Der Wikipedia-Pseudocode läßt sich supereinfach in C-Code umsetzen.
Leider scheint das Ergebnis nicht ganz genau zu sein.
Für verschiedene Tage zwischen 1990 und 2014 habe ich getestet, der Wikipedia-Code bekommt zwei Tage zuviel heraus.

Falsch rechnender Code nach Wikipedia:

long julianDay_wrong(int Jahr, int Monat, int Tag)
{
  float y = Jahr + (Monat - 2.85)/12;
  float A  = (long)(367 * y) - 1.75 * (long)(y) + Tag;
  float B  = (long)(A) - 0.75 * (long)(y / 100);
  return (long)(B) + 1721117;
}

Wenn man sich darauf verlassen könnte, dass stets zwei Tage zuviel herauskommen, könnte man auch damit arbeiten. Allein, mir fehlt das rechte Vertrauen.

Hier ist eine andere Möglichkeit zum errechnen der julianischen Tageszahl, bei der das richtige Ergebnis herauskommt:

long julianDay(int Jahr, int Monat, int Tag)
{
  Jahr+=8000;
  if(Monat<3) { Jahr--; Monat+=12; }
  return (Jahr*365L) +(Jahr/4) -(Jahr/100) +(Jahr/400) -1200820 +(Monat*153+3)/5-92 +Tag-1;
}

Ein Rechenschema zum zurückrechnen des Kalenderdatums aus der Julianischen Tageszahl kennt Wikipedia auch:

Ob dieser Algorithmus besser ist?
Bekommst Du das selber hin?

Hallo,

ja, der Arduino soll in diesem Porjekt ständig laufen. Die Idee mit dem Merken des vorherigen Datums ist genial und ziemlich einfach umzusetzen. Aber ich sehe da große Probleme bei den Scheibzyklen des EEPROMs. Wenn jeden Tag, über z.B. 5 Jahre das EEPROM beschrieben wird kommt da schon was zusammen. Da ist mir das Berechnen doch lieber.

Irgendwie hatte ich mir die Unix Timestamp-Lösung deutlich einfacher vorgestellt. In Porgrammiersprachen wie PHP ist das alles nicht so aufwändig, das Datum von gestern auszugeben. 8)

Vielen Dank für den Code, jurs!!
Ja das wäre super, wenn Du die zweite Umrechnungsfunktion auch noch portieren könntest. Meine portierte Funktion gibt für heute den 09.09.-4017 aus. =(

void berechneUnixzeit()// ************** Unix-Zeitstempel berechnen ****************************************
{
leseZeit(); // aktuelles Datum und Zeit holen
aktStunde=aktStunde-2; //Ausgleich Zeitzone+Sommerzeit
aktMonat = (aktMonat + 9) % 12;
aktJahr = aktJahr -aktMonat/10;
unixZeit= (365LaktJahr + aktJahr/4 - aktJahr/100 + aktJahr/400 + (aktMonat306 + 5)/10 + ( aktTag - 1 )-719468L) * 86400L + aktStunde3600L + aktMinute60 + aktSekunde;
}

Stammt, wie ich glaube, von jurs....

Hallo,
"Wenn jeden Tag, über z.B. 5 Jahre das EEPROM beschrieben"

Ja, da kommt etwas zusammen, da muß man aufpassen.
Damit auch etwas zusammenkommt rechne ich hier einmal mit 50 Jahren
Das sind dann so um die 18250 Schreiboperationen.
Gruß und Spaß
Andreas

Hallo,

warum soll/darf keine Librarie verwendet werden?

Gruß
Peter

Weil die durchweg irgendwie Murks sind..:wink:
Meine Uhr läuft auch längst ohne, da hat man einfach mehr Freiheiten.

Bei Verwendung der Time Librarie wäre die Berechnung des Datums von gestern oder vor einer Woche in wenigen Zeilen Code erledigt.

Warum sind die durchweg irgendwie Murks?

Er kann doch seine RTC (DS1307) behalten und weiterverwenden, aber eben nur zur Berechnung eines zurückliegenden Datums die Time Librarie verwenden.

Gruß
Peter

Weil sie alle irgendwelche Macken haben: eine ist verbuggt (mit der habe ich meine RTC nicht einmal gestellt bekommen, auch das mache ich inzwischen komplett "zu Fuss", ne andere frisst Unmengen Speicher- wenn mans selber macht (so schwer ist das wirklich nicht) hat man eben die absolute Kontrolle: das auslesen,was interessiert, das berechnen, was gebraucht wird (und eben auch nur dann, wenn es gebraucht wird)-und fertig.
Je nachdem, was im Projekt noch alles drin ist, merkt man das ander Performance dann schon.

Mahlzeit,

http://www.mikrocontroller.net/articles/AVR-Tutorial:_Speicher

EEPROM > 100000 Schreibzyklen

100.000 Tage / 365,25 ( Schaltjahre berücksichtigt ) = 273 Jahre

Ich denke, dass ist ausreichend.

Gruss
Kurti

-Jan-:
Vielen Dank für den Code, jurs!!

Ich habe nochmal den fehlerhaften Wikipedia-Algorithmus für die Berechnung der Julianischen Tageszahl überprüft und dabei festgestellt: Zumindest zwischen den Jahren 1582 und 2850 rechnet der dort angegebene Algorithmus stets eine um zwei Tage zu hohe Tageszahl aus. Der Algorithmus ist daher korrekt und nur die letzte Konstante ist falsch.

Statt 1721117 muss es im Wikipedia-Algorithmus einfach nur 1721115 heißen, dann passt es.

-Jan-:
Ja das wäre super, wenn Du die zweite Umrechnungsfunktion auch noch portieren könntest. Meine portierte Funktion gibt für heute den 09.09.-4017 aus. =(

Wieso hast Du solche Probleme, den Algorithmus von Wikipedia nach C umzusetzen?

Meine Funktion umgesetzt nach Wikipedia:

void calDate(long JD, int &Jahr, int &Monat, int &Tag)
{
  float Z = long(JD + 0.5);
  float g = long((Z - 1867216.25) / 36524.25);
  float A = Z + 1 + g - long(g/4);
  float B = A + 1524;
  float C = long((B-122.1) / 365.25);
  float D = long(365.25 * C);
  float E = long((B-D) / 30.6001);
  Tag = B - D - long(30.6001*E);
  if (E<14)  Monat = E - 1; else Monat = E - 13;
  if (Monat>2)  Jahr = C - 4716; else Jahr = C - 4715;
}

Für einen alternativen Algorithmus ohne die Verwendung von Gleitkommazahlen müßte man mal mit Google suchen, da gibt es sicher auch etwas.

-Jan-:
Irgendwie hatte ich mir die Unix Timestamp-Lösung deutlich einfacher vorgestellt. In Porgrammiersprachen wie PHP ist das alles nicht so aufwändig, das Datum von gestern auszugeben. 8)

In Standard C/C++ und anderen Sprachen ist das nur einfacher weil die Funktionen zur Konvertierung zwischen Epoche und Kalender Zeit schon implementiert sind. Vielleicht ist die Implementierung da einfacher, aber was gemacht wird, wird vor dir versteckt.

Hier ist z.B. was man alles in C++ auf dem PC machen kann:
http://en.cppreference.com/w/c/chrono

Hallo,
zwei Lösungen habe ich noch:

  1. einfach aktuelles Tagesdatum -1, wenn 0 herauskommt, ist es der Vormonat. Ob machbar, hängt von der nachfolgenden Auswertung ab
  2. stelle Deine RTC einfach auf das Datum von gestern. Ob machbar, hängt davon ab, ob das aktuelle Datum auch benötigt wird.

Gruss
Kurti

Hallo!

Okay, okay, 273 Jahre ist schon ein Wort. Aber ein Problem besteht immer noch. Der Arduino muss über die Nacht an sein, denn sonst verpennt er den Tageswechsel und das gestrige Datum fehlt. Gerade bei der Entwicklung (bei der der Arduino nicht nachts an ist) ist das nicht so gut. Da müsste ich dann jedes mal das Datum neu im EEPROM speichern.

Die Gründe, warum ich keine Library einsetzen möchte hat Rabenauge eigentlich schon genannt. Wenn man keine Library einsetzt und sich selbst eine Funktion schreibt (wenn man nicht zu blöde ist wie ich... :slight_smile: ) ist der Lerneffekt größer und man weiß auch, was genau da passiert.

Danke jurs für die zweite Funktion. Das größte Problem hatte ich bei den Variablentypen. Ich war mir nicht sicher ob ich jetzt "long", "float" oder "int" einsetzen muss.

Ja, aber dann muss ich auch Schaltjahre, Jahreswechsel, ... berücksichtigen. Die Uhr kann nicht neu gestellt werden. Die aktuelle Uhrzeit und das Datum werden nämlich auch noch gebraucht.

Das Thema kann somit als "solved" angesehen werden. Ich möchte mich nochmals recht herzlich bei allen für ihre Hilfe und Ideen bedanken! Finde es echt schön, wie hier auch den Anfängern geholfen wird und diese nicht dumm "angemacht" werden!!

Gruß

Hallo zusammen!

Ich sitze gerade an einem Projekt mit einer GPS Uhr und möchte auch den Tag berechnen. Dieser wird ja bekanntlich nicht mit dem NMEA Protokoll gesendet.
Ich habe jetzt im Int Format das Jahr, den Monat und den Tag. Nur bekomme ich die hier beschriebene Formel nicht zum laufen weil ich mit dem return Befehl nicht klar komme. Kann mir hier jemand mit meinem Problem helfen?

int Jahr=2014,Monat=8,Tag=15;

void setup(){
Serial.begin(300);

}

void loop()

{

}

void test()
{
long test;
Jahr+=8000;

if(Monat<3)
{
Jahr--; Monat+=12;
}
return (Jahr365L) +(Jahr/4) -(Jahr/100) +(Jahr/400) -1200820 +(Monat153+3)/5-92 +Tag-1;

}