I have a log that use the Calendar and I want to bold the days that have recorded info. I have them in an 3D array TDiaryLog = array[1900..2399] of array[1..12] of array[1..31] of POneDay;. But in the OnGetMonthInfo, when I must build the list with the bold days, it gives me only the Month, and not the Year. How should I know for what month I must pass the day if I don't have the year ? When December is displayed as the current month in the Calendar, there are a few days shown from the January next year !
procedure TMainForm.CalendarGetMonthInfo(Sender: TObject; Month: Cardinal;
var MonthBoldInfo: Cardinal);
begin
end;
I made a new component where I intercepted the MCN_GETDAYSTATE message and I extracted the year too from the message info... It was there all the time, but Delphi decided that year is not useful.
TOnGetMonthInfoExEvent = procedure(Sender: TObject; Year, Month: Word;
var MonthBoldInfo: LongWord) of object;
TNewMonthCalendar = class(TMonthCalendar)
private
FOnGetMonthInfoEx: TOnGetMonthInfoExEvent;
procedure CNNotify(var Msg: TWMNotifyMC); message CN_NOTIFY;
published
property OnGetMonthInfoEx: TOnGetMonthInfoExEvent read FOnGetMonthInfoEx write FOnGetMonthInfoEx;
end;
procedure TNewMonthCalendar.CNNotify(var Msg: TWMNotifyMC);
var
I: Integer;
Month, Year: Word;
DS: PNMDayState;
CurState: PMonthDayState;
begin
if (Msg.NMHdr.code = MCN_GETDAYSTATE) and Assigned(FOnGetMonthInfoEx) then begin
DS:= Msg.NMDayState;
FillChar(DS.prgDayState^, DS.cDayState * SizeOf(TMonthDayState), 0);
CurState:= DS.prgDayState;
for I:= 0 to DS.cDayState - 1 do begin
Year:= DS.stStart.wYear;
Month:= DS.stStart.wMonth + I;
if Month > 12 then begin Inc(Year); Dec(Month, 12); end;
FOnGetMonthInfoEx(Self, Year, Month, CurState^);
Inc(CurState);
end;
end
else inherited;
end;
BONUS
And, as a bonus, you need this to update the changes you made to the bold info of the current month view... because it doesn't work with Invalidate.
procedure TNewMonthCalendar.RefreshDayState;
var N: Cardinal;
Range: array[0..1] of TSystemTime;
Year, Month: Word;
States: array of TMonthDayState;
I: Integer;
begin
if not Assigned(FOnGetMonthInfoEx) then Exit;
N:= SendMessage(Handle, MCM_GETMONTHRANGE, GMR_DAYSTATE, LPARAM(#Range));
Year:= Range[0].wYear;
Month:= Range[0].wMonth;
SetLength(States, N);
FillChar(States[0], N * SizeOf(TMonthDayState), 0);
for I:= 0 to N-1 do begin
FOnGetMonthInfoEx(Self, Year, Month, States[I]);
Inc(Month);
if Month > 12 then
begin Dec(Month, 12); Inc(Year); end;
end;
SendMessage(Handle, MCM_SETDAYSTATE, N, LPARAM(#States[0]));
end;
Related
I'm Using Delphi Sydney 10.4 FMX, I have a ListView connected to a Database with liveBinding, ImageList that has 3 images.
In the ListView, I Have 3 Fields: Image, Expiry, Domain.
The Expiry and Domain are filled from the Database, but the image I want to show depends on the date, example :
Expiry = date of today or before today: I want to show imageindex 0
Expiry = from Tomorrow until 30 days from Today: I want to show imageindex 1
Expiry = 31 Days Later from today: I want to show imageindex 2
procedure TForm1.DomainsListViewUpdateObjects(const Sender: TObject;
const AItem: TListViewItem);
begin
var DT : TDateTime;
DT := Now;
if DomainsListView.Items[DomainsListView.ItemIndex].Data['expiry'].AsString < datetostr(DT+30) then
DomainsListView.Items[DomainsListView.ItemIndex].Data['image'] := 1
else if DomainsListView.Items[DomainsListView.ItemIndex].Data['expiry'].AsString < DateToStr(DT) then
DomainsListView.Items[DomainsListView.ItemIndex].Data['image'] := 0
else if DomainsListView.Items[DomainsListView.ItemIndex].Data['expiry'].AsString > DateToStr(DT+31) then
DomainsListView.Items[DomainsListView.ItemIndex].Data['image'] := 2
end;
I used this code, but does not works correctly
You are comparing String values, which doesn't work to compare dates. You should be comparing TDateTime values instead, as well as paying more attention to the order of your comparisons.
Try something more like this instead:
uses
..., System.SysUtils, System.DateUtils;
procedure TForm1.DomainsListViewUpdateObjects(const Sender: TObject;
const AItem: TListViewItem);
begin
var Item: TListViewItem := DomainsListView.Items[DomainsListView.ItemIndex]; // or: DomainsListView.Selected
var dtToday: TDateTime := System.DateUtils.Today;
var dtExpiry: TDateTime := System.SysUtils.StrToDate(Item.Data['expiry'].AsString);
if dtExpiry <= dtToday then
Item.Data['image'] := 0
else if dtExpiry < (dtToday+31) then
Item.Data['image'] := 1
else
Item.Data['image'] := 2;
end;
I have a program that tracks the days during the year which are booked. In order to display this I have a StringGrid which I use Colors to display the days booked. The days booked are stored in ar2Booking which is a 2D array which contains the days and months respectively.
procedure TfrmClient.stgYearPlan1DrawCell(Sender: TObject; ACol, ARow: Integer;
Rect: TRect; State: TGridDrawState);
var
k, iMonth, iDay : Integer;
begin
for k := 1 to 31 do
stgYearPlan1.Cells[k,0] := IntToStr(k);
for k := 1 to 12 do
stgYearPlan1.Cells[0,k] := ShortMonthNames[k];
for iDay := 1 to 31 do
for iMonth := 1 to 12 do
begin
if ar2Booking[iDay,iMonth] = 'Y' then
begin
if (ACol = iDay) and (ARow = iMonth) then
begin
stgYearPlan1.Canvas.Brush.Color := clBlack;
stgYearPlan1.Canvas.FillRect(Rect);
stgYearPlan1.Canvas.TextOut(Rect.Left,Rect.Top,stgYearPlan1.Cells[ACol, ARow]);
end;
end;
if ar2Booking[iDay,iMonth] = 'D' then
begin
if (ACol = iDay) and (ARow = iMonth) then
begin
stgYearPlan1.Canvas.Brush.Color := clSilver;
stgYearPlan1.Canvas.FillRect(Rect);
stgYearPlan1.Canvas.TextOut(Rect.Left+2,Rect.Top+2,stgYearPlan1.Cells[ACol, ARow]);
end;
end;
end;
end;
I then want to click a button during runtime which allows a user to book a date. I would then like the date they select to reflect in the StringGrid. If I update the array how would I be able to run the OnCellDraw again in order to reflect the new booked dates?
Thanks
Generally you would invalidate part of the control causing it to be redrawn with the next windows paint message. The methods of a TStringGrid to do this are protected so you need to use a cracker class to access them.
// -- add to the type section
type
TStringGridCracker = class(TStringGrid);
procedure TForm1.Button1Click(Sender: TObject);
begin
TStringGridCracker(StringGrid1).InvalidateCell(1,2);
end;
I discovered after a friend showed me, the StringGrid.Redraw procedure accomplishes what I need. Thanks everyone
I'm trying to count down to a time of the day (24-hour clock format). This is my solution so far:
function TimeDiffStr(const s1, s2: string): string;
var
t1, t2: TDateTime;
secs: Int64;
begin
t1 := StrToDateTime(s1);
t2 := StrToDateTime(s2);
secs := SecondsBetween(t1, t2);
Result := Format('%2.2d:%2.2d:%2.2d', [secs div 3600, (secs div 60) mod 60, secs mod 60]);
end;
procedure TForm1.Timer1Timer(Sender: TObject);
var
TargetTime: TTime;
s: string;
begin
s := TimeDiffStr(TimeToStr(Now), TimeToStr(TargetTime));
end;
If Now is, for example, 15:35:02 and the target time is 21:44:59, the output is correct (06:09:57). However, if Now is 15:35:02 and the target time is 01:32:23, instead of counting down from 09:57:21, it will count upwards, because the function does not know that the target time is on a different day.
How can I work out the difference between two times when the times are on different days?
First off, there is no need to pass strings around. If you start with TTime and convert to TTime, then simply pass TTime around.
Second, since you are dealing with just time values, if the target time is meant to be on the next day, you need to add 24 hours so that you have a TDateTime that actually represents the next day.
Try this:
uses
..., DateUtils;
function TimeDiffStr(const t1, t2: TTime): string;
var
d1, d2: TDateTime;
secs: Int64;
begin
d1 := t1;
if t2 < t1 then
d2 := IncDay(t2) // or IncHour(t2, 24)
else
d2 := t2;
secs := SecondsBetween(d1, d2);
Result := Format('%2.2d:%2.2d:%2.2d', [secs div 3600, (secs div 60) mod 60, secs mod 60]);
end;
procedure TForm1.Timer1Timer(Sender: TObject);
var
TargetTime: TTime;
s: string;
begin
TargetTime := ...;
s := TimeDiffStr(Time(), TargetTime);
end;
Is possible to convert
'Thu Jul 17 17:20:38 2014'
with this function? Tried my best, but no result. This format uses justin.tv API, for twitch.tv i use code below and it works. Thanks for help.
var
t1, t2: Tdate;
dzien: integer;
begin
t1 := StrToDateTime('"2014-07-21T12:49:08Z"');
t2 := TTimeZone.Local.ToUniversalTime(Now);
dzien := trunc(t2 - t1);
if dzien > 0 then
Result := (Format('%d days, %s', [dzien, FormatDateTime('hh:nn:ss',
Frac(t2 - t1))]))
else
Result := (Format('%s', [FormatDateTime('hh:nn:ss', Frac(t2 - t1))]));
end;
It is easy enough to parse the string yourself. Like this:
uses
Types, SysUtils, DateUtils, StrUtils;
function DecodeJustinTvDateTime(const Value: string): TDateTime;
function MonthNumber(const MonthStr: string): Integer;
var
FormatSettings: TFormatSettings;
begin
FormatSettings := TFormatSettings.Create('en-us');
for Result := low(FormatSettings.ShortMonthNames) to high(FormatSettings.ShortMonthNames) do begin
if SameText(MonthStr, FormatSettings.ShortMonthNames[Result]) then begin
exit;
end;
end;
raise EConvertError.Create('Unrecognised month name');
end;
var
items: TStringDynArray;
Day, Month, Year, Time, Hour, Minute, Second: string;
begin
items := SplitString(Value, ' ');
if Length(items)<>5 then begin
raise EConvertError.Create('Unrecognised date time format');
end;
// items[0] is day of the week which we can ignore
Month := items[1];
Day := items[2];
Time := items[3];
Year := items[4];
items := SplitString(Time, ':');
Assert(Length(items)=3);
if Length(items)<>3 then begin
raise EConvertError.Create('Unrecognised time format');
end;
Hour := items[0];
Minute := items[1];
Second := items[2];
Result := EncodeDateTime(
StrToInt(Year),
MonthNumber(Month),
StrToInt(Day),
StrToInt(Hour),
StrToInt(Minute),
StrToInt(Second),
0
);
end;
The error checking here is a little lame and you might care to improve on it.
procedure TForm6.Button1Click(Sender: TObject);
var
t1: TDateTime;
ts:TFormatSettings;
begin
ts:=TFormatSettings.Create;
ts.ShortDateFormat:='yyyy-MM-dd';
ts.DateSeparator:='-';
ts.TimeSeparator:=':';
t1 := StrToDateTime('2014-07-21T12:49:08Z',ts);
end;
t1 contains date and time from your string.
Using Delphi, I need a function to evaluate the current date and see if it's, for example, the Third Sunday of the month, etc.
In pseudocode:
function IsFirst(const CurrentDateTime: TDateTime; const Day: Word): Boolean;
/// Day can be 1-7 (monday to sunday)
begin
Result:= ??
end;
Another function would be needed to calculate the Second, Third, Forth and Last of the month. DateUtils seems to have nothing like that. Any ideas?
This function is what you need:
function IsFirst(const DateTime: TDateTime; const Day: Word): Boolean;
begin
Result := (DayOfTheWeek(DateTime)=Day) and
InRange(DayOfTheMonth(DateTime), 1, 7);
end;
The equivalent function for the second occurrence is:
function IsSecond(const DateTime: TDateTime; const Day: Word): Boolean;
begin
Result := (DayOfTheWeek(DateTime)=Day) and
InRange(DayOfTheMonth(DateTime), 8, 14);
end;
I'm sure you can fill out the details for third, fourth and fifth. You may prefer to write a single general function like this:
function IsNth(const DateTime: TDateTime; const Day: Word;
const N: Integer): Boolean;
var
FirstDayOfWeek, LastDayOfWeek: Integer;
begin
LastDayOfWeek := N*7;
FirstDayOfWeek = LastDayOfWeek-6;
Result := (DayOfTheWeek(DateTime)=Day) and
InRange(DayOfTheMonth(DateTime), FirstDayOfWeek, LastDayOfWeek);
end;
This can be done using simple math.
Get the DayOfTheWeek and divide the DayOf by seven.