Possible issue and fix with fractional seconds and AsString formatting.
Posted: 14.11.2020, 22:03
Hi all!
I was initially going to post this in user patches or do a ticket, but I realized that I'm not positive that this is a bug, it could easily be something I'm misunderstanding about how things are supposed to work. Also my patched method below is definitely not fully tested.
I believe that when using DateTime types with a scale (I'm testing with MySQL DateTime(n) and Oracle TimeStamp(n) fields btw) that TDateTime.AsString is supposed to be changing the format to add the fractional seconds based on the scale and the AdjustSecondFractionsFormat property which defaults to true. I believe that this does not work in the current 8 version (I started using Zeoslib on 7.3 alpha/8 so I can't say if it worked previously.)
The easiest way I've found to test this is using the following MySQL query:
The original results for this query wouldn't show the fractional time (except for the serverside formatted final field.) The fixed one looks like this:
And here's my modified version with each of my changes marked with "mjf:"
I hope this is helpful! I'd love to hear any thoughts.
-Mark
I was initially going to post this in user patches or do a ticket, but I realized that I'm not positive that this is a bug, it could easily be something I'm misunderstanding about how things are supposed to work. Also my patched method below is definitely not fully tested.
I believe that when using DateTime types with a scale (I'm testing with MySQL DateTime(n) and Oracle TimeStamp(n) fields btw) that TDateTime.AsString is supposed to be changing the format to add the fractional seconds based on the scale and the AdjustSecondFractionsFormat property which defaults to true. I believe that this does not work in the current 8 version (I started using Zeoslib on 7.3 alpha/8 so I can't say if it worked previously.)
The easiest way I've found to test this is using the following MySQL query:
Code: Select all
select now(), now(3), now(6), date_format(now(6), '%Y-%m-%d %h:%i:%s.%f') ServerFormat;
Code: Select all
now() now(3) now(6) ServerFormat
--------------------- ------------------------- ---------------------------- -----------------------------
2020-11-14 9:01:56 PM 2020-11-14 9:01:56.611 PM 2020-11-14 9:01:56.611936 PM 2020-11-14 09:01:56.611936 PM
Code: Select all
procedure TZDateTimeField.GetText(var Text: string; DisplayText: Boolean);
var
Frmt: string;
DT, D: TDateTime;
Delim: Char;
TS: TZTimeStamp;
I,J: LengthInt;
Fraction: Cardinal;
B: Boolean;
B2: Boolean; // mjf: Added as it appears that "B" was reused half way through the procedure for something else.
P: PChar;
Millis: Word;
begin
if FilledValueWasNull(TS)
then Text := ''
else begin
B := DisplayText and (DisplayFormat <> '');
if B
then Frmt := DisplayFormat
else begin //improve the "C" token of FormatDateTime
if FindFirstDateFormatDelimiter({$IFDEF WITH_FORMATSETTINGS}FormatSettings.{$ENDIF}ShortDateFormat, Delim) and
(Delim <> {$IFDEF WITH_FORMATSETTINGS}FormatSettings.{$ENDIF}DateSeparator)
then Frmt := ZSysUtils.ReplaceChar(Delim, {$IFDEF WITH_FORMATSETTINGS}FormatSettings.{$ENDIF}DateSeparator, {$IFDEF WITH_FORMATSETTINGS}FormatSettings.{$ENDIF}ShortDateFormat)
else Frmt := {$IFDEF WITH_FORMATSETTINGS}FormatSettings.{$ENDIF}ShortDateFormat;
if (FAdjSecFracFmt and (FScale > 0) and (TS.Fractions > 0) ) or
(TS.Hour <> 0) or (TS.Minute <> 0) or (TS.Second <> 0) then begin
Frmt := Frmt + ' ';
if FindFirstTimeFormatDelimiter({$IFDEF WITH_FORMATSETTINGS}FormatSettings.{$ENDIF}LongTimeFormat, Delim) and
(Delim <> {$IFDEF WITH_FORMATSETTINGS}FormatSettings.{$ENDIF}TimeSeparator)
then Frmt := Frmt + ZSysUtils.ReplaceChar(Delim, {$IFDEF WITH_FORMATSETTINGS}FormatSettings.{$ENDIF}TimeSeparator, {$IFDEF WITH_FORMATSETTINGS}FormatSettings.{$ENDIF}LongTimeFormat)
else Frmt := Frmt + {$IFDEF WITH_FORMATSETTINGS}FormatSettings.{$ENDIF}LongTimeFormat;
end;
end;
if Frmt <> FLastFormat[B] then begin
FLastFormat[B] := Frmt;
FSimpleFormat[b] := IsSimpleDateTimeFormat(Frmt);
if FAdjSecFracFmt and (FScale > 0) // mjf: added the FScale > 0 check.
then FFractionFormat[b] := ConvertAsFractionFormat(Frmt, FScale, not FSimpleFormat[b], FFractionLen[b])
else FFractionFormat[b] := Frmt;
end;
if FSimpleFormat[b] then begin
P := @FBuff[0];
Fraction := ts.Fractions;
if not FAdjSecFracFmt then
Fraction := RoundNanoFractionTo(Fraction, FScale);
// mjf: The original used FLastFormat[B], but I think it's supposed to use FFractionFormat[B].
// I := {$IFDEF UNICODE}DateTimeToUni{$ELSE}DateTimeToRaw{$ENDIF}(
// TS.Year, TS.Month, TS.Day, TS.Hour, TS.Minute,
// TS.Second, Fraction, P, FLastFormat[B], False, TS.IsNegative);
I := {$IFDEF UNICODE}DateTimeToUni{$ELSE}DateTimeToRaw{$ENDIF}(
TS.Year, TS.Month, TS.Day, TS.Hour, TS.Minute,
TS.Second, Fraction, P, FFractionFormat[B], False, TS.IsNegative);
System.SetString(Text, P, I);
end else begin
// mjf: This section changed "B" and I think it may just be a bug. I changed "B" to "B2" for this section
B2 := False;
if TryEncodeDate(TS.Year, TS.Month, TS.Day, d) then begin
if FAdjSecFracFmt
then Millis := 0
else Millis := RoundNanoFractionToMillis(TS.Fractions);
B2 := TryEncodeTime(TS.Hour, TS.Minute, TS.Second, Millis, DT);
if B2 then
if d < 0
then DT := D - DT
else DT := D + DT;
end;
if B2 then begin // mjf: End of "B" to "B2" change
//let the compiler do the complex stuff i.e. AM/PM and user defined additional tokens, week days etc.
DateTimeToString(Text, FFractionFormat[b], DT);
if FAdjSecFracFmt then begin
//if shortformat the position may be variable. no chance to cache that info
I := ZFastCode.Pos(MilliReplaceUnQuoted[FScale], Text);
if I > 0 then begin
P := Pointer(Text);
Inc(P, I-1);
Fraction := ts.Fractions;
Fraction := RoundNanoFractionTo(Fraction, FScale);
Fraction := Fraction div FractionLength2NanoSecondMulTable[FScale];
{$IFDEF UNICODE}IntToUnicode{$ELSE}IntToRaw{$ENDIF}(Fraction, P, Byte(FScale));
if FScale > FFractionLen[B] then begin
J := I+FScale;
P := Pointer(Text);
Inc(P, j-2);
Millis := 0;
while (J>I) and (P^ = ('0')) do begin
Inc(Millis);
Dec(J);
Dec(P);
end;
if Millis > 0 then
Delete(Text, J, Millis);
end;
end;
end;
end else begin
if DisplayText
then Text := FInvalidText
else Text := '';
Exit;
end;
if TS.IsNegative then
Text := '-'+Text;
end;
end;
end;
-Mark