unit Bcl.Utils;

{$I Bcl.Inc}

interface

uses
  {$IFNDEF PAS2JS}
  Character,
  {$ENDIF}
  SysUtils;

type
  TBclUtils = class
  {$IFNDEF PAS2JS}
  public
    class procedure InitFormatSettings(var FS: TFormatSettings); {$IFNDEF PAS2JS}static;{$ENDIF}
  {$ENDIF}
  public
    class function DateTimeToISO(const Value: TDateTime; FullNotation: boolean): string; {$IFNDEF PAS2JS}static;{$ENDIF}
//    class function ISOToDateTime(const Value: string): TDateTime; {$IFNDEF PAS2JS}static;{$ENDIF}
    class function TryISOToDateTime(const Value: string; out DateTime: TDateTime): boolean; {$IFNDEF PAS2JS}static;{$ENDIF}
    class function TimeToISO(const Value: TTime): string; {$IFNDEF PAS2JS}static;{$ENDIF}
//    class function ISOToTime(const Value: string): TTime; {$IFNDEF PAS2JS}static;{$ENDIF}
    class function TryISOToTime(const Value: string; out DateTime: TTime): boolean; {$IFNDEF PAS2JS}static;{$ENDIF}
    class function DateToISO(const Value: TDate): string; {$IFNDEF PAS2JS}static;{$ENDIF}
//    class function ISOToDate(const Value: string): TDate; {$IFNDEF PAS2JS}static;{$ENDIF}
    class function TryISOToDate(const Value: string; out DateTime: TDate): boolean; {$IFNDEF PAS2JS}static;{$ENDIF}
    class function DateTimeToUtcString(const Value: TDateTime): string; {$IFNDEF PAS2JS}static;{$ENDIF}
    class function EncodeBase64(const Input: TBytes): string; {$IFNDEF PAS2JS}static;{$ENDIF}
    class function DecodeBase64(const Input: string): TBytes; {$IFNDEF PAS2JS}static;{$ENDIF}
//    class function DateTimeToDayTimeDuration(const Value: TTime): string; {$IFNDEF PAS2JS}static;{$ENDIF}
//    class function TryDayTimeDurationToDateTime(const Value: string; out Time: TTime): boolean; {$IFNDEF PAS2JS}static;{$ENDIF}

    {$IFNDEF PAS2JS}
    class function TryStringToGuid(const Value: string; out Guid: TGuid): boolean; {$IFNDEF PAS2JS}static;{$ENDIF}
    {$ENDIF}

//    class function CombineUrlFast(const AbsoluteUrl, RelativeUrl: string): string; {$IFNDEF PAS2JS}static;{$ENDIF}
//    class function DateTimeToUnix(const Value: TDateTime): Int64; {$IFNDEF PAS2JS}static;{$ENDIF}
//    class function DateTimeToJson(const Value: TDateTime): string; {$IFNDEF PAS2JS}static;{$ENDIF}
//    class function GetStatusReason(const StatusCode: integer): string;
//    class function BasicAuthHeaderValue(const UserName, Password: string): string; {$IFNDEF PAS2JS}static;{$ENDIF}
//    class procedure GetNameAndAnnotation(const Text: string; out AName, AAnnotation: string); {$IFNDEF PAS2JS}static;{$ENDIF}
//
//    class function PercentEncode(const S: string): string; overload; {$IFNDEF PAS2JS}static;{$ENDIF}
//    class function PercentEncode(Segments: TArray<string>): string; overload; {$IFNDEF PAS2JS}static;{$ENDIF}
//    class function PercentDecode(const S: string): string; {$IFNDEF PAS2JS}static;{$ENDIF}
//
//    class function GetQueryParams(const Query: string): TArray<TPair<string, string>>;
//
//    class function AcceptsType(const Accept: string; const Value: string): boolean; overload;
//    class function AcceptsType(const Accept: string; const Values: TArray<string>): string; overload;
//
//    class function AcceptsEncoding(const Accept: string; const Value: string): boolean; overload;
//    class function AcceptsEncoding(const Accept: string; const Values: TArray<string>): string; overload;
  public
    {$IFNDEF PAS2JS}
    class function IsLetter(C: Char): Boolean; {$IFNDEF PAS2JS}static; inline;{$ENDIF}
    class function IsLower(C: Char): Boolean; {$IFNDEF PAS2JS}static; inline;{$ENDIF}
    class function IsUpper(C: Char): Boolean; {$IFNDEF PAS2JS}static; inline;{$ENDIF}
    class function IsDigit(C: Char): Boolean; {$IFNDEF PAS2JS}static; inline;{$ENDIF}
    {$ENDIF}
  public
    class function CombineUrlFast(const AbsoluteUrl, RelativeUrl: string): string; {$IFNDEF PAS2JS}static;{$ENDIF}
  public
    class function PercentEncode(const S: string): string; {$IFNDEF PAS2JS}static;{$ENDIF}
  end;

procedure FreeObj(var Obj); {$IFNDEF PAS2JS}inline;{$ENDIF}

implementation

uses
  {$IFNDEF PAS2JS}
  Types, StrUtils,
  {$ENDIF}
  DateUtils;

procedure FreeObj(var Obj);
{$IFDEF AUTOREFCOUNT}
begin
  if (Pointer(Obj) <> nil) then TObject(Obj).DisposeOf;
  TObject(Obj) := nil;
end;
{$ELSE}
begin
  FreeAndNil(Obj);
end;
{$ENDIF}


{ TBclUtils }

//type
//  TAcceptItem = record
//    TypeName: string;
//    SubType: string;
//    Q: double;
//  end;
//
//  TMatchWeight = record
//    Value: integer;
//    Q: double;
//    TypeName: string;
//  end;
//
//function ParseMediaItem(const Item: string): TAcceptItem;
//var
//  Parts: TStringDynArray;
//  Param, ParamName, ParamValue: string;
//  QValue: double;
//  I: integer;
//  P: integer;
//  FullType: string;
//begin
//  Parts := SplitString(Trim(Item), ';');
//
//  // parse params
//  Result.Q := 1;
//  for I := 1 to Length(Parts) - 1 do
//  begin
//    Param := Parts[I];
//    P := Pos('=', Param);
//    if P > 0 then
//    begin
//      ParamName := Copy(Param, 1, P - 1);
//      ParamValue := Copy(Param, P + 1, MaxInt);
//      if ParamName = 'q' then
//      begin
//        // ignore other parameters for now
//        if not TryStrToFloat(ParamValue, QValue) or (QValue < 0) or (QValue > 1) then
//          QValue := 1;
//        Result.Q := QValue;
//      end;
//    end;
//  end;
//
//  if Length(Parts) > 0 then
//    FullType := Parts[0]
//  else
//    FullType := '*/*';
//  if FullType = '*' then
//    FullType := '*/*';
//
//  P := Pos('/', FullType);
//  if P > 0 then
//  begin
//    Result.TypeName := Copy(FullType, 1, P - 1);
//    Result.SubType := Copy(FullType, P + 1, MaxInt);
//  end
//  else
//  begin
//    Result.TypeName := FullType;
//    Result.SubType := '*';
//  end;
//end;
//
//function MediaItemWeight(const Item: string; Acceptables: TArray<TAcceptItem>): TMatchWeight;
//var
//  Target: TAcceptItem;
//  Acceptable: TAcceptItem;
//  EqualsType, EqualsSubType: boolean;
//  TempWeight: integer;
//begin
//  Target := ParseMediaItem(Item);
//  Result.Value := -1;
//  Result.Q := 0;
//
//  for Acceptable in Acceptables do
//  begin
//    EqualsType := SameText(Target.TypeName, Acceptable.TypeName);
//    EqualsSubType := SameText(Target.SubType, Acceptable.SubType);
//    if (EqualsType or (Acceptable.TypeName = '*') or (Target.TypeName = '*'))
//      and
//      (EqualsSubType or (Acceptable.SubType = '*') or (Target.SubType = '*')) then
//    begin
//      TempWeight := 0;
//      if EqualsType then
//        TempWeight := TempWeight + 100;
//      if EqualsSubType then
//        TempWeight := TempWeight + 10;
//      if (TempWeight > Result.Value) then
//      begin
//        Result.Value := TempWeight;
//        Result.Q := Acceptable.Q;
//      end;
//    end;
//  end;
//end;
//
//function GetAcceptables(const Accept: string): TArray<TAcceptItem>;
//var
//  MediaItems: TStringDynArray;
//  I: integer;
//begin
//  MediaItems := SplitString(Accept, ',');
//  SetLength(Result, Length(MediaItems));
//  for I := 0 to Length(MediaItems) - 1 do
//    Result[I] := ParseMediaItem(MediaItems[I]);
//end;
//
//function BestAcceptableMatch(Acceptables: TArray<TAcceptItem>; Values: TArray<string>): TMatchWeight;
//var
//  Weight: TMatchWeight;
//  I: integer;
//begin
//  Result.Value := -1;
//  Result.Q := 0;
//  for I := 0 to Length(Values) - 1 do
//  begin
//    Weight := MediaItemWeight(Values[I], Acceptables);
//    if Weight.Value = -1 then Continue;
//
//    if (Weight.Value > Result.Value) or
//      ((Weight.Value = Result.Value) and (Weight.Q > Result.Q)) or
//      (Result.Q = 0) then
//    begin
//      Result := Weight;
//      Result.TypeName := Values[I];
//    end;
//  end;
////  if Result.Q > 0 then
////    Result := '';
//end;
//
//class function TBclUtils.AcceptsType(const Accept: string; const Value: string): boolean;
//begin
//  Result := AcceptsType(Accept, TArray<string>.Create(Value)) <> '';
//end;
//
//class function TBclUtils.AcceptsType(const Accept: string; const Values: TArray<string>): string;
//var
//  Acceptables: TArray<TAcceptItem>;
//  Match: TMatchWeight;
//begin
//  if Length(Values) = 0 then Exit('');
//  if Accept = '' then Exit(Values[0]);
//
//  Acceptables := GetAcceptables(Accept);
//
//  Match := BestAcceptableMatch(Acceptables, Values);
//  if Match.Q > 0 then
//    Result := Match.TypeName
//  else
//    Result := '';
//end;
//
//class function TBclUtils.AcceptsEncoding(const Accept: string; const Value: string): boolean;
//begin
//  Result := AcceptsType(Accept, TArray<string>.Create(Value)) <> '';
//end;
//
//class function TBclUtils.AcceptsEncoding(const Accept: string; const Values: TArray<string>): string;
//var
//  Acceptables: TArray<TAcceptItem>;
//  Acceptable: TAcceptItem;
//  HasIdentity: boolean;
//  Value: string;
//  Match: TMatchWeight;
//begin
//  if Length(Values) = 0 then Exit('');
////  if Accept = '' then Exit(Values[0]); // we must consider identity
//
//  HasIdentity := false;
//  Acceptables := GetAcceptables(Accept);
//  for Acceptable in Acceptables do
//  begin
//    if not HasIdentity and SameText(Acceptable.TypeName, 'identity') then
//      HasIdentity := True;
//  end;
//
//  Match := BestAcceptableMatch(Acceptables, Values);
//  if Match.Q > 0 then
//    Exit(Match.TypeName);
//
//  // check for identity
//  if not HasIdentity and not SameText(Match.TypeName, 'identity') then
//    for Value in Values do
//      if SameText(Value, 'identity') then
//        Exit(Value);
//end;
//
//class function TBclUtils.BasicAuthHeaderValue(const UserName, Password: string): string;
//begin
//  Result := 'Basic ' + EncodeBase64(TEncoding.UTF8.GetBytes(Format('%s:%s', [UserName, Password])));
//end;
//
//class function TBclUtils.CombineUrlFast(const AbsoluteUrl,
//  RelativeUrl: string): string;
//begin
//  // Do a faster url combination because we know RelativeUrl comes correct
//  Result := AbsoluteUrl;
//  if (Length(Result) > 0) and (Result[Length(Result)] <> '/') then
//    Result := Result + '/';
//  Result := Result + RelativeUrl;
//end;

//class function TBclUtils.DateTimeToDayTimeDuration(
//  const Value: TTime): string;
//var
//  D, H, M, S, MS: Word;
//begin
//  D := Trunc(Value);
//  DecodeTime(Value, H, M, S, MS);
//  Result := 'P';
//  if D > 0 then
//    Result := Result + IntToStr(D) + 'D';
//  if H > 0 then
//    Result := Result + IntToStr(H) + 'H';
//  if M > 0 then
//    Result := Result + IntToStr(M) + 'M';
//  if S > 0 then
//    Result := Result + IntToStr(S) + 'S';
//  if Result = 'P' then
//    Result := 'P0D';
//end;

class function TBclUtils.CombineUrlFast(const AbsoluteUrl,
  RelativeUrl: string): string;
begin
  // Do a faster url combination because we know RelativeUrl comes correct
  Result := AbsoluteUrl;
  if (Length(Result) > 0) and (Result[Length(Result)] <> '/') then
    Result := Result + '/';
  Result := Result + RelativeUrl;
end;

class function TBclUtils.DateTimeToISO(const Value: TDateTime; FullNotation: boolean): string;
var
  Year, Month, Day, Hour, Minute, Second, MS: Word;
begin
  if not FullNotation and (Value = DateOf(Value)) then
    Result := DateToISO(Value)
  else
  begin
    DecodeDateTime(Value, Year, Month, Day, Hour, Minute, Second, MS);
    if FullNotation or (DateOf(Value) <> 0) then
      Result := Format('%sT%s', [DateToISO(Value), TimeToISO(Value)])
    else
      Result := Format('%s', [TimeToISO(Value)])
  end;
end;

class function TBclUtils.DateTimeToUtcString(const Value: TDateTime): string;
const
  DayNames: array[1..7] of string = ('Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat');
  MonthNames: array[1..12] of string = ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul',
    'Aug', 'Sep', 'Oct', 'Nov', 'Dec');
var
  Y, M, D: Word;
  H, Mn, S, MS: Word;
begin
  DecodeDate(Value, Y, M, D);
  DecodeTime(Value, H, Mn, S, MS);
  Result := Format('%s, %.2d %s %.4d %.2d:%.2d:%.2d GMT',
    [DayNames[DayOfWeek(Value)], D, MonthNames[M], Y, H, Mn, S]
  );
end;

//class function TBclUtils.DateTimeToJson(const Value: TDateTime): string;
//begin
//  Result := Format('\/Date(%d)\/', [DateTimeToUnix(Value) * 1000]);
//end;
//
//class function TBclUtils.DateTimeToUnix(const Value: TDateTime): Int64;
//begin
//  Result := Round((Value - UnixDateDelta) * SecsPerDay);
//end;

type
  TPacket = packed record
    a: array[0..3] of Byte;
  end;

class function TBclUtils.DecodeBase64(const Input: string): TBytes;
const
  DecodeTable: array[#0..#127] of Integer = (
    61, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63,
    52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64,
    64,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
    15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64,
    64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
    41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64);

  function DecodePacket(Idx: Integer; var nChars: Integer): TPacket;
  begin
    Result.a[0] := (DecodeTable[Input[Idx + 0]] shl 2) or
      (DecodeTable[Input[Idx + 1]] shr 4);
    NChars := 1;
    if Input[Idx + 2] <> '=' then
    begin
      Inc(NChars);
      Result.a[1] := ((DecodeTable[Input[Idx + 1]] shl 4) or (DecodeTable[Input[Idx + 2]] shr 2)) and $FF;
    end;
    if Input[Idx + 3] <> '=' then
    begin
      Inc(NChars);
      Result.a[2] := ((DecodeTable[Input[Idx + 2]] shl 6) or DecodeTable[Input[Idx + 3]]) and $FF;
    end;
  end;

var
  I, J, K: Integer;
  Packet: TPacket;
  Len: integer;
begin
  SetLength(Result, Length(Input) div 4 * 3);
  Len := 0;
  for I := 1 to Length(Input) div 4 do
  begin
    Packet := DecodePacket((I - 1) * 4 + 1, J);
    K := 0;
    while J > 0 do
    begin
      Result[Len] := Packet.a[K];
      Inc(Len);
      Inc(K);
      Dec(J);
    end;
  end;
  SetLength(Result, Len);
end;

class function TBclUtils.EncodeBase64(const Input: TBytes): string;
const
  EncodeTable: array[0..63] of Char =
    'ABCDEFGHIJKLMNOPQRSTUVWXYZ' +
    'abcdefghijklmnopqrstuvwxyz' +
    '0123456789+/';
var
  Output: string;

  procedure EncodePacket(const Packet: TPacket; NumChars: Integer; Idx: Integer);
  begin
    Output[Idx + 0] := EnCodeTable[Packet.a[0] shr 2];
    Output[Idx + 1] := EnCodeTable[((Packet.a[0] shl 4) or (Packet.a[1] shr 4)) and $0000003f];

    if NumChars < 2 then
      Output[Idx + 2] := '='
    else
      Output[Idx + 2] := EnCodeTable[((Packet.a[1] shl 2) or (Packet.a[2] shr 6)) and $0000003f];

    if NumChars < 3 then
      Output[Idx + 3] := '='
    else
      Output[Idx + 3] := EnCodeTable[Packet.a[2] and $0000003f];
  end;

var
  I, K, J: Integer;
  Packet: TPacket;
begin
  Output := '';
  I := (Length(Input) div 3) * 4;
  if Length(Input) mod 3 > 0 then Inc(I, 4);
  SetLength(Output, I);
  J := 1;
  for I := 1 to Length(Input) div 3 do
  begin
    Packet.a[0] := Input[(I - 1) * 3];
    Packet.a[1] := Input[(I - 1) * 3 + 1];
    Packet.a[2] := Input[(I - 1) * 3 + 2];
    Packet.a[3] := 0;
    EncodePacket(Packet, 3, J);
    Inc(J, 4);
  end;
  K := 0;

  Packet.a[0] := 0;
  Packet.a[1] := 0;
  Packet.a[2] := 0;
  Packet.a[3] := 0;

  for I := Length(Input) - (Length(Input) mod 3) + 1 to Length(Input) do
  begin
    Packet.a[K] := Byte(Input[I - 1]);
    Inc(K);
    if I = Length(Input) then
      EncodePacket(Packet, Length(Input) mod 3, J);
  end;
  Result := Output;
end;

{$IFNDEF PAS2JS}
class procedure TBclUtils.InitFormatSettings(var FS: TFormatSettings);
begin
  {$IFDEF DELPHIXE2_LVL}
  FS := System.SysUtils.FormatSettings;
  {$ELSE}
    {$IFDEF DELPHIXE_LVL}
    FS := TFormatSettings.Create;
    {$ELSE}
    GetLocaleFormatSettings(0, FS);
    {$ENDIF}
  {$ENDIF}
end;
{$ENDIF}

{$IFNDEF PAS2JS}
class function TBclUtils.IsDigit(C: Char): Boolean;
begin
  {$IFDEF DELPHIXE4_LVL}
  Result := C.IsDigit;
  {$ELSE}
  Result := Character.IsDigit(C);
  {$ENDIF}
end;
{$ENDIF}

{$IFNDEF PAS2JS}
class function TBclUtils.IsLetter(C: Char): Boolean;
begin
  {$IFDEF DELPHIXE4_LVL}
  Result := C.IsLetter;
  {$ELSE}
  Result := Character.IsLetter(C);
  {$ENDIF}
end;
{$ENDIF}

{$IFNDEF PAS2JS}
class function TBclUtils.IsLower(C: Char): Boolean;
begin
  {$IFDEF DELPHIXE4_LVL}
  Result := C.IsLower;
  {$ELSE}
  Result := Character.IsLower(C);
  {$ENDIF}
end;
{$ENDIF}

{$IFNDEF PAS2JS}
class function TBclUtils.IsUpper(C: Char): Boolean;
begin
  {$IFDEF DELPHIXE4_LVL}
  Result := C.IsUpper;
  {$ELSE}
  Result := Character.IsUpper(C);
  {$ENDIF}
end;
{$ENDIF}

class function TBclUtils.PercentEncode(const S: string): string;
var
  {$IFDEF PAS2JS}
  Bytes: string;
  {$ELSE}
  Bytes: TBytes;
  {$ENDIF}
  B: Byte;
  I: integer;
  L: integer;
  H: string;
begin
  {$IFDEF PAS2JS}
  Bytes := S;
  {$ELSE}
  Bytes := TEncoding.UTF8.GetBytes(S);
  {$ENDIF}

  SetLength(Result, Length(Bytes) * 3); // final string will be maximum 3 times the original bytes
  L := 1;
  {$IFDEF PAS2JS}
  for I := 1 to Length(Bytes) do
  begin
    B := Ord(Bytes[I]);
  {$ELSE}
  for I := 0 to Length(Bytes) - 1 do
  begin
    B := Bytes[I];
  {$ENDIF}

    // Check if is unreserved char
    if ((B >= 65) and (B <= 90)) // A..Z
      or ((B >= 97) and (B <= 122)) // a..z
      or ((B >= 48) and (B <= 57)) //0..9
      or (B in [45, 46, 95, 126]) // - . _ ~
      or (B in [33, 39, 40, 41, 42]) then // ! ' ( ) *
    begin
      Result[L] := Chr(B);
      Inc(L);
    end else
    begin
      Result[L] := '%';
      H := IntToHex(B, 2);
      Result[L + 1] := H[1];
      Result[L + 2] := H[2];
      Inc(L, 3);
    end;
  end;
  SetLength(Result, L - 1);
end;

//class procedure TBclUtils.GetNameAndAnnotation(const Text: string; out AName,
//  AAnnotation: string);
//var
//  P: integer;
//begin
//  P := Pos('@', Text);
//  if P = 0 then
//  begin
//    AName := Text;
//    AAnnotation := '';
//  end else
//  begin
//    AName := Copy(Text, 1, P - 1);
//    AAnnotation := Copy(Text, P + 1, MaxInt);
//  end;
//end;
//
//class function TBclUtils.GetQueryParams(const Query: string): TArray<TPair<string, string>>;
//var
//  QueryItem: string;
//  QueryItems: TStringDynArray;
//  I, P: integer;
//  Name, Value: string;
//begin
//  if (Length(Query) > 0) and (Query[1] = '?') then
//    QueryItems := SplitString(Copy(Query, 2, MaxInt), '&')
//  else
//    QueryItems := SplitString(Query, '&');
//  SetLength(Result, Length(QueryItems));
//  for I := 0 to Length(QueryItems) - 1 do
//  begin
//    QueryItem := QueryItems[I];
//    P := Pos('=', QueryItem);
//    Name := Copy(QueryItem, 1, P - 1);
//    Value := Copy(QueryItem, P + 1, MaxInt);
//    Name := TBclUtils.PercentDecode(Name);
//    Value := TBclUtils.PercentDecode(Value);
//    Result[I].Key := Name;
//    Result[I].Value := Value;
//  end;
//end;
//
//class function TBclUtils.GetStatusReason(const StatusCode: integer): string;
//begin
//  case StatusCode of
//    100: Result := 'Continue';
//    101: Result := 'Switching Protocols';
//    102: Result := 'Processing';
//    200: Result := 'OK';
//    201: Result := 'Created';
//    202: Result := 'Accepted';
//    203: Result := 'Non-Authoritative Information';
//    204: Result := 'No Content';
//    205: Result := 'Reset Content';
//    206: Result := 'Partial Content';
//    207: Result := 'Multi-Status';
//    208: Result := 'Already Reported';
//    300: Result := 'Multiple Choices';
//    301: Result := 'Moved Permanently';
//    302: Result := 'Found';
//    303: Result := 'See Other';
//    304: Result := 'Not Modified';
//    305: Result := 'Use Proxy';
//    306: Result := 'Switch Proxy';
//    307: Result := 'Temporary Redirect';
//    400: Result := 'Bad Request';
//    401: Result := 'Unauthorized';
//    402: Result := 'Payment Required';
//    403: Result := 'Forbidden';
//    404: Result := 'Not Found';
//    405: Result := 'Method Not Allowed';
//    406: Result := 'Not Acceptable';
//    407: Result := 'Proxy Authentication Required';
//    408: Result := 'Request Timeout';
//    409: Result := 'Conflict';
//    410: Result := 'Gone';
//    411: Result := 'Length Required';
//    412: Result := 'Precondition Failed';
//    413: Result := 'Request Entity Too Large';
//    414: Result := 'Request-URI Too Long';
//    415: Result := 'Unsupported Media Type';
//    416: Result := 'Request Range Not Satisfiable';
//    417: Result := 'Expectation Failed';
//    422: Result := 'Unprocessable Entity';
//    423: Result := 'Locked';
//    424: Result := 'Failed Dependency';
//    500: Result := 'Internal Server Error';
//    501: Result := 'Not Implemented';
//    502: Result := 'Bad Gateway';
//    503: Result := 'Service Unavailable';
//    504: Result := 'Gateway Timeout';
//    505: Result := 'Http Version Not Supported';
//    506: Result := 'Variant Also Negotiates';
//    507: Result := 'Insufficient Storage';
//  else
//    Result := '';
//  end;
//end;
//
//class function TBclUtils.ISOToTime(const Value: string): TTime;
//begin
//  if Value = '' then Exit(0);
//  Result := StrToDateTime(Value, FISODateSettings);
//end;

class function TBclUtils.TimeToISO(const Value: TTime): string;
var
  Year, Month, Day, Hour, Minute, Second, MS: Word;
begin
  DecodeDateTime(Value, Year, Month, Day, Hour, Minute, Second, MS);
  if MS <> 0 then
    Result := Format('%.2d:%.2d:%.2d.%.3d', [Hour, Minute, Second, MS])
  else
  if Second <> 0 then
    Result := Format('%.2d:%.2d:%.2d', [Hour, Minute, Second])
  else
    Result := Format('%.2d:%.2d', [Hour, Minute]);
end;

class function TBclUtils.TryISOToTime(const Value: string; out DateTime: TTime): boolean;
var
  Hour, Min, Sec, MSec: Integer;
begin
  DateTime := 0;
  if Value = '' then
  begin
    Result := true;
  end else
  begin
    case Length(Value) of
      5:
        begin
          Result := (Value[3] = ':')
            and TryStrToInt(Copy(Value, 1, 2), Hour)
            and TryStrToInt(Copy(Value, 4, 2), Min);
          if Result then
            DateTime := EncodeTime(Hour, Min, 0, 0);
        end;
      8:
        begin
          Result := (Value[3] = ':') and (Value[6] = ':')
            and TryStrToInt(Copy(Value, 1, 2), Hour)
            and TryStrToInt(Copy(Value, 4, 2), Min)
            and TryStrToInt(Copy(Value, 7, 2), Sec);
          if Result then
            DateTime := EncodeTime(Hour, Min, Sec, 0);
        end;
      12:
        begin
          Result := (Value[3] = ':') and (Value[6] = ':') and (Value[9] = '.')
            and TryStrToInt(Copy(Value, 1, 2), Hour)
            and TryStrToInt(Copy(Value, 4, 2), Min)
            and TryStrToInt(Copy(Value, 7, 2), Sec)
            and TryStrToInt(Copy(Value, 10, 3), MSec);
          if Result then
            DateTime := EncodeTime(Hour, Min, Sec, MSec);
        end;
    else
      Result := False;
    end;
  end;
end;

//class function TBclUtils.ISOToDateTime(const Value: string): TDateTime;
//begin
//  if Value = '' then Exit(0);
//  Result := StrToDateTime(Value, FISODateSettings);
//end;
//
//class function TBclUtils.PercentDecode(const S: string): string;
//
//  function IsHexa(C: Char): boolean;
//  begin
//    Result := ((C >= '0') and (C <= '9'))
//      or ((C >= 'a') and (C <= 'f'))
//      or ((C >= 'A') and (C <= 'F'));
//  end;
//
//var
//  B: TBytes;
//  L: integer;
//  I: integer;
//begin
//  SetLength(B, Length(S));
//  L := 1;
//  I := 0;
//  while L <= Length(S) do
//  begin
//    if (S[L] = '%') and (L + 2 <= Length(S)) and IsHexa(S[L + 1]) and IsHexa(S[L + 2]) then
//    begin
//      B[I] := StrToInt('$' + S[L + 1] + S[L + 2]);
//      Inc(L, 3);
//    end
//    else
//    begin
//      B[I] := Ord(S[L]);
//      Inc(L);
//    end;
//    Inc(I);
//  end;
//  SetLength(B, I);
//  Result := TEncoding.UTF8.GetString(B);
//end;
//
//class function TBclUtils.PercentEncode(Segments: TArray<string>): string;
//var
//  Segment: string;
//begin
//  Result := '';
//  for Segment in Segments do
//  begin
//    if Result <> '' then
//      Result := Result + '/';
//    Result := Result + PercentEncode(Segment);
//  end;
//end;
//
//class function TBclUtils.PercentEncode(const S: string): string;
//var
//  B: TBytes;
//  I: integer;
//  L: integer;
//  H: string;
//begin
//  B := TEncoding.UTF8.GetBytes(S);
//  SetLength(Result, Length(B) * 3); // final string will be maximum 3 times the original bytes
//  L := 1;
//  for I := 0 to Length(B) - 1 do
//  begin
//    // Check if is unreserved char
//    if ((B[I] >= 65) and (B[I] <= 90)) // A..Z
//      or ((B[I] >= 97) and (B[I] <= 122)) // a..z
//      or ((B[I] >= 48) and (B[I] <= 57)) //0..9
//      or (B[I] in [45, 46, 95, 126]) // - . _ ~
//      or (B[I] in [33, 39, 40, 41, 42]) then // ! ' ( ) *
//    begin
//      Result[L] := Char(B[I]);
//      Inc(L);
//    end else
//    begin
//      Result[L] := '%';
//      H := IntToHex(B[I], 2);
//      Result[L + 1] := H[1];
//      Result[L + 2] := H[2];
//      Inc(L, 3);
//    end;
//  end;
//  SetLength(Result, L - 1);
//end;
//
//class function TBclUtils.TryDayTimeDurationToDateTime(const Value: string;
//  out Time: TTime): boolean;
//var
//  Len: integer;
//  Day, Hour, Min, Sec: integer;
//
//  function ExtractNumber(const Value: string; var I: integer): boolean;
//  var
//    Start: integer;
//    C: Char;
//    Number: integer;
//  begin
//    Start := I;
//    while (I <= Len) and
//      {$IFDEF DELPHIXE4_LVL}
//      Value[I].IsDigit
//      {$ELSE}
//      IsDigit(Value[I])
//      {$ENDIF}
//      do
//      Inc(I);
//    Result := TryStrToInt(Copy(Value, Start, I - Start), Number) and (I <= Len);
//    if Result then
//    begin
//      C := Value[I];
//      Inc(I);
//      if C = 'D' then
//        Day := Number
//      else
//      if C = 'H' then
//        Hour := Number
//      else
//      if C = 'M' then
//        Min := Number
//      else
//      if C = 'S' then
//        Sec := Number
//      else
//        Result := false;
//    end;
//  end;
//
//var
//  I: integer;
//begin
//  Len := Length(Value);
//  if (Len = 0) or (Value[1] <> 'P') then
//    Exit(false);
//  I := 2;
//
//  Day := 0;
//  Hour := 0;
//  Min := 0;
//  Sec := 0;
//
//  // at least one part must exist
//  if not ExtractNumber(Value, I) then
//    Exit(false);
//
//  // now extract as many as exist
//  while ExtractNumber(Value, I) do ;
//  Time := EncodeTime(Hour, Min, Sec, 0);
//  Result := true;
//end;

class function TBclUtils.TryISOToDateTime(const Value: string;
  out DateTime: TDateTime): boolean;
var
  DatePart: TDate;
  TimePart: TTime;
begin
  DateTime := 0;
  if Value = '' then
  begin
    Result := true;
  end else
  begin
    Result := False;
    if (Length(Value) >= 11) and (Value[11] = 'T') then
    begin
      Result := TryISOToDate(Copy(Value, 1, 10), DatePart) and
        TryISOToTime(Copy(Value, 12, MaxInt), TimePart);
      if Result then
        DateTime := DatePart + TimePart;
    end
    else
    if TryISOToDate(Value, DatePart) then
    begin
      Result := True;
      DateTime := DatePart;
    end
    else
    if TryISOToTime(Value, TimePart) then
    begin
      Result := True;
      DateTime := TimePart;
    end;
  end;
end;

{$IFNDEF PAS2JS}
class function TBclUtils.TryStringToGuid(const Value: string;
  out Guid: TGuid): boolean;
begin
  // TODO: Optimize this to a TryStringToGUID
  try
    Guid := StringToGUID('{' + Value + '}');
    Result := true;
  except
    Result := false;
  end;
end;
{$ENDIF}

class function TBclUtils.TryISOToDate(const Value: string; out DateTime: TDate): boolean;
var
  Year, Month, Day: Integer;
begin
  DateTime := 0;
  if Value = '' then
  begin
    Result := true;
  end else
  begin
    case Length(Value) of
      10:
        begin
          Result := (Value[5] = '-') and (Value[8] = '-')
            and TryStrToInt(Copy(Value, 1, 4), Year)
            and TryStrToInt(Copy(Value, 6, 2), Month)
            and TryStrToInt(Copy(Value, 9, 2), Day);
          if Result then
            DateTime := EncodeDate(Year, Month, Day);
        end;
    else
      Result := False;
    end;
  end;
end;

//class function TBclUtils.ISOToDate(const Value: string): TDate;
//begin
//  if Value = '' then Exit(0);
//  Result := StrToDate(Value, FISODateSettings);
//end;

class function TBclUtils.DateToISO(const Value: TDate): string;
var
  Year, Month, Day: Word;
begin
  DecodeDate(Value, Year, Month, Day);
  Result := Format('%.4d-%.2d-%.2d', [Year, Month, Day]);
end;

end.

