Skip to content

Commit

Permalink
[date] Add tz recipe as dependency
Browse files Browse the repository at this point in the history
* Initial implementation adding tz recipe as a dependency
* Apply patch from PR date#611 to add ability to specify tz database by
  environment variable and also enable the binary tz database to be
  parsed on windows
* Deprecate use_system_tz_db in favour of "with_tzdb" option to handle
  all mutually exclusive options
* Add with_db_format option to provide flexibility in how the tz recipe
  is consumed
  • Loading branch information
samuel-emrys committed Dec 11, 2023
1 parent 7ca6977 commit 6a8e364
Show file tree
Hide file tree
Showing 3 changed files with 280 additions and 5 deletions.
4 changes: 4 additions & 0 deletions recipes/date/all/conandata.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ patches:
- patch_file: "patches/cmake-3.0.1.patch"
patch_description: "Disable string view to workaround clang 5 not having it"
patch_type: "portability"
- patch_file: "patches/load-tzdb-from-envvar-and-windows-binary-db-support-3.0.1.patch"
patch_description: "Allow TZDB to be loaded by envvar and add ability for windows to read zic-compiled binary db"
patch_type: "portability"
patch_source: "https://github.com/HowardHinnant/date/pull/611"
"3.0.0":
- patch_file: "patches/cmake-3.0.0.patch"
patch_description: "Disable string view to workaround clang 5 not having it"
Expand Down
25 changes: 20 additions & 5 deletions recipes/date/all/conanfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from conan.tools.cmake import CMake, CMakeToolchain, CMakeDeps, cmake_layout
from conan.tools.files import get, rmdir, apply_conandata_patches, export_conandata_patches, copy
from conan.tools.scm import Version
from conan.errors import ConanInvalidConfiguration

import os

Expand All @@ -22,14 +23,18 @@ class DateConan(ConanFile):
"shared": [True, False],
"fPIC": [True, False],
"header_only": [True, False],
"use_system_tz_db": [True, False],
"use_system_tz_db": [True, False, "deprecated"],
"with_tzdb": [False, "system", "download", "manual", "tz"],
"with_db_format": ["source", "binary"],
"use_tz_db_in_dot": [True, False],
}
default_options = {
"shared": False,
"fPIC": True,
"header_only": False,
"use_system_tz_db": False,
"with_tzdb": "tz",
"with_db_format": "binary",
"use_tz_db_in_dot": False,
}

Expand All @@ -52,8 +57,11 @@ def layout(self):
cmake_layout(self, src_folder="src")

def requirements(self):
if not self.options.header_only and not self.options.use_system_tz_db:
if not self.options.header_only and not self.options.use_system_tz_db and self.options.with_tzdb == "download":
self.requires("libcurl/[>=7.78 <9]")
if self.options.with_tzdb == "tz":
#self.requires("tz/2023c", options={"with_binary_db": self.options.with_db_format == "binary"})
self.requires("tz/2023c")

def package_id(self):
if self.info.options.header_only:
Expand All @@ -62,16 +70,23 @@ def package_id(self):
def validate(self):
if self.settings.compiler.get_safe("cppstd"):
check_min_cppstd(self, 11)
if self.options.with_tzdb != "download" and self.options.use_tz_db_in_dot:
raise ConanInvalidConfiguration("'use_tz_db_in_dot=True' is only valid when 'with_tzdb=\"download\"'")
if self.options.use_system_tz_db and (self.options.with_tzdb not in [False, "system"]):
raise ConanInvalidConfiguration("'use_system_tz_db' is deprecated. You must set 'with_tzdb=system'")
if (self.options.use_system_tz_db or self.options.with_tzdb == "system") and not self.options.with_db_format == "binary":
raise ConanInvalidConfiguration("date only supports using the operating system database in a binary format. You must set 'with_db_format=binary'")

def source(self):
get(self, **self.conan_data["sources"][self.version], strip_root=True)

def generate(self):
tc = CMakeToolchain(self)
tc.variables["ENABLE_DATE_TESTING"] = False
tc.variables["USE_SYSTEM_TZ_DB"] = self.options.use_system_tz_db
tc.variables["USE_SYSTEM_TZ_DB"] = (self.options.use_system_tz_db or self.options.with_tzdb in ["system", "tz"]) and self.options.with_db_format == "binary"
tc.variables["USE_TZ_DB_IN_DOT"] = self.options.use_tz_db_in_dot
tc.variables["BUILD_TZ_LIB"] = not self.options.header_only
tc.variables["MANUAL_TZ_DB"] = self.options.with_tzdb in ["manual", "tz"] and not self.options.with_db_format == "binary"
# workaround for clang 5 not having string_view
if Version(self.version) >= "3.0.0" and self.settings.compiler == "clang" \
and Version(self.settings.compiler.version) <= "5.0":
Expand Down Expand Up @@ -123,10 +138,10 @@ def package_info(self):
self.cpp_info.components["date-tz"].system_libs.append("pthread")
self.cpp_info.components["date-tz"].system_libs.append("m")

if not self.options.use_system_tz_db:
if self.options.with_tzdb == "download":
self.cpp_info.components["date-tz"].requires.append("libcurl::libcurl")

if self.options.use_system_tz_db and not self.settings.os == "Windows":
if (self.options.use_system_tz_db or self.options.with_tzdb in ["system", "tz"]) and self.options.with_db_format == "binary":
use_os_tzdb = 1
else:
use_os_tzdb = 0
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 012512a..26f4b37 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -127,7 +127,7 @@ if( BUILD_TZ_LIB )
target_compile_definitions( date-tz PRIVATE AUTO_DOWNLOAD=1 HAS_REMOTE_API=1 )
endif()

- if ( USE_SYSTEM_TZ_DB AND NOT WIN32 AND NOT MANUAL_TZ_DB )
+ if ( USE_SYSTEM_TZ_DB AND NOT MANUAL_TZ_DB )
target_compile_definitions( date-tz PRIVATE INSTALL=. PUBLIC USE_OS_TZDB=1 )
else()
target_compile_definitions( date-tz PUBLIC USE_OS_TZDB=0 )
diff --git a/include/date/tz.h b/include/date/tz.h
index 4921068..03c0042 100644
--- a/include/date/tz.h
+++ b/include/date/tz.h
@@ -82,12 +82,6 @@ static_assert(HAS_REMOTE_API == 0 ? AUTO_DOWNLOAD == 0 : true,
# define USE_SHELL_API 1
#endif

-#if USE_OS_TZDB
-# ifdef _WIN32
-# error "USE_OS_TZDB can not be used on Windows"
-# endif
-#endif
-
#ifndef HAS_DEDUCTION_GUIDES
# if __cplusplus >= 201703
# define HAS_DEDUCTION_GUIDES 1
diff --git a/src/tz.cpp b/src/tz.cpp
index 26babbd..cbfde5b 100644
--- a/src/tz.cpp
+++ b/src/tz.cpp
@@ -81,6 +81,10 @@
# endif // __MINGW32__

# include <windows.h>
+# include <regex>
+# if !defined(S_ISDIR) && defined(S_IFMT) && defined(_S_IFDIR)
+# define S_ISDIR(m) (((m) & S_IFMT) == _S_IFDIR)
+# endif
#endif // _WIN32

#include "date/tz_private.h"
@@ -93,7 +97,12 @@
#endif

#if USE_OS_TZDB
-# include <dirent.h>
+# if !WIN32
+# include <dirent.h>
+# else
+# include <windef.h>
+# include <fstream>
+# endif
#endif
#include <algorithm>
#include <cctype>
@@ -141,7 +150,7 @@
# endif // HAS_REMOTE_API
#else // !_WIN32
# include <unistd.h>
-# if !USE_OS_TZDB && !defined(INSTALL)
+# if !USE_OS_TZDB
# include <wordexp.h>
# endif
# include <limits.h>
@@ -299,6 +308,14 @@ access_install()
#undef STRINGIZE
#endif // !INSTALL

+ {
+ static char* tz_local_env = getenv("TZDATA");
+ if (tz_local_env != nullptr) {
+ static std::string tz_local_env_s = tz_local_env;
+ return tz_local_env_s;
+ }
+ }
+
return install;
}

@@ -341,7 +358,6 @@ CONSTCD14 const sys_seconds min_seconds = sys_days(min_year/min_day);

#endif // USE_OS_TZDB

-#ifndef _WIN32

static
std::string
@@ -353,6 +369,16 @@ discover_tz_dir()
CONSTDATA auto tz_dir_default = "/usr/share/zoneinfo";
CONSTDATA auto tz_dir_buildroot = "/usr/share/zoneinfo/uclibc";

+ {
+ // TZDIR is from the posix naming
+ // https://man7.org/linux/man-pages/man3/tzset.3.html
+ static char* tz_local_env = getenv("TZDATA");
+ if (tz_local_env != nullptr) {
+ static std::string tz_local_env_s = tz_local_env;
+ return tz_local_env_s;
+ }
+ }
+
// Check special path which is valid for buildroot with uclibc builds
if(stat(tz_dir_buildroot, &sb) == 0 && S_ISDIR(sb.st_mode))
return tz_dir_buildroot;
@@ -396,7 +422,6 @@ get_tz_dir()
return tz_dir;
}

-#endif

// +-------------------+
// | End Configuration |
@@ -491,8 +516,6 @@ parse_month(std::istream& in)
return static_cast<unsigned>(++m);
}

-#if !USE_OS_TZDB
-
#ifdef _WIN32

static
@@ -703,6 +726,8 @@ load_timezone_mappings_from_xml_file(const std::string& input_path)

#endif // _WIN32

+
+#if !USE_OS_TZDB
// Parsing helpers

static
@@ -1732,9 +1757,15 @@ time_zone::time_zone(const std::string& s, detail::undocumented)

enum class endian
{
- native = __BYTE_ORDER__,
+#ifdef _WIN32
+ little = 0,
+ big = 1,
+ native = little
+#else
little = __ORDER_LITTLE_ENDIAN__,
- big = __ORDER_BIG_ENDIAN__
+ big = __ORDER_BIG_ENDIAN__,
+ native = __BYTE_ORDER__
+#endif
};

static
@@ -2041,8 +2072,10 @@ time_zone::init_impl()
{
using namespace std;
using namespace std::chrono;
- auto name = get_tz_dir() + ('/' + name_);
- std::ifstream inf(name);
+ auto name = get_tz_dir() + (folder_delimiter + name_);
+ // Some platforms will open the inf stream in text mode. Specify binary
+ // to avoid confusion.
+ std::ifstream inf(name, std::ios::in | std::ios::binary);
if (!inf.is_open())
throw std::runtime_error{"Unable to open " + name};
inf.exceptions(std::ios::failbit | std::ios::badbit);
@@ -2742,8 +2775,10 @@ init_tzdb()
//Iterate through folders
std::queue<std::string> subfolders;
subfolders.emplace(get_tz_dir());
- struct dirent* d;
+
struct stat s;
+#if !WIN32 // !WIN32
+ struct dirent* d;
while (!subfolders.empty())
{
auto dirname = std::move(subfolders.front());
@@ -2786,6 +2821,56 @@ init_tzdb()
}
closedir(dir);
}
+#else // WIN32
+ // POSIX dirent is not availible in Visual C++
+ // Use Windows file API instead
+ WIN32_FIND_DATA hFindData;
+ while (!subfolders.empty())
+ {
+ auto dirname = std::move(subfolders.front());
+ subfolders.pop();
+ HANDLE hFind = FindFirstFile((dirname + "\\\\*").c_str(), &hFindData);
+ if (hFind == INVALID_HANDLE_VALUE)
+ {
+ continue;
+ }
+
+ do
+ {
+ // Ignore these files:
+ if (hFindData.cFileName[0] == '.' || // curdir, prevdir, hidden
+ memcmp(hFindData.cFileName, "posix", 5) == 0 || // starts with posix
+ strcmp(hFindData.cFileName, "Factory") == 0 ||
+ strcmp(hFindData.cFileName, "iso3166.tab") == 0 ||
+ strcmp(hFindData.cFileName, "right") == 0 ||
+ strcmp(hFindData.cFileName, "+VERSION") == 0 ||
+ strcmp(hFindData.cFileName, "zone.tab") == 0 ||
+ strcmp(hFindData.cFileName, "zone1970.tab") == 0 ||
+ strcmp(hFindData.cFileName, "tzdata.zi") == 0 ||
+ strcmp(hFindData.cFileName, "leapseconds") == 0 ||
+ strcmp(hFindData.cFileName, "leap-seconds.list") == 0 )
+ {
+ continue;
+ }
+ auto subname = dirname + folder_delimiter + hFindData.cFileName;
+ if(stat(subname.c_str(), &s) == 0)
+ {
+ if(S_ISDIR(s.st_mode))
+ {
+ subfolders.push(subname);
+ }
+ else
+ {
+ std::string zone = subname.substr(get_tz_dir().size()+1);
+ db->zones.emplace_back(zone,
+ detail::undocumented{});
+ }
+ }
+ }
+ while (FindNextFile(hFind, &hFindData ));
+ FindClose(hFind);
+ }
+#endif // WIN32
db->zones.shrink_to_fit();
std::sort(db->zones.begin(), db->zones.end());
db->leap_seconds = find_read_and_leap_seconds();
@@ -3633,7 +3718,17 @@ locate_zone(std::string_view tz_name)
locate_zone(const std::string& tz_name)
#endif
{
- return get_tzdb().locate_zone(tz_name);
+ return get_tzdb().locate_zone(
+#if WIN32 && USE_OS_TZDB
+ // When USE_OS_TZDB=ON, the tz_name needs to be a valid path.
+ // For timezone labeling consistency in Windows, switch "/" to "\".
+ // This will allow for tz_name to have consistent naming regardless of
+ // if USE_OS_TZDB is ON.
+ std::regex_replace(tz_name, std::regex("\\/"), "\\")
+#else
+ tz_name
+#endif
+ );
}

#if USE_OS_TZDB
--
2.24.3 (Apple Git-128)

0 comments on commit 6a8e364

Please sign in to comment.