Дата / Время манипуляции - дружественная строка обратного отсчета - PullRequest
4 голосов
/ 22 ноября 2011

Я строю что-то, что имеет обратный отсчет до определенной даты / времени.У меня это работает - по крайней мере, часы, минуты и секунды работают нормально.Моя проблема в том, что когда я пытаюсь реализовать Days, это не дает правильного результата.Я знаю об устройстве DateUtils, но там так много всего, и я не знаю, как это сделать, тем более что я ужасно разбираюсь в математике.

У меня есть таймер с интервалом в 100. Затем яиметь глобальный fDestDT для конечной даты / времени, на котором будет отсчитываться отсчет.В таймере у меня есть локальный TDateTime с именем DT.Затем я разбиваю его на несколько строк и соединяю их в одну «дружественную» строку ...

procedure TForm1.TmrTimer(Sender: TObject);
var
  DT: TDateTime;
  D, H, N, S: String;
  Str: String;
begin
  DT:= fDestDT - Now; //fDest = destination date/time of countdown
  //Need to format only plural numbers with 's'
  D:= FormatDateTime('d', DT)+' Days';    //Get number of days
  H:= FormatDateTime('h', DT)+' Hours';   //Get number of hours
  N:= FormatDateTime('n', DT)+' Minutes'; //Get number of minutes
  S:= FormatDateTime('s', DT)+' Seconds'; //Get number of seconds
  Str:= D+', '+H+', '+N+', '+S;           //Build friendly string
  if lblTitle.Caption <> Str then
    lblTitle.Caption:= Str;               //Update caption only if it's changed
end;

Должно получиться что-то вроде ...

0 Days, 3 Hours, 1 Minute, 12 Seconds

Но вместо этого дни показываются неправильно, когда дата / время обратного отсчета идут на сегодняшнюю дату, они показывают 30 дней ...

30 Days, 3 Hours, 1 Minute, 12 Seconds

Я полагаючто если бы я поставил его более чем на 1 месяц вперед, это тоже не показывалось бы правильно.Как правильно узнать количество дней?И есть ли в модуле DateUtils что-нибудь, что может автоматизировать большую часть этой работы лучше, чем я?

РЕДАКТИРОВАТЬ: ИСПРАВЛЕНО!Проблема была в том, что я глупо вычитал с DT:= fDestDT - Now;, что было правильно в моем первом фрагменте кода, но после преобразования вместо использования DateUtils.DaysBetween мне нужно было удалить это вычитание и просто установить DT:= Now;.

Рабочий код:

procedure TForm1.TmrTimer(Sender: TObject);
var           
  DT: TDateTime;
  Days, Hours, Mins, Secs: Word;
  SDays, SHours, SMins, SSecs: String;
  Str: String;
begin     
  DT:= Now;
  Days:= DaysBetween(DT, fDestDT);
  Hours:= HoursBetween(fDestDT, DT) mod 24; // Remove total days
  Mins:= MinutesBetween(DT, fDestDT) mod 60;
  Secs := SecondsBetween(DT, fDestDT) mod 60;
  if Days =  1  then SDays:=  'Day'    else SDays:=  'Days';
  if Hours = 1  then SHours:= 'Hour'   else SHours:= 'Hours';
  if Mins =  1  then SMins:=  'Minute' else SMins:=  'Minutes';
  if Secs =  1  then SSecs:=  'Second' else SSecs:=  'Seconds';
  Str:= Format('%d '+SDays+' %d '+SHours+' %d '+SMins+' %d '+SSecs,
    [Days, Hours, Mins, Secs]);
  if lblTime.Caption <> Str then
    lblTime.Caption:= Str;
end;

Ответы [ 4 ]

7 голосов
/ 22 ноября 2011

См. DaysBetween, HoursBetween, MinutesBetween и SecondsBetween в DateUtils. Вы должны сделать небольшую математику. :)

Вот пример консольного приложения для демонстрации:

program Project2;

{$APPTYPE CONSOLE}

uses
  SysUtils, DateUtils;

procedure ShowTimeDiff(const StartDate, OldDate: TDateTime);
var
  Days, Hours, Mins, Secs: Word;
  OutputText: string;
begin
  Writeln(Format('Start: %s, Old: %s',
      [FormatDateTime('mm/dd/yyyy hh:nn:ss', StartDate),
      FormatDateTime('mm/dd/yyyy hh:nn:ss', OldDate)]));
  Days := DaysBetween(StartDate, OldDate);
  Hours := HoursBetween(OldDate, StartDate) mod 24; // Remove total days
  Mins := MinutesBetween(StartDate, OldDate) mod 60;
  Secs  := SecondsBetween(StartDate, OldDate) mod 60;
  OutputText := Format('  %d days, %d hours, %d min, %d secs',
                       [Days, Hours, Mins, Secs]);
  WriteLn(OutputText);

end;

var
  BeginDate, EndDate: TDateTime;
begin
  BeginDate := Now;
  EndDate := BeginDate - 0.5;   // about 12 hours earlier
  ShowTimeDiff(BeginDate, EndDate);

  EndDate := BeginDate - 2.53724;  // Create date about 2 1/2 days earlier
  ShowTimeDiff(EndDate, BeginDate);

  EndDate := BeginDate - 5.75724;  // Create date about 5 3/4 days earlier
  ShowTimeDiff(BeginDate, EndDate);
  ReadLn;
end.

Создает следующий вывод:

Time differences

Обратите внимание, что изменение порядка параметров между DaysBetween и HoursBetween является намеренным для демонстрации того, что функции всегда возвращают положительные значения, поэтому порядок параметров не важен. Это упоминается в документации.

5 голосов
/ 22 ноября 2011

Проблема в том, что когда вы вычитаете Now из fDestDT, вы ожидаете получить разницу между двумя датами, но на самом деле вы получите другое значение даты и времени. Поскольку значения, которые вы используете, почти одинаковы, вы получаете «нулевую дату» системы Delphi datetime , 30 dets 1899. Вот почему вы получаете «30 дней» для FormatDateTime('d', DT)+' Days'.

Поскольку наименьшее количество, которое вас интересует, - это второе, я предлагаю вам использовать SecondsBetween , чтобы получить разницу между двумя временными метками, а затем разделить ее на части, такие как

diff := SecondsBetween(Now, fDestDT);
S:= IntToStr(diff mod 60)+' Seconds';
diff := diff div 60;
N:= IntToStr(diff mod 60)+' Minutes';
diff := diff div 60;
H:= IntToStr(diff mod 24)+' Hours';
diff := diff div 24;
D:= IntToStr(diff)+' Days';
3 голосов
/ 22 ноября 2011

Если вы используете Delphi 2010 (я полагаю) или выше, вы, вероятно, можете упростить свой код и сделать его более понятным, используя модуль TimeSpan.pas, который содержит запись, которую вы можете использовать, чтобы выделить количество времени в данный промежуток времени.

0 голосов
/ 19 декабря 2018

Мне нужно что-то более гибкое, охватывающее разные форматы, поэтому я реализовал TTimeDiff как:

uses
  SysUtils,
  DateUtils,
  StrUtils,
  Math;

type
  TTimeDiff = record
    type TTimeDiffFormat = (tdfFull, tdfSignificant, tdfAllNonZeros, tdfXNonZeros);
    procedure Init(const ANow, AThen: TDateTime);
    class function TimeDiff(const ANow, AThen: TDateTime): TTimeDiff; static;
    function ToString(const TimeDiffFormat: TTimeDiffFormat; const Delimiter: string = ', ';
      const NonZerosCount: Byte = 1): string;
    case Integer of
      0: (Years, Months, Days, Houres, Minutes, Seconds: Word);
      1: (Values: array[0..5] of Word);
  end;

{ TTimeDiff }

class function TTimeDiff.TimeDiff(const ANow, AThen: TDateTime): TTimeDiff;
begin
  Result.Init(ANow, AThen);
end;

procedure TTimeDiff.Init(const ANow, AThen: TDateTime);
begin
  Years := YearsBetween(ANow, AThen);
  Months := MonthsBetween(ANow, AThen) mod 12;
  Days := DaysBetween(IncMonth(Min(ANow, AThen), Years * 12 + Months), Max(ANow, AThen));
  Houres := HoursBetween(ANow, AThen) mod 24;
  Minutes := MinutesBetween(ANow, AThen) mod 60;
  Seconds := SecondsBetween(ANow, AThen) mod 60;
end;

function TTimeDiff.ToString(const TimeDiffFormat: TTimeDiffFormat; const Delimiter: string = ', ';
  const NonZerosCount: Byte = 1): string;
const
  Captions: array [0..5] of string = ('year', 'month', 'day', 'hour', 'minute', 'second');
var
  I: Integer;
  VisitedNonZeros: Byte;
begin
  Result := '';
  VisitedNonZeros := 0;
  for I := 0 to 5 do
  begin
    if Values[I] > 0 then
      Inc(VisitedNonZeros);
    if
      (TimeDiffFormat = tdfFull) or
      ((TimeDiffFormat = tdfSignificant) and (VisitedNonZeros > 0)) or
      ((TimeDiffFormat in [tdfAllNonZeros, tdfXNonZeros]) and (Values[I] > 0))
    then
    begin
      Result := Result + Format('%d %s%s%s', [Values[I], Captions[I], IfThen(Values[I] = 1, '', 's'), Delimiter]);
      if (TimeDiffFormat = tdfXNonZeros) and (VisitedNonZeros = NonZerosCount) then
        Break;
    end;
  end;
  Result := Copy(Result, 1, Length(Result) - Length(Delimiter));
end;

TTimeDiffFormat объяснение:

  • tdfFull: включает все детали независимо от их значений (годы, месяцы, дни, часы, минуты и секунды соответственно).

  • tdfSignificant: исключая ЛЕДИНГОВЫЕ детали с нулевым значением

  • tdfAllNonZeros: исключая ВСЕ детали с нулевым значением

  • tdfXNonZeros: включает только первые X ненулевых частей, где X по умолчанию установлен в 1

Как использовать:

var
  ANow, AThen: TDateTime;
  Diff: TTimeDiff;
begin
  try
    ANow := DateUtils.EncodeDateTime(1993, 11, 3, 21, 22, 18, 0);
    AThen := DateUtils.EncodeDateTime(1993, 9, 21, 6, 21, 34, 0);
    Writeln('Difference between ');
    Writeln(FormatDateTime('YYYY/MM/DD HH:NN:SS', ANow), ' and');
    Writeln(FormatDateTime('YYYY/MM/DD HH:NN:SS', AThen), ' is:');
    Writeln('');

    Diff.Init(ANow, AThen);
    with Diff do
    begin

      Writeln(ToString(tdfFull));
      Writeln(ToString(tdfSignificant, ' and '));
      Writeln(TTimeDiff.TimeDiff(Athen, ANow).ToString(tdfSignificant), ' (inverted)');
      Writeln(ToString(tdfAllNonZeros));
      Writeln(ToString(tdfXNonZeros, ', ', 2));
      Writeln(ToString(tdfXNonZeros));
      readln;
    end;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.

Результаты:

Difference between
1993/11/03 21:22:18 and
1993/09/21 06:21:34 is:

0 years, 1 month, 13 days, 15 hours, 0 minutes, 43 seconds
1 month and 13 days and 15 hours and 0 minutes and 43 seconds
1 month, 13 days, 15 hours, 0 minutes, 43 seconds (inverted)
1 month, 13 days, 15 hours, 43 seconds
1 month, 13 days
1 month
...