ViewVC Help
View File | Revision Log | Show Annotations | Download File | View Changeset | Root Listing
root/public/ibx/branches/udr/client/IBUtils.pas
(Generate patch)

Comparing ibx/trunk/fbintf/IBUtils.pas (file contents):
Revision 263 by tony, Thu Dec 6 15:55:01 2018 UTC vs.
Revision 348 by tony, Wed Oct 6 09:38:14 2021 UTC

# Line 39 | Line 39 | unit IBUtils;
39   {$IFDEF FPC}
40   {$Mode Delphi}
41   {$codepage UTF8}
42 {$define HASREQEX}
42   {$ENDIF}
43  
44 + { $IF declared(CompilerVersion) and (CompilerVersion >= 22)}
45 + { $define HASDELPHIREQEX}
46 + { $IFEND}
47  
48   interface
49  
# Line 258 | Line 260 | type
260    sqltPlaceholder,
261    sqltSingleQuotes,
262    sqltDoubleQuotes,
263 +  sqltBackslash,
264    sqltComma,
265    sqltPeriod,
266    sqltEquals,
# Line 279 | Line 282 | type
282    sqltOpenBracket,
283    sqltCloseBracket,
284    sqltPipe,
285 +  sqltMinus,
286    sqltConcatSymbol,
287    sqltLT,
288    sqltGT,
# Line 592 | Line 596 | type
596      function TokenFound(var token: TSQLTokens): boolean; override;
597    end;
598  
599 +    { TSQLParamProcessor }
600 +
601 +  TSQLParamProcessor = class(TSQLwithNamedParamsTokeniser)
602 +  private
603 +  const
604 +    sIBXParam = 'IBXParam';  {do not localize}
605 +  private
606 +    FInString: AnsiString;
607 +    FIndex: integer;
608 +    function DoExecute(GenerateParamNames: boolean;
609 +        var slNames: TStrings): AnsiString;
610 +  protected
611 +    function GetChar: AnsiChar; override;
612 +  public
613 +    class function Execute(sSQL: AnsiString; GenerateParamNames: boolean;
614 +        var slNames: TStrings): AnsiString;
615 +  end;
616 +
617 +
618   function Max(n1, n2: Integer): Integer;
619   function Min(n1, n2: Integer): Integer;
620   function RandomString(iLength: Integer): AnsiString;
# Line 613 | Line 636 | function ParseConnectString(ConnectStrin
636                var PortNo: AnsiString): boolean;
637   function GetProtocol(ConnectString: AnsiString): TProtocolAll;
638  
639 + {$IF declared(TFormatSettings)}
640 + function ParseDateTimeTZString(aDateTimeStr: Ansistring; var aDateTime: TDateTime;
641 +              var aTimezone: AnsiString; aFormatSettings: TFormatSettings; TimeOnly: boolean=false): boolean; overload;
642 + {$IFEND}
643 + function ParseDateTimeTZString(aDateTimeStr: Ansistring; var aDateTime: TDateTime;
644 +              var aTimezone: AnsiString; TimeOnly: boolean=false): boolean;  overload;
645 + procedure FBDecodeTime(aTime: TDateTime; var Hour, Minute, Second: word; var DeciMillisecond: cardinal);
646 + function FBEncodeTime(Hour, Minute, Second, DeciMillisecond: cardinal): TDateTime;
647 + function FBFormatDateTime(fmt: AnsiString; aDateTime: TDateTime): AnsiString;
648 + function FormatTimeZoneOffset(EffectiveTimeOffsetMins: integer): AnsiString;
649 + function DecodeTimeZoneOffset(TZOffset: AnsiString; var dstOffset: integer): boolean;
650 + function StripLeadingZeros(Value: AnsiString): AnsiString;
651 +
652   implementation
653  
654   uses FBMessages
655  
656 < {$IFDEF HASREQEX}
656 > {$IFDEF FPC}
657   ,RegExpr
658 + {$ELSE}
659 + {$IF declared(CompilerVersion) and (CompilerVersion >= 22)}
660 + , RegularExpressions
661 + {$IFEND}
662   {$ENDIF};
663  
664 +
665   function Max(n1, n2: Integer): Integer;
666   begin
667    if (n1 > n2) then
# Line 716 | Line 757 | end;
757  
758   function QuoteIdentifier(Dialect: Integer; Value: AnsiString): AnsiString;
759   begin
760 +  Value := TrimRight(Value);
761    if Dialect = 1 then
762 <    Value := AnsiUpperCase(Trim(Value))
762 >    Value := AnsiUpperCase(Value)
763    else
764      Value := '"' + StringReplace (Value, '""', '"', [rfReplaceAll]) + '"';
765    Result := Value;
# Line 737 | Line 779 | begin
779    Result := true;
780   end;
781  
782 + function SchemeToProtocol(scheme: AnsiString): TProtocolAll;
783 + begin
784 +  scheme := AnsiUpperCase(scheme);
785 +  if scheme = 'INET' then
786 +    Result := inet
787 +  else
788 +  if scheme = 'INET4' then
789 +    Result := inet4
790 +  else
791 +  if scheme = 'INET6' then
792 +    Result := inet6
793 +  else
794 +  if scheme = 'XNET' then
795 +    Result := xnet
796 +  else
797 +  if scheme = 'WNET' then
798 +    Result := wnet
799 + end;
800 +
801   {Extracts the Database Connect string from a Create Database Statement}
802  
803 < {$IFDEF HASREQEX}
803 > {$IF declared(TRegexpr)}
804   function ExtractConnectString(const CreateSQL: AnsiString;
805    var ConnectString: AnsiString): boolean;
806   var RegexObj: TRegExpr;
# Line 762 | Line 823 | function ParseConnectString(ConnectStrin
823    DatabaseName: AnsiString; var Protocol: TProtocolAll; var PortNo: AnsiString
824    ): boolean;
825  
765  function GetProtocol(scheme: AnsiString): TProtocolAll;
766  begin
767    scheme := AnsiUpperCase(scheme);
768    if scheme = 'INET' then
769      Result := inet
770    else
771    if scheme = 'INET4' then
772      Result := inet4
773    else
774    if scheme = 'INET6' then
775      Result := inet6
776    else
777    if scheme = 'XNET' then
778      Result := xnet
779    else
780    if scheme = 'WNET' then
781      Result := wnet
782  end;
783
826   var RegexObj: TRegExpr;
827   begin
828    ServerName := '';
# Line 796 | Line 838 | begin
838      if Result then
839      begin
840        {URL type connect string}
841 <      Protocol := GetProtocol(RegexObj.Match[1]);
841 >      Protocol := SchemeToProtocol(RegexObj.Match[1]);
842        ServerName := RegexObj.Match[2];
843        if RegexObj.MatchLen[3] > 0 then
844          PortNo := system.Copy(ConnectString,RegexObj.MatchPos[3]+1,RegexObj.MatchLen[3]-1);
# Line 811 | Line 853 | begin
853        Result := RegexObj.Exec(ConnectString);
854        if Result then
855        begin
856 <        Protocol := GetProtocol(RegexObj.Match[1]);
856 >        Protocol := SchemeToProtocol(RegexObj.Match[1]);
857          DatabaseName := RegexObj.Match[2];
858        end
859        else
# Line 860 | Line 902 | begin
902    end;
903   end;
904  
905 < function GetProtocol(ConnectString: AnsiString): TProtocolAll;
906 < var ServerName,
907 <    DatabaseName: AnsiString;
908 <    PortNo: AnsiString;
905 > {$ELSE}
906 > {$IF declared(TRegex)}
907 > function ExtractConnectString(const CreateSQL: AnsiString;
908 >  var ConnectString: AnsiString): boolean;
909 > var Regex: TRegEx;
910 >    Match: TMatch;
911   begin
912 <  ParseConnectString(ConnectString,ServerName,DatabaseName,Result,PortNo);
912 >  Regex := TRegEx.Create('^ *CREATE +(DATABASE|SCHEMA) +''(.*)''',[roIgnoreCase]);
913 >  {extact database file spec}
914 >  Match := Regex.Match(CreateSQL);
915 >  Result := Match.Success and (Match.Groups.Count = 3);
916 >  if Result then
917 >    ConnectString := Match.Groups[2].Value;
918   end;
919  
920 + function ParseConnectString(ConnectString: AnsiString; var ServerName,
921 +  DatabaseName: AnsiString; var Protocol: TProtocolAll; var PortNo: AnsiString
922 +  ): boolean;
923 +
924 + var Regex: TRegEx;
925 +    Match: TMatch;
926 + begin
927 +  ServerName := '';
928 +  DatabaseName := ConnectString;
929 +  PortNo := '';
930 +  Protocol := unknownProtocol;
931 +  {extact database file spec}
932 +  Match := Regex.Match(ConnectString,'^([a-zA-Z46]+)://([a-zA-Z0-9\-\.]*)(|:[0-9a-zA-Z\-]+)/(.*)$',[roIgnoreCase]);
933 +  Result := Match.Success and (Match.Groups.Count = 5);
934 +  if Result then
935 +  begin
936 +    {URL type connect string}
937 +    Protocol := SchemeToProtocol(Match.Groups[1].Value);
938 +    ServerName := Match.Groups[2].Value;
939 +    PortNo := Match.Groups[3].Value;
940 +    DatabaseName := Match.Groups[4].Value;
941 +    if ServerName = '' then
942 +      DatabaseName := '/' + DatabaseName;
943 +  end
944 +  else
945 +  begin
946 +    {URL type connect string - local loop}
947 +    Match := Regex.Match(ConnectString,'^([a-zA-Z46]+)://(.*)$',[roIgnoreCase]);
948 +    Result := Match.Success and (Match.Groups.Count = 3);
949 +    if Result then
950 +    begin
951 +      Protocol := SchemeToProtocol(Match.Groups[1].Value);
952 +      DatabaseName := Match.Groups[2].Value;
953 +    end
954 +    else
955 +    begin
956 +      Match := Regex.Match(ConnectString,'^([a-zA-Z]:\\.*)',[roIgnoreCase]);
957 +      Result := Match.Success;
958 +      if Result then
959 +        Protocol := Local {Windows with leading drive ID}
960 +      else
961 +      begin
962 +        Match := Regex.Match(ConnectString,'^([a-zA-Z0-9\-\.]+)(|/[0-9a-zA-Z\-]+):(.*)$',[roIgnoreCase]);
963 +        Result := Match.Success and (Match.Groups.Count = 4);
964 +        if Result then
965 +        begin
966 +          {Legacy TCP Format}
967 +          ServerName := Match.Groups[1].Value;
968 +          PortNo := Match.Groups[2].Value;
969 +          DatabaseName := Match.Groups[3].Value;
970 +          Protocol := TCP;
971 +        end
972 +        else
973 +        begin
974 +          Match := Regex.Match(ConnectString,'^\\\\([a-zA-Z0-9\-\.]+)(|@[0-9a-zA-Z\-]+)\\(.*)$',[roIgnoreCase]);
975 +          Result := Match.Success and (Match.Groups.Count = 4);
976 +          if Result then
977 +          begin
978 +            {Netbui}
979 +            ServerName := Match.Groups[1].Value;
980 +            PortNo := Match.Groups[2].Value;
981 +            DatabaseName := Match.Groups[3].Value;
982 +            Protocol := NamedPipe
983 +          end
984 +          else
985 +          begin
986 +            Result := true;
987 +            Protocol := Local; {Assume local}
988 +          end;
989 +        end;
990 +      end;
991 +    end;
992 +  end;
993 + end;
994   {$ELSE}
995 < {cruder version of above for Delphi. Older versions lack regular expression
995 > {cruder version of above for Delphi < XE. Older versions lack regular expression
996   handling.}
997   function ExtractConnectString(const CreateSQL: AnsiString;
998    var ConnectString: AnsiString): boolean;
# Line 890 | Line 1013 | begin
1013    end;
1014   end;
1015  
893 function GetProtocol(ConnectString: AnsiString): TProtocolAll;
894 begin
895  Result := unknownProtocol; {not implemented for Delphi}
896 end;
897
1016   function ParseConnectString(ConnectString: AnsiString;
1017                var ServerName, DatabaseName: AnsiString; var Protocol: TProtocolAll;
1018                var PortNo: AnsiString): boolean;
# Line 902 | Line 1020 | begin
1020    Result := false;
1021   end;
1022  
1023 < {$ENDIF}
1023 > {$IFEND}
1024 > {$IFEND}
1025 >
1026 > function GetProtocol(ConnectString: AnsiString): TProtocolAll;
1027 > var ServerName,
1028 >    DatabaseName: AnsiString;
1029 >    PortNo: AnsiString;
1030 > begin
1031 >  if not ParseConnectString(ConnectString,ServerName,DatabaseName,Result,PortNo) then
1032 >    Result := unknownProtocol;
1033 > end;
1034  
1035   {Make a connect string in format appropriate protocol}
1036  
# Line 918 | Line 1046 | function MakeConnectString(ServerName, D
1046    end;
1047  
1048   begin
1049 +  if ServerName = '' then ServerName := 'localhost';
1050    if PortNo <> '' then
1051      case Protocol of
1052      NamedPipe:
# Line 948 | Line 1077 | end;
1077  
1078   function QuoteIdentifierIfNeeded(Dialect: Integer; Value: AnsiString): AnsiString;
1079   begin
1080 +  Value := TrimRight(Value);
1081    if (Dialect = 3) and
1082      (IsReservedWord(Value) or not IsSQLIdentifier(Value) or (AnsiUpperCase(Value) <> Value)) then
1083       Result := '"' + StringReplace (TrimRight(Value), '"', '""', [rfReplaceAll]) + '"'
# Line 974 | Line 1104 | begin
1104    Result := StringReplace(s,'''','''''',[rfReplaceAll]);
1105   end;
1106  
1107 + { TSQLParamProcessor }
1108 +
1109 + function TSQLParamProcessor.DoExecute(GenerateParamNames: boolean;
1110 +  var slNames: TStrings): AnsiString;
1111 + var token: TSQLTokens;
1112 +    iParamSuffix: Integer;
1113 + begin
1114 +  Result := '';
1115 +  iParamSuffix := 0;
1116 +
1117 +  while not EOF do
1118 +  begin
1119 +    token := GetNextToken;
1120 +    case token of
1121 +    sqltParam,
1122 +    sqltQuotedParam:
1123 +      begin
1124 +        Result := Result + '?';
1125 +        slNames.Add(TokenText);
1126 +      end;
1127 +
1128 +    sqltPlaceHolder:
1129 +      if GenerateParamNames then
1130 +      begin
1131 +        Inc(iParamSuffix);
1132 +        slNames.AddObject(sIBXParam + IntToStr(iParamSuffix),self); //Note local convention
1133 +                                            //add pointer to self to mark entry
1134 +        Result := Result + '?';
1135 +      end
1136 +      else
1137 +        IBError(ibxeSQLParseError, [SParamNameExpected]);
1138 +
1139 +    sqltQuotedString:
1140 +      Result := Result + '''' + SQLSafeString(TokenText) + '''';
1141 +
1142 +    sqltIdentifierInDoubleQuotes:
1143 +      Result := Result + '"' + StringReplace(TokenText,'"','""',[rfReplaceAll]) + '"';
1144 +
1145 +    sqltComment:
1146 +      Result := Result + '/*' + TokenText + '*/';
1147 +
1148 +    sqltCommentLine:
1149 +      Result := Result + '--' + TokenText + LineEnding;
1150 +
1151 +    sqltEOL:
1152 +      Result := Result + LineEnding;
1153 +
1154 +    else
1155 +      Result := Result + TokenText;
1156 +    end;
1157 +  end;
1158 + end;
1159 +
1160 + function TSQLParamProcessor.GetChar: AnsiChar;
1161 + begin
1162 +  if FIndex <= Length(FInString) then
1163 +  begin
1164 +    Result := FInString[FIndex];
1165 +    Inc(FIndex);
1166 +  end
1167 +  else
1168 +    Result := #0;
1169 + end;
1170 +
1171 + class function TSQLParamProcessor.Execute(sSQL: AnsiString;
1172 +  GenerateParamNames: boolean; var slNames: TStrings): AnsiString;
1173 + begin
1174 +  with self.Create do
1175 +  try
1176 +    FInString := sSQL;
1177 +    FIndex := 1;
1178 +    Result := DoExecute(GenerateParamNames,slNames);
1179 +  finally
1180 +    Free;
1181 +  end;
1182 + end;
1183 +
1184   { TSQLwithNamedParamsTokeniser }
1185  
1186   procedure TSQLwithNamedParamsTokeniser.Assign(source: TSQLTokeniser);
# Line 1042 | Line 1249 | begin
1249    stInBlock:
1250      begin
1251        case token of
1252 <      sqltBegin:
1252 >      sqltBegin,
1253 >      sqltCase:
1254            Inc(FNested);
1255  
1256        sqltEnd:
# Line 1093 | Line 1301 | begin
1301        Result := sqltSingleQuotes;
1302      '/':
1303        Result := sqltForwardSlash;
1304 +    '\':
1305 +      Result := sqltBackslash;
1306      '*':
1307        Result := sqltAsterisk;
1308      '(':
# Line 1111 | Line 1321 | begin
1321        Result := sqltOpenSquareBracket;
1322      ']':
1323        Result := sqltCloseSquareBracket;
1324 +    '-':
1325 +      Result := sqltMinus;
1326      '<':
1327        Result := sqltLT;
1328      '>':
# Line 1383 | Line 1595 | begin
1595                GetNext;
1596                FState := stInComment;
1597              end
1598 <            else
1599 <            if FNextToken = sqltForwardSlash then
1598 >          end;
1599 >
1600 >        sqltMinus:
1601 >          begin
1602 >            if FNextToken = sqltMinus then
1603              begin
1604                FString := '';
1605                GetNext;
# Line 1405 | Line 1620 | begin
1620            end;
1621  
1622          sqltIdentifier:
1623 <          if FNextToken = sqltIdentifier then
1623 >          if FNextToken in [sqltIdentifier,sqltNumberString] then
1624              FState := stInIdentifier;
1625  
1626          sqltNumberString:
# Line 1420 | Line 1635 | begin
1635    until TokenFound(Result) or EOF;
1636   end;
1637  
1638 + function ParseDateTimeTZString(aDateTimeStr: Ansistring; var aDateTime: TDateTime;
1639 +  var aTimezone: AnsiString; TimeOnly: boolean): boolean;
1640 + {$IF declared(TFormatSettings)}
1641 + begin
1642 +    {$IF declared(DefaultFormatSettings)}
1643 +    Result := ParseDateTimeTZString(aDateTimeStr,aDateTime,aTimeZone,DefaultFormatSettings,TimeOnly);
1644 +    {$ELSE}
1645 +    {$IF declared(FormatSettings)}
1646 +    Result := ParseDateTimeTZString(aDateTimeStr,aDateTime,aTimeZone,FormatSettings,TimeOnly);
1647 +    {$IFEND} {$IFEND}
1648 + end;
1649 +
1650 + function ParseDateTimeTZString(aDateTimeStr: Ansistring; var aDateTime: TDateTime;
1651 +              var aTimezone: AnsiString; aFormatSettings: TFormatSettings; TimeOnly: boolean=false): boolean;
1652 + {$IFEND}
1653 + const
1654 +  whitespacechars = [' ',#$09,#$0A,#$0D];
1655 + var i,j,l: integer;
1656 +    aTime: TDateTime;
1657 +    DMs: longint;
1658 + begin
1659 +  Result := false;
1660 +  aTimezone := '';
1661 +  if aDateTimeStr <> '' then
1662 +  {$if declared(TFormatSettings)}
1663 +  with aFormatSettings do
1664 +  {$IFEND}
1665 +  begin
1666 +    aDateTime := 0;
1667 +    {Parse to get time zone info}
1668 +    i := 1;
1669 +    while (i <= length(aDateTimeStr)) and (aDateTimeStr[i] in whitespacechars) do inc(i); {skip white space}
1670 +    if not TimeOnly then
1671 +    begin
1672 +      {decode date}
1673 +      j := i;
1674 +      while (j <= length(aDateTimeStr)) and (aDateTimeStr[j] in ['0'..'9',DateSeparator]) do inc(j);
1675 +      if TryStrToDate(system.copy(aDateTimeStr,i,j-i),aDateTime) then
1676 +        i := j; {otherwise start again i.e. assume time only}
1677 +    end;
1678 +
1679 +    while (i <= length(aDateTimeStr)) and (aDateTimeStr[i] in whitespacechars) do inc(i); {skip white space}
1680 +    {decode time}
1681 +    j := i;
1682 +    while (j <= length(aDateTimeStr)) and (aDateTimeStr[j] in ['0'..'9',TimeSeparator]) do inc(j);
1683 +    Result := TryStrToTime(system.copy(aDateTimeStr,i,j-i),aTime);
1684 +    if not Result then Exit;
1685 +    aDateTime := aDateTime + aTime;
1686 +    i := j;
1687 +
1688 +    {is there a factional second part}
1689 +    if (i <= length(aDateTimeStr)) and (aDateTimeStr[i] = '.') then
1690 +    begin
1691 +      inc(i);
1692 +      inc(j);
1693 +      while (j <= Length(aDateTimeStr)) and (aDateTimeStr[j] in ['0'..'9']) do inc(j);
1694 +      if j > i then
1695 +      begin
1696 +        l := j-i;
1697 +        if l > 4 then l := 4;
1698 +        Result := TryStrToInt(system.copy(aDateTimeStr,i,l),DMs);
1699 +        if not Result then Exit;
1700 +
1701 +        {adjust for number of significant digits}
1702 +        case l of
1703 +        3:   DMs := DMs * 10;
1704 +        2:   DMs := DMs * 100;
1705 +        1:   DMs := DMs * 1000;
1706 +        end;
1707 +       aDateTime := aDateTime + (DMs / (MsecsPerDay*10));
1708 +      end;
1709 +    end;
1710 +    i := j;
1711 +
1712 +    while (i <= length(aDateTimeStr)) and (aDateTimeStr[i] in whitespacechars) do inc(i); {skip white space}
1713 +    {decode time zone}
1714 +    if i < length(aDateTimeStr) then
1715 +    begin
1716 +      j := i;
1717 +      while (j <= length(aDateTimeStr)) and not (aDateTimeStr[j] in whitespacechars) do inc(j);
1718 +      aTimezone := system.copy(aDateTimeStr,i,j-i);
1719 +    end;
1720 +    Result := true;
1721 +  end
1722 + end;
1723 +
1724 + {The following is similar to FPC DecodeTime except that the Firebird standard
1725 + decimilliseconds is used instead of milliseconds for fractional seconds}
1726 +
1727 + procedure FBDecodeTime(aTime: TDateTime; var Hour, Minute, Second: word;
1728 +  var DeciMillisecond: cardinal);
1729 + var D : Double;
1730 +    l : cardinal;
1731 + begin
1732 +  {conversion to decimilliseconds hacked from FPC DateTimeToTimeStamp}
1733 +  D := aTime * MSecsPerDay *10;
1734 +  if D < 0 then
1735 +    D := D - 0.5
1736 +  else
1737 +    D := D + 0.5;
1738 +  {rest hacked from FPC DecodeTIme}
1739 +  l := Abs(Trunc(D)) Mod (MSecsPerDay*10);
1740 +  Hour   := l div 36000000;
1741 +  l := l mod 36000000;
1742 +  Minute := l div 600000;
1743 +  l := l mod 600000;
1744 +  Second := l div 10000;
1745 +  DeciMillisecond := l mod 10000;
1746 + end;
1747 +
1748 + {The following is similar to FPC EncodeTime except that the Firebird standard
1749 + decimilliseconds is used instead of milliseconds for fractional seconds}
1750 +
1751 + function FBEncodeTime(Hour, Minute, Second, DeciMillisecond: cardinal): TDateTime;
1752 + const DMSecsPerDay = MSecsPerDay*10;
1753 + var DMs: cardinal;
1754 +    D: Double;
1755 + begin
1756 +  if (Hour<24) and (Minute<60) and (Second<60) and (DeciMillisecond<10000) then
1757 +  begin
1758 +    DMs := Hour*36000000+Minute*600000+Second*10000+DeciMillisecond;
1759 +    D := DMs/DMSecsPerDay;
1760 +    Result:=TDateTime(d)
1761 +  end
1762 +  else
1763 +    IBError(ibxeBadTimeSpecification,[Hour, Minute, Second, DeciMillisecond]);
1764 + end;
1765 +
1766 + {The following is similar to FPC FormatDateTime except that it additionally
1767 + allows the timstamp to have a fractional seconds component with a resolution
1768 + of four decimal places. This is appended to the result for FormatDateTime
1769 + if the format string contains a "zzzz' string.}
1770 +
1771 + function FBFormatDateTime(fmt: AnsiString; aDateTime: TDateTime): AnsiString;
1772 + var Hour, Minute, Second: word;
1773 +    DeciMillisecond: cardinal;
1774 + begin
1775 +  if Pos('zzzz',fmt) > 0 then
1776 +  begin
1777 +    FBDecodeTime(aDateTime, Hour, Minute, Second, DeciMillisecond);
1778 +    fmt := StringReplace(fmt, 'zzzz', Format('%.4d',[DeciMillisecond]), [rfReplaceAll]);
1779 +  end;
1780 +  Result := FormatDateTime(fmt,aDateTime);
1781 + end;
1782 +
1783 + function FormatTimeZoneOffset(EffectiveTimeOffsetMins: integer): AnsiString;
1784 + begin
1785 +  if EffectiveTimeOffsetMins > 0 then
1786 +    Result := Format('+%.2d:%.2d',[EffectiveTimeOffsetMins div 60,abs(EffectiveTimeOffsetMins mod 60)])
1787 +  else
1788 +    Result := Format('%.2d:%.2d',[EffectiveTimeOffsetMins div 60,abs(EffectiveTimeOffsetMins mod 60)]);
1789 + end;
1790 +
1791 + function DecodeTimeZoneOffset(TZOffset: AnsiString; var dstOffset: integer): boolean;
1792 + var i: integer;
1793 + begin
1794 +  Result := false;
1795 +  TZOffset := Trim(TZOffset);
1796 +  for i := 1 to Length(TZOffset) do
1797 +    if not (TZOffset[i] in ['0'..'9','-','+',':']) then Exit;
1798 +
1799 +  Result := true;
1800 +  i := Pos(':',TZOffset);
1801 +  if i > 0 then
1802 +    dstOffset := StrToInt(copy(TZOffset,1,i-1)) * 60 + StrToInt(copy(TZOffset,i + 1))
1803 +  else
1804 +    dstOffset := StrToInt(TZOffset) * 60;
1805 + end;
1806 +
1807 + function StripLeadingZeros(Value: AnsiString): AnsiString;
1808 + var i: Integer;
1809 +    start: integer;
1810 + begin
1811 +  Result := '';
1812 +  start := 1;
1813 +  if (Length(Value) > 0) and (Value[1] = '-') then
1814 +  begin
1815 +    Result := '-';
1816 +    start := 2;
1817 +  end;
1818 +  for i := start to Length(Value) do
1819 +    if Value[i] <> '0' then
1820 +    begin
1821 +      Result := Result + system.copy(Value, i, MaxInt);
1822 +      Exit;
1823 +    end;
1824 + end;
1825 +
1826   end.

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines