Skip to content
GitLab
Next
    • GitLab: the DevOps platform
    • Explore GitLab
    • Install GitLab
    • How GitLab compares
    • Get started
    • GitLab docs
    • GitLab Learn
  • Pricing
  • Talk to an expert
  • /
  • Help
    • Help
    • Support
    • Community forum
    • Submit feedback
    • Contribute to GitLab
    Projects Groups Topics Snippets
  • Register
  • Sign in
  • FPC Source FPC Source
  • Project information
    • Project information
    • Activity
    • Labels
    • Members
  • Repository
    • Repository
    • Files
    • Commits
    • Branches
    • Tags
    • Contributor statistics
    • Graph
    • Compare revisions
    • Locked files
  • Issues 1.4k
    • Issues 1.4k
    • List
    • Boards
    • Service Desk
    • Milestones
    • Iterations
  • Merge requests 84
    • Merge requests 84
  • CI/CD
    • CI/CD
    • Pipelines
    • Jobs
    • Artifacts
    • Schedules
    • Test cases
  • Deployments
    • Deployments
    • Releases
  • Packages and registries
    • Packages and registries
    • Package Registry
    • Container Registry
    • Terraform modules
    • Model experiments
  • External wiki
    • External wiki
  • Activity
  • Graph
  • Create a new issue
  • Jobs
  • Commits
  • Issue Boards
Collapse sidebar
  • FPC
  • FPCFPC
  • FPC SourceFPC Source
  • Issues
  • #40140
Closed
Open
Issue created Feb 06, 2023 by Werner Pamler@wpam

[Patch] Rounding errors in FormatDateTime's interval mode

Summary

The function FormatDateTime() can be operated in an interval mode in which hour numbers can be greater than 23. However, the function does not handle rounding errors in the input value correctly, and the displayed string value can be wrong by the equivalent of 1 full day. Another issue is that negative time intervals are displayed as positive.

System Information

  • Operating system: Win-11
  • Processor architecture: x86-64
  • Compiler version: FPC/main 33dfb6cb
  • Device: PC

Steps to reproduce / Example project

In forum thread https://forum.lazarus.freepascal.org/index.php/topic,62118.0.html, the poster added work times of eight hours per day over 21 days and displayed the result via FormatDateTime in interval mode. His observation was that the displayed result, 144 hours, was off by one day from the correct result, 168 days.

See this sample project (formatdatetime_demo_project.zip )

program Project1;
uses SysUtils;
var
  time_per_day: TDateTime;
  sum: TDateTime;
  i: Integer;
begin
  time_per_day := EncodeTime(8, 0, 0, 0);
  sum := 0.0;
  for i := 1 to 21 do
    sum := sum + time_per_day;
  WriteLn('Sum:      ', FormatDateTime('[h]', sum, [fdoInterval]), ' hours');
  WriteLn('Expected: ', 8*21, ' hours');
  WriteLn(sum);
end. 

Output
Sum:      144 hours
Expected: 168 hours
 6.9999999999999973E+000

Explanation

The WriteLn(sum) in above code shows that the summed result is subject to the rounding errors typical of floating point numbers: the result is not 7 as expected, but 6.9999999999999973. The problem is magnified in FormatDateTime(), in which the number of full days exceeding the TTime range (<1.0) is calculated by means of the trunc() function. After dropping the decimals the 6.9999999999999973 becomes a 6 which is off by 1 full day, 24 hours.

Fix

The current code in DateTimeToString() (which is called by FormatDateTime()) is as follows:

if isInterval then
  StoreInt(Hour + trunc(abs(DateTime))*24, 0)

The issue can be fixed when a more elaborate function than trunc(abs(DateTime)) is used to calculate the number of full days: When the hour, minute, second and millisecond parts of the datetime value are zero the input value should correspond to a full day within reasonable accuracy. But when its fractional part is near 1 this indicates that the day count determined by trunc() will be too small by 1 und must be incremented.

function FullDays(ADateTime: TDateTime): Integer;
begin
  Result := trunc(ADateTime);
  if (frac(ADateTime) > 0.9) and (Hour = 0) and (Minute = 0) and (Second = 0) and (Millisecond = 0) then
    inc(Result);
end; 

The attached patch fixes the issue. It also takes care of negative time intervals.

formatdatetime_roundingerror.diff

Test application

A simple test application is attached. It compares the FormatDateTime output in fdoInterval mode for several limiting cases with the expected data.

formatdatetime_test_project.zip

Output of the test application before the patch

          Format                 Value                    Result        Expected
       [h]:nn:ss     1.1574074051168282E-005  --->       0:00:01         0:00:01   --->   OK
          [h]:nn     5.0000000000000000E-001  --->         12:00           12:00   --->   OK
          [h]:nn     7.5000000000000000E-001  --->         18:00           18:00   --->   OK
       [h]:nn:ss     9.9999999999989997E-001  --->       0:00:00        24:00:00   --->   ERROR
       [h]:nn:ss     1.0000000000000999E+000  --->      24:00:00        24:00:00   --->   OK
       [h]:nn:ss     1.5000000000000999E+000  --->      36:00:00        36:00:00   --->   OK
       [h]:nn:ss     1.9999884259259260E+000  --->      47:59:59        47:59:59   --->   OK
   [h]:nn:ss.zzz     1.9999999884259259E+000  --->  47:59:59.999    47:59:59.999   --->   OK
       [h]:nn:ss     1.9999999999999001E+000  --->      24:00:00        48:00:00   --->   ERROR
       [h]:nn:ss     2.0000000000000999E+000  --->      48:00:00        48:00:00   --->   OK
   [h]:nn:ss.zzz     2.0000000115740741E+000  --->  48:00:00.001    48:00:00.001   --->   OK
       [h]:nn:ss     2.0000115740740743E+000  --->      48:00:01        48:00:01   --->   OK
       [h]:nn:ss    -1.9999999999999001E+000  --->      24:00:00       -48:00:00   --->   ERROR
       [h]:nn:ss    -2.0000000000000999E+000  --->      48:00:00       -48:00:00   --->   ERROR

          [n]:ss     4.1666666751022693E-002  --->         60:00           60:00   --->   OK
          [n]:ss     9.9999999884259261E-001  --->          0:00         1440:00   --->   ERROR
          [n]:ss     1.0000000011574075E+000  --->       1440:00         1440:00   --->   OK
          [n]:ss     1.0416666655092592E+000  --->       1500:00         1500:00   --->   OK
          [n]:ss     1.0416666678240742E+000  --->       1500:00         1500:00   --->   OK
          [n]:ss    -1.0000000011574075E+000  --->       1440:00        -1440:00   --->   ERROR
          [n]:ss    -9.9999999884259261E-001  --->          0:00        -1440:00   --->   ERROR

             [s]     4.1666666751022693E-002  --->          3600            3600   --->   OK
             [s]     9.9999999884259261E-001  --->             0           86400   --->   ERROR
             [s]     1.0000000011574075E+000  --->         86400           86400   --->   OK
             [s]     1.0416666655092592E+000  --->         90000           90000   --->   OK
             [s]     1.0416666678240742E+000  --->         90000           90000   --->   OK

and after the patch: All tests are passed.

          Format                 Value                    Result        Expected
       [h]:nn:ss     1.1574074051168282E-005  --->       0:00:01         0:00:01   --->   OK
          [h]:nn     5.0000000000000000E-001  --->         12:00           12:00   --->   OK
          [h]:nn     7.5000000000000000E-001  --->         18:00           18:00   --->   OK
       [h]:nn:ss     9.9999999999989997E-001  --->      24:00:00        24:00:00   --->   OK
       [h]:nn:ss     1.0000000000000999E+000  --->      24:00:00        24:00:00   --->   OK
       [h]:nn:ss     1.5000000000000999E+000  --->      36:00:00        36:00:00   --->   OK
       [h]:nn:ss     1.9999884259259260E+000  --->      47:59:59        47:59:59   --->   OK
   [h]:nn:ss.zzz     1.9999999884259259E+000  --->  47:59:59.999    47:59:59.999   --->   OK
       [h]:nn:ss     1.9999999999999001E+000  --->      48:00:00        48:00:00   --->   OK
       [h]:nn:ss     2.0000000000000999E+000  --->      48:00:00        48:00:00   --->   OK
   [h]:nn:ss.zzz     2.0000000115740741E+000  --->  48:00:00.001    48:00:00.001   --->   OK
       [h]:nn:ss     2.0000115740740743E+000  --->      48:00:01        48:00:01   --->   OK
       [h]:nn:ss    -1.9999999999999001E+000  --->     -48:00:00       -48:00:00   --->   OK
       [h]:nn:ss    -2.0000000000000999E+000  --->     -48:00:00       -48:00:00   --->   OK

          [n]:ss     4.1666666751022693E-002  --->         60:00           60:00   --->   OK
          [n]:ss     9.9999999884259261E-001  --->       1440:00         1440:00   --->   OK
          [n]:ss     1.0000000011574075E+000  --->       1440:00         1440:00   --->   OK
          [n]:ss     1.0416666655092592E+000  --->       1500:00         1500:00   --->   OK
          [n]:ss     1.0416666678240740E+000  --->       1500:00         1500:00   --->   OK
          [n]:ss    -1.0000000011574075E+000  --->      -1440:00        -1440:00   --->   OK
          [n]:ss    -9.9999999884259261E-001  --->      -1440:00        -1440:00   --->   OK

             [s]     4.1666666751022693E-002  --->          3600            3600   --->   OK
             [s]     9.9999999884259261E-001  --->         86400           86400   --->   OK
             [s]     1.0000000011574075E+000  --->         86400           86400   --->   OK
             [s]     1.0416666655092592E+000  --->         90000           90000   --->   OK
             [s]     1.0416666678240740E+000  --->         90000           90000   --->   OK
Edited Apr 07, 2023 by Werner Pamler
To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information
Assignee
Assign to
Time tracking