【问题标题】:Date/Time manipulation - friendly countdown string日期/时间操作 - 友好的倒计时字符串
【发布时间】:2018-12-19 14:38:47
【问题描述】:

我正在构建一个对某个日期/时间有倒计时的东西。我有它的工作 - 至少小时,分钟和秒工作正常。我的问题是当我尝试实施 Days 时,它没有给出正确的结果。我知道 DateUtils 单元,但是那里的东西太多了,我不知道该怎么做,尤其是因为我的数学很糟糕。

我有一个间隔为 100 的计时器。然后我有一个全局 fDestDT 用于目标日期/时间,以作为倒计时的基础。在计时器中,我有一个名为DT 的本地TDateTime。然后我将它分解成多个字符串并将它们重新组合成 1 个“友好”字符串...

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; 做减法,这在我的第一个代码sn-p 中是正确的,但是在转换为使用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;

【问题讨论】:

    标签: delphi math datetime timer delphi-7


    【解决方案1】:

    参见DateUtils 中的DaysBetweenHoursBetweenMinutesBetweenSecondsBetween。你必须做一些小数学。 :)

    这是一个示例控制台应用程序来演示:

    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.
    

    产生以下输出:

    请注意,DaysBetweenHoursBetween 之间的参数顺序颠倒是为了证明函数总是返回正值,因此参数的顺序并不重要。这在文档中有所提及。

    【讨论】:

    • 我在这里得到了错误的秒数......把它放在一个计时器中并实时观察输出 - 你会看到它并没有完全正确,秒总是出现不到 30 秒。
    • 是的,秒和分应该是mod 60 而不是mod 24
    • 已经完成了。一定是在您发表最后一条评论时发生的。 :)
    【解决方案2】:

    问题在于,当您从 fDestDT 中减去 Now 时,您希望得到两个日期之间的差异,但实际上您得到了另一个日期时间值。由于您使用的值几乎相同,因此您将获得 Delphi 的 datetime 系统的“零日期”,即 30.dets 1899。这就是为什么您会获得 FormatDateTime('d', DT)+' Days' 的“30 天”。

    由于您感兴趣的最小数量是第二个,我建议您使用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';
    

    【讨论】:

    • 谢谢,但从那时起问题已被修改,现在使用 DateUtils 使用不同的方法。为答案+1。
    【解决方案3】:

    如果您使用的是 Delphi 2010(我相信)或更高版本,您可以通过使用 TimeSpan.pas 单元来简化您的代码并使其更清晰,该单元包含一个可用于分解时间量的记录在给定的时间跨度内。

    【讨论】:

    • 不,我有 Delphi 7,但有什么办法可以潜入这个 TimeSpan.pas 单元?听起来它正是我所需要的......
    • Nick,标签上写着 Delphi 7。:) Jerry,你可以通过升级你的 Delphi 副本获得TimeSpan。 ;)
    • 我已经使用了 2010 年,但我仍然喜欢 7:P 我想我会去获取该单元的副本并为 Delphi 7 解决它:D
    • Ken -- 糟糕,你是对的 -- 我查看了问题,但没有查看标签。
    【解决方案4】:

    我需要更灵活的东西来涵盖不同的格式,所以我将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:不包括 LEADING 零值部分

    • 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
    

    【讨论】:

      猜你喜欢
      • 2021-08-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-09-04
      相关资源
      最近更新 更多