unit ExtUtils;

interface

uses
  Classes, SysUtils;

function EDivMod(const Dividend, Divisor: Integer; var Reminder: Integer): Integer;
function EFormatNumber(const S: string; const Decimals: Integer): string;
function Einet_ntoa(const Address: Integer; const Buffer: PChar; const BufLen: Integer): Integer; overload;
function Einet_ntoa(const Address: Integer): string; overload;
function EIsZero(const S: string): Boolean;
function EPadRight(const S: UnicodeString; const Len: Integer): UnicodeString; overload;
function EPosNum(const Symbol: Char; const S: string; const Occurence: Integer): Integer;
function EUnixStampToDateTime(const UnixStamp: Integer): TDateTime;
function EWordNum(const Separator: Char; const S: string; const Num: Integer): string;
function EDisplayInt(const S: string): string;

var
// Used in EUnixTimeToStr & EUnixStrToTime functions
  UnixBias: Integer = - 3 * 60 * 60; // UTC = local time + bias

implementation

uses
  Windows, AnsiStrings, RTLConsts;

var
  BaseUnixDate: TDateTime;
  BaseUnixDays: Integer;

{ Returns True, if string S represents ZERO in any form
  (with or w/o sign, with or w/o decimal point or comma) }
function EIsZero(const S: string): Boolean;
var
  i: Integer;
begin
  i := 1;
  while (i <= Length(S)) and (S[i] = ' ') do Inc(i);
  if (i <= Length(S)) and ((S[i] = '+') or (S[i] = '-')) then Inc(i);
  while (i <= Length(S)) and ((S[i] = ' ') or (S[i] = '0')or (S[i] = '.') or (S[i] = ',')) do Inc(i);
  Result := i > Length(S);
end;

function EPadRight(const S: UnicodeString; const Len: Integer): UnicodeString;
var
  L: Integer;
begin
  L := Length(S);
  if L >= Len then
    Result := Copy(S, 1, Len)
  else
    Result := S + StringOfChar(' ', Len - L);
end;

function Einet_ntoa(const Address: Integer; const Buffer: PChar; const BufLen: Integer): Integer;
type
  TIpBytes = packed record
    b0, b1, b2, b3: Byte;
  end;
const
  sIpFormat = '%d.%d.%d.%d';
begin
  Result := FormatBuf(Buffer{$ifndef UNICODE}^{$endif}, BufLen, sIpFormat, Length(sIpFormat),
    [TIpBytes(Address).b0, TIpBytes(Address).b1, TIpBytes(Address).b2, TIpBytes(Address).b3]);
  if Result < BufLen then
    Buffer[Result] := #0;
end;

function Einet_ntoa(const Address: Integer): string;
var
  Buffer: array [0..15] of Char;
begin
  SetString(Result, Buffer, Einet_ntoa(Address, Buffer, Length(Buffer)));
end;

function EDivMod(const Dividend, Divisor: Integer; var Reminder: Integer): Integer;
begin
  Result := Dividend div Divisor;
  Reminder := Dividend - Result * Divisor;
end;

{ Convert string representation of number:
  - strips leading zeros;
  - inserts decimal point at specified position.
  Ex.: ConvertNumber('-000000324', 2) = '-3.24'
}
function EFormatNumber(const S: string; const Decimals: Integer): string;
var
  Len, I, NewLen, Minus, D: Integer;
begin
  Len := Length(S);
  I := 1;
  D := Decimals;
  Minus := 0;
  if (Len > 0) then
    if S[1] = '-' then
      Minus := 1
    else
      if S[1] = '+' then Inc(I);
  Inc(I, Minus);

  while I <= Len do if S[I] = '0' then Inc(I) else Break; // Strip leading zeros

  NewLen := Len - I + 1;
  if D <= 0 then           // don't place decimal point
  begin
    if NewLen = 0 then Inc(NewLen);
    SetLength(Result, NewLen + Minus);
    if I > Len then Result[Minus + 1] := '0'
      else Move(S[I], Result[Minus + 1], NewLen * SizeOf(Char));
  end
  else
  begin                           // place decimal point at correct place
    if NewLen <= D then NewLen := D + 2 + Minus
      else NewLen := NewLen + 1 + Minus;

    SetLength(Result, NewLen);
    I := NewLen;
    while I > Minus do
    begin
      if D = 0 then Result[I] := FormatSettings.DecimalSeparator else
      begin     // LPad with zeros if not enough digits
        if Len > Minus then Result[I] := S[Len] else Result[I] := '0';
        Dec(Len);
      end;
      Dec(D);
      Dec(I);
    end;
  end;
  if Minus > 0 then Result[1] := '-';
end;

function EPosNum(const Symbol: Char; const S: string; const Occurence: Integer): Integer;
var
  i, N: Integer;
begin
  i := 1;
  N := Occurence;
  while (N > 0) and (i <= Length(S)) do begin
    if S[i] = Symbol then Dec(N);
    Inc(i);
  end;
  if N > 0 then
    Result := Succ(Length(S))
  else
    Result := Pred(i);
end;

function EWordNum(const Separator: Char; const S: string; const Num: Integer): string;
var
  Start, Finish, L: Integer;
begin
  Start := EPosNum(Separator, S, Num - 1) + 1;
  //Finish := EPosNum(Separator, S, Num);
  Finish := Start;
  L := Length(S);
  while (Finish <= L) and (S[Finish] <> Separator) do Inc(Finish);
  Assert((Start > 0) and (Finish > 0) and (Finish >= Start),
    Format('EWordNum(%s, %d) failed: start=%d, finish=%d', [S, Num, Start, Finish]));
  Result := Copy(S, Start, Finish - Start);
end;

function EUnixStampToDateTime(const UnixStamp: Integer): TDateTime;
var
  TimeStamp: TTimeStamp;
  Time: Integer;
begin
  TimeStamp.Date := EDivMod(UnixStamp, SecsPerDay, TimeStamp.Time) + BaseUnixDays;
  Time := (TimeStamp.Time - UnixBias) * 1000 + MSecsPerDay;
  Inc(TimeStamp.Date, Time div MSecsPerDay - 1);
  TimeStamp.Time := Time mod MSecsPerDay;
  Result := TimeStampToDateTime(TimeStamp);
end;

procedure InitUnixBias;
var
  TZ: TTimeZoneInformation;
begin
  case GetTimeZoneInformation(TZ) of
    TIME_ZONE_ID_UNKNOWN: UnixBias := TZ.Bias;
    TIME_ZONE_ID_STANDARD: UnixBias := TZ.Bias + TZ.StandardBias;
    TIME_ZONE_ID_DAYLIGHT: UnixBias := TZ.Bias + TZ.DaylightBias;
  end;
  UnixBias := UnixBias * 60;
end;

function EDisplayInt(const S: string): string;
var
  I, Digits: Integer;
begin
  Result := S;
  I := 1;
  while (I <= Length(Result)) and ((Result[I] <> ',') and (Result[I] <> '.')) do Inc(I);
  Digits := 0;
  Dec(I);

  while I > 1 do
  begin
    Inc(Digits);
    if Digits = 3 then
    begin
      if (Result[I - 1] >= '0') and (Result[I - 1] <= '9') then
        Insert(FormatSettings.ThousandSeparator, Result, I);
      Digits := 0;
    end;
    Dec(I);
  end;
end;

initialization
  BaseUnixDate := EncodeDate(1970, 1, 1);
  BaseUnixDays := Round(BaseUnixDate) + DateDelta;
  InitUnixBias;

end.
