Delphi: różnica czasów i dat w dniach, minutach oraz godzinach

W Delphi wartości typu TDateTime można dodawać i odejmować jak zwykłe liczby rzeczywiste. Część całkowita oznacza liczbę dni, a część ułamkowa - czas w obrębie doby, dlatego różnicę między dwiema datami i godzinami najlepiej obliczać na kompletnych wartościach zawierających jednocześnie datę oraz czas.

Jeżeli trzeba obliczyć wyłącznie liczbę dni, można użyć funkcji DaysBetween. Dla całkowitej liczby minut odpowiednia jest funkcja MinutesBetween, dla godzin - HoursBetween, a dla sekund i milisekund - odpowiednio SecondsBetween oraz MilliSecondsBetween. Gdy potrzebny jest rozkład wyniku na dni, godziny i minuty, należy odjąć dwie pełne wartości typu TDateTime, a następnie rozłożyć uzyskany odstęp na poszczególne jednostki.

Jak Delphi zapisuje datę i czas

W Delphi, jeden dzień to wartość '1' a czas jest zapisany jako wartość dziesiętna. Jeżeli weźmiemy dzisiejszy dzień i godzinę 2:45 to otrzymasz wartość (strzelam z dniem) 42854.114576. Od tej wartości możesz odejmować te przykładowe 20 minut (0.013888) praktycznie tysiące razy i wszystko będzie się zgadzać, ponieważ wraz z przekroczeniem czasu 00:00 będzie się cofał także dzień.

Datę wraz z godziną można utworzyć funkcjami EncodeDate i EncodeTime, a następnie połączyć operatorem dodawania. Funkcja EncodeDateTime zwraca dane w postaci TDateTime na podstawie wartości określającej dzień, rok miesiąc, godzinę, minutę oraz sekundę.

Z podanej daty można wydobyć rok, miesiąc i dzień za pomocą funkcji DecodeDate, natomiast funkcja DecodeTime wydobywa godzinę, minuty, sekundy oraz milisekundy. Funkcja DecodeDateTime zwraca jednocześnie składniki daty i czasu.

Różnica między dwiema datami w dniach

Funkcja DaysBetween oblicza różnicę dni pomiędzy dwoma datami. Zwraca różnicę w ilościach dni pomiędzy dwoma datami (typu Integer).

Przykładowo dla dat 5 sierpnia 2003 i 1 lipca 2003 wynik wynosi 35 dni, jeżeli porównywane są same daty bez uwzględniania godzin.

PotrzebaFunkcjaTyp wyniku
Różnica dniDaysBetweenInteger
Różnica godzinHoursBetweenInteger
Różnica minutMinutesBetweenInteger
Różnica sekundSecondsBetweenInteger
Różnica milisekundMilliSecondsBetweenInteger
Różnica miesięcyMonthsBetweenInteger
Różnica latYearsBetweenInteger

Różnica między dwiema godzinami w minutach

Funkcja MinutesBetween zwraca różnicę w minutach pomiędzy dwoma datami typu TDateTime. Mimo nazwy parametry mogą zawierać zarówno samą godzinę, jak i pełną datę z godziną.

Do obliczenia różnicy między 17:35 i 12:40 można wykorzystać wartości utworzone przez EncodeTime. Wynik wynosi 295 minut, czyli 4 godziny i 55 minut.

Jeżeli potrzebny jest wynik w godzinach, można użyć funkcji HoursBetween. Funkcja ta zwraca różnicę w ilościach godzin pomiędzy dwoma datami typu TDateTime.

Różnica daty i godziny jako dni, godziny i minuty

Najważniejsze jest utworzenie obu punktów jako pełnych wartości TDateTime, a nie niezależne odejmowanie dat i godzin. Dla przykładu można porównać 5 sierpnia 2003 17:35 z 1 lipca 2003 12:40, a następnie rozłożyć różnicę na dni, godziny i minuty.

Wartość typu TDateTime przechowuje także przejście przez północ, ponieważ podczas odejmowania zmienia się również część odpowiadająca za dzień. Dzięki temu nie trzeba ręcznie dopisywać ani odejmować 24 godzin, kiedy wcześniejsza godzina znajduje się po północy.

Przykładowy schemat obliczenia jest następujący:

  1. Utwórz pierwszą pełną datę za pomocą EncodeDateTime.
  2. Utwórz drugą pełną datę za pomocą EncodeDateTime.
  3. Odejmij wcześniejszą wartość od późniejszej.
  4. Przelicz uzyskany odstęp na dni, godziny i minuty.

Funkcje DaySpan, HourSpan, MinuteSpan, MonthSpan oraz YearSpan zwracają różnicę odpowiednio w dniach, godzinach, minutach, miesiącach i latach jako wartość typu Double. Warianty DaysBetween, HoursBetween i MinutesBetween zwracają całkowite liczby jednostek.

Poprawiona wersja obliczenia

W pokazanym kodzie można pominąć ręczne rozbijanie daty i czasu na osobne zmienne oraz połączyć wartości za pomocą EncodeDateTime:

uses DateUtils, SysUtils;

var
StartDateTime, EndDateTime: TDateTime;
TotalMinutes: Int64;
Days, Hours, Minutes: Int64;
begin
StartDateTime := EncodeDateTime(2003, 7, 1, 12, 40, 0, 0);
EndDateTime := EncodeDateTime(2003, 8, 5, 17, 35, 0, 0);
TotalMinutes := MinutesBetween(StartDateTime, EndDateTime);
Days := TotalMinutes div (24 * 60);
TotalMinutes := TotalMinutes mod (24 * 60);
Hours := TotalMinutes div 60;
Minutes := TotalMinutes mod 60;
end;

W tym wariancie TotalMinutes zawiera całkowitą liczbę minut, a zmienne Days, Hours i Minutes zawierają rozłożony wynik. Operator div zwraca część całkowitą z dzielenia, natomiast operator mod zwraca resztę.

Obliczenie przez odejmowanie TDateTime

Daty/czasy w formacie TDateTime odejmuja sie i dodaja be zkloptu. Różnicę w formacie czasowym przekształcasz z powrotem na na jakis ludzki zapis i masz co chciałeś - ile upłynęło dni, lat, godzin itp.

Jeżeli pierwszy punkt jest wcześniejszy od drugiego, można obliczyć odstęp wprost:

Difference := EndDateTime - StartDateTime;

Uzyskana wartość zawiera liczbę dni wraz z ułamkiem odpowiadającym części doby. Całkowita liczba dni wynika z części całkowitej, a godziny i minuty można uzyskać z części ułamkowej albo - bezpieczniej - przez funkcje z modułu DateUtils.

W przypadku wartości ułamkowych należy pamiętać, że czas może zawierać sekundy i milisekundy, nawet jeśli interfejs pokazuje tylko godziny i minuty. Jeżeli dokładność ma obejmować wyłącznie minuty, sekundy i milisekundy należy wyzerować podczas wywołania EncodeDateTime.

Dlaczego Writeln powoduje błąd I/O error 105

Błąd I/O error 105 wynika z użycia instrukcji Writeln w aplikacji okienkowej bez prawidłowo otwartego standardowego wyjścia tekstowego. Writeln służy do zapisywania tekstu na standardowym wyjściu, typowym dla programu konsolowego, a nie do wyświetlania komunikatów w formularzu.

W aplikacji formularzowej należy użyć na przykład ShowMessage, etykiety albo kontrolki tekstowej:

ShowMessage(Format('%d dni, %d godzin, %d minut', [Days, Hours, Minutes]));

Jeżeli program jest aplikacją konsolową, należy zadbać o prawidłową obsługę standardowego wyjścia. W typowym programie okienkowym instrukcję Writeln należy usunąć albo zastąpić komunikatem wyświetlanym w interfejsie.

Funkcje Between w module DateUtils

Warto korzystać z całej rodziny funkcji służących do obliczania odstępów:

  • DaysBetween - oblicza różnicę dni pomiędzy dwoma datami.
  • HoursBetween - zwraca różnicę godzin na podstawie podanych wartości typu TDateTime.
  • MinutesBetween - zwraca różnicę w minutach pomiędzy dwoma datami.
  • SecondsBetween - zwraca różnicę w sekundach pomiędzy dwoma datami.
  • MilliSecondsBetween - zwraca różnicę w milisekundach pomiędzy dwoma datami.
  • MonthsBetween - zwraca różnicę w miesiącach pomiędzy dwoma datami.
  • YearsBetween - zwraca różnicę w latach pomiędzy dwoma datami.

Oprócz funkcji zwracających liczby całkowite dostępne są funkcje zwracające wartości typu Double: DaySpan, HourSpan, MinuteSpan, MonthSpan oraz YearSpan.

Funkcje DaysBetween, HoursBetween i MinutesBetween są wygodne, gdy potrzebna jest liczba pełnych jednostek. Jeżeli potrzebny jest szczegółowy rozkład odstępu na dni, godziny i minuty, należy najpierw obliczyć całkowitą liczbę minut, a następnie użyć dzielenia całkowitego i reszty z dzielenia.

Różnica względem bieżącego czasu

Jeżeli użytkownik podaje datę urodzin albo moment uruchomienia aplikacji, a program ma obliczyć czas, który upłynął do chwili obecnej, jako drugi argument można wykorzystać Now.

StartDateTime := EncodeDateTime(1986, 9, 26, 13, 41, 0, 0);
EndDateTime := Now;
TotalMinutes := MinutesBetween(StartDateTime, EndDateTime);

Jeżeli aplikacja ma obliczać czas działania na podstawie zapisanych momentów uruchomienia i zamknięcia, należy przechowywać pełne wartości zawierające datę oraz godzinę. Do rozpoczęcia pomiaru nie jest potrzebny timer, jeśli wynik ma wynikać z różnicy między zapisanym czasem startu i zapisanym czasem zakończenia.

Przy obliczaniu różnicy do chwili obecnej warto pamiętać, że wynik zmienia się wraz z czasem, dlatego Now należy pobierać w momencie wykonywania obliczenia. Jeżeli analizowany jest już zamknięty okres, jako drugi argument należy przekazać zapisany czas zakończenia.

Odejmowanie minut od godziny

Problem z odejmowaniem pojawia się wtedy, gdy używany jest wyłącznie sam czas bez daty. Problemem jest brak daty i używanie tylko samego czasu.

W Delphi, jeden dzień to wartość '1' a czas jest zapisany jako wartość dziesiętna. Jeżeli rozpoczniesz obliczenia od samego czasu 02:45 (0.114576) to te 20 minut możesz odjąć jedynie kilka razy zanim cała data przejdzie w wartości ujemne i zamiast czas odejmować, zacznie go dodawać.

Dlatego przy wielokrotnym odejmowaniu należy używać pełnej wartości TDateTime, zawierającej również datę. Od wartości odpowiadającej godzinie 02:45 można wtedy odejmować kolejne odstępy po 20 minut, a po przekroczeniu północy zmieni się także dzień.

Do zwiększania lub zmniejszania wartości daty i czasu można użyć funkcji IncMinute. Funkcja IncMinute zwiększa ilość minut na podstawie podanych parametrów, a wartość ujemna służy do odejmowania minut.

CurrentValue := IncMinute(CurrentValue, -20);

Podobnie działają funkcje IncDay, IncWeek, IncMonth, IncYear, IncSecond oraz IncMillisecond. Funkcje te automatycznie uwzględniają zmianę dnia, miesiąca albo roku.

Różnica czasu przechodząca przez północ

Jeżeli porównywane są wyłącznie godziny, na przykład 23:50 i 00:10, nie wiadomo bez dodatkowej informacji, czy druga godzina wystąpiła tego samego dnia, następnego dnia, czy w innym okresie. Sam czas nie zawiera informacji o przejściu przez północ.

Jeżeli godziny należą do konkretnych dat, należy przechowywać je jako pełne wartości TDateTime. Wtedy 23:50 jednego dnia i 00:10 następnego dnia są jednoznaczne, a odejmowanie zwróci 20 minut.

Jeżeli znana jest wyłącznie para godzin, a druga godzina oznacza czas następnego dnia, można zastosować regułę dodania jednej doby do wcześniejszej wartości. Pełna data jest jednak rozwiązaniem bardziej jednoznacznym i bezpieczniejszym.

Różnica czasu ze znakiem

Funkcje DaysBetween, HoursBetween i MinutesBetween służą do uzyskania odstępu, a nie do zachowania kierunku odejmowania. Jeżeli ważne jest, czy drugi punkt znajduje się przed pierwszym, należy najpierw odjąć pełne wartości TDateTime, a znak wyniku zachować.

Difference := EndDateTime - StartDateTime;

Wynik dodatni oznacza, że EndDateTime jest późniejszy, a wynik ujemny oznacza, że jest wcześniejszy. Przy rozbijaniu ujemnej wartości na dni, godziny i minuty trzeba najpierw ustalić kierunek, a następnie rozłożyć wartość bezwzględną.

Data i czas a lata oraz miesiące

Różnica wyrażona w dniach, godzinach i minutach jest jednoznaczna. Różnica wyrażona jako lata, miesiące i dni wymaga przyjęcia reguły, ponieważ miesiące mają różną długość, a lata mogą być przestępne.

Do obliczania pełnych różnic w latach i miesiącach można użyć funkcji YearsBetween oraz MonthsBetween. Funkcja IsLeapYear zwraca True, jeżeli podany rok jest rokiem przestępnym.

Dla wyniku w rodzaju „xx lat yy miesięcy zz dni vv godzin nn minut” zwykle trzeba wykonać obliczenia etapami: najpierw ustalić pełne lata, następnie pełne miesiące, potem dni, a na końcu godziny i minuty. Nie należy traktować każdego miesiąca jako stałej liczby dni, ponieważ taka konwersja prowadzi do niejednoznacznych wyników.

Najczęściej używane funkcje daty i czasu

  • Date - zwraca bieżącą datę.
  • Now - zwraca bieżącą datę i czas.
  • DateOf - na podstawie wartości typu TDateTime zwraca jedynie datę bez czasu.
  • TimeOf - zwraca jedynie czas z wartości typu TDateTime.
  • EncodeDate - tworzy wartość daty.
  • EncodeTime - tworzy wartość czasu.
  • EncodeDateTime - tworzy pełną wartość daty i czasu.
  • DecodeDate - wydobywa rok, miesiąc i dzień.
  • DecodeTime - wydobywa godzinę, minuty, sekundy i milisekundy.
  • CompareDate - porównuje dwie daty.
  • CompareTime - porównuje dwie wartości określające czas.
  • CompareDateTime - porównuje dwie wartości określające datę i czas.
  • IsSameDay - zwraca True, jeżeli w podanych datach dzień jest taki sam.
  • IsToday - zwraca True, jeżeli podany dzień odpowiada bieżącemu.
  • IsValidDateTime - sprawdza, czy podane wartości stanowią poprawną datę i czas.

Wersja wykorzystująca całkowitą liczbę minut

Jeżeli podstawowym wynikiem ma być liczba minut, najprostsze rozwiązanie polega na wykorzystaniu funkcji MinutesBetween:

Minutes := MinutesBetween(StartDateTime, EndDateTime);
ShowMessage(IntToStr(Minutes));

Jeżeli trzeba zachować znak różnicy, można obliczyć go bezpośrednio z odejmowania pełnych wartości i przeliczyć odstęp na minuty. Jedna doba zawiera 1440 minut, dlatego różnicę w dniach można przeliczyć na minuty przez pomnożenie przez 1440.

TotalMinutes := Round((EndDateTime - StartDateTime) * 24 * 60);

Przy takim przeliczeniu funkcja Round ogranicza skutki reprezentacji wartości zmiennoprzecinkowych. Jeżeli sekundy i milisekundy mają być pomijane, należy wcześniej ustalić odpowiednią dokładność albo użyć funkcji MinutesBetween.

Przykład formatowania wyniku

Po rozłożeniu wyniku można przygotować komunikat zawierający dni, godziny i minuty:

ShowMessage(Format('%d dni %d godzin %d minut', [Days, Hours, Minutes]));

Dla wyniku godzinowego można użyć zapisu z wiodącym zerem:

ShowMessage(Format('%d dni %s', [Days, FormatDateTime('hh:nn', EncodeTime(Hours, Minutes, 0, 0))]));

W formacie Delphi symbol nn oznacza minuty, ponieważ symbol mm jest używany w kontekście miesiąca. Dlatego dla czasu należy stosować wzorzec hh:nn, a nie zapis oparty na hh:mm.

Najważniejsze zasady

  • Do różnicy między momentami używaj pełnych wartości TDateTime, zawierających datę i czas.
  • Do samej liczby dni użyj DaysBetween.
  • Do całkowitej liczby minut użyj MinutesBetween.
  • Do całkowitej liczby godzin użyj HoursBetween.
  • Do wyniku w dniach, godzinach i minutach przelicz całkowitą liczbę minut za pomocą div i mod.
  • Do dodawania lub odejmowania minut użyj IncMinute na pełnej wartości daty i czasu.
  • Nie używaj Writeln jako komunikatu w aplikacji formularzowej, ponieważ może powodować błąd I/O error 105.
  • Nie odejmuj wielokrotnie samej wartości czasu pozbawionej daty, ponieważ po przejściu przez północ wynik może stać się nieprawidłowy.
  • Jeżeli potrzebujesz lat i miesięcy, korzystaj z YearsBetween oraz MonthsBetween, pamiętając, że miesiące nie mają jednakowej długości.
noimgs

tags: #delphi #roznica #czasow