Add unit tests for CXFA_TimeZoneProvider.
Demonstrate why FX_TIMEZONE is broken. Includes a cross-platform
ScopedSetTZ inspired by Crashpad's ScopedSetTZ.
Bug: pdfium:1662
Change-Id: Idd535c614d2d8090602719dfe5677b462df85540
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/78875
Reviewed-by: Tom Sepez <tsepez@chromium.org>
Commit-Queue: Lei Zhang <thestig@chromium.org>
diff --git a/testing/BUILD.gn b/testing/BUILD.gn
index 4942a4c..39c12a8 100644
--- a/testing/BUILD.gn
+++ b/testing/BUILD.gn
@@ -12,6 +12,8 @@
"invalid_seekable_read_stream.cpp",
"invalid_seekable_read_stream.h",
"pseudo_retainable.h",
+ "scoped_set_tz.cpp",
+ "scoped_set_tz.h",
"string_write_stream.cpp",
"string_write_stream.h",
"test_loader.cpp",
diff --git a/testing/scoped_set_tz.cpp b/testing/scoped_set_tz.cpp
new file mode 100644
index 0000000..a2d2e19
--- /dev/null
+++ b/testing/scoped_set_tz.cpp
@@ -0,0 +1,44 @@
+// Copyright 2021 PDFium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "testing/scoped_set_tz.h"
+
+#include <stdlib.h>
+#include <time.h>
+
+#include "build/build_config.h"
+#include "third_party/base/check_op.h"
+
+namespace {
+
+constexpr char kTZ[] = "TZ";
+
+#if defined(OS_WIN)
+#define SETENV(name, value) _putenv_s(name, value)
+#define TZSET _tzset
+#define UNSETENV(name) _putenv_s(name, "")
+#else
+#define SETENV(name, value) setenv(name, value, 1)
+#define TZSET tzset
+#define UNSETENV(name) unsetenv(name)
+#endif
+
+} // namespace
+
+ScopedSetTZ::ScopedSetTZ(const std::string& tz) {
+ const char* old_tz = getenv(kTZ);
+ if (old_tz)
+ old_tz_ = old_tz;
+
+ CHECK_EQ(0, SETENV(kTZ, tz.c_str()));
+ TZSET();
+}
+
+ScopedSetTZ::~ScopedSetTZ() {
+ if (old_tz_.has_value())
+ CHECK_EQ(0, SETENV(kTZ, old_tz_.value().c_str()));
+ else
+ CHECK_EQ(0, UNSETENV(kTZ));
+ TZSET();
+}
diff --git a/testing/scoped_set_tz.h b/testing/scoped_set_tz.h
new file mode 100644
index 0000000..37a0a32
--- /dev/null
+++ b/testing/scoped_set_tz.h
@@ -0,0 +1,23 @@
+// Copyright 2021 PDFium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TESTING_SCOPED_SET_TZ_H_
+#define TESTING_SCOPED_SET_TZ_H_
+
+#include <string>
+
+#include "third_party/base/optional.h"
+
+class ScopedSetTZ {
+ public:
+ explicit ScopedSetTZ(const std::string& tz);
+ ScopedSetTZ(const ScopedSetTZ&) = delete;
+ ScopedSetTZ& operator=(const ScopedSetTZ&) = delete;
+ ~ScopedSetTZ();
+
+ private:
+ Optional<std::string> old_tz_;
+};
+
+#endif // TESTING_SCOPED_SET_TZ_H_
diff --git a/xfa/fxfa/parser/BUILD.gn b/xfa/fxfa/parser/BUILD.gn
index 097e4ab..b0f85a8 100644
--- a/xfa/fxfa/parser/BUILD.gn
+++ b/xfa/fxfa/parser/BUILD.gn
@@ -702,6 +702,7 @@
"cxfa_measurement_unittest.cpp",
"cxfa_node_unittest.cpp",
"cxfa_nodeiteratortemplate_unittest.cpp",
+ "cxfa_timezoneprovider_unittest.cpp",
"cxfa_xmllocale_unittest.cpp",
"xfa_basic_data_unittest.cpp",
"xfa_utils_unittest.cpp",
diff --git a/xfa/fxfa/parser/cxfa_timezoneprovider_unittest.cpp b/xfa/fxfa/parser/cxfa_timezoneprovider_unittest.cpp
new file mode 100644
index 0000000..f4e7027
--- /dev/null
+++ b/xfa/fxfa/parser/cxfa_timezoneprovider_unittest.cpp
@@ -0,0 +1,72 @@
+// Copyright 2021 PDFium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "xfa/fxfa/parser/cxfa_timezoneprovider.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/scoped_set_tz.h"
+
+TEST(CXFA_TimeZoneProviderTest, HourOffsets) {
+ {
+ ScopedSetTZ scoped_set_tz("UTC");
+ CXFA_TimeZoneProvider provider;
+ EXPECT_EQ(0, FX_TimeZoneOffsetInMinutes(provider.GetTimeZone()));
+ }
+ {
+ ScopedSetTZ scoped_set_tz("UTC+1");
+ CXFA_TimeZoneProvider provider;
+ EXPECT_EQ(-60, FX_TimeZoneOffsetInMinutes(provider.GetTimeZone()));
+ }
+ {
+ ScopedSetTZ scoped_set_tz("UTC-1");
+ CXFA_TimeZoneProvider provider;
+ EXPECT_EQ(60, FX_TimeZoneOffsetInMinutes(provider.GetTimeZone()));
+ }
+ {
+ ScopedSetTZ scoped_set_tz("UTC+14");
+ CXFA_TimeZoneProvider provider;
+ EXPECT_EQ(-840, FX_TimeZoneOffsetInMinutes(provider.GetTimeZone()));
+ }
+ {
+ ScopedSetTZ scoped_set_tz("UTC-14");
+ CXFA_TimeZoneProvider provider;
+ EXPECT_EQ(840, FX_TimeZoneOffsetInMinutes(provider.GetTimeZone()));
+ }
+}
+
+TEST(CXFA_TimeZoneProviderTest, HalfHourOffsets) {
+ {
+ ScopedSetTZ scoped_set_tz("UTC+0:30");
+ CXFA_TimeZoneProvider provider;
+ // TODO(crbug.com/pdfium/1662): Should be -30.
+ EXPECT_EQ(30, FX_TimeZoneOffsetInMinutes(provider.GetTimeZone()));
+ }
+ {
+ ScopedSetTZ scoped_set_tz("UTC-0:30");
+ CXFA_TimeZoneProvider provider;
+ EXPECT_EQ(30, FX_TimeZoneOffsetInMinutes(provider.GetTimeZone()));
+ }
+ {
+ ScopedSetTZ scoped_set_tz("UTC+1:30");
+ CXFA_TimeZoneProvider provider;
+ // TODO(crbug.com/pdfium/1662): Should be -90.
+ EXPECT_EQ(-30, FX_TimeZoneOffsetInMinutes(provider.GetTimeZone()));
+ }
+ {
+ ScopedSetTZ scoped_set_tz("UTC-1:30");
+ CXFA_TimeZoneProvider provider;
+ EXPECT_EQ(90, FX_TimeZoneOffsetInMinutes(provider.GetTimeZone()));
+ }
+ {
+ ScopedSetTZ scoped_set_tz("UTC+9:30");
+ CXFA_TimeZoneProvider provider;
+ // TODO(crbug.com/pdfium/1662): Should be -570.
+ EXPECT_EQ(-510, FX_TimeZoneOffsetInMinutes(provider.GetTimeZone()));
+ }
+ {
+ ScopedSetTZ scoped_set_tz("UTC-9:30");
+ CXFA_TimeZoneProvider provider;
+ EXPECT_EQ(570, FX_TimeZoneOffsetInMinutes(provider.GetTimeZone()));
+ }
+}