unit DateEdit;
interface
uses
Windows, SysUtils, Messages, Classes, Controls, Forms, StdCtrls, Graphics;
type
TCheckDateEvent =
function(Sender: TObject; Date: TDateTime): boolean of object;
TDateEdit = class(TCustomEdit)
private
FLastValidDate: TDateTime;
FUseOSDateFormat: boolean;
FDateFormat: string;
fEpoch: integer;
FBlankDate: string;
FColorOK, FColorInvalid: TColor;
fZeroDateIsValid: boolean;
fChangingColorOnly: boolean;
FDoCheckDate: TCheckDateEvent;
FDoInvalidDate: TNotifyEvent;
procedure SetUseOSDateFormat(value: boolean);
function GetDisplayedDate(out dt: TDateTime): boolean;
function GetText: string;
procedure AdjustWidth;
procedure ReformatText;
function GetDateFormat: string;
procedure SetDateFormat(const value: string);
function GetDate: TDateTime;
procedure SetDate(NewDate: TDateTime);
procedure SetEpoch(value: integer);
procedure CMExit(var Message: TCMExit); message CM_EXIT;
procedure CMFontChanged(var Message: TMessage); message CM_FONTCHANGED;
protected
function DoCheckDate(Date: TDatetime): boolean; virtual;
procedure CreateWnd; override;
procedure KeyPress(var Key: Char); override;
procedure Change; override;
public
function IsValidDate: boolean;
constructor Create(AOwner: TComponent); override;
property Date: TDateTime read GetDate write SetDate;
procedure Clear; override;
property Epoch: integer read fEpoch write SetEpoch;
published
property AutoSelect;
property AutoSize;
property BorderStyle;
property Color;
property ColorTextErr: TColor read fColorInvalid write fColorInvalid;
property ColorTextOK: TColor read fColorOK write fColorOK;
property Ctl3D;
property DateFormat: string read GetDateFormat write SetDateFormat;
property Enabled;
property Font;
property HideSelection;
property ParentColor;
property ParentCtl3D;
property ParentFont;
property ParentShowHint;
property PopupMenu;
property ReadOnly;
property ShowHint;
property TabOrder;
property Text: string read GetText;
property UseOSDateFormat: boolean
read FUseOSDateFormat write SetUseOSDateFormat;
property Visible;
property ZeroDateIsValid: boolean
read fZeroDateIsValid write fZeroDateIsValid;
property OnCheckDate: TCheckDateEvent
read FDoCheckDate write FDoCheckDate;
property OnInvalidDate: TNotifyEvent
read FDoInvalidDate write FDoInvalidDate;
property OnChange;
property OnClick;
property OnDragDrop;
property OnDragOver;
property OnEndDrag;
property OnEnter;
property OnExit;
property OnKeyDown;
property OnKeyPress;
property OnKeyUp;
property OnMouseDown;
property OnMouseMove;
property OnMouseUp;
end;
procedure Register;
implementation
resourcestring
s_invalid_date = 'Invalid Date: ';
s_invalid_integer = 'Invalid Integer';
procedure Register;
begin
RegisterComponents('Samples', [TDateEdit]);
end;
function strToWord(const str: string; startPos, len: integer): word;
var
i: integer;
begin
if (length(str) < startPos + len -1) or (len < 1) or (startPos < 1) then
raise Exception.Create(s_invalid_integer);
result := 0;
for i := 1 to len do
begin
if not (str[startPos] in ['0'..'9']) then
raise Exception.Create(s_invalid_integer);
result := (result*10) + ord(str[startPos]) - ord('0');
inc(startPos);
end;
end;
function TryEncodeDate(Year, Month, Day: Word; out Date: TDateTime): Boolean;
var
I: Integer;
DayTable: PDayTable;
begin
Result := False;
DayTable := @MonthDays[IsLeapYear(Year)];
if (Year >= 1) and (Year <= 9999) and (Month >= 1) and (Month <= 12) and
(Day >= 1) and (Day <= DayTable^[Month]) then
begin
for I := 1 to Month - 1 do Inc(Day, DayTable^[I]);
I := Year - 1;
Date := I * 365 + I div 4 - I div 100 + I div 400 + Day - DateDelta;
Result := True;
end;
end;
constructor TDateEdit.Create(AOwner: TComponent);
var
yy,mm,dd: word;
begin
inherited Create(AOwner);
fZeroDateIsValid := true;
FColorInvalid := clRed;
FColorOK := clBlack;
DecodeDate(sysUtils.Date,yy,mm,dd);
fEpoch := round(yy /10)*10 - 80;
SetDateFormat(ShortDateFormat);
FUseOSDateFormat := true;
SetDate(sysUtils.date);
end;
procedure TDateEdit.SetEpoch(value: integer);
begin
if (value < 1900) or (value > 2100) then exit;
fEpoch := value;
end;
function TDateEdit.GetText: string;
begin
result := inherited Text;
end;
procedure TDateEdit.Clear;
begin
ReformatText;
end;
function TDateEdit.GetDateFormat: string;
begin
if FUseOSDateFormat then
result := '' else
result := FDateFormat;
end;
procedure TDateEdit.SetDateFormat(const value: string);
var
ch: char;
begin
if not (csLoading in ComponentState) and FUseOSDateFormat then exit;
if value = '' then ch := 'Y'
else ch := upcase(value[1]);
case ch of
'D': FDateFormat := 'dd/mm/yyyy';
'M': FDateFormat := 'mm/dd/yyyy';
else FDateFormat := 'yyyy/mm/dd';
end;
if FDateFormat[1] = 'y' then
FBlankDate := format('0000%s00%0:s00',[dateSeparator]) else
FBlankDate := format('00%s00%0:s0000',[dateSeparator]);
ReformatText;
AdjustWidth;
end;
procedure TDateEdit.ReformatText;
var
dd,mm,yy: word;
begin
if (FLastValidDate = 0) then
begin
if (Owner is TCustomForm) and (TCustomForm(Owner).ActiveControl = self) then
inherited text := FBlankDate else
inherited text := '';
end else
begin
DecodeDate(FLastValidDate,yy,mm,dd);
case FDateFormat[1] of
'd': inherited Text :=
Format('%2.2d%s%2.2d%1:s%3:4.4d',[dd,DateSeparator,mm,yy]);
'm': inherited Text :=
Format('%2.2d%s%2.2d%1:s%3:4.4d',[mm,DateSeparator,dd,yy]);
else inherited Text :=
Format('%4.4d%s%2.2d%1:s%3:2.2d',[yy,DateSeparator,mm,dd]);
end;
end;
end;
function TDateEdit.GetDisplayedDate(out dt: TDateTime): boolean;
var
dd,mm,yy: word;
i, len: integer;
hasCentury: boolean;
tmpSep1, tmpSep2: integer;
begin
result := false;
len := length(text);
if (len = 0) then
begin
result := fZeroDateIsValid;
dt := 0;
exit;
end;
if len > 10 then exit;
tmpSep1 := 0; tmpSep2 := 0;
for i := 1 to len do
if text[i] = dateSeparator then
begin
if i = 1 then exit
else if tmpSep1 = 0 then tmpSep1 := i
else if (i = tmpSep1+1) or (tmpSep2 > 0) then exit
else tmpSep2 := i;
end else
if not (text[i] in ['0'..'9']) then exit;
if ((tmpSep1 = 0) and not (len in [6,8])) or
((tmpSep1 > 0) and (tmpSep2 = 0)) or
((tmpSep1 > 0) and (tmpSep2 - tmpSep1 > 3)) or (tmpSep2 = len) then exit;
hasCentury :=
((tmpSep1 = 0) and (len = 8)) or
((tmpSep1 = 5) and (FDateFormat[1] = 'y')) or
((FDateFormat[1] <> 'y') and (tmpSep2 = len-4));
try
case FDateFormat[1] of
'y': if (tmpSep1 > 0) then
begin
if tmpSep1 = 4 then exit;
yy := strToWord(text,1,tmpSep1-1);
mm := strToWord(text,tmpSep1+1,tmpSep2-tmpSep1-1);
dd := strToWord(text,tmpSep2+1,len-tmpSep2);
end else
begin
if hasCentury then i := 3 else i := 5;
yy := strToWord(text,1,i-1);
mm := strToWord(text,i,2);
dd := strToWord(text,i+2,2);
end;
'd': if (tmpSep1 > 0) then
begin
if tmpSep2 = len - 3 then exit;
dd := strToWord(text,1,tmpSep1-1);
mm := strToWord(text,tmpSep1+1,tmpSep2-tmpSep1-1);
yy := strToWord(text,tmpSep2+1,len-tmpSep2);
end else
begin
dd := strToWord(text,1,2);
mm := strToWord(text,3,2);
yy := strToWord(text,5,len-4);
end;
else if (tmpSep1 > 0) then
begin
if tmpSep2 = len - 3 then exit;
mm := strToWord(text,1,tmpSep1-1);
dd := strToWord(text,tmpSep1+1,tmpSep2-tmpSep1-1);
yy := strToWord(text,tmpSep2+1,len-tmpSep2);
end else
begin
mm := strToWord(text,1,2);
dd := strToWord(text,3,2);
yy := strToWord(text,5,len-4);
end;
end;
if yy + mm + dd = 0 then
begin
result := fZeroDateIsValid;
dt := 0;
exit;
end;
if not hasCentury then
begin
if yy >= fEpoch mod 100 then
inc(yy, (fEpoch div 100)*100) else
inc(yy, (fEpoch div 100)*100 +100);
end;
result := TryEncodeDate(yy,mm,dd,dt) and DoCheckDate(dt);
except
result := false;
end;
end;
function TDateEdit.IsValidDate: boolean;
var
tmpDt: TDateTime;
begin
Result := GetDisplayedDate(tmpDt);
end;
procedure TDateEdit.SetUseOSDateFormat(value: boolean);
begin
if value = fUseOSDateFormat then exit;
if value or (csDesigning in ComponentState) then
begin
fUseOSDateFormat := false;
SetDateFormat(ShortDateFormat);
end;
fUseOSDateFormat := value;
end;
function TDateEdit.GetDate: TDateTime;
begin
if not GetDisplayedDate(result) then
result := FLastValidDate;
end;
procedure TDateEdit.SetDate(NewDate: TDateTime);
begin
if (NewDate = 0) and not fZeroDateIsValid then
raise Exception.Create(s_invalid_date + FBlankDate)
else if not DoCheckDate(NewDate) then
raise Exception.Create(s_invalid_date + FormatDatetime(FDateFormat,NewDate));
FLastValidDate := NewDate;
ReformatText;
end;
procedure TDateEdit.CMExit(var Message: TCMExit);
var
dudStr: string;
tmpDt: TDateTime;
begin
if GetDisplayedDate(tmpDt) then
begin
FLastValidDate := tmpDt;
ReformatText;
inherited;
end
else if assigned(FDoInvalidDate) then
FDoInvalidDate(self)
else
begin
dudStr := text;
SetFocus;
Font.Color := FColorOK;
raise Exception.create(format('%s"%s"',[s_invalid_date,dudStr]));
end;
end;
procedure TDateEdit.CreateWnd;
begin
inherited CreateWnd;
AdjustWidth;
end;
procedure TDateEdit.CMFontChanged(var Message: TMessage);
begin
if not fChangingColorOnly then
AdjustWidth;
inherited;
end;
procedure TDateEdit.AdjustWidth;
var
DC: HDC;
SaveFont: HFont;
Size: TSize;
begin
if not handleAllocated then exit;
DC := GetDC(handle);
try
SaveFont := SelectObject(DC, Font.Handle);
if gettextextentpoint32(DC,pchar(FBlankDate+'0'),
length(FBlankDate)+1,Size) then
clientwidth := Size.cx;
SelectObject(DC, SaveFont);
finally
ReleaseDC(handle, DC);
end;
end;
procedure TDateEdit.KeyPress(var Key: Char);
var
tmpSelstart: integer;
begin
inherited KeyPress(Key);
if key = #27 then
begin
ReformatText;
SelectAll;
key := #0;
end;
if key < #32 then exit;
if (key in ['+','-']) and not (FLastValidDate = 0) and IsValidDate then
begin
if (key = '+') and DoCheckDate(FLastValidDate +1) then
Date := FLastValidDate +1
else if (key = '-') and DoCheckDate(FLastValidDate -1) then
Date := FLastValidDate -1;
Key := #0;
SelectAll;
end
else if not (Key in [dateSeparator, '0'..'9']) then
begin
Key := #0;
beep;
end
else if (sellength = 0) and (length(text) > 9) then
begin
tmpSelstart := selstart;
inherited text := copy(text,1,selstart);
selstart := tmpSelstart;
end;
end;
procedure TDateEdit.Change;
begin
inherited;
if not handleallocated then exit;
fChangingColorOnly := true;
try
if not IsValidDate then
Font.Color := FColorInvalid else
Font.Color := FColorOK;
finally
fChangingColorOnly := false;
end;
end;
function TDateEdit.DoCheckDate(Date: TDatetime): boolean;
begin
if assigned(FDoCheckDate) then
Result := FDoCheckDate(self, Date) else
Result := true;
end;
end.
|