1 /*
2  * SPDX-License-Identifier: BSD-3-Clause
3  *
4  * Copyright © 2021 R. Diez
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  *
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  *
13  * 2. Redistributions in binary form must reproduce the above
14  *    copyright notice, this list of conditions and the following
15  *    disclaimer in the documentation and/or other materials provided
16  *    with the distribution.
17  *
18  * 3. Neither the name of the copyright holder nor the names of its
19  *    contributors may be used to endorse or promote products derived
20  *    from this software without specific prior written permission.
21  *
22  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
25  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
26  * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
27  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
28  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
29  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
31  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
32  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
33  * OF THE POSSIBILITY OF SUCH DAMAGE.
34  */
35 
36 #define _DEFAULT_SOURCE
37 #include <stdlib.h>
38 #include <stdio.h>
39 #include <time.h>
40 
41 
42 #define TIME_TM_YEAR_BASE 1900
43 
44 
45 static void
init_struct_tm(struct tm * stm)46 init_struct_tm ( struct tm * stm )
47 {
48   if ( sizeof( struct tm ) != 9 * sizeof( int ) )
49   {
50     puts("Error: struct tm has changed, so these tests probably need adjusting.");
51     exit(1);
52   }
53 
54   stm->tm_sec   = 0;
55   stm->tm_min   = 0;
56   stm->tm_hour  = 0;
57   stm->tm_mday  = 0;
58   stm->tm_mon   = 0;
59   stm->tm_year  = 0;
60   stm->tm_wday  = 0;
61   stm->tm_yday  = 0;
62   stm->tm_isdst = 0;
63 }
64 
65 
66 int
main(void)67 main(void)
68 {
69   // Time zone ART3 is America/Buenos_Aires.
70   // There is no daylight saving time (aka sommer time).
71 
72   if ( 0 != setenv( "TZ", "ART3", 1 ) )
73   {
74     puts("Error calling setenv().");
75     exit(1);
76   }
77 
78   tzset();
79 
80 
81   struct tm dtForTimegm1;
82   init_struct_tm( &dtForTimegm1 );
83   // This is some arbitrary point in time.
84   dtForTimegm1.tm_mday  = 3;  // 3rd
85   dtForTimegm1.tm_mon   = 1;  // of February
86   dtForTimegm1.tm_year  = 1970 - TIME_TM_YEAR_BASE;
87 
88   // Make a copy, because mktime() and friends modify the fields of the tm structure passed as an argument.
89   struct tm dtForMktime2 = dtForTimegm1;
90 
91   // timegm() should be not affected by the time zone.
92 
93   time_t t1 = timegm( &dtForTimegm1 );
94 
95   if ( t1 != 2851200 ||
96        dtForTimegm1.tm_wday != 2 )  // 2 means Tuesday.
97   {
98     puts("Test t1 failed.");
99     exit(1);
100   }
101 
102   // mktime() is affected by the time zone. Check that the offset is the expected one.
103 
104   time_t t2 = mktime( &dtForMktime2 );
105 
106   if ( t2 != t1 + 3 * 60 * 60 ||  // The chosen time zone has a positive 3-hour difference.
107        dtForMktime2.tm_wday != 2 )
108   {
109     puts("Test t2 failed.");
110     exit(1);
111   }
112 
113 
114   // The European Central Time goes the other direction (negative)
115   // and has a daylight saving time (aka sommer time).
116 
117   if ( 0 != setenv( "TZ", "CET-1CEST,M3.5.0,M10.5.0/3", 1 ) )
118   {
119     puts("Error calling setenv().");
120     exit(1);
121   }
122 
123   tzset();
124 
125 
126   struct tm dtForTimegm3;
127   init_struct_tm( &dtForTimegm3 );
128 
129   // This is some arbitrary point in time with daylight saving time.
130   dtForTimegm3.tm_mday  = 1;   // 1st
131   dtForTimegm3.tm_mon   = 4;   // of May
132   dtForTimegm3.tm_year  = 2021 - TIME_TM_YEAR_BASE;
133   dtForTimegm3.tm_isdst = 1;  // Daylight saving time is in effect. That only affects local time.
134 
135   struct tm dtForMktime4 = dtForTimegm3;
136 
137   time_t t3 = timegm( &dtForTimegm3 );
138 
139   if ( t3 != 1619827200           ||
140        dtForTimegm3.tm_wday  != 6 ||  // 6 means Saturday.
141        dtForTimegm3.tm_isdst != 0  )  // timegm() should reset the daylight saving time flag.
142   {
143     puts("Test t3 failed.");
144     exit(1);
145   }
146 
147   time_t t4 = mktime( &dtForMktime4 );
148 
149   // The offset is -2 hours: 1 hour because of the time zone, and 1 hour because of the sommer time.
150   if ( t4 != t3 - 2 * 60 * 60     ||
151        dtForMktime4.tm_wday  != 6 ||  // 6 means Saturday.
152        dtForMktime4.tm_isdst != 1  )  // mktime() should leave the daylight saving time flag set.
153   {
154     puts("Test t4 failed.");
155     exit(1);
156   }
157 
158 
159   // Test the first non-negative time_t value of 0,
160   // which is the "UNIX Epoch time" of 1st Januar 1970 00:00:00.
161 
162   struct tm dtForTimegm5;
163   init_struct_tm( &dtForTimegm5 );
164 
165   dtForTimegm5.tm_mday  = 1;   // 1st day of the month.
166   dtForTimegm5.tm_year  = 1970 - TIME_TM_YEAR_BASE;
167   dtForTimegm5.tm_isdst = -123;  // A negative value makes mktime() calculate this flag,
168                                  // but timegm() should unconditionally reset it.
169 
170   time_t t5 = timegm( &dtForTimegm5 );
171 
172   if ( t5 != 0                    ||
173        dtForTimegm5.tm_wday  != 4 ||  // 4 means Thursday.
174        dtForTimegm5.tm_isdst != 0  )
175   {
176     puts("Test t5 failed.");
177     exit(1);
178   }
179 
180 
181   // Test the last time_t value of 0x7FFFFFFF before the 32-bit signed integer overflow,
182   // aka "year 2038 problem". That corresponds to 2038-01-19 03:14:07.
183 
184   struct tm dtForTimegm6;
185   init_struct_tm( &dtForTimegm6 );
186 
187   dtForTimegm6.tm_sec  =  7;
188   dtForTimegm6.tm_min  = 14;
189   dtForTimegm6.tm_hour =  3,
190   dtForTimegm6.tm_mon   = 0;  // January.
191   dtForTimegm6.tm_mday  = 19;
192   dtForTimegm6.tm_year  = 2038 - TIME_TM_YEAR_BASE;
193   dtForTimegm6.tm_isdst = 123;  // Some value that should be reset.
194 
195   struct tm dtForTimegm7 = dtForTimegm6;  // Reuse the date for the next test.
196 
197   time_t t6 = timegm( &dtForTimegm6 );
198 
199   if ( t6 != 0x7FFFFFFF            ||
200        dtForTimegm6.tm_wday  != 2  ||  // 2 means Tuesday.
201        dtForTimegm6.tm_yday  != 18 ||
202        dtForTimegm6.tm_isdst != 0   )
203   {
204     puts("Test t6 failed.");
205     exit(1);
206   }
207 
208 
209   // Test the next time_t value of 0x80000000 right after
210   // the 32-bit signed integer overflow, aka "year 2038 problem".
211 
212   dtForTimegm7.tm_sec++;
213 
214   time_t t7 = timegm( &dtForTimegm7 );
215 
216   if ( t7 != 0x80000000            ||
217        dtForTimegm7.tm_wday  != 2  ||  // 2 means Tuesday.
218        dtForTimegm7.tm_yday  != 18 ||
219        dtForTimegm7.tm_isdst != 0   )
220   {
221     puts("Test t7 failed.");
222     exit(1);
223   }
224 
225 
226   // Test the 29th of February in a leap year far in the future.
227 
228   struct tm dtForTimegm8;
229   init_struct_tm( &dtForTimegm8 );
230 
231   dtForTimegm8.tm_mon   = 1;  // February.
232   dtForTimegm8.tm_mday  = 29;
233   dtForTimegm8.tm_year  = 2104 - TIME_TM_YEAR_BASE;
234 
235   time_t t8 = timegm( &dtForTimegm8 );
236 
237   if ( t8 != 4233686400           ||  // That is much higher than INT32_MAX.
238        dtForTimegm8.tm_wday != 5  ||  // 5 means Friday.
239        dtForTimegm8.tm_yday != 59  )
240   {
241     puts("Test t8 failed.");
242     exit(1);
243   }
244 
245 
246   return 0;
247 }
248