@@ -5,7 +5,21 @@ use std::{env, mem, ptr};
5
5
fn main ( ) {
6
6
test_clocks ( ) ;
7
7
test_posix_gettimeofday ( ) ;
8
- test_localtime_r ( ) ;
8
+ test_localtime_r_gmt ( ) ;
9
+ test_localtime_r_pst ( ) ;
10
+ test_localtime_r_epoch ( ) ;
11
+ #[ cfg( any(
12
+ target_os = "linux" ,
13
+ target_os = "macos" ,
14
+ target_os = "freebsd" ,
15
+ target_os = "android"
16
+ ) ) ]
17
+ test_localtime_r_multiple_calls_deduplication ( ) ;
18
+ // Architecture-specific tests.
19
+ #[ cfg( target_pointer_width = "32" ) ]
20
+ test_localtime_r_future_32b ( ) ;
21
+ #[ cfg( target_pointer_width = "64" ) ]
22
+ test_localtime_r_future_64b ( ) ;
9
23
}
10
24
11
25
/// Tests whether clock support exists at all
@@ -46,14 +60,9 @@ fn test_posix_gettimeofday() {
46
60
assert_eq ! ( is_error, -1 ) ;
47
61
}
48
62
49
- fn test_localtime_r ( ) {
50
- // Set timezone to GMT.
51
- let key = "TZ" ;
52
- env:: set_var ( key, "GMT" ) ;
53
-
54
- const TIME_SINCE_EPOCH : libc:: time_t = 1712475836 ;
55
- let custom_time_ptr = & TIME_SINCE_EPOCH ;
56
- let mut tm = libc:: tm {
63
+ /// Helper function to create an empty tm struct.
64
+ fn create_empty_tm ( ) -> libc:: tm {
65
+ libc:: tm {
57
66
tm_sec : 0 ,
58
67
tm_min : 0 ,
59
68
tm_hour : 0 ,
@@ -77,7 +86,17 @@ fn test_localtime_r() {
77
86
target_os = "android"
78
87
) ) ]
79
88
tm_zone : std:: ptr:: null_mut :: < libc:: c_char > ( ) ,
80
- } ;
89
+ }
90
+ }
91
+
92
+ /// Original GMT test
93
+ fn test_localtime_r_gmt ( ) {
94
+ // Set timezone to GMT.
95
+ let key = "TZ" ;
96
+ env:: set_var ( key, "GMT" ) ;
97
+ const TIME_SINCE_EPOCH : libc:: time_t = 1712475836 ; // 2024-04-07 07:43:56 GMT
98
+ let custom_time_ptr = & TIME_SINCE_EPOCH ;
99
+ let mut tm = create_empty_tm ( ) ;
81
100
let res = unsafe { libc:: localtime_r ( custom_time_ptr, & mut tm) } ;
82
101
83
102
assert_eq ! ( tm. tm_sec, 56 ) ;
@@ -95,20 +114,204 @@ fn test_localtime_r() {
95
114
target_os = "freebsd" ,
96
115
target_os = "android"
97
116
) ) ]
98
- assert_eq ! ( tm. tm_gmtoff, 0 ) ;
117
+ {
118
+ assert_eq ! ( tm. tm_gmtoff, 0 ) ;
119
+ unsafe {
120
+ assert_eq ! ( std:: ffi:: CStr :: from_ptr( tm. tm_zone) . to_str( ) . unwrap( ) , "+00" ) ;
121
+ }
122
+ }
123
+
124
+ // The returned value is the pointer passed in.
125
+ assert ! ( ptr:: eq( res, & mut tm) ) ;
126
+
127
+ // Remove timezone setting.
128
+ env:: remove_var ( key) ;
129
+ }
130
+
131
+ /// PST timezone test (testing different timezone handling).
132
+ fn test_localtime_r_pst ( ) {
133
+ let key = "TZ" ;
134
+ env:: set_var ( key, "PST8PDT" ) ;
135
+ const TIME_SINCE_EPOCH : libc:: time_t = 1712475836 ; // 2024-04-07 07:43:56 GMT
136
+ let custom_time_ptr = & TIME_SINCE_EPOCH ;
137
+ let mut tm = create_empty_tm ( ) ;
138
+
139
+ let res = unsafe { libc:: localtime_r ( custom_time_ptr, & mut tm) } ;
140
+
141
+ assert_eq ! ( tm. tm_sec, 56 ) ;
142
+ assert_eq ! ( tm. tm_min, 43 ) ;
143
+ assert_eq ! ( tm. tm_hour, 0 ) ; // 7 - 7 = 0 (PDT offset)
144
+ assert_eq ! ( tm. tm_mday, 7 ) ;
145
+ assert_eq ! ( tm. tm_mon, 3 ) ;
146
+ assert_eq ! ( tm. tm_year, 124 ) ;
147
+ assert_eq ! ( tm. tm_wday, 0 ) ;
148
+ assert_eq ! ( tm. tm_yday, 97 ) ;
149
+ assert_eq ! ( tm. tm_isdst, -1 ) ; // DST information unavailable
150
+
99
151
#[ cfg( any(
100
152
target_os = "linux" ,
101
153
target_os = "macos" ,
102
154
target_os = "freebsd" ,
103
155
target_os = "android"
104
156
) ) ]
105
- unsafe {
106
- assert_eq ! ( std:: ffi:: CStr :: from_ptr( tm. tm_zone) . to_str( ) . unwrap( ) , "+00" )
107
- } ;
157
+ {
158
+ assert_eq ! ( tm. tm_gmtoff, -7 * 3600 ) ; // -7 hours in seconds
159
+ unsafe {
160
+ assert_eq ! ( std:: ffi:: CStr :: from_ptr( tm. tm_zone) . to_str( ) . unwrap( ) , "-07" ) ;
161
+ }
162
+ }
108
163
109
- // The returned value is the pointer passed in.
110
164
assert ! ( ptr:: eq( res, & mut tm) ) ;
165
+ env:: remove_var ( key) ;
166
+ }
111
167
112
- // Remove timezone setting.
168
+ /// Unix epoch test (edge case testing).
169
+ fn test_localtime_r_epoch ( ) {
170
+ let key = "TZ" ;
171
+ env:: set_var ( key, "GMT" ) ;
172
+ const TIME_SINCE_EPOCH : libc:: time_t = 0 ; // 1970-01-01 00:00:00
173
+ let custom_time_ptr = & TIME_SINCE_EPOCH ;
174
+ let mut tm = create_empty_tm ( ) ;
175
+
176
+ let res = unsafe { libc:: localtime_r ( custom_time_ptr, & mut tm) } ;
177
+
178
+ assert_eq ! ( tm. tm_sec, 0 ) ;
179
+ assert_eq ! ( tm. tm_min, 0 ) ;
180
+ assert_eq ! ( tm. tm_hour, 0 ) ;
181
+ assert_eq ! ( tm. tm_mday, 1 ) ;
182
+ assert_eq ! ( tm. tm_mon, 0 ) ;
183
+ assert_eq ! ( tm. tm_year, 70 ) ;
184
+ assert_eq ! ( tm. tm_wday, 4 ) ; // Thursday
185
+ assert_eq ! ( tm. tm_yday, 0 ) ;
186
+ assert_eq ! ( tm. tm_isdst, -1 ) ;
187
+
188
+ #[ cfg( any(
189
+ target_os = "linux" ,
190
+ target_os = "macos" ,
191
+ target_os = "freebsd" ,
192
+ target_os = "android"
193
+ ) ) ]
194
+ {
195
+ assert_eq ! ( tm. tm_gmtoff, 0 ) ;
196
+ unsafe {
197
+ assert_eq ! ( std:: ffi:: CStr :: from_ptr( tm. tm_zone) . to_str( ) . unwrap( ) , "+00" ) ;
198
+ }
199
+ }
200
+
201
+ assert ! ( ptr:: eq( res, & mut tm) ) ;
202
+ env:: remove_var ( key) ;
203
+ }
204
+
205
+ /// Future date test (testing large values).
206
+ #[ cfg( target_pointer_width = "64" ) ]
207
+ fn test_localtime_r_future_64b ( ) {
208
+ let key = "TZ" ;
209
+ env:: set_var ( key, "GMT" ) ;
210
+
211
+ // Using 2050-01-01 00:00:00 for 64-bit systems
212
+ // value that's safe for 64-bit time_t
213
+ const TIME_SINCE_EPOCH : libc:: time_t = 2524608000 ;
214
+ let custom_time_ptr = & TIME_SINCE_EPOCH ;
215
+ let mut tm = create_empty_tm ( ) ;
216
+
217
+ let res = unsafe { libc:: localtime_r ( custom_time_ptr, & mut tm) } ;
218
+
219
+ assert_eq ! ( tm. tm_sec, 0 ) ;
220
+ assert_eq ! ( tm. tm_min, 0 ) ;
221
+ assert_eq ! ( tm. tm_hour, 0 ) ;
222
+ assert_eq ! ( tm. tm_mday, 1 ) ;
223
+ assert_eq ! ( tm. tm_mon, 0 ) ;
224
+ assert_eq ! ( tm. tm_year, 150 ) ; // 2050 - 1900
225
+ assert_eq ! ( tm. tm_wday, 6 ) ; // Saturday
226
+ assert_eq ! ( tm. tm_yday, 0 ) ;
227
+ assert_eq ! ( tm. tm_isdst, -1 ) ;
228
+
229
+ #[ cfg( any(
230
+ target_os = "linux" ,
231
+ target_os = "macos" ,
232
+ target_os = "freebsd" ,
233
+ target_os = "android"
234
+ ) ) ]
235
+ {
236
+ assert_eq ! ( tm. tm_gmtoff, 0 ) ;
237
+ unsafe {
238
+ assert_eq ! ( std:: ffi:: CStr :: from_ptr( tm. tm_zone) . to_str( ) . unwrap( ) , "+00" ) ;
239
+ }
240
+ }
241
+
242
+ assert ! ( ptr:: eq( res, & mut tm) ) ;
243
+ env:: remove_var ( key) ;
244
+ }
245
+
246
+ /// Future date test (testing large values for 32b target).
247
+ #[ cfg( target_pointer_width = "32" ) ]
248
+ fn test_localtime_r_future_32b ( ) {
249
+ let key = "TZ" ;
250
+ env:: set_var ( key, "GMT" ) ;
251
+
252
+ // Using 2030-01-01 00:00:00 for 32-bit systems
253
+ // Safe value within i32 range
254
+ const TIME_SINCE_EPOCH : libc:: time_t = 1893456000 ;
255
+ let custom_time_ptr = & TIME_SINCE_EPOCH ;
256
+ let mut tm = create_empty_tm ( ) ;
257
+
258
+ let res = unsafe { libc:: localtime_r ( custom_time_ptr, & mut tm) } ;
259
+
260
+ // Verify 2030-01-01 00:00:00
261
+ assert_eq ! ( tm. tm_sec, 0 ) ;
262
+ assert_eq ! ( tm. tm_min, 0 ) ;
263
+ assert_eq ! ( tm. tm_hour, 0 ) ;
264
+ assert_eq ! ( tm. tm_mday, 1 ) ;
265
+ assert_eq ! ( tm. tm_mon, 0 ) ;
266
+ assert_eq ! ( tm. tm_year, 130 ) ; // 2030 - 1900
267
+ assert_eq ! ( tm. tm_wday, 2 ) ; // Tuesday
268
+ assert_eq ! ( tm. tm_yday, 0 ) ;
269
+ assert_eq ! ( tm. tm_isdst, -1 ) ;
270
+
271
+ #[ cfg( any(
272
+ target_os = "linux" ,
273
+ target_os = "macos" ,
274
+ target_os = "freebsd" ,
275
+ target_os = "android"
276
+ ) ) ]
277
+ {
278
+ assert_eq ! ( tm. tm_gmtoff, 0 ) ;
279
+ unsafe {
280
+ assert_eq ! ( std:: ffi:: CStr :: from_ptr( tm. tm_zone) . to_str( ) . unwrap( ) , "+00" ) ;
281
+ }
282
+ }
283
+
284
+ assert ! ( ptr:: eq( res, & mut tm) ) ;
113
285
env:: remove_var ( key) ;
114
286
}
287
+
288
+ /// Tests the behavior of `localtime_r` with multiple calls to ensure deduplication of `tm_zone` pointers.
289
+ #[ cfg( any( target_os = "linux" , target_os = "macos" , target_os = "freebsd" , target_os = "android" ) ) ]
290
+ fn test_localtime_r_multiple_calls_deduplication ( ) {
291
+ let key = "TZ" ;
292
+ env:: set_var ( key, "PST8PDT" ) ;
293
+
294
+ const TIME_SINCE_EPOCH_BASE : libc:: time_t = 1712475836 ; // Base timestamp: 2024-04-07 07:43:56 GMT
295
+ const NUM_CALLS : usize = 50 ;
296
+
297
+ let mut unique_pointers = std:: collections:: HashSet :: new ( ) ;
298
+
299
+ for i in 0 ..NUM_CALLS {
300
+ let timestamp = TIME_SINCE_EPOCH_BASE + ( i as libc:: time_t * 3600 ) ; // Increment by 1 hour for each call
301
+ let mut tm: libc:: tm = create_empty_tm ( ) ;
302
+ let tm_ptr = unsafe { libc:: localtime_r ( & timestamp, & mut tm) } ;
303
+
304
+ assert ! ( !tm_ptr. is_null( ) , "localtime_r failed for timestamp {timestamp}" ) ;
305
+
306
+ unique_pointers. insert ( tm. tm_zone ) ;
307
+ }
308
+
309
+ let unique_count = unique_pointers. len ( ) ;
310
+
311
+ assert ! (
312
+ unique_count >= 2 && unique_count <= ( NUM_CALLS - 1 ) ,
313
+ "Unexpected number of unique tm_zone pointers: {} (expected between 2 and {})" ,
314
+ unique_count,
315
+ NUM_CALLS - 1
316
+ ) ;
317
+ }
0 commit comments