Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions src/Horse.Commons.pas
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,13 @@ interface
Classes,
SysUtils,
StrUtils,
DateUtils,
RegExpr;
{$ELSE}
System.Classes,
System.SysUtils,
System.StrUtils,
System.DateUtils,
System.RegularExpressions;
{$ENDIF}

Expand Down Expand Up @@ -134,9 +137,80 @@ function StringCommandToMethodType(const ACommand: string): TMethodType;
{$ENDIF}

function MatchRoute(const AText: string; const AValues: array of string): Boolean;
function IsDateTime(const AText: string): Boolean; overload;
function IsDateTime(const AText: string; out AParsedValue: TDateTime): Boolean; overload;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Boa noite!

Como essa função faz o parse e possui parâmetro de saída, seria legal ter um nome mais significativo como TryParseToDateTime(...).

Suggested change
function IsDateTime(const AText: string; out AParsedValue: TDateTime): Boolean; overload;
function TryParseToDateTime(const AText: string; out AParsedValue: TDateTime): Boolean; overload;


implementation

function IsDateTime(const AText: string): Boolean;
var
LDateTime: TDateTime;
begin
Result := IsDateTime(AText, LDateTime);
end;

function IsDateTime(const AText: string; out AParsedValue: TDateTime): Boolean;
const
C_DATE_FORMATS: array of string = [
'dd/MM/yyyy', 'dd-MM-yyyy', 'dd.MM.yyyy',
'dd/MM/yy', 'dd-MM-yy', 'dd.MM.yy',
'yyyy/MM/dd', 'yyyy-MM-dd', 'yyyy.MM.dd',
'yy/MM/dd', 'yy-MM-dd', 'yy.MM.dd',
'MM/dd/yyyy', 'MM-dd-yyyy', 'MM.dd.yyyy',
'MM/dd/yy', 'MM-dd-yy', 'MM.dd.yy',
'yyyy-MM-dd"T"', 'yyyy-MM-dd'
];

C_TIME_FORMATS: array of string = [
'hh:nn', 'hh:nn AM/PM',
'hh:nn:ss', 'hh:nn:ss AM/PM',
'hh:nn:ss.zzz', 'hh:nn:ss.zzz AM/PM',
'"T"hh:nn:ss',
'"T"hh:nn:ss"Z"',
'"T"hh:nn:sszzz',
'"T"hh:nn:ss.zzz"Z"',
'"T"hh:nn:ss'
];
var
LText: string;
LDateFormat: string;
LTimeFormat: string;
LFormatSettings: TFormatSettings;
begin
Result := False;
AParsedValue := 0;
LText := AText.Trim.TrimLeft(['"']).TrimRight(['"']);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
LText := AText.Trim.TrimLeft(['"']).TrimRight(['"']);
LText := AText.Trim([' ', '"']);


if (Length(LText) >= 5) and ((Length(LText) - LastDelimiter('-', LText)) in [5, 6]) then
LText := Copy(LText, 1, LastDelimiter('-', LText) - 1);

LFormatSettings := {$IF DEFINED(FPC)}DefaultFormatSettings{$ELSE}TFormatSettings.Create{$ENDIF};
for LDateFormat in C_DATE_FORMATS do
begin
LFormatSettings.ShortDateFormat := LDateFormat;
for LTimeFormat in C_TIME_FORMATS do
begin
LFormatSettings.ShortTimeFormat := LTimeFormat;
AParsedValue := StrToDateTimeDef(LText, 0, LFormatSettings);
if (AParsedValue > 0) then Exit(True);
end;
end;

for LDateFormat in C_DATE_FORMATS do
begin
LFormatSettings.ShortDateFormat := LDateFormat;
AParsedValue := {$IF DEFINED(FPC)}StrToDateTimeDef{$ELSE}StrToDateDef{$ENDIF}(LText, 0, LFormatSettings);
if (AParsedValue > 0) then Exit(True);
end;

for LTimeFormat in C_TIME_FORMATS do
begin
LFormatSettings.ShortTimeFormat := LTimeFormat;
AParsedValue := {$IF DEFINED(FPC)}StrToDateTimeDef{$ELSE}StrToTimeDef{$ENDIF}(LText, 0, LFormatSettings);
if (AParsedValue > 0) then Exit(True);
end;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Notei que a função possui 3 loops (e sub loop).
Porque não definir a máscara de data/hora na inicialização, com um escopo estático? Assim não precisaria aumentar a complexidade do parser validando cada opção de formato.

Isso fortaleceria um padrão para parser de tipos de data/hora, padronizando os contratos de todos os endpoints para uma mesma formatação de entrada/saída.

end;

{$IF DEFINED(FPC)}
function StringCommandToMethodType(const ACommand: string): TMethodType;
begin
Expand Down
54 changes: 31 additions & 23 deletions src/Horse.Core.Param.Field.pas
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ THorseCoreParamField = class
FStream: TStream;
FLhsBrackets: THorseCoreParamFieldLhsBrackets;

function GetFormatSettings: TFormatSettings;
procedure RaiseHorseException(const AMessage: string); overload;
procedure RaiseHorseException(const AMessage: string; const Args: array of const); overload;
function TryISO8601ToDate(const AValue: string; out Value: TDateTime): Boolean;
Expand Down Expand Up @@ -97,16 +96,27 @@ function THorseCoreParamField.AsCurrency: Currency;

function THorseCoreParamField.AsDate: TDateTime;
var
{$IF DEFINED(FPC)}
LDay: Word;
LMonth: Word;
LYear: Word;
{$ENDIF}
LStrParam: string;
LFormat: TFormatSettings;
LDateTime: TDateTime;
begin
Result := 0;
LStrParam := Trim(AsString);
try
if LStrParam = EmptyStr then
Exit;
LFormat := GetFormatSettings;
Result := StrToDate(Copy(LStrParam, 1, Length(FDateFormat)), LFormat);
if not IsDateTime(LStrParam, LDateTime) then
raise EConvertError.Create('');

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
raise EConvertError.Create('');
raise EConvertError.CreateFmt('Could not parse TDateTime for "%s"', [LStrParam]);

{$IF DEFINED(FPC)}
DecodeDate(LDateTime, LYear, LMonth, LDay);
Result := EncodeDate(LYear, LMonth, LDay);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Realmente é necessário fazer esse encode/decode aqui?

{$ELSE}
Result.SetDate(LDateTime.Year, LDateTime.Month, LDateTime.Day);
{$ENDIF}
except
on E: EConvertError do
RaiseHorseException(FInvalidFormatMessage, [FFieldName, LStrParam, 'date']);
Expand All @@ -116,15 +126,14 @@ function THorseCoreParamField.AsDate: TDateTime;
function THorseCoreParamField.AsDateTime: TDateTime;
var
LStrParam: string;
LFormat: TFormatSettings;
begin
Result := 0;
LStrParam := Trim(AsString);
try
if LStrParam = EmptyStr then
Exit;
LFormat := GetFormatSettings;
Result := StrToDateTime(LStrParam, LFormat);
if not IsDateTime(LStrParam, Result) then
raise EConvertError.Create('');
except
on E: EConvertError do
RaiseHorseException(FInvalidFormatMessage, [FFieldName, LStrParam, 'datetime']);
Expand Down Expand Up @@ -280,16 +289,28 @@ function THorseCoreParamField.AsString: string;

function THorseCoreParamField.AsTime: TTime;
var
{$IF DEFINED(FPC)}
LHour: Word;
LMinute: Word;
LSecond: Word;
LMilliSecond: Word;
{$ENDIF}
LStrParam: string;
LFormat: TFormatSettings;
LDateTime: TDateTime;
begin
Result := 0;
LStrParam := Trim(AsString);
try
if LStrParam = EmptyStr then
Exit;
LFormat := GetFormatSettings;
Result := StrToTime(Copy(LStrParam, 1, Length(FTimeFormat)), LFormat);
if not IsDateTime(LStrParam, LDateTime) then
raise EConvertError.Create('');
{$IF DEFINED(FPC)}
DecodeTime(LDateTime, LHour, LMinute, LSecond, LMilliSecond);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Realmente é necessário fazer esse encode/decode aqui?

Result := EncodeTime(LHour, LMinute, LSecond, LMilliSecond);
{$ELSE}
TDateTime(Result).SetTime(LDateTime.Hour, LDateTime.Minute, LDateTime.Second, 0);
{$ENDIF}
except
on E: EConvertError do
RaiseHorseException(FInvalidFormatMessage, [FFieldName, LStrParam, 'time']);
Expand Down Expand Up @@ -341,19 +362,6 @@ function THorseCoreParamField.DateFormat(const AValue: string): THorseCoreParamF
FDateFormat := AValue;
end;

function THorseCoreParamField.GetFormatSettings: TFormatSettings;
begin
{$IF DEFINED(FPC)}
Result := DefaultFormatSettings;
{$ELSE}
Result := TFormatSettings.Create;
{$ENDIF}
if FDateFormat.IndexOf('-') > 0 then
Result.DateSeparator := '-';
Result.ShortDateFormat := FDateFormat;
Result.ShortTimeFormat := FTimeFormat;
end;

procedure THorseCoreParamField.InitializeLhsBrackets(const AParams: TDictionary<string, string>; const AFieldName: string);
var
LLhsBracketType: TLhsBracketsType;
Expand Down