ringelkrat
Goto Top

Zeit-Differenz zwischen Zeit- und Datumsangaben mit Batchprogramm berechnen

Ich habe lange nach einem Skript gesucht, welches Zeit-Differenzen berechnet. Schließlich habe ich versucht es selber zu programmieren. Vielleicht kann ja jemand davon profitieren.
Mit Batchprogrammen habe mich bisher nur wenig beschäftigt. Ich bin noch "blutiger Anfänger" und programmiere normalerweise in höheren Sprachen.

Ich habe mir die Mühe gemacht ein Skript zu schreiben, welches die Zeit berechnet, welche zwischen zwei Datumsangaben liegt. Dabei werden die Regeln von Schaltjahren beachtet.
Die Datumsangaben werden als Parameter übergeben und das Resultat wird über %result% zurückgegeben.
Es gibt bestimmt Möglichkeiten das Batch-Skript zu verbessern! Ich hoffe, dass es aber keine Fehler gibt.

Das Skript kann z.B. so aufgerufen werden:
datums_diff.bat 09.11.1990 13:00:59 18.10.2008 00:00:00
echo %result%

Hier der Quellcode von datums_diff.bat in der Version 1.1.33:
@echo off
REM Dieses Skript liefert die Differenz von zwei Datums-Angaben (mit Uhrzeit) in Sekunden und beachtet dabei die Schaltjahre
REM Die Zeiten müssen in folgender Form übergeben werden: datums_diff.bat 31.12.1987 02:34 01.04.2009 14:01
:start_DatumsDiff
	REM Rufe die Funktion DateToSec zwei mal auf
	set datum=%1 && set zeit=%2
	call :DateToSec
	set /a sek1=%result%
	set datum=%3 && set zeit=%4
	call :DateToSec
	set /a sek2=%result%

	REM Berechne Differenz
	set /a result=%sek1%-%sek2%
	goto ende
:end_DatumsDiff

:DateToSec
	REM Hier ist die Subroutine für die Berechnung der Sekunden seit dem 00.00.0000 00:00:00. Benötigt werden die Parameter %zeit% und %datum% in denen die Daten in folgender Form stehen: HH:MM:SS bzw. DD.MM.YYYY mit führenden Nullen. Der Rückgabewert liegt in %result%.
	REM Parameter einlesen (dabei werden die führenden Nullen eliminiert):
	for /F "eol=; tokens=1,2,3 delims=." %%i in ("%datum%") do (  
		set /a d1=1%%i-100 && set /a m1=1%%j-100 && set /a y1=1%%k-10000
	)

	for /F "eol=; tokens=1,2,3 delims=:" %%i in ("%zeit%") do (  
		set /a h1=1%%i-100 && set /a i1=1%%j-100 && set /a s1=1%%k-100
	)
	REM @echo Folgendes wurde also eingegeben: %d1%.%m1%.%y1% %h1%:%i1%:%s1%

	REM Tage für die Jahre hinzufügen
	set /a d1=%d1%+(365*%y1%)

	REM Tage für die Monate hinzufügen
	if %m1% EQU 2 set /a d1=%d1%+31
	if %m1% EQU 3 set /a d1=%d1%+59
	if %m1% EQU 4 set /a d1=%d1%+90
	if %m1% EQU 5 set /a d1=%d1%+120
	if %m1% EQU 6 set /a d1=%d1%+151
	if %m1% EQU 7 set /a d1=%d1%+181
	if %m1% EQU 8 set /a d1=%d1%+212
	if %m1% EQU 9 set /a d1=%d1%+243
	if %m1% EQU 10 set /a d1=%d1%+273
	if %m1% EQU 11 set /a d1=%d1%+304
	if %m1% EQU 12 set /a d1=%d1%+334

	REM Schaltjahres-Korrektur (dies ist nur für die Tage nach dem 15. Oktober 1582 gültig)
	set /a zusaetzlicheTage=%y1%/4
	set /a zusaetzlicheTage=%zusaetzlicheTage% - (%y1%/100)
	set /a zusaetzlicheTage=%zusaetzlicheTage% + (%y1%/400)
	set /a d1=%d1%+%zusaetzlicheTage%

	REM Berechne nun die Sekunden
	set /a result=%s1% + 60*(%i1% + 60*(%h1% + 24*%d1%))
	goto :eof
:end_DateToSec

:ende
Ich habe auch bewusst auf VBS, PowerShell o.a. externe Hilfsmittel verzichtet.

In der aktuellen Version wurden die Verbesserungsvorschläge von @bastla (Zeit-Differenz zwischen Zeit- und Datumsangaben mit Batchprogramm berechnen) berücksichtigt.

Content-ID: 127350

Url: https://administrator.de/contentid/127350

Ausgedruckt am: 23.11.2024 um 05:11 Uhr

bastla
bastla 18.10.2009 um 12:13:33 Uhr
Goto Top
Hallo Ringelkrat und willkommen im Forum!

Nur als Anmerkung: Den Aufwand für das Entfernen der führenden Nullen kannst Du (am Beispiel von der in %%i enthaltenen Zahl) so reduzieren:
set /a d1=1%%i-100
Grüße
bastla
Biber
Biber 18.10.2009 um 13:00:54 Uhr
Goto Top
Moin Ringelkrat,

willkommen im Forum und danke für diesen Beitrag.

Gefällt mir bezüglich Aufbau, Struktur, Nachvollziehbarkeit und Kommentierung sehr gut.
Ich habe es deshalb von "Allgemeinem Beitrag" als "Anleitung" umgestuft.

Und ich bin gespannt, ob auch diese Skriptversion 1.0 im Lauf der Zeit noch Updates erfahren wird. face-wink

Rein handwerklich bzw. aus Gewohnheit würde ich zwei Sachverhalte anders angehen:

1) In einem Algorithmus wie diesem
...
REM Tage für die Monate hinzufügen
if %m1% GTR 1 set /a d1=%d1%+31
if %m1% GTR 2 set /a d1=%d1%+28
if %m1% GTR 3 set /a d1=%d1%+31
if %m1% GTR 4 set /a d1=%d1%+30
if %m1% GTR 5 set /a d1=%d1%+31
if %m1% GTR 6 set /a d1=%d1%+30
if %m1% GTR 7 set /a d1=%d1%+31
if %m1% GTR 8 set /a d1=%d1%+31
if %m1% GTR 9 set /a d1=%d1%+30
if %m1% GTR 10 set /a d1=%d1%+31
if %m1% GTR 11 set /a d1=%d1%+30
...
...wird ein Programm im Schnitt 6x, im worst case 12x erst eine IF-Prüfung und dann eine SET /A-Anweisung ausführen.

Wenn Du es so machst:
REM Tage für die Monate hinzufügen
if %m1% EQU 1 set /a d1=%d1%+31
if %m1% EQU 2 set /a d1=%d1%+31+28
if %m1% EQU 3 set /a d1=%d1%+31+28+31
if %m1% EQU 4 set /a d1=%d1%+31+38+31+30
....
...dann sind es im Schnitt 6, im schlimmsten Fall 12 IF-Prüfungen und immer nur EINE SET /A-Anweisung.

2)
Ich würde, wenn doch die Berechnung/Umrechnung für "Datumswert1" und "Datumswert2" den gleichen Algorithmus verwendet, diesen wiederverwendbaren Algorithmus in eine Unterroutine/einen Call:Block packen.
Aber das ist rein handwerklich - und wie gesagt: Dein Beispiel ist sehr gut verständlich und nachvollziehbar aus und macht, was ein Skript machen soll.

Werde es entsprechend bewerten.

Grüße
Biber
bastla
bastla 18.10.2009 um 13:27:28 Uhr
Goto Top
Hallo Ringelkrat und Biber!

Zu 1) ließe sich zB auch ein Array nachbilden:
set /a md1=0
set /a md2=31
set /a md3=31+28
set /a md4=31+28+31
...

call set /a d1+=md%m1%
Grüße
bastla
Ringelkrat
Ringelkrat 18.10.2009 um 14:36:59 Uhr
Goto Top
Hallo bastla,

vielen Dank für den Verbesserungsvorschlag! Er hat den Umfang des Codes gut reduziert. Habe ihn eben in der Anleitung eingebaut (s.o.).

Grüße, Ringelkrat
Ringelkrat
Ringelkrat 18.10.2009 um 14:47:01 Uhr
Goto Top
Hallo Biber,

freut mich, dass mein Beitrag Dir gefällt. Ich war mir nicht sicher, ob ich ihn als Anleitung einstufen sollte, da die Aussicht bestand, dass man einiges verbessern könnte und somit noch nicht als Anleitung dienen kann. Aber ich denke der Beitrag ist jetzt richtig platziert.

Zu:
  1. Es geht natürlich schneller wenn man die Variable nur einmal verändert. Habe deinen Vorschlag übernommen (s.o.).
  2. Auch diesen Vorschlag habe ich mir zu Herzen genommen. Er hat den Umfang des Codes gut reduziert. Habe den Beitrag 'HowTo - Wie man Subroutinen in Batchfiles erstellt' von @fritzo als Hilfe genommen und ihn in die Anleitung eingebaut (s.o.).

Grüße, Ringelkrat
Ringelkrat
Ringelkrat 18.10.2009 um 15:02:36 Uhr
Goto Top
Hallo bastla,

interessante Methode mit dem Array! Was passiert denn genau in Zeile 07.:
call set /a d1+=md%m1%
?

Ach so, warum sollte eigentlich die Methode über das Array performanter sein?
    • In der Array-Version müssen 12 Variablen angelegt werden und am Ende wieder freigegeben (von cmd.exe). Hinzu kommt noch ein Aufruf und eine Zuweisung.
    • In Bibers Vorschlag müssen lediglich 11 Vergleiche durchgeführt werden (die auch parallel durchgeführt werden können) und eine Zuweisung.

Das würde ich gerne besser verstehen.

Grüße, Ringelkrat
Biber
Biber 18.10.2009 um 15:54:58 Uhr
Goto Top
Moin Ringelkrat,

zu deinem Bedenken...
Ich war mir nicht sicher, ob ich ihn als Anleitung einstufen sollte...
Der Bereich "Batch & Shell" ist ja ein Unterbereich von "Entwicklung" und "Programmierung".

Ich sehe deine Anleitung als Anleitung,
  • weil du aufzeigst wie ein mittelkomplexes Problem schrittweise strukturiert in lösbare Teilprobleme zerlegt wird
  • weil du einen Algorithmus entwickelst, der nachvollziehbar und übertragbar ist
  • weil du auf Lesbarkeit und Wartbarkeit Wert legst

Ob da nun handwerklich etwas zu verbessern sein mag, ob es sich nun auf 20 oder 30 Zeilen eindampfen ließe, das ist nicht das Wesentliche.

Irgendwelche wundersamen Zauberzeilen, die irgendwas ganz furchtbar Undokumentiertes vollbringen, die lassen wir in den Tankstellen-PC-Zeitschriften - hier im Forum sind mir Algorithmen wichtiger.

Grüße
Biber
bastla
bastla 18.10.2009 um 19:06:59 Uhr
Goto Top
Hallo Ringelkrat!
warum sollte eigentlich die Methode über das Array performanter sein?
Habe ich ja gar nicht behauptet ...

Du hattest erwähnt, dass Du ansonsten in "höheren" Sprachen schreibst, weshalb ich annahm, dass Dich das "Batch-Array-Konzept" interessieren könnte.
Was passiert denn genau in Zeile 07
Durch "call" wird temporär eine weitere CMD-Instanz aufgerufen, in welcher der "Index" (die Variable %m1%) aufgelöst wird, wodurch sich im "Hauptprogramm" eine Zuweisung der Art
set /a d1+=md3
für ein Datum im März ergibt - dadurch wird dann der Wert von %d1% um den Wert von %md3% erhöht (die Schreibweise += ist Dir ja sicher geläufig).

Grüße
bastla
Ringelkrat
Ringelkrat 18.10.2009 um 19:55:18 Uhr
Goto Top
Hi bastla,

Danke für die Erklärung. Ich glaube es in etwa verstanden zu haben. Das ist wahrscheinlich wie wenn man in PHP:
<? 
  $foo=2; 
  $wrapper="foo";   
  $bar=$$wrapper; //$bar==2
?>
verwendet. Bin mir aber nicht sicher, habe auch schon lange kein PHP mehr geschrieben.

Was mir neu ist ist, dass man eine Zuweisung der Art
set /a d1+=md3
machen kann. Ich dachte, dass man die Variablen auf der rechten Seite der Zuweisung in Prozent-Zeichen schreiben muss. Warum macht man dann nicht gleich:
set /a array1=17
set /a index=1
set /a erg=array%index%
echo %erg%
statt dem Code mit dem call davor!?

Grüße, Ringelkrat
bastla
bastla 18.10.2009 um 20:17:44 Uhr
Goto Top
Hallo Ringelkrat!

Du hast mit Deinen Überlegungen völlig Recht - es genügte eigentlich (auch wenn das "call" nicht schadet, aber wir sind ja am Optimieren face-wink)
set /a d1+=md%m1%
Da es hier um eine Berechnung geht, gilt:
Alle nicht-nummerischen Zeichenfolgen im Ausdruck werden als Zeichenfolgen von Umgebungsvariablen behandelt, deren Werte vor der Verwendung in Zahlen konvertiert werden. Wenn eine Umgebungsvariable angegeben wird, die nicht definiert ist, wird für diese der Wert Null verwendet. Somit können Sie mit Umgebungsvariablen Berechnungen vornehmen, ohne %-Zeichen einzugeben (siehe Online-Hilfe zu "set").

Ich hatte zunächst gewohnheitsmäßig (da die Prozentzeichen bei einem gewöhnlichen "set" erforderlich sind):
call set /a d1+=%%md%m1%%%
In dieser Form wird tatsächlich "call" benötigt, um zunächst %m1% aufzulösen (wie oben als Beispiel zum Wert 3) und danach dann %md3%.

Bei meinem vorigen Post hatte ich offensichtlich noch immer diese Version im Hinterkopf ... face-sad

Grüße
bastla
Ringelkrat
Ringelkrat 03.11.2009 um 12:54:55 Uhr
Goto Top
Hey Biber,

noch mal ein wichtiger Nachtrag zu 1.: Du hast geschrieben:
if %m1% EQU 1 set /a d1=%d1%+31
. Damit werden aber im Januar 31 Tage addiert, im Februar 28 usw. Richtig muss es aber heißen:
if %m1% EQU 2 set /a d1=%d1%+31
Hatte es leider genau so übernommen. Ist mir jetzt im neuen Monat aufgefallen und ich habe es auch oben korrigiert (Version 1.1.33).

Leider ist mir noch ein schlimmer Fehler unterlaufen: Ich hatte die Schalttage abgezogen statt zu addieren. Ist jetzt auch behoben.

Ich hoffe es haben noch nicht so viele davon Gebrauch gemacht und, dass jetzt alles richtig ist!

Grüße, Ringelkrat
Biber
Biber 03.11.2009 um 13:59:13 Uhr
Goto Top
Moin Ringelkrat,

besten Dank für den Bugfix und deine Rückmeldung.

Den Bug hab ich genauso dreimal überlesen und hätte es wahrscheinlich nie erfahren ohne dich.

Grüße
Biber
pieh-ejdsch
pieh-ejdsch 30.05.2010, aktualisiert am 18.10.2012 um 18:42:20 Uhr
Goto Top
Hi Ringelkrat,

wenn ein Schaltjahr ist und das Datum ist vor dem 1.3. , dann musst Du den Schalttag wieder abziehen weil dieser ja noch nicht vergangen ist.
:: LY  = Schaltjahr
set /a LY = JJ / 4 - ( JJ - 1 ) / 4  - (JJ / 100 - ( JJ - 1 ) / 100) + ( JJ / 400 - ( JJ - 1 ) / 400 )

Normal berechnest Du ja immer die Vergangenen Jahre aber bei den zusätzlichen Tagen den Schalttag vom Aktuellem Jahr, also musst Du den Schalttag LY für Januar und bis 28. Februar wieder rausrechnen.
if %m1% EQU 1 set /a d1 - = LY
if %m1% EQU 2  if not %d1% == 29 set /a d1 + = 31 - LY

dann nicht gleich die Sekunden komplett ausrechnen, da ja CMD nur bis 2.147.483.647 (2^32) richtig rechnet. also erst bis zu den Vergangenen Minuten ausrechen und die Sekunden in eine ExtraVariable schreiben. Wenn Du dann von den ersten Vergangenen Minuten die zweiten Verganen Minuten abziehst wird es aber auch ein NegativWert. Also Zeile 14. den Zweiten Wert vom ersten abziehen. Dann wenn die Minuten fertig abgezogen sind in Sekunden umrechnen und die ersten sekunden abziehen und die zweiten Sekunden dazurechnen.

Wenn Du in Sekunden ausrechnest kannst Du nur eine Spanne von ca. 68 Jahren und knapp 3 Wochen berechnen!

ein anderer Ansatz fürs Datumausrechnen in Tage allerdings ab 01.01.1600 ist von hier Datei verschieben um bestimmte Uhrzeit.
echo off
for /f "tokens=2 delims=()" %%i in ('echo.^|date') do set "Format=%%i"  
set "Format=%Format:-=.%"  
echo %Format:JJ=JJJJ% %date%
for /f "tokens=1-3 delims=-." %%i in ("%Format%") do for /f "tokens=1-3 delims=.-" %%l in ("%date%") do set "%%i=%%l" & set "%%j=%%m" & set "%%k=%%n"  
:: LY              Schaltjahr
set /a LY = JJ / 4 - ( JJ - 1 ) / 4  - (JJ / 100 - ( JJ - 1 ) / 100) + ( JJ / 400 - ( JJ - 1 ) / 400 )
set /a xday = 0
set /a MM=10%MM% %% 100
:: beforemonthdays    Vergangene Tage im Jahr bis zum Vormonat
if %MM% lss 3 (
	set /a LY = 0
	if %MM% equ 2 (set /a xday = 2
	) else set /a xday = 1
) else if %MM% equ 3 set /a xday = - 1
set /a beforemonthdays =  (( MM - 4 + MM / 8 ) / 2 + ( MM - 1 ) * 30 ) + LY + xday
set /a TT=10%TT% %% 100
:: alldays         Vergangene Tage bis Dato als TageZahl
set /a alldays=((JJ-1600)*365)+((JJ-1601)/4)+((JJ-1601)/100)-((JJ-1601)/400) + beforemonthdays + TT
echo Vergangene Tage von 1600 sind %alldays%&pause

[Edit] in der Set /a Anweisung kannst Du die Prozentzeichen um die Variable weglassen. [Edit]
Gruß Phil