decodedate delphi time delphi-2010

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:

  1. Deje su código sin cambios. Continúa llamando a MinutesBetween etc.
  2. Cuando realice la actualización, su código que llama a MinutesBetween etc. ahora funcionará.
  3. Mientras tanto, arregle MinutesBetween etc. con código hooks . Cuando venga a actualizar, simplemente puede quitar los ganchos.