mirror of
https://github.com/HowardHinnant/date.git
synced 2025-08-03 20:54:27 +02:00
Allow set_install()
on Windows to use a file path containing Unicode
Anywhere we utilize that install file path, we carefully convert it to UTF-16 and utilize wide character helpers for file manipulation
This commit is contained in:
committed by
Howard Hinnant
parent
d9049ee697
commit
529a09267f
155
src/tz.cpp
155
src/tz.cpp
@@ -198,6 +198,35 @@ namespace
|
|||||||
using co_task_mem_ptr = std::unique_ptr<wchar_t[], task_mem_deleter>;
|
using co_task_mem_ptr = std::unique_ptr<wchar_t[], task_mem_deleter>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
std::wstring
|
||||||
|
convert_utf8_to_utf16(const std::string& s)
|
||||||
|
{
|
||||||
|
std::wstring out;
|
||||||
|
const int size = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, NULL, 0);
|
||||||
|
|
||||||
|
if (size == 0)
|
||||||
|
{
|
||||||
|
std::string msg = "Failed to determine required size when converting \"";
|
||||||
|
msg += s;
|
||||||
|
msg += "\" to UTF-16.";
|
||||||
|
throw std::runtime_error(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
out.resize(size);
|
||||||
|
const int check = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, &out[0], size);
|
||||||
|
|
||||||
|
if (size != check)
|
||||||
|
{
|
||||||
|
std::string msg = "Failed to convert \"";
|
||||||
|
msg += s;
|
||||||
|
msg += "\" to UTF-16.";
|
||||||
|
throw std::runtime_error(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
// We might need to know certain locations even if not using the remote API,
|
// We might need to know certain locations even if not using the remote API,
|
||||||
// so keep these routines out of that block for now.
|
// so keep these routines out of that block for now.
|
||||||
static
|
static
|
||||||
@@ -267,6 +296,89 @@ get_download_folder()
|
|||||||
|
|
||||||
# endif // !_WIN32
|
# endif // !_WIN32
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This class is provided to mimic the following usage of `ifstream`:
|
||||||
|
*
|
||||||
|
* std::ifstream is(filename);
|
||||||
|
*
|
||||||
|
* file_streambuf ibuf(filename);
|
||||||
|
* std::istream is(&ibuf);
|
||||||
|
*
|
||||||
|
* This is required because `ifstream` does not support opening files
|
||||||
|
* containing wide characters on Windows. On Windows, `file_streambuf` uses
|
||||||
|
* `file_open()` to convert the file name to UTF-16 before opening it with
|
||||||
|
* `_wfopen()`.
|
||||||
|
*
|
||||||
|
* Note that this is not an exact re-implementation of `ifstream`,
|
||||||
|
* but is enough for usage here.
|
||||||
|
*
|
||||||
|
* It is partially based on these two implementations:
|
||||||
|
* - fdinbuf from http://www.josuttis.com/cppcode/fdstream.html
|
||||||
|
* - stdiobuf https://stackoverflow.com/questions/12342542/convert-file-to-ifstream-c-android-ndk
|
||||||
|
*
|
||||||
|
* Apparently MSVC provides non-standard overloads of `ifstream` that support
|
||||||
|
* a `const wchar_t*` file name, but MinGW does not https://stackoverflow.com/a/822032
|
||||||
|
*/
|
||||||
|
class file_streambuf
|
||||||
|
: public std::streambuf
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
FILE* file_;
|
||||||
|
static const int buffer_size_ = 1024;
|
||||||
|
char buffer_[buffer_size_];
|
||||||
|
|
||||||
|
public:
|
||||||
|
~file_streambuf()
|
||||||
|
{
|
||||||
|
if (file_)
|
||||||
|
{
|
||||||
|
::fclose(file_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_streambuf(const file_streambuf&) = delete;
|
||||||
|
file_streambuf& operator=(const file_streambuf&) = delete;
|
||||||
|
|
||||||
|
file_streambuf(const std::string& filename)
|
||||||
|
: file_(file_open(filename))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual
|
||||||
|
int_type
|
||||||
|
underflow()
|
||||||
|
{
|
||||||
|
if (gptr() == egptr() && file_)
|
||||||
|
{
|
||||||
|
const size_t size = ::fread(buffer_, 1, buffer_size_, file_);
|
||||||
|
setg(buffer_, buffer_, buffer_ + size);
|
||||||
|
}
|
||||||
|
return (gptr() == egptr())
|
||||||
|
? traits_type::eof()
|
||||||
|
: traits_type::to_int_type(*gptr());
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
FILE*
|
||||||
|
file_open(const std::string& filename)
|
||||||
|
{
|
||||||
|
# ifdef _WIN32
|
||||||
|
std::wstring wfilename = convert_utf8_to_utf16(filename);
|
||||||
|
FILE* file = ::_wfopen(wfilename.c_str(), L"rb");
|
||||||
|
# else // !_WIN32
|
||||||
|
FILE* file = ::fopen(filename.c_str(), "rb");
|
||||||
|
# endif // _WIN32
|
||||||
|
if (file == NULL)
|
||||||
|
{
|
||||||
|
std::string msg = "Error opening file \"";
|
||||||
|
msg += filename;
|
||||||
|
msg += "\".";
|
||||||
|
throw std::runtime_error(msg);
|
||||||
|
}
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
#endif // !USE_OS_TZDB
|
#endif // !USE_OS_TZDB
|
||||||
|
|
||||||
namespace date
|
namespace date
|
||||||
@@ -559,15 +671,8 @@ load_timezone_mappings_from_xml_file(const std::string& input_path)
|
|||||||
std::vector<detail::timezone_mapping> mappings;
|
std::vector<detail::timezone_mapping> mappings;
|
||||||
std::string line;
|
std::string line;
|
||||||
|
|
||||||
std::ifstream is(input_path);
|
file_streambuf ibuf(input_path);
|
||||||
if (!is.is_open())
|
std::istream is(&ibuf);
|
||||||
{
|
|
||||||
// We don't emit file exceptions because that's an implementation detail.
|
|
||||||
std::string msg = "Error opening time zone mapping file \"";
|
|
||||||
msg += input_path;
|
|
||||||
msg += "\".";
|
|
||||||
throw std::runtime_error(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto error = [&input_path, &line_num](const char* info)
|
auto error = [&input_path, &line_num](const char* info)
|
||||||
{
|
{
|
||||||
@@ -697,7 +802,6 @@ load_timezone_mappings_from_xml_file(const std::string& input_path)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is.close();
|
|
||||||
return mappings;
|
return mappings;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2836,7 +2940,8 @@ bool
|
|||||||
file_exists(const std::string& filename)
|
file_exists(const std::string& filename)
|
||||||
{
|
{
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
return ::_access(filename.c_str(), 0) == 0;
|
std::wstring wfilename = convert_utf8_to_utf16(filename);
|
||||||
|
return ::_waccess(wfilename.c_str(), 0) == 0;
|
||||||
#else
|
#else
|
||||||
return ::access(filename.c_str(), F_OK) == 0;
|
return ::access(filename.c_str(), F_OK) == 0;
|
||||||
#endif
|
#endif
|
||||||
@@ -3413,16 +3518,27 @@ std::string
|
|||||||
get_version(const std::string& path)
|
get_version(const std::string& path)
|
||||||
{
|
{
|
||||||
std::string version;
|
std::string version;
|
||||||
std::ifstream infile(path + "version");
|
|
||||||
if (infile.is_open())
|
std::string path_version = path + "version";
|
||||||
|
|
||||||
|
if (file_exists(path_version))
|
||||||
{
|
{
|
||||||
|
file_streambuf inbuf(path_version);
|
||||||
|
std::istream infile(&inbuf);
|
||||||
|
|
||||||
infile >> version;
|
infile >> version;
|
||||||
|
|
||||||
if (!infile.fail())
|
if (!infile.fail())
|
||||||
return version;
|
return version;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
std::string path_news = path + "NEWS";
|
||||||
|
|
||||||
|
if (file_exists(path_news))
|
||||||
{
|
{
|
||||||
infile.open(path + "NEWS");
|
file_streambuf inbuf(path_news);
|
||||||
|
std::istream infile(&inbuf);
|
||||||
|
|
||||||
while (infile)
|
while (infile)
|
||||||
{
|
{
|
||||||
infile >> version;
|
infile >> version;
|
||||||
@@ -3433,6 +3549,7 @@ get_version(const std::string& path)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
throw std::runtime_error("Unable to get Timezone database version from " + path);
|
throw std::runtime_error("Unable to get Timezone database version from " + path);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3504,7 +3621,13 @@ init_tzdb()
|
|||||||
|
|
||||||
for (const auto& filename : files)
|
for (const auto& filename : files)
|
||||||
{
|
{
|
||||||
std::ifstream infile(path + filename);
|
std::string file_path = path + filename;
|
||||||
|
if (!file_exists(file_path))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
file_streambuf inbuf(file_path);
|
||||||
|
std::istream infile(&inbuf);
|
||||||
while (infile)
|
while (infile)
|
||||||
{
|
{
|
||||||
std::getline(infile, line);
|
std::getline(infile, line);
|
||||||
|
Reference in New Issue
Block a user