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.

function dwm(miesiac, rok : word):byte;  // dni w miesiącu<br>
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;
</p>

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;

<br>
Przykład wywołania procedury :
<br>
```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;
</p>

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;

<br>
Jak widać można ładniej wyświetlić datę :)</p>
<hr size="1" color="#008080" noshade>
<p align="left"><b>Podsumowanie, cały kod</b> <br>
<br>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 :)<br>Oto kod całego programu (wystarczy na formie położyć jeden przycisk) :
<br>
```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.
</p>

11 komentarzy

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.

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);

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ę :)