]>
Commit | Line | Data |
---|---|---|
7c673cae FG |
1 | // |
2 | // Copyright (c) 2009-2011 Artyom Beilis (Tonkikh) | |
3 | // | |
4 | // Distributed under the Boost Software License, Version 1.0. (See | |
5 | // accompanying file LICENSE_1_0.txt or copy at | |
6 | // http://www.boost.org/LICENSE_1_0.txt) | |
7 | // | |
8 | #define BOOST_LOCALE_SOURCE | |
9 | #include "time_zone.hpp" | |
10 | ||
11 | // | |
12 | // Bug - when ICU tries to find a file that is equivalent to /etc/localtime it finds /usr/share/zoneinfo/localtime | |
13 | // that is just a symbolic link to /etc/localtime. | |
14 | // | |
15 | // It started in 4.0 and was fixed in version 4.6, also the fix was backported to the 4.4 branch so it should be | |
16 | // available from 4.4.3... So we test if the workaround is required | |
17 | // | |
18 | // It is also relevant only for Linux, BSD and Apple (as I see in ICU code) | |
19 | // | |
20 | ||
21 | #if U_ICU_VERSION_MAJOR_NUM == 4 && (U_ICU_VERSION_MINOR_NUM * 100 + U_ICU_VERSION_PATCHLEVEL_NUM) <= 402 | |
22 | # if defined(__linux) || defined(__FreeBSD__) || defined(__APPLE__) | |
23 | # define BOOST_LOCALE_WORKAROUND_ICU_BUG | |
24 | # endif | |
25 | #endif | |
26 | ||
27 | #ifdef BOOST_LOCALE_WORKAROUND_ICU_BUG | |
28 | #include <dirent.h> | |
29 | #include <sys/types.h> | |
30 | #include <sys/stat.h> | |
31 | #include <unistd.h> | |
32 | #include <fstream> | |
33 | #include <pthread.h> | |
34 | #include <string.h> | |
35 | #include <memory> | |
36 | #endif | |
37 | ||
11fdf7f2 TL |
38 | #include <boost/locale/hold_ptr.hpp> |
39 | ||
7c673cae FG |
40 | namespace boost { |
41 | namespace locale { | |
42 | namespace impl_icu { | |
43 | ||
44 | #ifndef BOOST_LOCALE_WORKAROUND_ICU_BUG | |
45 | ||
46 | // This is normal behavior | |
47 | ||
48 | icu::TimeZone *get_time_zone(std::string const &time_zone) | |
49 | { | |
50 | ||
51 | if(time_zone.empty()) { | |
52 | return icu::TimeZone::createDefault(); | |
53 | } | |
54 | else { | |
55 | return icu::TimeZone::createTimeZone(time_zone.c_str()); | |
56 | } | |
57 | } | |
58 | ||
59 | #else | |
60 | ||
61 | // | |
62 | // This is a workaround for an ICU timezone detection bug. | |
63 | // It is \b very ICU specific and should not be used | |
64 | // in general. It is also designed to work only on | |
65 | // specific patforms: Linux, BSD and Apple, where this bug may actually | |
66 | // occur | |
67 | // | |
68 | namespace { | |
69 | ||
70 | // Under BSD, Linux and Mac OS X dirent has normal size | |
71 | // so no issues with readdir_r | |
72 | ||
73 | class directory { | |
74 | public: | |
75 | directory(char const *name) : d(0),read_result(0) | |
76 | { | |
77 | d=opendir(name); | |
78 | if(!d) | |
79 | return; | |
80 | } | |
81 | ~directory() | |
82 | { | |
83 | if(d) | |
84 | closedir(d); | |
85 | } | |
86 | bool is_open() | |
87 | { | |
88 | return d; | |
89 | } | |
90 | char const *next() | |
91 | { | |
92 | if(d && readdir_r(d,&de,&read_result)==0 && read_result!=0) | |
93 | return de.d_name; | |
94 | return 0; | |
95 | } | |
96 | private: | |
97 | DIR *d; | |
98 | struct dirent de; | |
99 | struct dirent *read_result; | |
100 | }; | |
101 | ||
102 | bool files_equal(std::string const &left,std::string const &right) | |
103 | { | |
104 | char l[256],r[256]; | |
105 | std::ifstream ls(left.c_str()); | |
106 | if(!ls) | |
107 | return false; | |
108 | std::ifstream rs(right.c_str()); | |
109 | if(!rs) | |
110 | return false; | |
111 | do { | |
112 | ls.read(l,sizeof(l)); | |
113 | rs.read(r,sizeof(r)); | |
114 | size_t n; | |
115 | if((n=ls.gcount())!=size_t(rs.gcount())) | |
116 | return false; | |
117 | if(memcmp(l,r,n)!=0) | |
118 | return false; | |
119 | }while(!ls.eof() || !rs.eof()); | |
120 | if(bool(ls.eof())!=bool(rs.eof())) | |
121 | return false; | |
122 | return true; | |
123 | } | |
124 | ||
125 | std::string find_file_in(std::string const &ref,size_t size,std::string const &dir) | |
126 | { | |
127 | directory d(dir.c_str()); | |
128 | if(!d.is_open()) | |
129 | return std::string(); | |
130 | ||
131 | char const *name=0; | |
132 | while((name=d.next())!=0) { | |
133 | std::string file_name = name; | |
134 | if( file_name == "." | |
135 | || file_name ==".." | |
136 | || file_name=="posixrules" | |
137 | || file_name=="localtime") | |
138 | { | |
139 | continue; | |
140 | } | |
141 | struct stat st; | |
142 | std::string path = dir+"/"+file_name; | |
143 | if(stat(path.c_str(),&st)==0) { | |
144 | if(S_ISDIR(st.st_mode)) { | |
145 | std::string res = find_file_in(ref,size,path); | |
146 | if(!res.empty()) | |
147 | return file_name + "/" + res; | |
148 | } | |
149 | else { | |
150 | if(size_t(st.st_size) == size && files_equal(path,ref)) { | |
151 | return file_name; | |
152 | } | |
153 | } | |
154 | } | |
155 | } | |
156 | return std::string(); | |
157 | } | |
158 | ||
159 | // This actually emulates ICU's search | |
160 | // algorithm... just it ignores localtime | |
161 | std::string detect_correct_time_zone() | |
162 | { | |
163 | ||
164 | char const *tz_dir = "/usr/share/zoneinfo"; | |
165 | char const *tz_file = "/etc/localtime"; | |
166 | ||
167 | struct stat st; | |
168 | if(::stat(tz_file,&st)!=0) | |
169 | return std::string(); | |
170 | size_t size = st.st_size; | |
171 | std::string r = find_file_in(tz_file,size,tz_dir); | |
172 | if(r.empty()) | |
173 | return r; | |
174 | if(r.compare(0,6,"posix/")==0 || r.compare(0,6,"right/",6)==0) | |
175 | return r.substr(6); | |
176 | return r; | |
177 | } | |
178 | ||
179 | ||
180 | // | |
181 | // Using pthread as: | |
182 | // - This bug is relevant for only Linux, BSD, Mac OS X and | |
183 | // pthreads are native threading API | |
184 | // - The dependency on boost.thread may be removed when using | |
185 | // more recent ICU versions (so TLS would not be needed) | |
186 | // | |
187 | // This the dependency on Boost.Thread is eliminated | |
188 | // | |
189 | ||
190 | pthread_once_t init_tz = PTHREAD_ONCE_INIT; | |
191 | std::string default_time_zone_name; | |
192 | ||
193 | extern "C" { | |
194 | static void init_tz_proc() | |
195 | { | |
196 | try { | |
197 | default_time_zone_name = detect_correct_time_zone(); | |
198 | } | |
199 | catch(...){} | |
200 | } | |
201 | } | |
202 | ||
203 | std::string get_time_zone_name() | |
204 | { | |
205 | pthread_once(&init_tz,init_tz_proc); | |
206 | return default_time_zone_name; | |
207 | } | |
208 | ||
209 | ||
210 | } // namespace | |
211 | ||
212 | icu::TimeZone *get_time_zone(std::string const &time_zone) | |
213 | { | |
214 | ||
215 | if(!time_zone.empty()) { | |
216 | return icu::TimeZone::createTimeZone(time_zone.c_str()); | |
217 | } | |
11fdf7f2 | 218 | hold_ptr<icu::TimeZone> tz(icu::TimeZone::createDefault()); |
7c673cae FG |
219 | icu::UnicodeString id; |
220 | tz->getID(id); | |
221 | // Check if there is a bug? | |
222 | if(id != icu::UnicodeString("localtime")) | |
223 | return tz.release(); | |
224 | // Now let's deal with the bug and run the fixed | |
225 | // search loop as that of ICU | |
226 | std::string real_id = get_time_zone_name(); | |
227 | if(real_id.empty()) { | |
228 | // if we failed fallback to ICU's time zone | |
229 | return tz.release(); | |
230 | } | |
231 | return icu::TimeZone::createTimeZone(real_id.c_str()); | |
232 | } | |
233 | #endif // bug workaround | |
234 | ||
235 | } | |
236 | } | |
237 | } | |
238 | ||
239 | // vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 |