Zabawa z datą ;)

piotr_12345

Zacznijmy od typu TDate. W dużym skrócie TDate jest 8 bajtową liczbą rzeczywistą typu double.

Zmienna typu TDate przyjmuje wartość 0 dokładnie dla daty 30-12-1899 i godziny 0:00. W dalszej część pominiemy wartość godziny zaokrąglając zmienną korzystając z procedury trunc().

Każdy kolejny dzień od daty 30.12.1899 posiada wartość 1. Tabela poniżej powinna pomóc to zrozumieć.

<font size="3">Wartość TDate *</span><font size="3">Prawdziwa data (dd, mm, rr)</span>
<font size="3">0</span><font size="3">30.12.1899</span>
<font size="3">1</span><font size="3">31.12.1899</span>
<font size="3">2</span><font size="3">01.01.1900</span>
<font size="3">12345</span><font size="3">18.10.1933</span>
<font size="3">54321</span><font size="3">20.09.2048</span>

Jeżeli chcesz samemu przeliczyć wartość TDate skorzystaj z poniższej procedury :

ShowMessage(DateToStr(1000)); // wartość 1000 jest dowolna


Teraz przejdziemy do części praktycznej. Oto kilka prostych ale czasem bardzo przydatnych procedur.

Przykład 1 :

Poniższa funkcja zwraca ilość dni w miesiącu. Opiera się ona na zasadach kalendarza gregoriańskiego czyli :

  • 31 dni posiadają miesiące 1,3,5,7,8,10,12

  • 30 dni posiadają miesiące 4,6,9,11

  • rok ma długość 365 lub 366 dni gdy jest rokiem przestępnym

  • miesiąc luty w roku przestępnym posiada 29 a zwykłym 28 dni

  • rok przestępny to rok który dzieli się przez 4 bez reszty

  • wyjątkowo rokiem przestępnym nie jest rok który dzieli się przez 100 i 400, czyli rok 2000 jest rokiem przestępnym, ale już 1900 i 2100 nie jest.

Parametry dla funkcji to jak można się domyśleć to miesiąc i rok.

```delphi function dwm(miesiac, rok : word):byte; // dni w miesiącu
begin     case miesiac of       1, 3, 5, 7, 8, 10, 12 : result := 31; // miesiące które mają 31 dni       4, 6, 9, 11 : result := 30; // miesiące które mają 30 dni       2 : begin // luty - odjazd ;-)             if (rok mod 4 = 0) then               begin              if (rok mod 100 = 0) then                  begin                     if (rok mod 400 = 0) then result:=29 else result:= 28;                   end else result := 29               end else result := 28;           end;       else result := 0;     end; end; ```


Przykład 2

Poniższa funkcja jest skróconą wersją powyższej, korzysta również z wszystkich wymienionych powyżej reguł, jako parametr przyjmuje rok a zwraca liczbę jego dni.
```delphi function dwr(rok : word):word; // dni w roku
begin     if (rok mod 4 = 0) then       begin         if (rok mod 100 = 0) then           begin             if (rok mod 400 = 0) then result:=366 else result:= 365;           end else result := 366;       end else result := 365; end; ```


Przykład 3
Tym razem procedura jest już bardziej skomplikowana. Jej działanie w skrócie można określić jako wyciąganie ze zmiennej typu TDate, danych tj. dnia, miesiąca i roku. Wykorzystuje ona obie powyższe funkcje. Jako parametr należy podać rok oraz zmienne którym zostanie przypisana wartość dd, mm, rr, czyli odpowiednio dzień, miesiąc i rok.

```delphi procedure pd(data : TDate; var dd, mm, rr : word); // podziel datę
var   tmpData : integer; begin   tmpData := trunc(data); // zaokrąglenie "do dołu" wartości daty
  rr := 1899; // zmienna TDate dla daty 30.12.1899 przyjmuje wartość 0   mm := 12;   dd := 30;
  while tmpData>dwr(rr) do // aby skrócić proces, zmienną tmpData skracamy o kolejne lata     begin       inc(rr);       dec(tmpData,dwr(rr));     end;   inc(dd,tmpData); // sumujemy i przypisujemy powstałą datę do zmiennej dd - dzień   while dd>dwm(mm,rr) do // rozdzielanie dni na odpowiednie miesiące     begin       dec(dd,dwm(mm,rr)); // co zostanie to dzień       inc(mm);       if mm>12 then // jeżeli miesięcy jest za dużo         begin           mm:=1;           inc(rr); // dodajemy jeden rok         end;     end; end; ```
Przykład wywołania procedury :
```delphi procedure TForm1.Button1Click(Sender: TObject); var   dd, mm, rr : word; begin   pd(date,dd,mm,rr);  // date - aktualna data, do zmiennych dd, rr, mm zostaną przypisane odpowiednie wartości   ShowMessage('IntToStr(dd)+' '+IntToStr(mm)+' '+IntToStr(rr)); end; ```


Przykład 4
Teraz prosty przykład formatowania daty w którym wykorzystamy wyżej podaną procedurę i obie funkcje.
```delphi procedure TForm1.Button1Click(Sender: TObject); const   miesiace : array[1..12] of string = ('Styczeń','Luty','Marzec','Kwiecień','Maj','Czerwiec','Lipiec','Sierpień','Wrzesień','Październik','Listopad','Grudzień'); var   dd, mm, rr : word; begin   pd(date,dd,mm,rr);   ShowMessage('Aktualna data : '+IntToStr(dd)+' '+miesiace[mm]+' '+IntToStr(rr)+' r.'); end; ```
Jak widać można ładniej wyświetlić datę :)


Podsumowanie, cały kod 

To by było na dzisiaj koniec, zachęcam do testowania i rozwijania moich pomysłów. Powyższe procedury i funkcje choć są już i tak bardzo szybkie, można jeszcze zoptymalizować ale to zostawiam już Tobie :)
Oto kod całego programu (wystarczy na formie położyć jeden przycisk) :
```delphi unit Unit1; interface uses   Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type   TForm1 = class(TForm)     Button1: TButton;     procedure Button1Click(Sender: TObject);   private     { Private declarations }   public     { Public declarations }   end; var   Form1: TForm1; implementation {$R *.dfm} function dwm(miesiac, rok : word):byte; // dni w miesiącu begin   case miesiac of     1, 3, 5, 7, 8, 10, 12 : result := 31; // miesiące które mają 31 dni     4, 6, 9, 11 : result := 30; // miesiące które mają 30 dni     2 : begin // luty - odjazd ;-)           if (rok mod 4 = 0) then             begin               if (rok mod 100 = 0) then                 begin                  if (rok mod 400 = 0) then result:=29 else result:= 28;                 end else result := 29             end else result := 28;         end;     else result := 0; // taki miesiąc nie istnieje   end; end; function dwr(rok : word):word; // dni w roku begin   if (rok mod 4 = 0) then     begin       if (rok mod 100 = 0) then         begin           if (rok mod 400 = 0) then result:=366 else result:= 365;         end else result := 366;     end else result := 365; end; procedure pd(data : TDate; var dd, mm, rr : word); // podziel datę var tmpData : integer; begin   tmpData := trunc(data); // zaokrąglenie do dołu wartości daty   rr := 1899; // zmienna TDate dla daty 30.12.1899 przyjmuje wartość 0   mm := 12;   dd := 30;   while tmpData>dwr(rr) do // aby skrócić proces date skracamy o kolejne lata     begin       inc(rr);       dec(tmpData,dwr(rr));   end;   inc(dd,tmpData); // sumujemy i przypisujemy powstałą datę do zmiennej dd - dzień   while dd>dwm(mm,rr) do // rozdzielenie dni na odpowiednie misiące     begin       dec(dd,dwm(mm,rr)); // co zostanie to dzień       inc(mm);       if mm>12 then // jeżeli miesięcy jest za dużo         begin           mm:=1;           inc(rr); // dodajemy jeden rok         end;     end; end; procedure TForm1.Button1Click(Sender: TObject); const   miesiace : array[1..12] of string = ('Styczeń','Luty','Marzec','Kwiecień','Maj','Czerwiec','Lipiec','Sierpień','Wrzesień','Październik','Listopad','Grudzień'); var   dd, mm, rr : word; begin   pd(date,dd,mm,rr);   ShowMessage('Aktualna data : '+IntToStr(dd)+' '+miesiace[mm]+' '+IntToStr(rr)+' r.'); end; end. ```

11 komentarzy

Wybacz, ale moim zdanie Twoja praca to wyważanie otwartych drzwi. Wystarczy użyć funkcji z unitów SysUtils i DateUtils, by uzyskać ten sam efekt. Nie bardzo widzę sens tworzenia czegoś co już jest. Poniżej Twoje przykłady z ich odpowiednikami we wspomnianych unitach.

Przykład 1

Poniższa funkcja zwraca ilość dni w miesiącu.
function dwm(miesiac, rok : word):byte;

DaysInAMonth (DateUtils)
Returns the number of days in a specified month of a specified year.

Przykład 2

Poniższa funkcja jest skróconą wersją powyższej, korzysta również z wszystkich wymienionych powyżej reguł, jako parametr przyjmuje rok a zwraca
liczbę jego dni.

DaysInAYear (DateUtils)
Returns the number of days in a specified year.

Przykład 3

Tym razem procedura jest już bardziej skomplikowana. Jej działanie w skrócie można określić jako wyciąganie ze zmiennej typu TDate, danych tj. dnia, miesiąca i roku. Wykorzystuje ona obie powyższe funkcje. Jako parametr należy podać rok oraz zmienne, którym zostanie przypisana wartość dd, mm, rr, czyli odpowiednio dzień, miesiąc i rok.

DecodeDate (SysUtils)
Returns Year, Month, and Day values for a TDateTime value.

Przykład 4

Teraz prosty przykład formatowania daty w którym wykorzystamy wyżej podaną procedurę i obie funkcje.

Moim zdaniem tak jest o wiele prościej …
Jeśli mamy ustawiony język polski to nie trzeba definiować nazw miesięcy. Oczywiście, jak komuś nie odpowiadają te nazwy może je zmienić. Wystarczy wpisać nowe nazwy do tablicy LongMonthNames[1..12] (w przypadku pełnych nazw) lub do tablicy ShortDayNames w przypadku nazw skróconych. Analogicznie sprawa ma się z nazwami dni – odpowiadające im tablice to LongDayNames i ShortDayNames obie o wymiarach [1..7].

LongMonthNames[<numer_miesiąca>] := '<twoja_nazwa_miesiąca>';
ShortDateFormat := 'dd mmmm yyyy r.';
DateToStr(Date);

a jak pobrać jaki dzień tygodnia jest w danym miesiącu

Do sprawdzenia, czy dany rok jest przestępny można użyć funkcji IsLeapYear. Kod będzie czytelniejszy.

Hehe, algorytm rozpisania TDate na dzien, miesiac i rok jest co prawda ciekawy, no ale duzo latwiej tak:
aa:= DateToStr(Data);
dd:= StrToInt(aa[7] + aa[8]);
mm:= StrToInt(aa[4] + aa[5]);
rr:= StrToInt(aa[1] + aa[2]);

@Drajew : Może i łatwiej, ale formatDateTime jest dość okrojony. Np. co jeżeli chcesz zmienić język lub skrócić nazwy miesiąca czy dni?

nie łatwiej formatdatetime('format',now); ?

No mam nadzieje ze wkrotce sie pokaże...

W kolejenej części :)

No nawet fajne funcke ale ja bym tutaj jeszcze opisal zecz ktora pewnie wielu by sie przydala ... Mianowicie liczenie sekund, minut, godzin, dni, misiecy, lat od okreslonej daty.
Liczenie ile zostało do daty w przyszłości, itd.

@-CD- : Twój sposób jest łatwiejszy ale zawodny :( Dlaczego? Spróbuj uruchomić Twoją procedurę w Windows XP i Windows 98. W Windows XP rok zapisywane jest w formacie rrrr, a windows 98 rr, tak więc dla roku 2003 odpowiednio uzyskasz efekt : rok 20 dla Win XP oraz rok 03 dla Win 98 :)

Miałem napisać ciąg dalszy i ... napisałem, ale padł dysk i musze napisać od nowa :( Jak zbiorę siły i chęci to coś wyskrobię :)