9
9
// except according to those terms.
10
10
11
11
use std:: cell:: RefCell ;
12
- use std:: collections:: hash_map;
13
12
use std:: env;
14
13
use std:: fs;
15
- use std:: hash:: Hasher ;
16
14
use std:: path:: { Path , PathBuf } ;
17
15
use std:: time:: SystemTime ;
18
16
@@ -51,6 +49,10 @@ struct CachedTzInfo {
51
49
zone : Option < TimeZone > ,
52
50
source : Source ,
53
51
last_checked : SystemTime ,
52
+ tz_var : Option < TzEnvVar > ,
53
+ tz_name : Option < String > ,
54
+ path : Option < PathBuf > ,
55
+ tzdb_dir : Option < PathBuf > ,
54
56
}
55
57
56
58
impl CachedTzInfo {
@@ -79,26 +81,25 @@ impl CachedTzInfo {
79
81
self . last_checked = now;
80
82
}
81
83
82
- /// Check if any of the `TZ` environment variable or `/etc/localtime` have changed.
84
+ /// Check if any of the environment variables or files have changed, or the name of the current
85
+ /// time zone as determined by the `iana_time_zone` crate.
83
86
fn needs_update ( & self ) -> bool {
84
- let env_tz = env:: var ( "TZ" ) . ok ( ) ;
85
- let env_ref = env_tz. as_deref ( ) ;
86
- let new_source = Source :: new ( env_ref) ;
87
-
88
- match ( & self . source , & new_source) {
89
- ( Source :: Environment { hash : old_hash } , Source :: Environment { hash } )
90
- if old_hash == hash =>
91
- {
92
- false
93
- }
94
- ( Source :: LocalTime , Source :: LocalTime ) => {
95
- match fs:: symlink_metadata ( "/etc/localtime" ) . and_then ( |m| m. modified ( ) ) {
96
- Ok ( mtime) => mtime > self . last_checked ,
97
- Err ( _) => false ,
98
- }
99
- }
100
- _ => true ,
87
+ if self . tz_env_var_changed ( ) {
88
+ return true ;
89
+ }
90
+ if self . source == Source :: TzEnvVar {
91
+ return false ; // No need for further checks if the cached value came from the `TZ` var.
101
92
}
93
+ if self . symlink_changed ( ) {
94
+ return true ;
95
+ }
96
+ if self . source == Source :: Localtime {
97
+ return false ; // No need for further checks if the cached value came from the symlink.
98
+ }
99
+ if self . tz_name_changed ( ) {
100
+ return true ;
101
+ }
102
+ false
102
103
}
103
104
104
105
/// Try to get the current time zone data.
@@ -112,12 +113,14 @@ impl CachedTzInfo {
112
113
/// - the global IANA time zone name in combination with the platform time zone database
113
114
/// - fall back to UTC if all else fails
114
115
fn read_tz_info ( & mut self ) {
115
- self . source = Source :: new ( env:: var ( "TZ" ) . ok ( ) . as_deref ( ) ) ;
116
-
117
116
let tz_var = TzEnvVar :: get ( ) ;
118
- if let Some ( tz_var) = tz_var {
119
- if self . read_from_tz_env ( & tz_var) . is_ok ( ) {
120
- return ;
117
+ match tz_var {
118
+ None => self . tz_var = None ,
119
+ Some ( tz_var) => {
120
+ if self . read_from_tz_env ( & tz_var) . is_ok ( ) {
121
+ self . tz_var = Some ( tz_var) ;
122
+ return ;
123
+ }
121
124
}
122
125
}
123
126
#[ cfg( not( target_os = "android" ) ) ]
@@ -128,63 +131,104 @@ impl CachedTzInfo {
128
131
return ;
129
132
}
130
133
self . zone = Some ( TimeZone :: utc ( ) ) ;
134
+ self . source = Source :: Utc ;
131
135
}
132
136
133
137
/// Read the `TZ` environment variable or the TZif file that it points to.
134
138
fn read_from_tz_env ( & mut self , tz_var : & TzEnvVar ) -> Result < ( ) , ( ) > {
135
139
match tz_var {
136
140
TzEnvVar :: TzString ( tz_string) => {
137
141
self . zone = Some ( TimeZone :: from_tz_string ( tz_string) . map_err ( |_| ( ) ) ?) ;
142
+ self . path = None ;
138
143
}
139
144
TzEnvVar :: Path ( path) => {
140
145
let path = PathBuf :: from ( & path[ 1 ..] ) ;
141
- let tzif = fs:: read ( path) . map_err ( |_| ( ) ) ?;
146
+ let tzif = fs:: read ( & path) . map_err ( |_| ( ) ) ?;
142
147
self . zone = Some ( TimeZone :: from_tz_data ( & tzif) . map_err ( |_| ( ) ) ?) ;
148
+ self . path = Some ( path) ;
143
149
}
144
150
TzEnvVar :: TzName ( tz_id) => self . read_tzif ( & tz_id[ 1 ..] ) ?,
145
151
#[ cfg( not( target_os = "android" ) ) ]
146
152
TzEnvVar :: LocaltimeSymlink => self . read_from_symlink ( ) ?,
147
153
} ;
154
+ self . source = Source :: TzEnvVar ;
148
155
Ok ( ( ) )
149
156
}
150
157
158
+ /// Check if the `TZ` environment variable has changed, or the file it points to.
159
+ fn tz_env_var_changed ( & self ) -> bool {
160
+ let tz_var = TzEnvVar :: get ( ) ;
161
+ match ( & self . tz_var , & tz_var) {
162
+ ( None , None ) => false ,
163
+ ( Some ( TzEnvVar :: TzString ( a) ) , Some ( TzEnvVar :: TzString ( b) ) ) if a == b => false ,
164
+ ( Some ( TzEnvVar :: Path ( a) ) , Some ( TzEnvVar :: Path ( b) ) ) if a == b => {
165
+ self . mtime_changed ( self . path . as_deref ( ) )
166
+ }
167
+ ( Some ( TzEnvVar :: TzName ( a) ) , Some ( TzEnvVar :: TzName ( b) ) ) if a == b => {
168
+ self . mtime_changed ( self . path . as_deref ( ) ) || self . tzdb_dir_changed ( )
169
+ }
170
+ #[ cfg( not( target_os = "android" ) ) ]
171
+ ( Some ( TzEnvVar :: LocaltimeSymlink ) , Some ( TzEnvVar :: LocaltimeSymlink ) ) => {
172
+ self . symlink_changed ( )
173
+ }
174
+ _ => true ,
175
+ }
176
+ }
177
+
151
178
/// Read the Tzif file that `/etc/localtime` is symlinked to.
152
179
#[ cfg( not( target_os = "android" ) ) ]
153
180
fn read_from_symlink ( & mut self ) -> Result < ( ) , ( ) > {
154
181
let tzif = fs:: read ( "/etc/localtime" ) . map_err ( |_| ( ) ) ?;
155
182
self . zone = Some ( TimeZone :: from_tz_data ( & tzif) . map_err ( |_| ( ) ) ?) ;
183
+ self . source = Source :: Localtime ;
156
184
Ok ( ( ) )
157
185
}
158
186
187
+ /// Check if the `/etc/localtime` symlink or its target has changed.
188
+ fn symlink_changed ( & self ) -> bool {
189
+ self . mtime_changed ( Some ( Path :: new ( "/etc/localtime" ) ) )
190
+ }
191
+
159
192
/// Get the IANA time zone name of the system by whichever means the `iana_time_zone` crate gets
160
193
/// it, and try to read the corresponding TZif data.
161
194
fn read_with_tz_name ( & mut self ) -> Result < ( ) , ( ) > {
162
195
let tz_name = iana_time_zone:: get_timezone ( ) . map_err ( |_| ( ) ) ?;
163
- self . read_tzif ( & tz_name)
196
+ self . read_tzif ( & tz_name) ?;
197
+ self . tz_name = Some ( tz_name) ;
198
+ self . source = Source :: TimeZoneName ;
199
+ Ok ( ( ) )
200
+ }
201
+
202
+ /// Check if the IANA time zone name has changed, or the file it points to.
203
+ fn tz_name_changed ( & self ) -> bool {
204
+ self . tz_name != iana_time_zone:: get_timezone ( ) . ok ( )
205
+ || self . tzdb_dir_changed ( )
206
+ || self . mtime_changed ( self . path . as_deref ( ) )
164
207
}
165
208
166
209
/// Try to read the TZif data for the specified time zone name.
167
210
fn read_tzif ( & mut self , tz_name : & str ) -> Result < ( ) , ( ) > {
168
- let tzif = self . read_tzif_inner ( tz_name) ?;
211
+ let ( tzif, path ) = self . read_tzif_inner ( tz_name) ?;
169
212
self . zone = Some ( TimeZone :: from_tz_data ( & tzif) . map_err ( |_| ( ) ) ?) ;
213
+ self . path = path;
170
214
Ok ( ( ) )
171
215
}
172
216
173
217
#[ cfg( not( target_os = "android" ) ) ]
174
- fn read_tzif_inner ( & self , tz_name : & str ) -> Result < Vec < u8 > , ( ) > {
218
+ fn read_tzif_inner ( & mut self , tz_name : & str ) -> Result < ( Vec < u8 > , Option < PathBuf > ) , ( ) > {
175
219
let path = self . tzdb_dir ( ) ?. join ( tz_name) ;
176
- let tzif = fs:: read ( path) . map_err ( |_| ( ) ) ?;
177
- Ok ( tzif)
220
+ let tzif = fs:: read ( & path) . map_err ( |_| ( ) ) ?;
221
+ Ok ( ( tzif, Some ( path ) ) )
178
222
}
179
223
#[ cfg( target_os = "android" ) ]
180
- fn read_tzif_inner ( & self , tz_name : & str ) -> Result < Vec < u8 > , ( ) > {
224
+ fn read_tzif_inner ( & mut self , tz_name : & str ) -> Result < ( Vec < u8 > , Option < PathBuf > ) , ( ) > {
181
225
let tzif = android_tzdata:: find_tz_data ( & tz_name) . map_err ( |_| ( ) ) ?;
182
- Ok ( tzif)
226
+ Ok ( ( tzif, None ) )
183
227
}
184
228
185
229
/// Get the location of the time zone database directory with TZif files.
186
230
#[ cfg( not( target_os = "android" ) ) ]
187
- fn tzdb_dir ( & self ) -> Result < PathBuf , ( ) > {
231
+ fn tzdb_dir ( & mut self ) -> Result < PathBuf , ( ) > {
188
232
// Possible system timezone directories
189
233
const ZONE_INFO_DIRECTORIES : [ & str ; 4 ] =
190
234
[ "/usr/share/zoneinfo" , "/share/zoneinfo" , "/etc/zoneinfo" , "/usr/share/lib/zoneinfo" ] ;
@@ -199,14 +243,56 @@ impl CachedTzInfo {
199
243
}
200
244
}
201
245
246
+ // Use the cached value
247
+ if let Some ( dir) = self . tzdb_dir . as_ref ( ) {
248
+ return Ok ( PathBuf :: from ( dir) ) ;
249
+ }
250
+
251
+ // No cached value yet, try the various possible system timezone directories.
202
252
for dir in & ZONE_INFO_DIRECTORIES {
203
253
let path = PathBuf :: from ( dir) ;
204
254
if path. exists ( ) {
255
+ self . tzdb_dir = Some ( path. clone ( ) ) ;
205
256
return Ok ( path) ;
206
257
}
207
258
}
208
259
Err ( ( ) )
209
260
}
261
+
262
+ /// Check if the location that the `TZDIR` environment variable points to has changed.
263
+ fn tzdb_dir_changed ( & self ) -> bool {
264
+ #[ cfg( not( target_os = "android" ) ) ]
265
+ if let Some ( tz_dir) = env:: var_os ( "TZDIR" ) {
266
+ if !tz_dir. is_empty ( )
267
+ && Some ( tz_dir. as_os_str ( ) ) != self . tzdb_dir . as_ref ( ) . map ( |d| d. as_os_str ( ) )
268
+ {
269
+ return true ;
270
+ }
271
+ }
272
+ false
273
+ }
274
+
275
+ /// Returns `true` if the modification time of the TZif file or symlink is more recent then
276
+ /// `self.last_checked`.
277
+ ///
278
+ /// Also returns `true` if there was an error getting the modification time.
279
+ /// If the file is a symlink this method checks the symlink and the final target.
280
+ fn mtime_changed ( & self , path : Option < & Path > ) -> bool {
281
+ fn inner ( path : & Path , last_checked : SystemTime ) -> Result < bool , std:: io:: Error > {
282
+ let metadata = fs:: symlink_metadata ( path) ?;
283
+ if metadata. modified ( ) ? > last_checked {
284
+ return Ok ( true ) ;
285
+ }
286
+ if metadata. is_symlink ( ) && fs:: metadata ( path) ?. modified ( ) ? > last_checked {
287
+ return Ok ( true ) ;
288
+ }
289
+ Ok ( false )
290
+ }
291
+ match path {
292
+ Some ( path) => inner ( path, self . last_checked ) . unwrap_or ( true ) ,
293
+ None => false ,
294
+ }
295
+ }
210
296
}
211
297
212
298
thread_local ! {
@@ -215,31 +301,23 @@ thread_local! {
215
301
zone: None ,
216
302
source: Source :: Uninitialized ,
217
303
last_checked: SystemTime :: UNIX_EPOCH ,
304
+ tz_var: None ,
305
+ tz_name: None ,
306
+ path: None ,
307
+ tzdb_dir: None ,
218
308
}
219
309
) } ;
220
310
}
221
311
222
312
#[ derive( PartialEq ) ]
223
313
enum Source {
224
- Environment { hash : u64 } ,
225
- LocalTime ,
314
+ TzEnvVar ,
315
+ Localtime ,
316
+ TimeZoneName ,
317
+ Utc ,
226
318
Uninitialized ,
227
319
}
228
320
229
- impl Source {
230
- fn new ( env_tz : Option < & str > ) -> Source {
231
- match env_tz {
232
- Some ( tz) => {
233
- let mut hasher = hash_map:: DefaultHasher :: new ( ) ;
234
- hasher. write ( tz. as_bytes ( ) ) ;
235
- let hash = hasher. finish ( ) ;
236
- Source :: Environment { hash }
237
- }
238
- None => Source :: LocalTime ,
239
- }
240
- }
241
- }
242
-
243
321
/// Type of the `TZ` environment variable.
244
322
///
245
323
/// Supported formats are:
0 commit comments