Witam,
chciałbym zrobić odliczanie do konkretnej daty podane w godzinach minutach i sekundach. Mam podana date w takim formacie:
Fri Oct 25 8:00
W jaki sposób zrobić odliczanie od teraz do tej konkretnej daty? Mógłby mi ktoś pomóc?
pozdrawiam!
Temat ewidentnie pasuje do Newbie, ale nie przenoszę. Bo później i tak tamten dział zniknie. A poziom wiedzy potrzebnej do rozwiązania "problemu" autora wątku, śmiało można określić jako podstawowy. I tak później go zgodnie z planami oznaczymy gdy będzie to już dostępne dla Moderatorów/Adminów. Co do pobrania danych z tak podanego ciągu, o jaki pytasz to są również do tego odpowiednie funkcje formatujące. Więcej informacji znajdziesz w google na wielu stronach. Jak rownież w pomocy do IDE. Trzeba tylko chcieć poszukać samemu.
uses
DateUtils;
var
EndDate : TDateTime;
procedure TForm1.FormCreate(Sender : TObject);
begin
Timer1.Enabled := False;
Timer1.Interval := 1000;
end;
procedure TForm1.Button1Click(Sender : TObject);
begin
EndDate := EncodeDateTime(2013, 10, 25, 8, 0, 0, 0);
Timer1.Enabled := True;
end;
procedure TForm1.Timer1Timer(Sender : TObject);
var
ADate : TDateTime;
begin
ADate := IncSecond(Now, -1);
Caption := DateTimeToStr(ADate);
if ADate >= EndDate then
begin
Timer1.Enabled := False;
ShowMessage('A teraz jazda do kursów i uczyć się podstaw! ;)');
end;
end;
Ale jemu chodzi o to że ma to podane jako string a chyba w Delphi nie da się z takiej daty w "normalny sposób" czyli za pomocą formatowania uzyskać TDateTime (przez to że są nazwy miesięcy i dni tygodnia).
Trzeba się pobawić trochę na piechotę np tak:
procedure TForm1.Button1Click(Sender: TObject);
const
SMN: array [1..12] of string = ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec');
var
DateTime: TDateTime;
fs: TFormatSettings;
i: Integer;
MyDateTime: string;
begin
MyDateTime:= 'Fri Oct 25 8:00';
Delete(MyDateTime, 1, 4); //dzień tygodnia niepotrzebny
for i:= Low(SMN) to High(SMN) do
begin
MyDateTime:= StringReplace(MyDateTime, SMN[i] + ' ', Format('%0.2d/', [i]), [rfReplaceAll]);
end;
//tu MyDateTime = 10/25 8:00
fs.TimeSeparator:= ':'; //ustawiam własne formatowanie daty
fs.DateSeparator:= '/';
fs.ShortDateFormat:= 'mm/dd';
fs.ShortTimeFormat:= 'h:nn';
DateTime:= StrToDateTime(MyDateTime, fs); //tu masz datę w formacie TDateTime i możesz dodawać odejmować
ShowMessage(DateTimeToStr(DateTime));
end;
dzieki wielkie Panowie :) nikt nie powiedzial, ze nie jestem Newbie :D
@kAzek: no ok, myślałem że większym problemem jest odliczanie do daty. Przecież takie rzeczy jak wyłuskanie poszczegółnych rzeczy z tak prostego stringu, toż to proste, jak budowa cepa niemalżę. Oczywiście dałeś idealny przykład. Bo powinno się robić takie rzeczy w oparciu o tablice i jak najprościej. Ale jeśli mamy ludzi uczyć użycia StringReplace
, StrToDateTime
czy też nawet Pos
/ Copy
lub Delete
to bysmy musieli założyć żłobek dla programistów i wibjać jakimś młotem oczywiste oczywiśtości do głów ;) Wtedy google musiano by zamknąć ;)
@karpov: no ok, Newbie czy nie. Korzystnie z Google to żaden wstyd. Teraz zatwierdź rozwiąznie bardziej pomocne. I tutaj niedoskonalość tego, że można zatwierdzić tylko jedno ;) A @kAzek ma ich procentowo więcej, ale ja się nie obrażę ;P
kAzek napisał(a)
Ale jemu chodzi o to że ma to podane jako string a chyba w Delphi nie da się z takiej daty w "normalny sposób" czyli za pomocą formatowania uzyskać TDateTime (przez to że są nazwy miesięcy i dni tygodnia).
Ale nic nie stoi na przeszkodzie, aby sobie taką funkcję zaimplementować :]
W Lazarusie w module DateUtils
jest co prawda funkcja ScanDateTime
, jednak nie jest przystosowana do rozpoznawania dni tygodnia/miesiąca - konwertuje tylko jeśli elementy daty/czasu są cyframi/liczbami;
Jutro pobawię się nieco i spróbuje napisać taki algorytm (razem z obsługą nazw miesięcy) - do południa postaram się go udostępnić.
Dzieki wszystkim za pomoc:) Mam jeszcze pytanie. Skorzystalem z Waszych rozwiazan i majac juz 2 daty w formacie TDateTime uzywam DaysBetween() no i zwraca mi wynik np. 5. A ja chcialem uzyskac wynik rowniez w godzinach, minutach i sekundach zeby moc zrobic odliczanie do tej daty. Istnieje jakas funkcja oprocz DayBetween, ktora by to robila?
jest DateUtils tutaj masz spis wszystkich funkcji znajdujących się w tym module (szkoda, że po przeprowadzce gdzieś "przyjaźniejszy" spis funkcji zaginął) np. MinuteSpan czy MinutesBetween
DaysBetween - zwraca czy liczbę zmiennoprzecinkową w dniach.
Trunc(Frac(DaysBetween)*24) - to będą godziny
Trunc(Frac(DaysBetween*24)*60) - to będą minuty
Trunc(Frac(DaysBetween*24*60)*60) - to będą sekundy
Dzięki wszystkim za pomoc:) Problem rozwiązany: wyswietlanie godzin, minut i sekund rozwiazalem w taki sposob:
godz:= HoursBetween(Now, EndDate);
godzin.caption:=inttostr(godz mod 60);
min:=MinutesBetween(Now, EndDate);
minut.caption:=inttostr(min mod 60);
sec := SecondsBetween(Now, EndDate);
sekund.caption:=inttostr(sec mod 60);
Trochę późno piszę, ale skoro obiecałem to lepiej późno niż wcale; Wczoraj miałem dłuższą chwilę i napisałem zestaw funkcji do konwersji daty/czasu do łańcucha i z powrotem, ale że dostawca Internetu miał awarię - dopiero teraz mogę napisać;
Całość pisana w Lazarusie, ale zmodyfikowałem kod, tak by działał także pod Delphi7, w którym nie obsługiwany jest cplusplusowaty operator +=
oraz brak możliwości inicjowania zmienych lokalnych w procedurach/funkcjach;
Nie są one tak wypasione jak FormatDateTime, ale są w pełni odwracalne; Źródła:
Delphi7 - http://4programmers.net/Pastebin/2497
Lazarus - http://4programmers.net/Pastebin/2498
Funkcje InternalDateTimeToStrConv
i InternalStrToDateTimeConv
są funkcjami wewnętrznymi, które dokonują właściwej kunwersji; Funkcje DateTimeToStrConv
i StrToDateTimeConv
są gotowe do użytku, a przeładowane dlatego, że różnią się liczbą argumentów - można podać lub nie własny rekord typu TFormatSettings;
Obsługiwane formaty:
Format | Opis |
---|---|
Data |
D | dzień bez zera wiodącego
DD | dzień z zerem wiodącym
DDD | krótka nazwa dnia (pobrana z TFormatSettings.ShortDayNames
)
DDDD | długa nazwa dnia (pobrana z TFormatSettings.LongDayNames
)
M | miesiąc bez zera wiodącego
MM | miesiąc z zerem wiodącym
MMM | krótka nazwa miesiąca (pobrana z TFormatSettings.ShortMonthNames
)
MMMM | długa nazwa miesiąca (pobrana z TFormatSettings.LongMonthNames
)
YYYY | rok w formacie czterocyfrowym
Czas
H | godzina bez zera wiodącego
HH | godzina z zerem wiodącym
N | minuta bez zera wiodącego
NN | minuta z zerem wiodącym
S | sekunda bez zera wiodącego
SS | sekunda z zerem wiodącym
Z | milisekunda w formacie jednocyfrowym
ZZZ | milisekunda w formacie trzycyfrowym
AM/PM | flaga oznaczająca zegar 12-godzinny
Plusy:
- obsługuje poprawnie liczbowe formaty,
- obsługuje poprawnie skrócone i pełne nazwy dni i miesięcy,
- poprawnie konwertuje łańcuch na datę/czas jeśli w łańcuchu jest tylko skrócona/pełna nazwa miesiąca bez jej liczbowego odpowiednika (np. gdy łańcuch zawiera jedynie
22 październik 2013
- miesiąc zostanie poprawnie odczytany), - poprawnie konwertuje godzinę na format 12-godzinnego zegara,
- jest zupełnie odwracalna (pod warunkiem użycia jedynie ww. w tabelce formatów),
- poprawnie konwertuje date/czas na podstawie własnego typu TFormatSettings w przeciwieństwie do RTLowskich funkcji;
Minusy:
- brak obsługi dwucyfrowego formatu roku (nie chciało mi się już bawić z
TwoDigitYearCenturyWindow
), - brak obsługi czasów wschodnioazjatyckich (cokolwiek to znaczy),
- każdy format musi być oddzielony od kolejnego co najmniej jednym zanakiem "nieformatowalnym" (nie może być np.
HHNN
, trzeba je oddzielić, np. spacjąHH NN
lub separatoremHH:NN
) - to chyba normalne jest, bo RTLowskie funkcje najprawdopodobniej także nie poradzą sobie z połączonymi formatami (nie sprawdzałem), - flaga
AM/PM
nie może stać na początku łańcucha (może być przed formatami czasu, ale nie na początku łańcucha);
Oczywiście można te funkcje wzbogacić o obsługę dodatkowych formatów (pozostawiłem case
y jakby ktoś chciał sobie coś dodać);
Przykład użycia:
const
MASK_STRING = AnsiString('D DD DDD DDDD M MM MMM MMMM YYYY H HH N NN S SS Z ZZZ AM/PM');
var
dtValue: TDateTime;
strValue: AnsiString;
begin
dtValue := EncodeDateTime(2013, 10, 21, 21, 14, 52, 98);
{ RTL date time to string }
strValue := FormatDateTime(MASK_STRING, dtValue);
WriteLn('"', strValue, '"');
{ date time to string }
strValue := DateTimeToStrConv(dtValue, MASK_STRING);
WriteLn('"', strValue, '"');
{ string to date time & date time to string }
dtValue := StrToDateTimeConv(strValue, MASK_STRING);
strValue := DateTimeToStrConv(dtValue, MASK_STRING);
WriteLn('"', strValue, '"');
{ finally }
ReadLn;
end.
W pierwszej linijce wyświetlony zostanie rezultat RTLowskiej funkcji FormatDateTime, w drugiej rezultat funkcji DateTimeToStrConv
, a w trzeciej StrToDateTimeConv
(podwójna konwersja własnymi funkcjami, żeby znów uzyskać łańcuch gotowy do wyświetlenia) - wartości w drugiej i trzeciej linii to rezultaty działania własnych funkcji, więc one musza być takie same żeby mówić o odwracalności;
Output (Lazarus):
"21 21 Pn poniedzia│ek 10 10 pač pačdziernik 2013 9 09 14 14 52 52 98 098 PM"
"21 21 Pn poniedzia│ek 10 10 pač pačdziernik 2013 9 09 14 14 52 52 98 098 PM"
"21 21 Pn poniedzia│ek 10 10 pač pačdziernik 2013 9 09 14 14 52 52 98 098 PM"
Output (Delphi7):
"21 21 Pn poniedzia│ek 10 10 pač pačdziernik 2013 21 09 14 14 52 52 98 098 PM"
"21 21 Pn poniedzia│ek 10 10 pač pačdziernik 2013 9 09 14 14 52 52 98 098 PM"
"21 21 Pn poniedzia│ek 10 10 pač pačdziernik 2013 9 09 14 14 52 52 98 098 PM"
W przypadku Delphi7 funkcja FormatDateTime z RTL Delphi7 nie poradziła sobie najlepiej;
Obie funkcje (DateTimeToStrConv
i StrToDatTimeConv
) testowałem na domyślnym TFormatSettings, jak i na własnym i w obu przypadkach po modyfikacji rekordu działa bezbłędnie (czego o FormatDateTime powiedzieć już nie można);
Nie wiem dokładnie dlaczego funkcja ScanDateTime
z Lazarusa nie potrafi odwrócić łańcucha, jeśli zawiera np. nazwę dnia czy miesiąca, w każdym razie moja funkcja konwersji łańcucha na TDateTime do porównywania łańcuchów nazw miesięcy wykorzystuje funkcję CompareStr, która robi to jak widać poprawnie;
Kod wklejam do Pastebin bo jest długi; Być może przyda Ci się @karpov, a jeśli nie, to może kiedyś kiedyś ktoś trafi do tego wątku z Google. Enjoy :]
wow super praca:) wielkie dzięki, na pewno wypróbuje to rozwiązanie :)
Jeśli masz źródła RTL z Delphi (ja niestety nie mam) to możesz sobie zobaczyć jak wygląda oryginalna wersja funkcji FormatDateTime i nieco rozbudować mój kod, ale najważniejsze rzeczy są zaimplementowane; No i jest w pełni odwracalna, a to najważniejsze :]
EDIT:
Nie piszę nowego postu, jednak z racji tej, że poprawiłem oba algorytmy konwersji muszę stwierdzić, że znalazłem kilka niedociągnięć w kodzie, przez co dawał mniejsze możliwości;
Dla zainteresowanych (a raczej tych, którym algorytmy się spodobały i zaplusowali tamten post, czyli @karpov, @olesio, @kAzek i @babubabu) przedstawiam poprawiony kod wszystkich funkcji, zarówno z Lazarusa jak i Delphi7:
- Lazarus: http://4programmers.net/Pastebin/2541
- Delphi7: http://4programmers.net/Pastebin/2542
Aby wartość AM
/PM
mogła stać na początku łańcucha należało w funkcji InternalStrToDateTimeConv
poprawić GetFormatInfo
i bez względu na zawartość zmiennej chrFormat
przypisać do zmiennej chrFormatSep
zawartość wskaźnika pchrMaskCurr
;
Drugim problemem był warunek obsługujący format A
- w starej wersji od razu była sprawdzana i poprawiana godzina, co było błędem, bo jeśli wartość AM
/PM
była przed formatem godziny - zmienna intHour
zawsze była ustawiona na 0
(po wywołaniu procedury InitDateTimeComponents
), dlatego też nie działało to prawidłowo;
Rozwiązaniem okazało się dodanie jeszcze dwóch zmiennych - bool12HourClock
i boolIsAMHour
, które to zostają ustawione w obsłudze formatu A
; Ta pierwsza zawsze na True
(bo skoro ten format został napotkany to czas na pewno jest podany jako 12-godzinny), a ta druga w zależności od tego czy zmienna strFormatVal
jest taka sama jak ASettings.TimeAMString
czy ASettings.TimePMString
; Jeśli to pierwsze to zostaje ustawiona na True
, jeśli nie to na False
;
Ostatnim krokiem było obsłużenie zmiennych bool12HourClock
i boolIsAMHour
już po wyjściu z pętli - wtedy już zmienna intHour
posiada przekonwertowaną wartość z łańcucha AValue
;
Do listy argumentów dodałem także ADefault: TDateTime
po to, aby w razie błędu konwersji zwrócić wartość domyślną; Dlatego też właściwa konwersja dokonywana jest funkcją TryEncodeDateTime
i jeśli wystąpi błąd konwersji (funkcja zwróci False
) - do rezultatu funkcji zostanie wpisana wartość spod argumentu ADefault
;
Dodatkowo jeśli którykolwiek z argumentów AValue
lub AMask
jest pusty - także zostaje zwrócona wartość domyślna;
Obie przeładowane funkcje konwersji z łańcucha do TDateTime zostały także przystosowane do obsługi wartości domyślnej;
Podsumowując - dzięki tym zabiegom po wywołaniu poniższego kodu:
const
MASK_STRING = AnsiString('D DD DDD DDDD M MM AM/PM MMM MMMM YYYY H HH N NN S SS Z ZZZ');
var
dtValue: TDateTime;
strValue: AnsiString;
begin
dtValue := EncodeDateTime(2013, 10, 21, 21, 14, 52, 98);
{ RTL date time to string }
strValue := FormatDateTime(MASK_STRING, dtValue);
WriteLn('"', strValue, '"');
{ date time to string }
strValue := DateTimeToStrConv(dtValue, MASK_STRING);
WriteLn('"', strValue, '"');
{ string to date time & date time to string }
dtValue := StrToDateTimeConv(strValue, MASK_STRING, 0);
strValue := DateTimeToStrConv(dtValue, MASK_STRING);
WriteLn('"', strValue, '"');
{ finally }
ReadLn;
end.
na ekranie zobaczymy:
"21 21 Pn poniedzia│ek 10 10 PM pač pačdziernik 2013 9 09 14 14 52 52 98 098"
"21 21 Pn poniedzia│ek 10 10 PM pač pačdziernik 2013 9 09 14 14 52 52 98 098"
"21 21 Pn poniedzia│ek 10 10 PM pač pačdziernik 2013 9 09 14 14 52 52 98 098"
czyli jak widać funkcje działają poprawnie - są w pełni odwracalne, a o to w sumie chodziło.