2010-11-08 6 views
13

Existe-t-il une routine dans Delphi qui arrondit une valeur TDateTime à la seconde la plus proche, l'heure la plus proche, les 5 minutes les plus proches, la demi-heure la plus proche, etc.En Delphi: Comment arrondir un TDateTime à la seconde, minute, cinq minutes etc. la plus proche?

MISE À JOUR:

Gabr fourni une réponse. Il y avait quelques petites erreurs, peut-être en raison de l'absence totale de test ;-)

Je nettoyé un peu et testé, et voici la version finale (?):

function RoundDateTimeToNearestInterval(vTime : TDateTime; vInterval : TDateTime = 5*60/SecsPerDay) : TDateTime; 
var 
    vTimeSec,vIntSec,vRoundedSec : int64; 
begin 
    //Rounds to nearest 5-minute by default 
    vTimeSec := round(vTime * SecsPerDay); 
    vIntSec := round(vInterval * SecsPerDay); 

    if vIntSec = 0 then exit(vTimeSec/SecsPerDay); 

    vRoundedSec := round(vTimeSec/vIntSec) * vIntSec; 

    Result := vRoundedSec/SecsPerDay; 
end; 
+0

Quel était le problème avec ma réponse? –

+0

Rien, vraiment, je viens juste de tester la solution de Gabr en premier. De plus, sa suggestion d'un seul paramètre pour le type d'intervalle ET la taille était plus élégante qu'une solution avec deux paramètres pour la même chose. À mon avis au moins. –

+0

C'est un morceau de code très utile, je trouve que le datetime a tendance à "dériver" si vous l'incrémentez plusieurs heures ou minutes. ce qui peut gâcher les choses si vous travaillez à une série temporelle stricte. Quelques remarques sur votre exemple mais Svein, la valeur par défaut n'a pas fonctionné pour moi, aussi le '(vTimeSec/SecsPerDay)' après la sortie je pense est une erreur, il ne devrait pas être là. Mon code avec des corrections et des commentaires, est: – SolarBrian

Répondre

8

Quelque chose comme ça (complètement non testé, écrit directement dans le navigateur):

function RoundToNearest(time, interval: TDateTime): TDateTime; 
var 
    time_sec, int_sec, rounded_sec: int64; 
begin 
    time_sec := Round(time * SecsPerDay); 
    int_sec := Round(interval * SecsPerDay); 
    rounded_sec := (time_sec div int_sec) * int_sec; 
    if (rounded_sec + int_sec - time_sec) - (time_sec - rounded_sec) then 
    rounded_sec := rounded_sec + time+sec; 
    Result := rounded_sec/SecsPerDay; 
end; 

Le code suppose que vous voulez arrondir avec une deuxième précision. Les millisecondes sont jetées.

+0

Merci! Il y a eu quelques petites erreurs, mais je l'ai nettoyé un peu :-) –

2

Voici un code non testé avec une précision réglable.

Type 
    TTimeDef = (tdSeconds, tdMinutes, tdHours, tdDays) 

function ToClosest(input : TDateTime; TimeDef : TTimeDef ; Range : Integer) : TDateTime 
var 
    Coeff : Double; 
RInteger : Integer; 
DRInteger : Integer; 
begin 
    case TimeDef of 
    tdSeconds : Coeff := SecsPerDay; 
    tdMinutes : Coeff := MinsPerDay; 
    tdHours : Coeff := MinsPerDay/60; 
    tdDays : Coeff := 1; 
    end; 

    RInteger := Trunc(input * Coeff); 
    DRInteger := RInteger div Range * Range 
    result := DRInteger/Coeff; 
    if (RInteger - DRInteger) >= (Range/2) then 
    result := result + Range/Coeff; 

end; 
2

Essayez l'unité DateUtils.
Mais pour arrondir une minute, une heure ou même une seconde, il suffit de décoder puis d'encoder la valeur de la date, avec millisecondes, secondes et minutes définies sur zéro. Arrondir à des multiples de minutes ou d'heures signifie simplement: décoder, arrondir en haut ou en bas les heures ou les minutes, puis encoder à nouveau.
Pour encoder/décoder des valeurs de temps, utilisez EncodeTime/DecodeTime de SysUtils. Utilisez EncodeDate/DecodeDate pour les dates. Il devrait être possible de créer vos propres fonctions d'arrondi avec tout cela.
En outre, la fonction SysUtils a des constantes comme MSecsPerDay, SecsPerDay, SecsPerMin, MinsPerHour et HoursPerDay. Un temps est fondamentalement le nombre de millisecondes après minuit. Vous pouvez multiplier Frac (Time) par MSecsPerDay, qui est le nombre exact de millisecondes.
Malheureusement, comme les valeurs de temps sont flottantes, il y a toujours une chance de petites erreurs d'arrondi, donc vous pourriez ne pas obtenir la valeur attendue ...

7

Wow! les gars, comment compliquez-vous trop quelque chose de si simple ... aussi la plupart d'entre vous perdre l'option de arrondir au 1/100 de seconde près, etc ...

Celui-ci est beaucoup plus simple et peut également arrondir à milisenconds parties:

function RoundToNearest(TheDateTime,TheRoundStep:TDateTime):TdateTime; 
    begin 
     if 0=TheRoundStep 
     then begin // If round step is zero there is no round at all 
        RoundToNearest:=TheDateTime; 
       end 
     else begin // Just round to nearest multiple of TheRoundStep 
        RoundToNearest:=Round(TheDateTime/TheRoundStep)*TheRoundStep; 
       end; 
    end; 

Vous pouvez tester avec ces exemples communs ou non si communs:

// Note: Scroll to bottom to see examples of round to 1/10 of a second, etc 

// Round to nearest multiple of one hour and a half (round to 90'=1h30') 
ShowMessage(FormatDateTime('hh:nn:ss.zzz' 
          ,RoundToNearest(EncodeTime(15,31,37,156) 
             ,EncodeTime(1,30,0,0)) 
         ) 
      ); 

// Round to nearest multiple of one hour and a quarter (round to 75'=1h15') 
ShowMessage(FormatDateTime('hh:nn:ss.zzz' 
          ,RoundToNearest(EncodeTime(15,31,37,156) 
             ,EncodeTime(1,15,0,0)) 
         ) 
      ); 

// Round to nearest multiple of 60 minutes (round to hours) 
ShowMessage(FormatDateTime('hh:nn:ss.zzz' 
          ,RoundToNearest(EncodeTime(15,31,37,156) 
             ,EncodeTime(1,0,0,0)) 
         ) 
      ); 

// Round to nearest multiple of 60 seconds (round to minutes) 
ShowMessage(FormatDateTime('hh:nn:ss.zzz' 
          ,RoundToNearest(EncodeTime(15,31,37,156) 
             ,EncodeTime(0,1,0,0)) 
         ) 
      ); 

// Round to nearest multiple of second (round to seconds) 
ShowMessage(FormatDateTime('hh:nn:ss.zzz' 
          ,RoundToNearest(EncodeTime(15,31,37,156) 
             ,EncodeTime(0,0,1,0)) 
         ) 
      ); 

// Round to nearest multiple of 1/100 seconds 
ShowMessage(FormatDateTime('hh:nn:ss.zzz' 
          ,RoundToNearest(EncodeTime(15,31,37,141) 
             ,EncodeTime(0,0,0,100)) 
         ) 
      ); 

// Round to nearest multiple of 1/100 seconds 
    ShowMessage(FormatDateTime('hh:nn:ss.zzz' 
          ,RoundToNearest(EncodeTime(15,31,37,156) 
             ,EncodeTime(0,0,0,100)) 
         ) 
      ); 

// Round to nearest multiple of 1/10 seconds 
    ShowMessage(FormatDateTime('hh:nn:ss.zzz' 
          ,RoundToNearest(EncodeTime(15,31,37,151) 
             ,EncodeTime(0,0,0,10)) 
         ) 
      ); 

// Round to nearest multiple of 1/10 seconds 
    ShowMessage(FormatDateTime('hh:nn:ss.zzz' 
          ,RoundToNearest(EncodeTime(15,31,37,156) 
             ,EncodeTime(0,0,0,10)) 
         ) 
      ); 

Hope this aide les gens comme moi, qui ont besoin d'arrondir 1/100, 1/25 ou 1/10 secondes.

5

Si vous voulez RoundUp ou ArrondiInférieur ... comme ... étage et Ceil

Ici il y a (ne pas oublier d'ajouter l'unité Math à votre clause uses):

function RoundUpToNearest(TheDateTime,TheRoundStep:TDateTime):TDateTime; 
    begin 
     if 0=TheRoundStep 
     then begin // If round step is zero there is no round at all 
        RoundUpToNearest:=TheDateTime; 
       end 
     else begin // Just round up to nearest bigger or equal multiple of TheRoundStep 
        RoundUpToNearest:=Ceil(TheDateTime/TheRoundStep)*TheRoundStep; 
       end; 
    end; 

function RoundDownToNearest(TheDateTime,TheRoundStep:TDateTime):TDateTime; 
    begin 
     if 0=TheRoundStep 
     then begin // If round step is zero there is no round at all 
        RoundDownToNearest:=TheDateTime; 
       end 
     else begin // Just round down to nearest lower or equal multiple of TheRoundStep 
        RoundDownToNearest:=Floor(TheDateTime/TheRoundStep)*TheRoundStep; 
       end; 
    end; 

Et bien sûr avec une modification mineure (utilisez le type Float au lieu du type TDateTime) si vous pouvez également l'utiliser pour les valeurs décimales/flottantes Round, RoundUp et RoundDown en une étape décimale/flottante.

ils sont ici:

function RoundUpToNearest(TheValue,TheRoundStep:Float):Float; 
    begin 
     if 0=TheRoundStep 
     then begin // If round step is zero there is no round at all 
        RoundUpToNearest:=TheValue; 
       end 
     else begin // Just round up to nearest bigger or equal multiple of TheRoundStep 
        RoundUpToNearest:=Ceil(TheValue/TheRoundStep)*TheRoundStep; 
       end; 
    end; 

function RoundToNearest(TheValue,TheRoundStep:Float):Float; 
    begin 
     if 0=TheRoundStep 
     then begin // If round step is zero there is no round at all 
        RoundToNearest:=TheValue; 
       end 
     else begin // Just round to nearest multiple of TheRoundStep 
        RoundToNearest:=Floor(TheValue/TheRoundStep)*TheRoundStep; 
       end; 
    end; 

function RoundDownToNearest(TheValue,TheRoundStep:Float):Float; 
    begin 
     if 0=TheRoundStep 
     then begin // If round step is zero there is no round at all 
        RoundDownToNearest:=TheDateTime; 
       end 
     else begin // Just round down to nearest lower or equal multiple of TheRoundStep 
        RoundDownToNearest:=Floor(TheValue/TheRoundStep)*TheRoundStep; 
       end; 
    end; 

Si vous voulez utiliser les deux types (TDateTime et flotteur) sur la même unité ... ajouter directive de surcharge sur la section d'interface, par exemple:

function RoundUpToNearest(TheDateTime,TheRoundStep:TDateTime):TDateTime;overload; 
function RoundToNearest(TheDateTime,TheRoundStep:TDateTime):TDateTime;overload; 
function RoundDownToNearest(TheDateTime,TheRoundStep:TDateTime):TDateTime;overload; 

function RoundUpToNearest(TheValue,TheRoundStep:Float):Float;overload; 
function RoundToNearest(TheValue,TheRoundStep:Float):Float;overload; 
function RoundDownToNearest(TheValue,TheRoundStep:Float):Float;overload; 
0

Cette est un morceau de code très utile, je l'utilise parce que je trouve que le datetime tend à «dériver» si vous l'incrémentez plusieurs heures ou minutes, ce qui peut gâcher des choses si vous travaillez sur une série temporelle stricte. par exemple 00: 00: 00.000 devient 23: 59: 59.998 J'ai implémenté la version Sveins du code Gabrs, mais je suggère quelques modifications: La valeur par défaut n'a pas fonctionné pour moi, aussi le '(vTimeSec/SecsPerDay)' après le sortie je pense est une erreur, il ne devrait pas être là. Mon code avec corrections & commentaires, est:

Procedure TNumTool.RoundDateTimeToNearestInterval 
         (const ATime:TDateTime; AInterval:TDateTime{=5*60/SecsPerDay}; Var Result:TDateTime); 
    var           //Rounds to nearest 5-minute by default 
     vTimeSec,vIntSec,vRoundedSec : int64;  //NB datetime values are in days since 12/30/1899 as a double 
    begin 
     if AInterval = 0 then 
     AInterval := 5*60/SecsPerDay;     // no interval given - use default value of 5 minutes 
     vTimeSec := round(ATime * SecsPerDay);   // input time in seconds as integer 
     vIntSec := round(AInterval * SecsPerDay);  // interval time in seconds as integer 
     if vIntSec = 0 then 
     exit;           // interval is zero -cannot round the datetime; 
     vRoundedSec := round(vTimeSec/vIntSec) * vIntSec; // rounded time in seconds as integer 
     Result  := vRoundedSec/SecsPerDay;    // rounded time in days as tdatetime (double) 
    end; 
Questions connexes