@@ -5,7 +5,21 @@ use std::{env, mem, ptr};
55fn main ( ) {
66 test_clocks ( ) ;
77 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 ( ) ;
923}
1024
1125/// Tests whether clock support exists at all
@@ -46,14 +60,9 @@ fn test_posix_gettimeofday() {
4660 assert_eq ! ( is_error, -1 ) ;
4761}
4862
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 {
5766 tm_sec : 0 ,
5867 tm_min : 0 ,
5968 tm_hour : 0 ,
@@ -77,7 +86,17 @@ fn test_localtime_r() {
7786 target_os = "android"
7887 ) ) ]
7988 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 ( ) ;
81100 let res = unsafe { libc:: localtime_r ( custom_time_ptr, & mut tm) } ;
82101
83102 assert_eq ! ( tm. tm_sec, 56 ) ;
@@ -95,20 +114,202 @@ fn test_localtime_r() {
95114 target_os = "freebsd" ,
96115 target_os = "android"
97116 ) ) ]
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+
99151 #[ cfg( any(
100152 target_os = "linux" ,
101153 target_os = "macos" ,
102154 target_os = "freebsd" ,
103155 target_os = "android"
104156 ) ) ]
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+ }
108163
109- // The returned value is the pointer passed in.
110164 assert ! ( ptr:: eq( res, & mut tm) ) ;
165+ env:: remove_var ( key) ;
166+ }
111167
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+ #[ cfg( target_pointer_width = "32" ) ]
247+ fn test_localtime_r_future_32b ( ) {
248+ let key = "TZ" ;
249+ env:: set_var ( key, "GMT" ) ;
250+
251+ // Using 2030-01-01 00:00:00 for 32-bit systems
252+ // Safe value within i32 range
253+ const TIME_SINCE_EPOCH : libc:: time_t = 1893456000 ;
254+ let custom_time_ptr = & TIME_SINCE_EPOCH ;
255+ let mut tm = create_empty_tm ( ) ;
256+
257+ let res = unsafe { libc:: localtime_r ( custom_time_ptr, & mut tm) } ;
258+
259+ // Verify 2030-01-01 00:00:00
260+ assert_eq ! ( tm. tm_sec, 0 ) ;
261+ assert_eq ! ( tm. tm_min, 0 ) ;
262+ assert_eq ! ( tm. tm_hour, 0 ) ;
263+ assert_eq ! ( tm. tm_mday, 1 ) ;
264+ assert_eq ! ( tm. tm_mon, 0 ) ;
265+ assert_eq ! ( tm. tm_year, 130 ) ; // 2030 - 1900
266+ assert_eq ! ( tm. tm_wday, 2 ) ; // Tuesday
267+ assert_eq ! ( tm. tm_yday, 0 ) ;
268+ assert_eq ! ( tm. tm_isdst, -1 ) ;
269+
270+ #[ cfg( any(
271+ target_os = "linux" ,
272+ target_os = "macos" ,
273+ target_os = "freebsd" ,
274+ target_os = "android"
275+ ) ) ]
276+ {
277+ assert_eq ! ( tm. tm_gmtoff, 0 ) ;
278+ unsafe {
279+ assert_eq ! ( std:: ffi:: CStr :: from_ptr( tm. tm_zone) . to_str( ) . unwrap( ) , "+00" ) ;
280+ }
281+ }
282+
283+ assert ! ( ptr:: eq( res, & mut tm) ) ;
113284 env:: remove_var ( key) ;
114285}
286+
287+ #[ cfg( any( target_os = "linux" , target_os = "macos" , target_os = "freebsd" , target_os = "android" ) ) ]
288+ fn test_localtime_r_multiple_calls_deduplication ( ) {
289+ let key = "TZ" ;
290+ env:: set_var ( key, "PST8PDT" ) ;
291+
292+ const TIME_SINCE_EPOCH_BASE : libc:: time_t = 1712475836 ; // Base timestamp: 2024-04-07 07:43:56 GMT
293+ const NUM_CALLS : usize = 50 ;
294+
295+ let mut unique_pointers = std:: collections:: HashSet :: new ( ) ;
296+
297+ for i in 0 ..NUM_CALLS {
298+ let timestamp = TIME_SINCE_EPOCH_BASE + ( i as libc:: time_t * 3600 ) ; // Increment by 1 hour for each call
299+ let mut tm: libc:: tm = create_empty_tm ( ) ;
300+ let tm_ptr = unsafe { libc:: localtime_r ( & timestamp, & mut tm) } ;
301+
302+ assert ! ( !tm_ptr. is_null( ) , "localtime_r failed for timestamp {timestamp}" ) ;
303+
304+ unique_pointers. insert ( tm. tm_zone ) ;
305+ }
306+
307+ let unique_count = unique_pointers. len ( ) ;
308+
309+ assert ! (
310+ unique_count >= 2 && unique_count <= ( NUM_CALLS - 1 ) ,
311+ "Unexpected number of unique tm_zone pointers: {} (expected between 2 and {})" ,
312+ unique_count,
313+ NUM_CALLS - 1
314+ ) ;
315+ }
0 commit comments