В Delphi: Как мне пройти TDateTime до ближайшей секунды, минуты, пяти минут и т.д.?
Существует ли в Delphi подпрограмма, которая округляет значение TDateTime до ближайшего второго, ближайшего часа, ближайшего 5-минутного, ближайшего полчаса и т.д.?
UPDATE:
Габр дал ответ. Были некоторые небольшие ошибки, возможно, из-за полного отсутствия тестирования; -)
Я немного почистил его и протестировал, и вот финальная версия (?):
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;
Ответы
Ответ 1
Нечто подобное (полностью не проверено, написано прямо в браузере):
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;
Код предполагает, что вы хотите округлить со второй точностью. Миллисекунды выбрасываются.
Ответ 2
Ничего себе! ребята, как вы усложняете слишком много чего-то такого простого... также большинство из вас теряют возможность округлять до ближайшей 1/100 секунды и т.д.
Это намного проще и может также округлить до частей milisenconds:
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;
Вы можете просто протестировать его с помощью таких распространенных или не очень распространенных примеров:
// 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))
)
);
Надеюсь, что это помогает людям вроде меня, которым нужно округлить до 1/100, 1/25 или 1/10 секунд.
Ответ 3
Если вы хотите RoundUp или RoundDown... как Ceil и Floor...
Здесь есть (не забудьте добавить модуль Math в свой раздел 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;
И, конечно, с незначительными изменениями (используйте тип Float вместо типа TDateTime), если их также можно использовать для округления, округления и округления до десятичных/плавающих значений до десятичного/плавающего шага.
Вот они:
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;
Если вы хотите использовать оба типа (TDateTime и Float) на одном устройстве... добавьте директиву по перегрузке в разделе раздела, например:
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;
Ответ 4
Вот непроверенный код с регулируемой точностью.
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;
Ответ 5
Попробуйте блок DateUtils.
Но чтобы округлить минуту, час или даже секунду, просто декодируйте, а затем закодируйте значение даты, с миллисекундами, секундами и минутами, установленными на ноль. Округление до нескольких минут или часов просто означает: декодировать, округлять или уменьшать часы или минуты, а затем снова закодировать.
Для кодирования/декодирования значений времени используйте EncodeTime/DecodeTime из SysUtils. Используйте EncodeDate/DecodeDate для дат. Должно быть возможно создать ваши собственные функции округления со всем этим.
Кроме того, функция SysUtils имеет такие константы, как MSecsPerDay, SecsPerDay, SecsPerMin, MinsPerHour и HoursPerDay. Время в основном составляет миллисекунды за полночь. Вы можете использовать Frac (Time) с помощью MSecsPerDay, который является точным числом миллисекунд.
К сожалению, поскольку значения времени являются поплавками, всегда есть вероятность небольших ошибок округления, поэтому вы не можете получить ожидаемое значение...
Ответ 6
Это очень полезный бит кода, я использую это, потому что я нахожу, что datetime имеет тенденцию "дрейфовать", если вы увеличиваете его на несколько часов или минут много раз, что может испортить ситуацию, если вы работаете со строгим Временные ряды. например, 00: 00: 00.000 становится 23: 59: 59.998
Я внедрил версию кода Gabrs для Sveins, но я предлагаю несколько поправок: значение по умолчанию для меня не срабатывало, а также "(vTimeSec/SecsPerDay)" после выхода, я думаю, это ошибка, его не должно быть. Мой код с исправлениями и комментариями:
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;