From e1d1ddbe8d282e55bc8562351fdb1db169c5a96b Mon Sep 17 00:00:00 2001 From: Weslley Capelari Date: Mon, 9 Jun 2025 15:58:51 -0300 Subject: [PATCH 1/4] =?UTF-8?q?Melhorias=20na=20exporta=C3=A7=C3=A3o=20de?= =?UTF-8?q?=20um=20field=20para=20Date,=20Time=20ou=20DateTime.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Horse.Commons.pas | 108 +++++++++++++++++++++++++++++++++ src/Horse.Core.Param.Field.pas | 50 ++++++++------- 2 files changed, 135 insertions(+), 23 deletions(-) diff --git a/src/Horse.Commons.pas b/src/Horse.Commons.pas index bd3ec1d..536bc5c 100644 --- a/src/Horse.Commons.pas +++ b/src/Horse.Commons.pas @@ -12,10 +12,12 @@ interface Classes, SysUtils, StrUtils, + DateUtils, RegExpr; {$ELSE} System.Classes, System.SysUtils, + System.DateUtils, System.RegularExpressions; {$ENDIF} @@ -134,9 +136,115 @@ function StringCommandToMethodType(const ACommand: string): TMethodType; {$ENDIF} function MatchRoute(const AText: string; const AValues: array of string): Boolean; +function IsDateTime(const AText: string; out AParsedValue: TDateTime): Boolean; implementation +function IsDateTime(const AText: string; out AParsedValue: TDateTime): Boolean; +const + // Formatos de data + C_DATE_FORMATS: array of string = [ + // Dia, mês, ano + 'dd/MM/yyyy', 'dd-MM-yyyy', 'dd.MM.yyyy', + 'dd/MM/yy', 'dd-MM-yy', 'dd.MM.yy', + + // Ano, mês, dia + 'yyyy/MM/dd', 'yyyy-MM-dd', 'yyyy.MM.dd', + 'yy/MM/dd', 'yy-MM-dd', 'yy.MM.dd', + + // Mês, dia, ano + 'MM/dd/yyyy', 'MM-dd-yyyy', 'MM.dd.yyyy', + 'MM/dd/yy', 'MM-dd-yy', 'MM.dd.yy', + + // ISO padrão com T + 'yyyy-MM-dd"T"', // usado como prefixo para os formatos DateTime ISO + 'yyyy-MM-dd' + ]; + + // Formatos de hora + C_TIME_FORMATS: array of string = [ + // Horas e minutos + 'hh:nn', 'hh:nn AM/PM', + + // Horas, minutos e segundos + 'hh:nn:ss', 'hh:nn:ss AM/PM', + + // Com milissegundos + 'hh:nn:ss.zzz', 'hh:nn:ss.zzz AM/PM', + + // ISO 8601 / UTC com T + '"T"hh:nn:ss', // T + hora + '"T"hh:nn:ss"Z"', // UTC (Z) + '"T"hh:nn:sszzz', // UTC com milissegundos + '"T"hh:nn:ss.zzz"Z"', // UTC com ms e Z + + // ISO 8601 com timezone + '"T"hh:nn:ss+hh:nn', '"T"hh:nn:ss-hh:nn', + '"T"hh:nn:ss+hhmm', '"T"hh:nn:ss-hhmm' + ]; + + // Formatos DateTime combinados + C_DATETIME_FORMATS: array of string = [ + // Combinações comuns + 'dd/MM/yyyy hh:nn:ss', + 'dd-MM-yyyy hh:nn:ss', + 'yyyy-MM-dd hh:nn:ss', + 'yyyy/MM/dd hh:nn:ss', + 'dd/MM/yyyy hh:nn:ss AM/PM', + 'yyyy-MM-dd hh:nn:ss AM/PM', + 'yyyy-MM-dd"T"hh:nn:ss', + 'yyyy-MM-dd"T"hh:nn:ss.zzz', + 'yyyy-MM-dd"T"hh:nn:ss"Z"', + 'yyyy-MM-dd"T"hh:nn:ss.zzz"Z"', + 'yyyy-MM-dd"T"hh:nn:ss+hh:nn', + 'yyyy-MM-dd"T"hh:nn:ss-hh:nn', + 'yyyy-MM-dd"T"hh:nn:ss+hhmm', + 'yyyy-MM-dd"T"hh:nn:ss-hhmm' + ]; +var + LFormat: string; + LFormatSettings: TFormatSettings; +begin + Result := False; + AParsedValue := 0; + LFormatSettings := {$IF DEFINED(FPC)}DefaultFormatSettings{$ELSE}TFormatSettings.Create{$ENDIF}; + // Tentativa com formatos DateTime + for LFormat in C_DATETIME_FORMATS do + begin + try + LFormatSettings.LongDateFormat := LFormat; + AParsedValue := StrToDateTime(AText, LFormatSettings); + Exit(True); + except + // Ignora erro de conversão + end; + end; + + // Tentativa com formatos apenas de data + for LFormat in C_DATE_FORMATS do + begin + try + LFormatSettings.ShortDateFormat := LFormat; + AParsedValue := StrToDate(AText, LFormatSettings); + Exit(True); + except + // Ignora erro de conversão + end; + end; + + // Tentativa com formatos apenas de hora + for LFormat in C_TIME_FORMATS do + begin + try + LFormatSettings.ShortTimeFormat := LFormat; + AParsedValue := StrToTime(AText, LFormatSettings); + Exit(True); + except + // Ignora erro de conversão + end; + end; +end; + {$IF DEFINED(FPC)} function StringCommandToMethodType(const ACommand: string): TMethodType; begin diff --git a/src/Horse.Core.Param.Field.pas b/src/Horse.Core.Param.Field.pas index 0b34c47..0c05b66 100644 --- a/src/Horse.Core.Param.Field.pas +++ b/src/Horse.Core.Param.Field.pas @@ -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; @@ -97,16 +96,25 @@ function THorseCoreParamField.AsCurrency: Currency; function THorseCoreParamField.AsDate: TDateTime; var + LDay: Word; + LMonth: Word; + LYear: Word; 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(''); + {$IF DEFINED(FPC)} + DecodeDate(LDateTime, LYear, LMonth, LDay); + Result := EncodeDate(LYear, LMonth, LDay); + {$ELSE} + Result.SetDate(LDateTime.Year, LDateTime.Month, LDateTime.Day); + {$ENDIF} except on E: EConvertError do RaiseHorseException(FInvalidFormatMessage, [FFieldName, LStrParam, 'date']); @@ -116,15 +124,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']); @@ -280,16 +287,26 @@ function THorseCoreParamField.AsString: string; function THorseCoreParamField.AsTime: TTime; var + LHour: Word; + LMinute: Word; + LSecond: Word; + LMilliSecond: Word; 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); + 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']); @@ -341,19 +358,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; const AFieldName: string); var LLhsBracketType: TLhsBracketsType; From 950f1e720e1e95d1e5cd1056e42dc651523addb6 Mon Sep 17 00:00:00 2001 From: Weslley Capelari Date: Mon, 9 Jun 2025 17:24:32 -0300 Subject: [PATCH 2/4] =?UTF-8?q?Melhorias=20na=20fun=C3=A7=C3=A3o=20de=20co?= =?UTF-8?q?nvers=C3=A3o=20de=20string=20para=20TDateTime?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Horse.Commons.pas | 115 ++++++++++++--------------------- src/Horse.Core.Param.Field.pas | 6 +- 2 files changed, 45 insertions(+), 76 deletions(-) diff --git a/src/Horse.Commons.pas b/src/Horse.Commons.pas index 536bc5c..540ebbf 100644 --- a/src/Horse.Commons.pas +++ b/src/Horse.Commons.pas @@ -17,6 +17,7 @@ interface {$ELSE} System.Classes, System.SysUtils, + System.StrUtils, System.DateUtils, System.RegularExpressions; {$ENDIF} @@ -136,112 +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; out AParsedValue: TDateTime): Boolean; +function IsDateTime(const AText: string): Boolean; overload; +function IsDateTime(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 - // Formatos de data C_DATE_FORMATS: array of string = [ - // Dia, mês, ano 'dd/MM/yyyy', 'dd-MM-yyyy', 'dd.MM.yyyy', 'dd/MM/yy', 'dd-MM-yy', 'dd.MM.yy', - - // Ano, mês, dia 'yyyy/MM/dd', 'yyyy-MM-dd', 'yyyy.MM.dd', 'yy/MM/dd', 'yy-MM-dd', 'yy.MM.dd', - - // Mês, dia, ano 'MM/dd/yyyy', 'MM-dd-yyyy', 'MM.dd.yyyy', 'MM/dd/yy', 'MM-dd-yy', 'MM.dd.yy', - - // ISO padrão com T - 'yyyy-MM-dd"T"', // usado como prefixo para os formatos DateTime ISO - 'yyyy-MM-dd' + 'yyyy-MM-dd"T"', 'yyyy-MM-dd' ]; - // Formatos de hora C_TIME_FORMATS: array of string = [ - // Horas e minutos 'hh:nn', 'hh:nn AM/PM', - - // Horas, minutos e segundos 'hh:nn:ss', 'hh:nn:ss AM/PM', - - // Com milissegundos 'hh:nn:ss.zzz', 'hh:nn:ss.zzz AM/PM', - - // ISO 8601 / UTC com T - '"T"hh:nn:ss', // T + hora - '"T"hh:nn:ss"Z"', // UTC (Z) - '"T"hh:nn:sszzz', // UTC com milissegundos - '"T"hh:nn:ss.zzz"Z"', // UTC com ms e Z - - // ISO 8601 com timezone - '"T"hh:nn:ss+hh:nn', '"T"hh:nn:ss-hh:nn', - '"T"hh:nn:ss+hhmm', '"T"hh:nn:ss-hhmm' - ]; - - // Formatos DateTime combinados - C_DATETIME_FORMATS: array of string = [ - // Combinações comuns - 'dd/MM/yyyy hh:nn:ss', - 'dd-MM-yyyy hh:nn:ss', - 'yyyy-MM-dd hh:nn:ss', - 'yyyy/MM/dd hh:nn:ss', - 'dd/MM/yyyy hh:nn:ss AM/PM', - 'yyyy-MM-dd hh:nn:ss AM/PM', - 'yyyy-MM-dd"T"hh:nn:ss', - 'yyyy-MM-dd"T"hh:nn:ss.zzz', - 'yyyy-MM-dd"T"hh:nn:ss"Z"', - 'yyyy-MM-dd"T"hh:nn:ss.zzz"Z"', - 'yyyy-MM-dd"T"hh:nn:ss+hh:nn', - 'yyyy-MM-dd"T"hh:nn:ss-hh:nn', - 'yyyy-MM-dd"T"hh:nn:ss+hhmm', - 'yyyy-MM-dd"T"hh:nn:ss-hhmm' + '"T"hh:nn:ss', + '"T"hh:nn:ss"Z"', + '"T"hh:nn:sszzz', + '"T"hh:nn:ss.zzz"Z"', + '"T"hh:nn:ss' ]; var - LFormat: string; + LText: string; + LDateFormat: string; + LTimeFormat: string; LFormatSettings: TFormatSettings; begin Result := False; AParsedValue := 0; + LText := AText; + + if MatchStr(LText[Length(LText) - 4], ['+', '-']) then + LText := Copy(LText, 1, Length(LText) - 5); + + if MatchStr(LText[Length(LText) - 5], ['+', '-']) then + LText := Copy(LText, 1, Length(LText) - 6); + LFormatSettings := {$IF DEFINED(FPC)}DefaultFormatSettings{$ELSE}TFormatSettings.Create{$ENDIF}; - // Tentativa com formatos DateTime - for LFormat in C_DATETIME_FORMATS do + for LDateFormat in C_DATE_FORMATS do begin - try - LFormatSettings.LongDateFormat := LFormat; - AParsedValue := StrToDateTime(AText, LFormatSettings); - Exit(True); - except - // Ignora erro de conversão + 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; - // Tentativa com formatos apenas de data - for LFormat in C_DATE_FORMATS do + for LDateFormat in C_DATE_FORMATS do begin - try - LFormatSettings.ShortDateFormat := LFormat; - AParsedValue := StrToDate(AText, LFormatSettings); - Exit(True); - except - // Ignora erro de conversão - end; + LFormatSettings.ShortDateFormat := LDateFormat; + AParsedValue := {$IF DEFINED(FPC)}StrToDateTimeDef{$ELSE}StrToDateDef{$ENDIF}(LText, 0, LFormatSettings); + if (AParsedValue > 0) then Exit(True); end; - // Tentativa com formatos apenas de hora - for LFormat in C_TIME_FORMATS do + for LTimeFormat in C_TIME_FORMATS do begin - try - LFormatSettings.ShortTimeFormat := LFormat; - AParsedValue := StrToTime(AText, LFormatSettings); - Exit(True); - except - // Ignora erro de conversão - end; + LFormatSettings.ShortTimeFormat := LTimeFormat; + AParsedValue := {$IF DEFINED(FPC)}StrToDateTimeDef{$ELSE}StrToTimeDef{$ENDIF}(LText, 0, LFormatSettings); + if (AParsedValue > 0) then Exit(True); end; end; diff --git a/src/Horse.Core.Param.Field.pas b/src/Horse.Core.Param.Field.pas index 0c05b66..d8b4fa9 100644 --- a/src/Horse.Core.Param.Field.pas +++ b/src/Horse.Core.Param.Field.pas @@ -95,7 +95,7 @@ function THorseCoreParamField.AsCurrency: Currency; end; function THorseCoreParamField.AsDate: TDateTime; -var +var LDay: Word; LMonth: Word; LYear: Word; @@ -286,7 +286,7 @@ function THorseCoreParamField.AsString: string; end; function THorseCoreParamField.AsTime: TTime; -var +var LHour: Word; LMinute: Word; LSecond: Word; @@ -300,7 +300,7 @@ function THorseCoreParamField.AsTime: TTime; if LStrParam = EmptyStr then Exit; if not IsDateTime(LStrParam, LDateTime) then - raise EConvertError.Create(''); + raise EConvertError.Create(''); {$IF DEFINED(FPC)} DecodeTime(LDateTime, LHour, LMinute, LSecond, LMilliSecond); Result := EncodeTime(LHour, LMinute, LSecond, LMilliSecond); From a2173ac09be3d97498e2645603ae2adc3080cc49 Mon Sep 17 00:00:00 2001 From: Weslley Capelari Date: Mon, 9 Jun 2025 17:28:26 -0300 Subject: [PATCH 3/4] =?UTF-8?q?Corre=C3=A7=C3=A3o=20de=20Hints?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Horse.Core.Param.Field.pas | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Horse.Core.Param.Field.pas b/src/Horse.Core.Param.Field.pas index d8b4fa9..62a84fa 100644 --- a/src/Horse.Core.Param.Field.pas +++ b/src/Horse.Core.Param.Field.pas @@ -95,10 +95,12 @@ function THorseCoreParamField.AsCurrency: Currency; end; function THorseCoreParamField.AsDate: TDateTime; -var +var + {$IF DEFINED(FPC)} LDay: Word; LMonth: Word; LYear: Word; + {$ENDIF} LStrParam: string; LDateTime: TDateTime; begin @@ -286,11 +288,13 @@ function THorseCoreParamField.AsString: string; end; function THorseCoreParamField.AsTime: TTime; -var +var + {$IF DEFINED(FPC)} LHour: Word; LMinute: Word; LSecond: Word; LMilliSecond: Word; + {$ENDIF} LStrParam: string; LDateTime: TDateTime; begin From ca898e68183103f3665dabbba8c387ea97944013 Mon Sep 17 00:00:00 2001 From: Weslley Capelari Date: Wed, 2 Jul 2025 11:40:56 -0300 Subject: [PATCH 4/4] =?UTF-8?q?Corre=C3=A7=C3=A3o=20na=20l=C3=B3gica?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Horse.Commons.pas | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/Horse.Commons.pas b/src/Horse.Commons.pas index 540ebbf..ab7a560 100644 --- a/src/Horse.Commons.pas +++ b/src/Horse.Commons.pas @@ -179,13 +179,10 @@ function IsDateTime(const AText: string; out AParsedValue: TDateTime): Boolean; begin Result := False; AParsedValue := 0; - LText := AText; + LText := AText.Trim.TrimLeft(['"']).TrimRight(['"']); - if MatchStr(LText[Length(LText) - 4], ['+', '-']) then - LText := Copy(LText, 1, Length(LText) - 5); - - if MatchStr(LText[Length(LText) - 5], ['+', '-']) then - LText := Copy(LText, 1, Length(LText) - 6); + 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