decodedate - formatdatetime delphi
¿Cómo puedo evitar la incapacidad de Delphi para manejar con precisión las manipulaciones de fecha y hora? (2)
Soy nuevo en Delphi (he estado programando en él durante aproximadamente 6 meses). Hasta el momento, ha sido una experiencia extremadamente frustrante, la mayor parte proviene de lo mal que Delphi está en el manejo de las fechas y los horarios. Tal vez creo que es malo porque no sé cómo usar TDate y TTime correctamente, no sé. Esto es lo que está sucediendo en mí ahora mismo:
// This shows 570, as expected
ShowMessage(IntToStr(MinutesBetween(StrToTime(''8:00''), StrToTime(''17:30''))));
// Here I would expect 630, but instead 629 is displayed. WTF!?
ShowMessage(IntToStr(MinutesBetween(StrToTime(''7:00''), StrToTime(''17:30''))));
Ese no es el código exacto que uso, todo está en variables y se usa en otro contexto, pero creo que se puede ver el problema. ¿Por qué ese cálculo está mal? ¿Cómo se supone que debo solucionar este problema?
Dado
a := StrToTime(''7:00'');
b := StrToTime(''17:30'');
ShowMessage(FloatToStr(a));
ShowMessage(FloatToStr(b));
su código, al usar MinutesBetween
, hace esto de manera efectiva:
ShowMessage(IntToStr(trunc(MinuteSpan(a, b)))); // Gives 629
Sin embargo, podría ser mejor redondear:
ShowMessage(IntToStr(round(MinuteSpan(a, b)))); // Gives 630
¿Cuál es en realidad el valor de coma flotante?
ShowMessage(FloatToStr(MinuteSpan(a, b))); // Gives 630
entonces claramente estás sufriendo los problemas tradicionales de coma flotante aquí.
Actualizar:
El mayor beneficio de Round
es que si el lapso de minutos está muy cerca de un entero, entonces el valor redondeado será ese entero, mientras que el valor truncado podría ser el entero anterior.
El principal beneficio de Trunc
es que en realidad podría querer este tipo de lógica: de hecho, si cumple 18 años en cinco días, legalmente todavía no puede solicitar un permiso de conducción sueco.
Entonces, si desea utilizar Round
lugar de Trunc
, puede agregar
function MinutesBetween(const ANow, AThen: TDateTime): Int64;
begin
Result := Round(MinuteSpan(ANow, AThen));
end;
a su unidad. Entonces, el identificador MinutesBetween
se referirá a este, en la misma unidad, en lugar de uno en DateUtils
. La regla general es que el compilador usará la función que encontró más reciente. Entonces, por ejemplo, si coloca esta función arriba en su propia unidad DateUtilsFix
, entonces
implementation
uses DateUtils, DateUtilsFix
utilizará el nuevo MinutesBetween
, dado que DateUtilsFix
ocurre a la derecha de DateUtils
.
Actualización 2:
Otro enfoque plausible podría ser
function MinutesBetween(const ANow, AThen: TDateTime): Int64;
var
spn: double;
begin
spn := MinuteSpan(ANow, AThen);
if SameValue(spn, round(spn)) then
result := round(spn)
else
result := trunc(spn);
end;
Esto devolverá round(spn)
el span está dentro del rango de fuzz de un entero, y trunc(spn)
caso contrario.
Por ejemplo, usando este enfoque
07:00:00 and 07:00:58
rendirá 0 minutos, al igual que la versión original basada en trunc
, y al igual que le gustaría a la Trafikverket sueca. Pero no sufrirá el problema que desencadenó la pregunta del OP.
Este es un problema que se resuelve en las últimas versiones de Delphi. Así que podría actualizar, o simplemente usar el nuevo código en Delphi 2010. Por ejemplo, este programa produce el resultado que espera:
{$APPTYPE CONSOLE}
uses
SysUtils, DateUtils;
function DateTimeToMilliseconds(const ADateTime: TDateTime): Int64;
var
LTimeStamp: TTimeStamp;
begin
LTimeStamp := DateTimeToTimeStamp(ADateTime);
Result := LTimeStamp.Date;
Result := (Result * MSecsPerDay) + LTimeStamp.Time;
end;
function MinutesBetween(const ANow, AThen: TDateTime): Int64;
begin
Result := Abs(DateTimeToMilliseconds(ANow) - DateTimeToMilliseconds(AThen))
div (MSecsPerSec * SecsPerMin);
end;
begin
Writeln(IntToStr(MinutesBetween(StrToTime(''7:00''), StrToTime(''17:30''))));
Readln;
end.
El código Delphi 2010 para MinutesBetween
ve así:
function SpanOfNowAndThen(const ANow, AThen: TDateTime): TDateTime;
begin
if ANow < AThen then
Result := AThen - ANow
else
Result := ANow - AThen;
end;
function MinuteSpan(const ANow, AThen: TDateTime): Double;
begin
Result := MinsPerDay * SpanOfNowAndThen(ANow, AThen);
end;
function MinutesBetween(const ANow, AThen: TDateTime): Int64;
begin
Result := Trunc(MinuteSpan(ANow, AThen));
end;
Entonces, MinutesBetween
efectivamente se reduce a una resta de punto flotante de los dos valores de fecha / hora. Debido a la in-exactness inherente de la aritmética de coma flotante, esta resta puede producir un valor que está ligeramente por encima o por debajo del valor verdadero. Cuando está por debajo del valor verdadero, el uso de Trunc
te llevará hasta el último minuto. Simplemente reemplazar Trunc
con Round
resolvería el problema.
Da la casualidad de que las últimas versiones de Delphi revisan por completo los cálculos de fecha / hora. Hay cambios importantes en DateUtils
. Es un poco más difícil de analizar, pero la nueva versión se basa en DateTimeToTimeStamp
. Eso convierte la porción de tiempo del valor en la cantidad de milisegundos desde la medianoche. Y lo hace así:
function DateTimeToTimeStamp(DateTime: TDateTime): TTimeStamp;
var
LTemp, LTemp2: Int64;
begin
LTemp := Round(DateTime * FMSecsPerDay);
LTemp2 := (LTemp div IMSecsPerDay);
Result.Date := DateDelta + LTemp2;
Result.Time := Abs(LTemp) mod IMSecsPerDay;
end;
Tenga en cuenta el uso de Round
. El uso de Round
lugar de Trunc
es la razón por la cual el último código Delphi maneja MinutesBetween
de manera robusta.
Asumiendo que no puede actualizar ahora, trataría el problema de esta manera:
- Deje su código sin cambios. Continúa llamando a
MinutesBetween
etc. - Cuando realice la actualización, su código que llama a
MinutesBetween
etc. ahora funcionará. - Mientras tanto, arregle
MinutesBetween
etc. con código hooks . Cuando venga a actualizar, simplemente puede quitar los ganchos.