aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJay Berkenbilt <ejb@ql.org>2021-02-09 20:56:35 +0100
committerJay Berkenbilt <ejb@ql.org>2021-02-09 23:50:24 +0100
commitbf0e6eb3022bf2fde5623a0a3d151c07f5e82945 (patch)
tree72a4c73a318fd7020ad1a01ffb2469b0f2b7d62e
parentbfbeec5497c04e58e25fa207773ece1d29b67000 (diff)
downloadqpdf-bf0e6eb3022bf2fde5623a0a3d151c07f5e82945.tar.zst
Add QUtil methods for dealing with PDF timestamp strings
-rw-r--r--ChangeLog6
-rw-r--r--include/qpdf/QUtil.hh46
-rw-r--r--libqpdf/QUtil.cc103
-rw-r--r--libtests/qtest/qutil/qutil.out4
-rw-r--r--libtests/qutil.cc23
-rw-r--r--manual/qpdf-manual.xml8
6 files changed, 189 insertions, 1 deletions
diff --git a/ChangeLog b/ChangeLog
index c81f88e0..a6f1c4b4 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,9 @@
+2021-02-09 Jay Berkenbilt <ejb@ql.org>
+
+ * Add methods to QUtil for working with PDF timestamp strings:
+ pdf_time_to_qpdf_time, qpdf_time_to_pdf_time,
+ get_current_qpdf_time.
+
2021-02-07 Jay Berkenbilt <ejb@ql.org>
* Add new functions QUtil::pipe_file and QUtil::file_provider for
diff --git a/include/qpdf/QUtil.hh b/include/qpdf/QUtil.hh
index fe18c9b7..09a6c181 100644
--- a/include/qpdf/QUtil.hh
+++ b/include/qpdf/QUtil.hh
@@ -158,7 +158,6 @@ namespace QUtil
QPDF_DLL
void setLineBuf(FILE*);
-
// May modify argv0
QPDF_DLL
char* getWhoami(char* argv0);
@@ -172,6 +171,51 @@ namespace QUtil
QPDF_DLL
time_t get_current_time();
+ // Portable structure representing a point in time with second
+ // granularity and time zone offset
+ struct QPDFTime
+ {
+ QPDFTime() = default;
+ QPDFTime(QPDFTime const&) = default;
+ QPDFTime& operator=(QPDFTime const&) = default;
+ QPDFTime(int year, int month, int day, int hour,
+ int minute, int second, int tz_delta) :
+ year(year),
+ month(month),
+ day(day),
+ hour(hour),
+ minute(minute),
+ second(second),
+ tz_delta(tz_delta)
+ {
+ }
+ int year; // actual year, no 1900 stuff
+ int month; // 1--12
+ int day; // 1--31
+ int hour;
+ int minute;
+ int second;
+ int tz_delta; // minutes before UTC
+ };
+
+ QPDF_DLL
+ QPDFTime get_current_qpdf_time();
+
+ // Convert a QPDFTime structure to a PDF timestamp string, which
+ // is "D:yyyymmddhhmmss<z>" where <z> is either "Z" for UTC or
+ // "-hh'mm'" or "+hh'mm'" for timezone offset. Examples:
+ // "D:20210207161528-05'00'", "D:20210207211528Z". See
+ // get_current_qpdf_time and the QPDFTime structure above.
+ QPDF_DLL
+ std::string qpdf_time_to_pdf_time(QPDFTime const&);
+
+ // Convert a PDF timestamp string to a QPDFTime. If syntactically
+ // valid, return true and fill in qtm. If not valid, return false,
+ // and do not modify qtm. If qtm is null, just check the validity
+ // of the string.
+ QPDF_DLL
+ bool pdf_time_to_qpdf_time(std::string const&, QPDFTime* qtm = nullptr);
+
// Return a string containing the byte representation of the UTF-8
// encoding for the unicode value passed in.
QPDF_DLL
diff --git a/libqpdf/QUtil.cc b/libqpdf/QUtil.cc
index dc847679..4df76fa8 100644
--- a/libqpdf/QUtil.cc
+++ b/libqpdf/QUtil.cc
@@ -23,6 +23,7 @@
#include <fcntl.h>
#include <memory>
#include <locale>
+#include <regex>
#ifndef QPDF_NO_WCHAR_T
# include <cwchar>
#endif
@@ -823,6 +824,108 @@ QUtil::get_current_time()
#endif
}
+QUtil::QPDFTime
+QUtil::get_current_qpdf_time()
+{
+#ifdef _WIN32
+ SYSTEMTIME ltime;
+ GetLocalTime(&ltime);
+ TIME_ZONE_INFORMATION tzinfo;
+ GetTimeZoneInformation(&tzinfo);
+ return QPDFTime(static_cast<int>(ltime.wYear),
+ static_cast<int>(ltime.wMonth),
+ static_cast<int>(ltime.wDay),
+ static_cast<int>(ltime.wHour),
+ static_cast<int>(ltime.wMinute),
+ static_cast<int>(ltime.wSecond),
+ static_cast<int>(tzinfo.Bias));
+#else
+ struct tm ltime;
+ time_t now = time(0);
+ tzset();
+ localtime_r(&now, &ltime);
+ return QPDFTime(static_cast<int>(ltime.tm_year + 1900),
+ static_cast<int>(ltime.tm_mon + 1),
+ static_cast<int>(ltime.tm_mday),
+ static_cast<int>(ltime.tm_hour),
+ static_cast<int>(ltime.tm_min),
+ static_cast<int>(ltime.tm_sec),
+ static_cast<int>(timezone / 60));
+#endif
+}
+
+std::string
+QUtil::qpdf_time_to_pdf_time(QPDFTime const& qtm)
+{
+ std::string tz_offset;
+ int t = qtm.tz_delta;
+ if (t == 0)
+ {
+ tz_offset = "Z";
+ }
+ else
+ {
+ if (t < 0)
+ {
+ t = -t;
+ tz_offset += "+";
+ }
+ else
+ {
+ tz_offset += "-";
+ }
+ tz_offset +=
+ QUtil::int_to_string(t / 60, 2) + "'" +
+ QUtil::int_to_string(t % 60, 2) + "'";
+ }
+ return ("D:" +
+ QUtil::int_to_string(qtm.year, 4) +
+ QUtil::int_to_string(qtm.month, 2) +
+ QUtil::int_to_string(qtm.day, 2) +
+ QUtil::int_to_string(qtm.hour, 2) +
+ QUtil::int_to_string(qtm.minute, 2) +
+ QUtil::int_to_string(qtm.second, 2) +
+ tz_offset);
+}
+
+bool
+QUtil::pdf_time_to_qpdf_time(std::string const& str, QPDFTime* qtm)
+{
+ static std::regex pdf_date("^D:([0-9]{4})([0-9]{2})([0-9]{2})"
+ "([0-9]{2})([0-9]{2})([0-9]{2})"
+ "(?:(Z)|([\\+\\-])([0-9]{2})'([0-9]{2})')$");
+ std::smatch m;
+ if (! std::regex_match(str, m, pdf_date))
+ {
+ return false;
+ }
+ int tz_delta = 0;
+ auto to_i = [](std::string const& s) {
+ return QUtil::string_to_int(s.c_str());
+ };
+
+ if (m[7] == "")
+ {
+ tz_delta = ((to_i(m[9]) * 60) +
+ to_i(m[10]));
+ if (m[8] == "+")
+ {
+ tz_delta = -tz_delta;
+ }
+ }
+ if (qtm)
+ {
+ *qtm = QPDFTime(to_i(m[1]),
+ to_i(m[2]),
+ to_i(m[3]),
+ to_i(m[4]),
+ to_i(m[5]),
+ to_i(m[6]),
+ tz_delta);
+ }
+ return true;
+}
+
std::string
QUtil::toUTF8(unsigned long uval)
{
diff --git a/libtests/qtest/qutil/qutil.out b/libtests/qtest/qutil/qutil.out
index 5fe841ac..a5b79e7f 100644
--- a/libtests/qtest/qutil/qutil.out
+++ b/libtests/qtest/qutil/qutil.out
@@ -105,3 +105,7 @@ rename file
create file
rename over existing
delete file
+---- timestamp
+D:20210209144925-05'00'
+D:20210210011925+05'30'
+D:20210209191925Z
diff --git a/libtests/qutil.cc b/libtests/qutil.cc
index 94454374..b67b7580 100644
--- a/libtests/qutil.cc
+++ b/libtests/qutil.cc
@@ -581,6 +581,27 @@ void rename_delete_test()
assert_no_file("old\xcf\x80.~tmp");
}
+void timestamp_test()
+{
+ auto check = [](QUtil::QPDFTime const& t) {
+ std::string pdf = QUtil::qpdf_time_to_pdf_time(t);
+ std::cout << pdf << std::endl;
+ QUtil::QPDFTime t2;
+ assert(QUtil::pdf_time_to_qpdf_time(pdf, &t2));
+ assert(QUtil::qpdf_time_to_pdf_time(t2) == pdf);
+ };
+ check(QUtil::QPDFTime(2021, 2, 9, 14, 49, 25, 300));
+ check(QUtil::QPDFTime(2021, 2, 10, 1, 19, 25, -330));
+ check(QUtil::QPDFTime(2021, 2, 9, 19, 19, 25, 0));
+ assert(! QUtil::pdf_time_to_qpdf_time("potato"));
+ // Round trip on the current time without actually printing it.
+ // Manual testing was done to ensure that we are actually getting
+ // back the current time in various timezones.
+ assert(QUtil::pdf_time_to_qpdf_time(
+ QUtil::qpdf_time_to_pdf_time(
+ QUtil::get_current_qpdf_time())));
+}
+
int main(int argc, char* argv[])
{
try
@@ -611,6 +632,8 @@ int main(int argc, char* argv[])
hex_encode_decode_test();
std::cout << "---- rename/delete" << std::endl;
rename_delete_test();
+ std::cout << "---- timestamp" << std::endl;
+ timestamp_test();
}
catch (std::exception& e)
{
diff --git a/manual/qpdf-manual.xml b/manual/qpdf-manual.xml
index 39f3d969..05526958 100644
--- a/manual/qpdf-manual.xml
+++ b/manual/qpdf-manual.xml
@@ -4943,6 +4943,14 @@ print "\n";
</listitem>
<listitem>
<para>
+ Add <function>QUtil::get_current_qpdf_time</function>,
+ <function>QUtil::pdf_time_to_qpdf_time</function>, and
+ <function>QUtil::qpdf_time_to_pdf_time</function> for
+ working with PDF timestamp strings.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
Add <function>warn</function> to
<classname>QPDF</classname>'s public API.
</para>