Add QUtil methods for dealing with PDF timestamp strings

This commit is contained in:
Jay Berkenbilt 2021-02-09 14:56:35 -05:00
parent bfbeec5497
commit bf0e6eb302
6 changed files with 189 additions and 1 deletions

View File

@ -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

View File

@ -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

View File

@ -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)
{

View File

@ -105,3 +105,7 @@ rename file
create file
rename over existing
delete file
---- timestamp
D:20210209144925-05'00'
D:20210210011925+05'30'
D:20210209191925Z

View File

@ -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)
{

View File

@ -4941,6 +4941,14 @@ print "\n";
details.
</para>
</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