View Issue Details

IDProjectCategoryView StatusLast Update
0038462FPCRTLpublic2021-02-10 12:40
Reporterwp Assigned ToMichael Van Canneyt  
PrioritynormalSeverityminorReproducibilityalways
Status closedResolutionfixed 
Fixed in Version3.3.1 
Summary0038462: ScanDateTime issues in AM/PM mode
DescriptionScanDateTime converts a string to a date/time value according to a format mask string. AM/PM modifiers are allowed to take care of the 12-hour clock format.

In the convention that the time within the first hour after midnight/noon is named "12" rather than "0" (https://www.timeanddate.com/time/am-and-pm.html). A discussion in the forum (https://forum.lazarus.freepascal.org/index.php/topic,53235.msg393540.html) made clear that ScanDateTime does not follow this convention.

The attached patch fixes this issue. It also takes care of the case that an hour >=13 is specified in AM/PM mode.

Enclosed is a test program which covers a variety of cases.
Steps To ReproduceRun the attached test program. It displays "ERROR" for the test times within the first hour after midnight/noon. After application of the patch, all tests are passed.
Additional InformationAn EPS value is introduced to protect the numeric comparisons from floating point round-off errors. This value is assumed to be 1E-15 here. I am not sure whether this is the correct choice because FPC is used on a varietry of platforms which I could not test. Since ScanDateTime cannot detect time differences smaller than 1 ms due to the format mask string it is maybe wiser to set EPS to something like 0.1 ms = (0.1/(24*60*60*1000) = 1E-9.
TagsNo tags attached.
Fixed in Revision48580
FPCOldBugId
FPCTarget3.2.2
Attached Files

Activities

wp

2021-02-10 09:57

reporter  

scandatetime.patch (6,682 bytes)   
Index: packages/rtl-objpas/src/inc/dateutil.inc
===================================================================
--- packages/rtl-objpas/src/inc/dateutil.inc	(revision 48549)
+++ packages/rtl-objpas/src/inc/dateutil.inc	(working copy)
@@ -2379,6 +2379,8 @@
 end;
 
 function scandatetime(const pattern:string;const s:string;const fmt:TFormatSettings;startpos:integer=1) : tdatetime;
+const
+  EPS = 1E-15;
 
 var len ,ind  : integer;
     yy,mm,dd  : integer;
@@ -2558,44 +2560,59 @@
                        end;
                      end;
                'A' : begin
-                            i:=findimatch(AMPMformatting,@ptrn[pind]);
-                            case i of
-                              0: begin
-                                   i:=findimatch(['AM','PM'],@s[ind]);
-                                   case i of
-                                     0: ;
-                                     1: timeval:=timeval+12*hrfactor;
-                                   else
-                                     arraymatcherror
-                                     end;
-                                   inc(pind,length(AMPMformatting[0]));
-                                   inc(ind,2);
-                                 end;
-                              1: begin
-                                    case upcase(s[ind]) of
-                                     'A' : ;
-                                     'P' : timeval:=timeval+12*hrfactor;
-                                   else
-                                     arraymatcherror
-                                     end;
-                                   inc(pind,length(AMPMformatting[1]));
-                                   inc(ind);
-                                 end;
-                               2: begin
-                                    i:=findimatch([fmt.timeamstring,fmt.timepmstring],@s[ind]);
-                                    case i of
-                                     0: inc(ind,length(fmt.timeamstring));
-                                     1: begin
-                                          timeval:=timeval+12*hrfactor;
-                                          inc(ind,length(fmt.timepmstring));
-                                        end;
-                                   else
-                                     arraymatcherror
-                                     end;
-                                   inc(pind,length(AMPMformatting[2]));
-                                 end;
-                            else  // no AM/PM match. Assume 'a' is simply a char
-                                matchchar(ptrn[pind]);
+                        i:=findimatch(AMPMformatting,@ptrn[pind]);
+                        case i of
+                            0: begin
+                                 if timeval >= 13*hrfactor - EPS then
+                                   raiseexception(SAMPMError);
+                                 i:=findimatch(['AM','PM'],@s[ind]);
+                                 case i of
+                                   0: if timeval >= 12*hrfactor then
+                                        timeval := timeval - 12*hrfactor;
+                                   1: if (timeval + EPS >= hrfactor) and (timeval + EPS <= 12*hrfactor) then
+                                        timeval:=timeval+12*hrfactor;
+                                 else
+                                   arraymatcherror
+                                   end;
+                                 inc(pind,length(AMPMformatting[0]));
+                                 inc(ind,2);
+                               end;
+                            1: begin
+                                 if timeval >= 13*hrfactor - EPS then
+                                   raiseexception(SAMPMError);
+                                  case upcase(s[ind]) of
+                                   'A' : if timeval >= 12*hrfactor then
+                                           timeval := timeval - 12*hrfactor;
+                                   'P' : if (timeval + EPS >= hrfactor) and (timeval + EPS <= 12*hrfactor) then
+                                           timeval := timeval + 12*hrfactor;
+                                 else
+                                   arraymatcherror
+                                   end;
+                                 inc(pind,length(AMPMformatting[1]));
+                                 inc(ind);
+                               end;
+                             2: begin
+                                  if timeval >= 13*hrfactor - EPS then
+                                    raiseexception(SAMPMError);
+                                  i:=findimatch([fmt.timeamstring,fmt.timepmstring],@s[ind]);
+                                  case i of
+                                   0: begin
+                                        if timeval >= 12*hrfactor then
+                                          timeval := timeval - 12*hrfactor;
+                                        inc(ind,length(fmt.timeamstring));
+                                      end;
+                                   1: begin
+                                        if (timeval + EPS >= hrfactor) and (timeval + EPS <= 12*hrfactor) then
+                                          timeval:=timeval + 12*hrfactor;
+                                        inc(ind,length(fmt.timepmstring));
+                                      end;
+                                 else
+                                   arraymatcherror
+                                  end;
+                                 inc(pind,length(AMPMformatting[2]));
+                               end;
+                           else  // no AM/PM match. Assume 'a' is simply a char
+                               matchchar(ptrn[pind]);
                              end;
                          end;
                '/' : matchchar(fmt.dateSeparator);
Index: rtl/objpas/sysconst.pp
===================================================================
--- rtl/objpas/sysconst.pp	(revision 48549)
+++ rtl/objpas/sysconst.pp	(working copy)
@@ -145,6 +145,7 @@
   SHHMMError                    = 'mm in a sequence hh:mm is interpreted as minutes. No longer versions allowed! (Position : %d).' ;
   SFullpattern                  = 'Couldn''t match entire pattern string. Input too short at pattern position %d.';
   SPatternCharMismatch          = 'Pattern mismatch char "%s" at position %d.';
+  SAMPMError                    = 'Hour >= 13 not allowed in AM/PM mode.';
 
   SShortMonthNameJan = 'Jan';
   SShortMonthNameFeb = 'Feb';
scandatetime.patch (6,682 bytes)   

Michael Van Canneyt

2021-02-10 12:01

administrator   ~0128855

Applied as is, added test to testsuite. Thanks for the patch.

wp

2021-02-10 12:40

reporter   ~0128858

Thanks

Issue History

Date Modified Username Field Change
2021-02-10 09:57 wp New Issue
2021-02-10 09:57 wp File Added: scandatetime.patch
2021-02-10 09:57 wp File Added: Test_ScanDateTime_AMPM.zip
2021-02-10 12:01 Michael Van Canneyt Assigned To => Michael Van Canneyt
2021-02-10 12:01 Michael Van Canneyt Status new => resolved
2021-02-10 12:01 Michael Van Canneyt Resolution open => fixed
2021-02-10 12:01 Michael Van Canneyt Fixed in Version => 3.3.1
2021-02-10 12:01 Michael Van Canneyt Fixed in Revision => 48580
2021-02-10 12:01 Michael Van Canneyt FPCTarget => 3.2.2
2021-02-10 12:01 Michael Van Canneyt Note Added: 0128855
2021-02-10 12:40 wp Status resolved => closed
2021-02-10 12:40 wp Note Added: 0128858