Hi,

I've created 3 patches with svn diff to apply one after the other, feel free to reorg if you think the work can be splitted in some other, better way

I've moved some implementation detail to the .cpp file, to avoid leaking internal details in the header file (see last patch) Then I've splitted the parsing function in multiple parts, as it was getting a little bit to complex handling everything at once.
The third patch improves the parsing, and with that all tests are good.

I've tried using the same coding style already used for PdfDate.

Federico

On 05/01/2021 23.18, Federico Kircheis wrote:
Hi Dominik, I rechecked the algorithm, D:20120530235959Z00'00' does not fail anymore, i did not parse correctly Z.


D:2012010
D:20120

are still failing... the solution is a bit more convoluted:

Instead of returning a simple, boolean, ParseFixLenNumber should return one of possible 3 states, something like an enum {OK, Missing, Error}.


I think something like

----
enum EParseFixLenNumberResult {OK, Missing, Error};
EParseFixLenNumberResult PdfDate::ParseFixLenNumber(const char *&in, unsigned int length, int min, int max, int &ret_)
{
     if ( in == NULL || *in == '\0') return Missing;
     int ret = 0;
     for(unsigned int i=0;i<length;i++)
     {
         if ( in == NULL || !isdigit(*in)) return Error;
         ret = ret*10+ (*in-'0');
         in++;
     }
     if ( ret < min || ret > max ) return Error;
     ret_ = ret;
     return Ok;
}
----

should do the work, but first one should decide if those optional fields should really be treated as errors...

Federico

Index: src/podofo/base/PdfDate.cpp
===================================================================
--- src/podofo/base/PdfDate.cpp	(revision 2016)
+++ src/podofo/base/PdfDate.cpp	(working copy)
@@ -36,7 +36,31 @@
 
 #include <string.h>
 #include <sstream>
+namespace  {
 
+/** Parse fixed length number from string
+  *  \param in string to read number from
+  *  \param length exact number characters to read
+  *  \param min minimal value of number
+  *  \param max maximal value of number
+  *  \param ret parsed number (updated only on success)
+  */
+bool ParseFixLenNumber(const char *&in, unsigned int length, int min, int max, int &ret_)
+{
+    int ret = 0;
+    for(unsigned int i=0;i<length;i++)
+    {
+        if ( in == NULL || !isdigit(*in)) return false;
+        ret = ret*10+ (*in-'0');
+        in++;
+    }
+    if ( ret < min || ret > max ) return false;
+    ret_ = ret;
+    return true;
+}
+
+}
+
 namespace PoDoFo {
 
 PdfDate::PdfDate()
@@ -47,17 +71,14 @@
 }
 
 PdfDate::PdfDate( const time_t & t )
-    : m_bValid( false )
+    : m_time( t ), m_bValid( false )
 {
-    m_time = t;
     CreateStringRepresentation();
 }
 
 PdfDate::PdfDate( const PdfString & sDate )
-    : m_bValid( false )
+    : m_time( -1 ), m_bValid( false )
 {
-    m_time = -1;
-
     if ( !sDate.IsValid() ) 
     {
         m_szDate[0] = 0;
@@ -66,11 +87,8 @@
 
     strncpy(m_szDate,sDate.GetString(),PDF_DATE_BUFFER_SIZE);
 
-    struct tm _tm;
-    memset( &_tm, 0, sizeof(_tm) );
-    int nZoneShift = 0;
-    int nZoneHour = 0;
-    int nZoneMin = 0;
+    struct tm _tm{};
+    _tm.tm_mday = 1;
 
     const char * pszDate = sDate.GetString();
     if ( pszDate == NULL ) return;
@@ -79,53 +97,57 @@
         if ( *pszDate++ != ':' ) return;
     }
 
-    if ( ParseFixLenNumber(pszDate,4,0,9999,_tm.tm_year) == false ) 
+    // year is not optional
+    if ( !ParseFixLenNumber(pszDate,4,0,9999,_tm.tm_year) )
         return;
-
     _tm.tm_year -= 1900;
-    if ( *pszDate != '\0' ) {
-        if ( ParseFixLenNumber(pszDate,2,1,12,_tm.tm_mon) == false ) 
-            return;
 
+    // all other values are optional, if not set they are 0-init (except mday)
+    if ( ParseFixLenNumber(pszDate,2,1,12,_tm.tm_mon) )
+    {
         _tm.tm_mon--;
-        if ( *pszDate != '\0' ) {
-            if ( ParseFixLenNumber(pszDate,2,1,31,_tm.tm_mday) == false ) return;
-            if ( *pszDate != '\0' ) {
-                if ( ParseFixLenNumber(pszDate,2,0,23,_tm.tm_hour) == false ) return;
-                if ( *pszDate != '\0' ) {
-                    if ( ParseFixLenNumber(pszDate,2,0,59,_tm.tm_min) == false ) return;
-                    if ( *pszDate != '\0' ) {
-                        if ( ParseFixLenNumber(pszDate,2,0,59,_tm.tm_sec) == false ) return;
-                        if ( *pszDate != '\0' ) {
-                            switch(*pszDate++) {
-                            case '+':
-                                nZoneShift = -1;
-                                break;
-                            case '-':
-                                nZoneShift = 1;
-                                break;
-                            case 'Z':
-                                nZoneShift = 0;
-                                break;
-                            default:
-                                return;
-                            }
-                            if ( ParseFixLenNumber(pszDate,2,0,59,nZoneHour) == false ) return;
-                            if ( *pszDate == '\'' ) {
-                                pszDate++;
-                                if ( ParseFixLenNumber(pszDate,2,0,59,nZoneMin) == false ) return;
-                                if ( *pszDate != '\'' ) return;
-                                pszDate++;
-                            }
-                        }
-                    }
-                }
+        if ( ParseFixLenNumber(pszDate,2,1,31,_tm.tm_mday) )
+        {
+            if ( ParseFixLenNumber(pszDate,2,0,23,_tm.tm_hour) )
+            {
+                if ( ParseFixLenNumber(pszDate,2,0,59,_tm.tm_min) )
+                    ParseFixLenNumber(pszDate,2,0,59,_tm.tm_sec);
             }
         }
     }
 
+    // zone is optional
+    int nZoneShift = 0;
+    int nZoneHour = 0;
+    int nZoneMin = 0;
+
     if ( *pszDate != '\0' ) 
     {
+        switch (*pszDate) {
+        case '+':
+            nZoneShift = -1;
+            break;
+        case '-':
+            nZoneShift = 1;
+            break;
+        case 'Z':
+            nZoneShift = 0;
+            break;
+        default:
+            return;
+        }
+        pszDate++;
+        if ( !ParseFixLenNumber(pszDate,2,0,59,nZoneHour) ) return;
+        if (*pszDate == '\'') {
+            pszDate++;
+            if ( !ParseFixLenNumber(pszDate,2,0,59,nZoneMin) ) return;
+            if (*pszDate != '\'')
+                return;
+            pszDate++;
+        }
+    }
+    if ( *pszDate != '\0' ) 
+    {
         return;
     }
 
@@ -136,7 +158,7 @@
         return;
     }
 
-    m_time += nZoneShift*(nZoneHour*3600 + nZoneMin*60);
+    m_time += nZoneShift*(nZoneHour*3600 + nZoneMin*60) ;//- timezone;
     m_bValid = true;
 }
 
@@ -205,20 +227,4 @@
     m_bValid = true;
 }
 
-
-bool PdfDate::ParseFixLenNumber(const char *&in, unsigned int length, int min, int max, int &ret)
-{
-    ret = 0;
-    for(unsigned int i=0;i<length;i++)
-    {
-        if ( in == NULL || !isdigit(*in)) return false;
-        ret = ret*10+ (*in-'0');
-        in++;
-    }
-    if ( ret < min || ret > max ) return false;
-    return true;
-}
-
 };
-
-
Index: src/podofo/base/PdfDate.h
===================================================================
--- src/podofo/base/PdfDate.h	(revision 2016)
+++ src/podofo/base/PdfDate.h	(working copy)
@@ -119,15 +119,6 @@
      */
     void CreateStringRepresentation();
 
-    /** Parse fixed length number from string
-     *  \param in string to read number from
-     *  \param length of number to read 
-     *  \param min minimal value of number
-     *  \param max maximal value of number
-     *  \param ret parsed number
-     */
-    bool ParseFixLenNumber(const char *&in, unsigned int length, int min, int max, int &ret);
-
  private:
     time_t m_time;
     char   m_szDate[PDF_DATE_BUFFER_SIZE + 1]; // include also room for a nul-terminator in the buffer
Index: test/unit/DateTest.cpp
===================================================================
--- test/unit/DateTest.cpp	(revision 2016)
+++ test/unit/DateTest.cpp	(working copy)
@@ -38,7 +38,14 @@
 {
     PdfString tmp(pszDate);
     PdfDate date(tmp);
-    CPPUNIT_ASSERT_EQUAL(bExpected,date.IsValid());
+    if( pszDate != NULL )
+    {
+      CPPUNIT_ASSERT_EQUAL_MESSAGE(pszDate,bExpected,date.IsValid());
+    }
+    else
+    {
+      CPPUNIT_ASSERT_EQUAL_MESSAGE("NULL",bExpected,date.IsValid());
+    }
 }
 
 void DateTest::testCreateDateFromString()
@@ -66,8 +73,10 @@
 
 void DateTest::testDateValue()
 {
-    PdfDate date(PdfString("D:20120530235959Z00'00'"));
-    CPPUNIT_ASSERT_EQUAL(true,date.IsValid());
+    const char* pszDate = "D:20120530235959Z00'00'";
+    PdfString tmp(pszDate);
+    PdfDate date(tmp);
+    CPPUNIT_ASSERT_EQUAL_MESSAGE(std::string(pszDate),true,date.IsValid());
     const time_t &time = date.GetTime();
     struct tm  _tm;
     memset (&_tm, 0, sizeof(struct tm));
@@ -81,4 +90,27 @@
     CPPUNIT_ASSERT_EQUAL(true,time==time2);
 }
 
+void DateTest::testAdditional()
+{
+  struct name_date {
+    std::string name;
+    std::string date;
+  };
 
+  const name_date data[] = {
+			    {"sample from pdf_reference_1_7.pdf", "D:199812231952-08'00'"},
+			    // UTC 1998-12-24 03:52:00
+			    {"all fields set", "D:20201223195200-08'00'"},   // UTC 2020-12-03:52:00
+			    {"set year", "D:2020"},   // UTC 2020-01-01 00:00:00
+			    {"set year, month", "D:202001"},   // UTC 2020-01-01 00:00:00
+			    {"set year, month, day", "D:20200101"},   // UTC 202001-01 00:00:00
+			    {"only year and timezone set", "D:2020-08'00'"},   // UTC 2020-01-01 08:00:00
+			    {"berlin", "D:20200315120820+01'00'"},   // UTC 2020-03-15 11:08:20
+  };
+
+  for (const auto& d : data) {
+    std::cout << "Parse " << d.name << "\n";
+    assert(PoDoFo::PdfDate(d.date).IsValid());
+  }
+}
+
Index: test/unit/DateTest.h
===================================================================
--- test/unit/DateTest.h	(revision 2016)
+++ test/unit/DateTest.h	(working copy)
@@ -29,6 +29,7 @@
     CPPUNIT_TEST_SUITE( DateTest );
     CPPUNIT_TEST( testCreateDateFromString );
     CPPUNIT_TEST( testDateValue );
+    CPPUNIT_TEST( testAdditional );
     CPPUNIT_TEST_SUITE_END();
 public:
     void setUp();
@@ -36,6 +37,8 @@
 
     void testCreateDateFromString();
     void testDateValue();
+    void testAdditional();
+
 };
 
 #endif
Index: src/podofo/base/PdfDate.cpp
===================================================================
--- src/podofo/base/PdfDate.cpp	(revision 2016)
+++ src/podofo/base/PdfDate.cpp	(working copy)
@@ -36,7 +36,94 @@
 
 #include <string.h>
 #include <sstream>
+namespace  {
 
+/** Parse fixed length number from string
+  *  \param in string to read number from
+  *  \param length exact number characters to read
+  *  \param min minimal value of number
+  *  \param max maximal value of number
+  *  \param ret parsed number (updated only on success)
+  */
+bool ParseFixLenNumber(const char *&in, unsigned int length, int min, int max, int &ret_)
+{
+    int ret = 0;
+    for(unsigned int i=0;i<length;i++)
+    {
+        if ( in == NULL || !isdigit(*in)) return false;
+        ret = ret*10+ (*in-'0');
+        in++;
+    }
+    if ( ret < min || ret > max ) return false;
+    ret_ = ret;
+    return true;
+}
+
+void ParseOptionalFields(const char *&pszDate, tm& _tm)
+{
+    if ( ParseFixLenNumber(pszDate,2,1,12,_tm.tm_mon) )
+    {
+        _tm.tm_mon--;
+        if ( ParseFixLenNumber(pszDate,2,1,31,_tm.tm_mday) )
+        {
+            if ( ParseFixLenNumber(pszDate,2,0,23,_tm.tm_hour) )
+            {
+                if ( ParseFixLenNumber(pszDate,2,0,59,_tm.tm_min) )
+                    ParseFixLenNumber(pszDate,2,0,59,_tm.tm_sec);
+            }
+        }
+    }
+}
+
+time_t ParseZoneShift(const char *&pszDate, tm& _tm)
+{
+    int nZoneShift = 0;
+    int nZoneHour = 0;
+    int nZoneMin = 0;
+
+    if ( *pszDate != '\0' )
+    {
+        switch (*pszDate) {
+        case '+':
+            nZoneShift = -1;
+            break;
+        case '-':
+            nZoneShift = 1;
+            break;
+        case 'Z':
+            nZoneShift = 0;
+            break;
+        default:
+            return time_t(-1);
+        }
+        pszDate++;
+        if ( !ParseFixLenNumber(pszDate,2,0,59,nZoneHour) ) return time_t(-1);
+        if (*pszDate == '\'') {
+            pszDate++;
+            if ( !ParseFixLenNumber(pszDate,2,0,59,nZoneMin) ) return time_t(-1);
+            if (*pszDate != '\'')
+                return time_t(-1);
+            pszDate++;
+        }
+    }
+    if ( *pszDate != '\0' ) 
+    {
+        return time_t(-1);
+    }
+
+    // convert to 
+    time_t m_time = mktime(&_tm);
+    if ( m_time == -1 ) 
+    {
+        return m_time;
+    }
+
+    m_time += nZoneShift*(nZoneHour*3600 + nZoneMin*60);
+    return m_time;
+}
+
+}
+
 namespace PoDoFo {
 
 PdfDate::PdfDate()
@@ -47,18 +134,15 @@
 }
 
 PdfDate::PdfDate( const time_t & t )
-    : m_bValid( false )
+    : m_time( t ), m_bValid( false )
 {
-    m_time = t;
     CreateStringRepresentation();
 }
 
 PdfDate::PdfDate( const PdfString & sDate )
-    : m_bValid( false )
+    : m_time( -1 ), m_bValid( false )
 {
-    m_time = -1;
-
-    if ( !sDate.IsValid() ) 
+    if ( !sDate.IsValid() )
     {
         m_szDate[0] = 0;
         return;
@@ -66,11 +150,8 @@
 
     strncpy(m_szDate,sDate.GetString(),PDF_DATE_BUFFER_SIZE);
 
-    struct tm _tm;
-    memset( &_tm, 0, sizeof(_tm) );
-    int nZoneShift = 0;
-    int nZoneHour = 0;
-    int nZoneMin = 0;
+    struct tm _tm{};
+    _tm.tm_mday = 1;
 
     const char * pszDate = sDate.GetString();
     if ( pszDate == NULL ) return;
@@ -79,65 +160,17 @@
         if ( *pszDate++ != ':' ) return;
     }
 
-    if ( ParseFixLenNumber(pszDate,4,0,9999,_tm.tm_year) == false ) 
+    // year is not optional
+    if ( !ParseFixLenNumber(pszDate,4,0,9999,_tm.tm_year) )
         return;
-
     _tm.tm_year -= 1900;
-    if ( *pszDate != '\0' ) {
-        if ( ParseFixLenNumber(pszDate,2,1,12,_tm.tm_mon) == false ) 
-            return;
 
-        _tm.tm_mon--;
-        if ( *pszDate != '\0' ) {
-            if ( ParseFixLenNumber(pszDate,2,1,31,_tm.tm_mday) == false ) return;
-            if ( *pszDate != '\0' ) {
-                if ( ParseFixLenNumber(pszDate,2,0,23,_tm.tm_hour) == false ) return;
-                if ( *pszDate != '\0' ) {
-                    if ( ParseFixLenNumber(pszDate,2,0,59,_tm.tm_min) == false ) return;
-                    if ( *pszDate != '\0' ) {
-                        if ( ParseFixLenNumber(pszDate,2,0,59,_tm.tm_sec) == false ) return;
-                        if ( *pszDate != '\0' ) {
-                            switch(*pszDate++) {
-                            case '+':
-                                nZoneShift = -1;
-                                break;
-                            case '-':
-                                nZoneShift = 1;
-                                break;
-                            case 'Z':
-                                nZoneShift = 0;
-                                break;
-                            default:
-                                return;
-                            }
-                            if ( ParseFixLenNumber(pszDate,2,0,59,nZoneHour) == false ) return;
-                            if ( *pszDate == '\'' ) {
-                                pszDate++;
-                                if ( ParseFixLenNumber(pszDate,2,0,59,nZoneMin) == false ) return;
-                                if ( *pszDate != '\'' ) return;
-                                pszDate++;
-                            }
-                        }
-                    }
-                }
-            }
-        }
-    }
+    // all other values are optional, if not set they are 0-init (except mday)
+    ParseOptionalFields(pszDate, _tm);
 
-    if ( *pszDate != '\0' ) 
-    {
-        return;
-    }
-
-    // convert to 
-    m_time = mktime(&_tm);
-    if ( m_time == -1 ) 
-    {
-        return;
-    }
-
-    m_time += nZoneShift*(nZoneHour*3600 + nZoneMin*60);
-    m_bValid = true;
+    // zone is optional
+    m_time = ParseZoneShift(pszDate, _tm);
+    m_bValid = ( m_time != -1);
 }
 
 PdfDate::~PdfDate()
@@ -205,20 +238,4 @@
     m_bValid = true;
 }
 
-
-bool PdfDate::ParseFixLenNumber(const char *&in, unsigned int length, int min, int max, int &ret)
-{
-    ret = 0;
-    for(unsigned int i=0;i<length;i++)
-    {
-        if ( in == NULL || !isdigit(*in)) return false;
-        ret = ret*10+ (*in-'0');
-        in++;
-    }
-    if ( ret < min || ret > max ) return false;
-    return true;
-}
-
 };
-
-
Index: src/podofo/base/PdfDate.h
===================================================================
--- src/podofo/base/PdfDate.h	(revision 2016)
+++ src/podofo/base/PdfDate.h	(working copy)
@@ -119,15 +119,6 @@
      */
     void CreateStringRepresentation();
 
-    /** Parse fixed length number from string
-     *  \param in string to read number from
-     *  \param length of number to read 
-     *  \param min minimal value of number
-     *  \param max maximal value of number
-     *  \param ret parsed number
-     */
-    bool ParseFixLenNumber(const char *&in, unsigned int length, int min, int max, int &ret);
-
  private:
     time_t m_time;
     char   m_szDate[PDF_DATE_BUFFER_SIZE + 1]; // include also room for a nul-terminator in the buffer
Index: test/unit/DateTest.cpp
===================================================================
--- test/unit/DateTest.cpp	(revision 2016)
+++ test/unit/DateTest.cpp	(working copy)
@@ -38,7 +38,14 @@
 {
     PdfString tmp(pszDate);
     PdfDate date(tmp);
-    CPPUNIT_ASSERT_EQUAL(bExpected,date.IsValid());
+    if( pszDate != NULL )
+    {
+      CPPUNIT_ASSERT_EQUAL_MESSAGE(pszDate,bExpected,date.IsValid());
+    }
+    else
+    {
+      CPPUNIT_ASSERT_EQUAL_MESSAGE("NULL",bExpected,date.IsValid());
+    }
 }
 
 void DateTest::testCreateDateFromString()
@@ -66,8 +73,10 @@
 
 void DateTest::testDateValue()
 {
-    PdfDate date(PdfString("D:20120530235959Z00'00'"));
-    CPPUNIT_ASSERT_EQUAL(true,date.IsValid());
+    const char* pszDate = "D:20120530235959Z00'00'";
+    PdfString tmp(pszDate);
+    PdfDate date(tmp);
+    CPPUNIT_ASSERT_EQUAL_MESSAGE(std::string(pszDate),true,date.IsValid());
     const time_t &time = date.GetTime();
     struct tm  _tm;
     memset (&_tm, 0, sizeof(struct tm));
@@ -81,4 +90,27 @@
     CPPUNIT_ASSERT_EQUAL(true,time==time2);
 }
 
+void DateTest::testAdditional()
+{
+  struct name_date {
+    std::string name;
+    std::string date;
+  };
 
+  const name_date data[] = {
+			    {"sample from pdf_reference_1_7.pdf", "D:199812231952-08'00'"},
+			    // UTC 1998-12-24 03:52:00
+			    {"all fields set", "D:20201223195200-08'00'"},   // UTC 2020-12-03:52:00
+			    {"set year", "D:2020"},   // UTC 2020-01-01 00:00:00
+			    {"set year, month", "D:202001"},   // UTC 2020-01-01 00:00:00
+			    {"set year, month, day", "D:20200101"},   // UTC 202001-01 00:00:00
+			    {"only year and timezone set", "D:2020-08'00'"},   // UTC 2020-01-01 08:00:00
+			    {"berlin", "D:20200315120820+01'00'"},   // UTC 2020-03-15 11:08:20
+  };
+
+  for (const auto& d : data) {
+    std::cout << "Parse " << d.name << "\n";
+    assert(PoDoFo::PdfDate(d.date).IsValid());
+  }
+}
+
Index: test/unit/DateTest.h
===================================================================
--- test/unit/DateTest.h	(revision 2016)
+++ test/unit/DateTest.h	(working copy)
@@ -29,6 +29,7 @@
     CPPUNIT_TEST_SUITE( DateTest );
     CPPUNIT_TEST( testCreateDateFromString );
     CPPUNIT_TEST( testDateValue );
+    CPPUNIT_TEST( testAdditional );
     CPPUNIT_TEST_SUITE_END();
 public:
     void setUp();
@@ -36,6 +37,8 @@
 
     void testCreateDateFromString();
     void testDateValue();
+    void testAdditional();
+
 };
 
 #endif
Index: src/podofo/base/PdfDate.cpp
===================================================================
--- src/podofo/base/PdfDate.cpp	(revision 2016)
+++ src/podofo/base/PdfDate.cpp	(working copy)
@@ -36,7 +36,140 @@
 
 #include <string.h>
 #include <sstream>
+namespace  {
 
+/** Parse fixed length number from string
+  *  \param in string to read number from
+  *  \param length exact number characters to read
+  *  \param min minimal value of number
+  *  \param max maximal value of number
+  *  \param ret parsed number (updated only on success)
+  */
+enum EParseFixLenNumberResult {Ok, Missing, Error};
+EParseFixLenNumberResult ParseFixLenNumber(const char *&in, unsigned int length, int min, int max, int &ret_)
+{
+    if ( in == NULL || *in == '\0' || *in == '+' || *in == '-' || *in == 'Z') return Missing;
+    int ret = 0;
+    for(unsigned int i=0;i<length;i++)
+    {
+        if ( in == NULL || !isdigit(*in)) return Error;
+        ret = ret*10+ (*in-'0');
+        in++;
+    }
+    if ( ret < min || ret > max ) return Error;
+    ret_ = ret;
+    return Ok;
+}
+
+bool ParseOptionalFields(const char *&pszDate, tm& _tm)
+{
+    EParseFixLenNumberResult res = ParseFixLenNumber(pszDate,2,1,12,_tm.tm_mon);
+    if ( res == Error )
+    {
+        return false;
+    }
+    else if( res == Missing)
+    {
+        return true;
+    }
+    _tm.tm_mon--;
+
+    res = ParseFixLenNumber(pszDate,2,1,31,_tm.tm_mday);
+    if ( res == Error )
+    {
+        return false;
+    }
+    else if( res == Missing)
+    {
+        return true;
+    }
+
+    res = ParseFixLenNumber(pszDate,2,0,23,_tm.tm_hour);
+    if ( res == Error )
+    {
+        return false;
+    }
+    else if( res == Missing)
+    {
+        return true;
+    }
+
+    res = ParseFixLenNumber(pszDate,2,0,59,_tm.tm_min);
+    if ( res == Error )
+    {
+        return false;
+    }
+    else if( res == Missing)
+    {
+        return true;
+    }
+
+    res = ParseFixLenNumber(pszDate,2,0,59,_tm.tm_sec);
+    if ( res == Error )
+    {
+        return false;
+    }
+    return true;
+}
+
+
+time_t ParseZoneShift(const char *&pszDate, tm& _tm)
+{
+    int nZoneShift = 0;
+    int nZoneHour = 0;
+    int nZoneMin = 0;
+
+    if ( *pszDate != '\0' )
+    {
+        switch (*pszDate) {
+        case '+':
+            nZoneShift = -1;
+            break;
+        case '-':
+            nZoneShift = 1;
+            break;
+        case 'Z':
+            nZoneShift = 0;
+            break;
+        default:
+            return time_t(-1);
+        }
+        pszDate++;
+        if ( ParseFixLenNumber(pszDate,2,0,59,nZoneHour) != Ok)
+        {
+            return time_t(-1);
+        }
+        if (*pszDate == '\'') {
+            pszDate++;
+            if ( ParseFixLenNumber(pszDate,2,0,59,nZoneMin) != Ok)
+            {
+                return time_t(-1);
+            }
+            if (*pszDate != '\'')
+            {
+                return time_t(-1);
+            }
+            pszDate++;
+        }
+    }
+    if ( *pszDate != '\0' ) 
+    {
+        return time_t(-1);
+    }
+
+    // convert to 
+    time_t m_time = mktime(&_tm);
+    if ( m_time == -1 ) 
+    {
+        return m_time;
+    }
+
+    m_time += nZoneShift*(nZoneHour*3600 + nZoneMin*60);
+    return m_time;
+}
+
+}
+
 namespace PoDoFo {
 
 PdfDate::PdfDate()
@@ -47,18 +180,15 @@
 }
 
 PdfDate::PdfDate( const time_t & t )
-    : m_bValid( false )
+    : m_time( t ), m_bValid( false )
 {
-    m_time = t;
     CreateStringRepresentation();
 }
 
 PdfDate::PdfDate( const PdfString & sDate )
-    : m_bValid( false )
+    : m_time( -1 ), m_bValid( false )
 {
-    m_time = -1;
-
-    if ( !sDate.IsValid() ) 
+    if ( !sDate.IsValid() )
     {
         m_szDate[0] = 0;
         return;
@@ -66,11 +196,8 @@
 
     strncpy(m_szDate,sDate.GetString(),PDF_DATE_BUFFER_SIZE);
 
-    struct tm _tm;
-    memset( &_tm, 0, sizeof(_tm) );
-    int nZoneShift = 0;
-    int nZoneHour = 0;
-    int nZoneMin = 0;
+    struct tm _tm{};
+    _tm.tm_mday = 1;
 
     const char * pszDate = sDate.GetString();
     if ( pszDate == NULL ) return;
@@ -79,65 +206,20 @@
         if ( *pszDate++ != ':' ) return;
     }
 
-    if ( ParseFixLenNumber(pszDate,4,0,9999,_tm.tm_year) == false ) 
+    // year is not optional
+    if ( ParseFixLenNumber(pszDate,4,0,9999,_tm.tm_year) != Ok)
         return;
-
     _tm.tm_year -= 1900;
-    if ( *pszDate != '\0' ) {
-        if ( ParseFixLenNumber(pszDate,2,1,12,_tm.tm_mon) == false ) 
-            return;
 
-        _tm.tm_mon--;
-        if ( *pszDate != '\0' ) {
-            if ( ParseFixLenNumber(pszDate,2,1,31,_tm.tm_mday) == false ) return;
-            if ( *pszDate != '\0' ) {
-                if ( ParseFixLenNumber(pszDate,2,0,23,_tm.tm_hour) == false ) return;
-                if ( *pszDate != '\0' ) {
-                    if ( ParseFixLenNumber(pszDate,2,0,59,_tm.tm_min) == false ) return;
-                    if ( *pszDate != '\0' ) {
-                        if ( ParseFixLenNumber(pszDate,2,0,59,_tm.tm_sec) == false ) return;
-                        if ( *pszDate != '\0' ) {
-                            switch(*pszDate++) {
-                            case '+':
-                                nZoneShift = -1;
-                                break;
-                            case '-':
-                                nZoneShift = 1;
-                                break;
-                            case 'Z':
-                                nZoneShift = 0;
-                                break;
-                            default:
-                                return;
-                            }
-                            if ( ParseFixLenNumber(pszDate,2,0,59,nZoneHour) == false ) return;
-                            if ( *pszDate == '\'' ) {
-                                pszDate++;
-                                if ( ParseFixLenNumber(pszDate,2,0,59,nZoneMin) == false ) return;
-                                if ( *pszDate != '\'' ) return;
-                                pszDate++;
-                            }
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    if ( *pszDate != '\0' ) 
+    // all other values are optional, if not set they are 0-init (except mday)
+    if(!ParseOptionalFields(pszDate, _tm))
     {
         return;
     }
 
-    // convert to 
-    m_time = mktime(&_tm);
-    if ( m_time == -1 ) 
-    {
-        return;
-    }
-
-    m_time += nZoneShift*(nZoneHour*3600 + nZoneMin*60);
-    m_bValid = true;
+    // zone is optional
+    m_time = ParseZoneShift(pszDate, _tm);
+    m_bValid = ( m_time != -1);
 }
 
 PdfDate::~PdfDate()
@@ -205,20 +287,4 @@
     m_bValid = true;
 }
 
-
-bool PdfDate::ParseFixLenNumber(const char *&in, unsigned int length, int min, int max, int &ret)
-{
-    ret = 0;
-    for(unsigned int i=0;i<length;i++)
-    {
-        if ( in == NULL || !isdigit(*in)) return false;
-        ret = ret*10+ (*in-'0');
-        in++;
-    }
-    if ( ret < min || ret > max ) return false;
-    return true;
-}
-
 };
-
-
Index: src/podofo/base/PdfDate.h
===================================================================
--- src/podofo/base/PdfDate.h	(revision 2016)
+++ src/podofo/base/PdfDate.h	(working copy)
@@ -119,15 +119,6 @@
      */
     void CreateStringRepresentation();
 
-    /** Parse fixed length number from string
-     *  \param in string to read number from
-     *  \param length of number to read 
-     *  \param min minimal value of number
-     *  \param max maximal value of number
-     *  \param ret parsed number
-     */
-    bool ParseFixLenNumber(const char *&in, unsigned int length, int min, int max, int &ret);
-
  private:
     time_t m_time;
     char   m_szDate[PDF_DATE_BUFFER_SIZE + 1]; // include also room for a nul-terminator in the buffer
Index: test/unit/DateTest.cpp
===================================================================
--- test/unit/DateTest.cpp	(revision 2016)
+++ test/unit/DateTest.cpp	(working copy)
@@ -38,7 +38,14 @@
 {
     PdfString tmp(pszDate);
     PdfDate date(tmp);
-    CPPUNIT_ASSERT_EQUAL(bExpected,date.IsValid());
+    if( pszDate != NULL )
+    {
+      CPPUNIT_ASSERT_EQUAL_MESSAGE(pszDate,bExpected,date.IsValid());
+    }
+    else
+    {
+      CPPUNIT_ASSERT_EQUAL_MESSAGE("NULL",bExpected,date.IsValid());
+    }
 }
 
 void DateTest::testCreateDateFromString()
@@ -66,8 +73,10 @@
 
 void DateTest::testDateValue()
 {
-    PdfDate date(PdfString("D:20120530235959Z00'00'"));
-    CPPUNIT_ASSERT_EQUAL(true,date.IsValid());
+    const char* pszDate = "D:20120530235959Z00'00'";
+    PdfString tmp(pszDate);
+    PdfDate date(tmp);
+    CPPUNIT_ASSERT_EQUAL_MESSAGE(std::string(pszDate),true,date.IsValid());
     const time_t &time = date.GetTime();
     struct tm  _tm;
     memset (&_tm, 0, sizeof(struct tm));
@@ -81,4 +90,27 @@
     CPPUNIT_ASSERT_EQUAL(true,time==time2);
 }
 
+void DateTest::testAdditional()
+{
+  struct name_date {
+    std::string name;
+    std::string date;
+  };
 
+  const name_date data[] = {
+			    {"sample from pdf_reference_1_7.pdf", "D:199812231952-08'00'"},
+			    // UTC 1998-12-24 03:52:00
+			    {"all fields set", "D:20201223195200-08'00'"},   // UTC 2020-12-03:52:00
+			    {"set year", "D:2020"},   // UTC 2020-01-01 00:00:00
+			    {"set year, month", "D:202001"},   // UTC 2020-01-01 00:00:00
+			    {"set year, month, day", "D:20200101"},   // UTC 202001-01 00:00:00
+			    {"only year and timezone set", "D:2020-08'00'"},   // UTC 2020-01-01 08:00:00
+			    {"berlin", "D:20200315120820+01'00'"},   // UTC 2020-03-15 11:08:20
+  };
+
+  for (const auto& d : data) {
+    std::cout << "Parse " << d.name << "\n";
+    assert(PoDoFo::PdfDate(d.date).IsValid());
+  }
+}
+
Index: test/unit/DateTest.h
===================================================================
--- test/unit/DateTest.h	(revision 2016)
+++ test/unit/DateTest.h	(working copy)
@@ -29,6 +29,7 @@
     CPPUNIT_TEST_SUITE( DateTest );
     CPPUNIT_TEST( testCreateDateFromString );
     CPPUNIT_TEST( testDateValue );
+    CPPUNIT_TEST( testAdditional );
     CPPUNIT_TEST_SUITE_END();
 public:
     void setUp();
@@ -36,6 +37,8 @@
 
     void testCreateDateFromString();
     void testDateValue();
+    void testAdditional();
+
 };
 
 #endif
_______________________________________________
Podofo-users mailing list
Podofo-users@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/podofo-users

Reply via email to