/src/php-src/ext/date/lib/tm2unixtime.c
Line | Count | Source |
1 | | /* |
2 | | * The MIT License (MIT) |
3 | | * |
4 | | * Copyright (c) 2015-2019 Derick Rethans |
5 | | * |
6 | | * Permission is hereby granted, free of charge, to any person obtaining a copy |
7 | | * of this software and associated documentation files (the "Software"), to deal |
8 | | * in the Software without restriction, including without limitation the rights |
9 | | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
10 | | * copies of the Software, and to permit persons to whom the Software is |
11 | | * furnished to do so, subject to the following conditions: |
12 | | * |
13 | | * The above copyright notice and this permission notice shall be included in |
14 | | * all copies or substantial portions of the Software. |
15 | | * |
16 | | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
17 | | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
18 | | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
19 | | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
20 | | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
21 | | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
22 | | * THE SOFTWARE. |
23 | | */ |
24 | | |
25 | | #include "timelib.h" |
26 | | #include "timelib_private.h" |
27 | | |
28 | | /* dec jan feb mrt apr may jun jul aug sep oct nov dec */ |
29 | | static int days_in_month_leap[13] = { 31, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; |
30 | | static int days_in_month[13] = { 31, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; |
31 | | |
32 | | static void do_range_limit(timelib_sll start, timelib_sll end, timelib_sll adj, timelib_sll *a, timelib_sll *b) |
33 | 11.1k | { |
34 | 11.1k | if (*a < start) { |
35 | | /* We calculate 'a + 1' first as 'start - *a - 1' causes an int64_t overflows if *a is |
36 | | * LONG_MIN. 'start' is 0 in this context, and '0 - LONG_MIN > LONG_MAX'. */ |
37 | 46 | timelib_sll a_plus_1 = *a + 1; |
38 | | |
39 | 46 | *b -= (start - a_plus_1) / adj + 1; |
40 | | |
41 | | /* This code add the extra 'adj' separately, as otherwise this can overflow int64_t in |
42 | | * situations where *b is near LONG_MIN. */ |
43 | 46 | *a += adj * ((start - a_plus_1) / adj); |
44 | 46 | *a += adj; |
45 | 46 | } |
46 | 11.1k | if (*a >= end) { |
47 | 1.65k | *b += *a / adj; |
48 | 1.65k | *a -= adj * (*a / adj); |
49 | 1.65k | } |
50 | 11.1k | } |
51 | | |
52 | | static void inc_month(timelib_sll *y, timelib_sll *m) |
53 | 0 | { |
54 | 0 | (*m)++; |
55 | 0 | if (*m > 12) { |
56 | 0 | *m -= 12; |
57 | 0 | (*y)++; |
58 | 0 | } |
59 | 0 | } |
60 | | |
61 | | static void dec_month(timelib_sll *y, timelib_sll *m) |
62 | 0 | { |
63 | 0 | (*m)--; |
64 | 0 | if (*m < 1) { |
65 | 0 | *m += 12; |
66 | 0 | (*y)--; |
67 | 0 | } |
68 | 0 | } |
69 | | |
70 | | static void do_range_limit_days_relative(timelib_sll *base_y, timelib_sll *base_m, timelib_sll *y, timelib_sll *m, timelib_sll *d, timelib_sll invert) |
71 | 8 | { |
72 | 8 | timelib_sll leapyear; |
73 | 8 | timelib_sll month, year; |
74 | 8 | timelib_sll days; |
75 | | |
76 | 8 | do_range_limit(1, 13, 12, base_m, base_y); |
77 | | |
78 | 8 | year = *base_y; |
79 | 8 | month = *base_m; |
80 | | |
81 | | /* |
82 | | printf( "S: Y%d M%d %d %d %d %d\n", year, month, *y, *m, *d, days); |
83 | | */ |
84 | 8 | if (!invert) { |
85 | 8 | while (*d < 0) { |
86 | 0 | dec_month(&year, &month); |
87 | 0 | leapyear = timelib_is_leap(year); |
88 | 0 | days = leapyear ? days_in_month_leap[month] : days_in_month[month]; |
89 | | |
90 | | /* printf( "I Y%d M%d %d %d %d %d\n", year, month, *y, *m, *d, days); */ |
91 | |
|
92 | 0 | *d += days; |
93 | 0 | (*m)--; |
94 | 0 | } |
95 | 8 | } else { |
96 | 0 | while (*d < 0) { |
97 | 0 | leapyear = timelib_is_leap(year); |
98 | 0 | days = leapyear ? days_in_month_leap[month] : days_in_month[month]; |
99 | | |
100 | | /* printf( "I Y%d M%d %d %d %d %d\n", year, month, *y, *m, *d, days); */ |
101 | |
|
102 | 0 | *d += days; |
103 | 0 | (*m)--; |
104 | 0 | inc_month(&year, &month); |
105 | 0 | } |
106 | 0 | } |
107 | | /* |
108 | | printf( "E: Y%d M%d %d %d %d %d\n", year, month, *y, *m, *d, days); |
109 | | */ |
110 | 8 | } |
111 | | |
112 | | static int do_range_limit_days(timelib_sll *y, timelib_sll *m, timelib_sll *d) |
113 | 2.95k | { |
114 | 2.95k | timelib_sll leapyear; |
115 | 2.95k | timelib_sll previous_month, previous_year; |
116 | 2.95k | timelib_sll days_in_previous_month; |
117 | 2.95k | int retval = 0; |
118 | 2.95k | int *days_per_month_current_year; |
119 | | |
120 | | /* can jump an entire leap year period quickly */ |
121 | 2.95k | if (*d >= DAYS_PER_ERA || *d <= -DAYS_PER_ERA) { |
122 | 9 | *y += YEARS_PER_ERA * (*d / DAYS_PER_ERA); |
123 | 9 | *d -= DAYS_PER_ERA * (*d / DAYS_PER_ERA); |
124 | 9 | } |
125 | | |
126 | 2.95k | do_range_limit(1, 13, 12, m, y); |
127 | | |
128 | 2.95k | leapyear = timelib_is_leap(*y); |
129 | 2.95k | days_per_month_current_year = leapyear ? days_in_month_leap : days_in_month; |
130 | | |
131 | 2.95k | while (*d <= 0 && *m > 0) { |
132 | 2 | previous_month = (*m) - 1; |
133 | 2 | if (previous_month < 1) { |
134 | 1 | previous_month += 12; |
135 | 1 | previous_year = (*y) - 1; |
136 | 1 | } else { |
137 | 1 | previous_year = (*y); |
138 | 1 | } |
139 | 2 | leapyear = timelib_is_leap(previous_year); |
140 | 2 | days_in_previous_month = leapyear ? days_in_month_leap[previous_month] : days_in_month[previous_month]; |
141 | | |
142 | 2 | *d += days_in_previous_month; |
143 | 2 | (*m)--; |
144 | 2 | retval = 1; |
145 | 2 | } |
146 | 22.7k | while (*d > 0 && *m <= 12 && *d > days_per_month_current_year[*m]) { |
147 | 19.7k | *d -= days_per_month_current_year[*m]; |
148 | 19.7k | (*m)++; |
149 | 19.7k | retval = 1; |
150 | 19.7k | } |
151 | 2.95k | return retval; |
152 | 2.95k | } |
153 | | |
154 | | static void do_adjust_for_weekday(timelib_time* time) |
155 | 13 | { |
156 | 13 | timelib_sll current_dow, difference; |
157 | | |
158 | 13 | current_dow = timelib_day_of_week(time->y, time->m, time->d); |
159 | 13 | if (time->relative.weekday_behavior == 2) |
160 | 0 | { |
161 | | /* To make "this week" work, where the current DOW is a "sunday" */ |
162 | 0 | if (current_dow == 0 && time->relative.weekday != 0) { |
163 | 0 | time->relative.weekday -= 7; |
164 | 0 | } |
165 | | |
166 | | /* To make "sunday this week" work, where the current DOW is not a |
167 | | * "sunday" */ |
168 | 0 | if (time->relative.weekday == 0 && current_dow != 0) { |
169 | 0 | time->relative.weekday = 7; |
170 | 0 | } |
171 | |
|
172 | 0 | time->d -= current_dow; |
173 | 0 | time->d += time->relative.weekday; |
174 | 0 | return; |
175 | 0 | } |
176 | 13 | difference = time->relative.weekday - current_dow; |
177 | 13 | if ((time->relative.d < 0 && difference < 0) || (time->relative.d >= 0 && difference <= -time->relative.weekday_behavior)) { |
178 | 13 | difference += 7; |
179 | 13 | } |
180 | 13 | if (time->relative.weekday >= 0) { |
181 | 13 | time->d += difference; |
182 | 13 | } else { |
183 | 0 | time->d -= (7 - (abs(time->relative.weekday) - current_dow)); |
184 | 0 | } |
185 | 13 | time->relative.have_weekday_relative = 0; |
186 | 13 | } |
187 | | |
188 | | void timelib_do_rel_normalize(timelib_time *base, timelib_rel_time *rt) |
189 | 8 | { |
190 | 8 | do_range_limit(0, 1000000, 1000000, &rt->us, &rt->s); |
191 | 8 | do_range_limit(0, 60, 60, &rt->s, &rt->i); |
192 | 8 | do_range_limit(0, 60, 60, &rt->i, &rt->h); |
193 | 8 | do_range_limit(0, 24, 24, &rt->h, &rt->d); |
194 | 8 | do_range_limit(0, 12, 12, &rt->m, &rt->y); |
195 | | |
196 | 8 | do_range_limit_days_relative(&base->y, &base->m, &rt->y, &rt->m, &rt->d, rt->invert); |
197 | 8 | do_range_limit(0, 12, 12, &rt->m, &rt->y); |
198 | 8 | } |
199 | | |
200 | | void timelib_do_normalize(timelib_time* time) |
201 | 1.37k | { |
202 | 1.37k | if (time->us != TIMELIB_UNSET) do_range_limit(0, 1000000, 1000000, &time->us, &time->s); |
203 | 1.37k | if (time->s != TIMELIB_UNSET) do_range_limit(0, 60, 60, &time->s, &time->i); |
204 | 1.37k | if (time->s != TIMELIB_UNSET) do_range_limit(0, 60, 60, &time->i, &time->h); |
205 | 1.37k | if (time->s != TIMELIB_UNSET) do_range_limit(0, 24, 24, &time->h, &time->d); |
206 | 1.37k | do_range_limit(1, 13, 12, &time->m, &time->y); |
207 | | |
208 | | /* Short cut if we're doing things against the Epoch */ |
209 | 1.37k | if (time->y == 1970 && time->m == 1) { |
210 | 92 | timelib_date_from_epoch_days(time->d - 1, &time->y, &time->m, &time->d); |
211 | 92 | return; |
212 | 92 | } |
213 | | |
214 | 2.95k | do {} while (do_range_limit_days(&time->y, &time->m, &time->d)); |
215 | 1.28k | do_range_limit(1, 13, 12, &time->m, &time->y); |
216 | 1.28k | } |
217 | | |
218 | | static void do_adjust_relative(timelib_time* time) |
219 | 343 | { |
220 | 343 | if (time->relative.have_weekday_relative) { |
221 | 13 | do_adjust_for_weekday(time); |
222 | 13 | } |
223 | 343 | timelib_do_normalize(time); |
224 | | |
225 | 343 | if (time->have_relative) { |
226 | 35 | time->us += time->relative.us; |
227 | | |
228 | 35 | time->s += time->relative.s; |
229 | 35 | time->i += time->relative.i; |
230 | 35 | time->h += time->relative.h; |
231 | | |
232 | 35 | time->d += time->relative.d; |
233 | 35 | time->m += time->relative.m; |
234 | 35 | time->y += time->relative.y; |
235 | 35 | } |
236 | | |
237 | 343 | switch (time->relative.first_last_day_of) { |
238 | 0 | case TIMELIB_SPECIAL_FIRST_DAY_OF_MONTH: /* first */ |
239 | 0 | time->d = 1; |
240 | 0 | break; |
241 | 0 | case TIMELIB_SPECIAL_LAST_DAY_OF_MONTH: /* last */ |
242 | 0 | time->d = 0; |
243 | 0 | time->m++; |
244 | 0 | break; |
245 | 343 | } |
246 | | |
247 | 343 | timelib_do_normalize(time); |
248 | 343 | } |
249 | | |
250 | | static void do_adjust_special_weekday(timelib_time* time) |
251 | 0 | { |
252 | 0 | timelib_sll count, dow, rem; |
253 | |
|
254 | 0 | count = time->relative.special.amount; |
255 | 0 | dow = timelib_day_of_week(time->y, time->m, time->d); |
256 | | |
257 | | /* Add increments of 5 weekdays as a week, leaving the DOW unchanged. */ |
258 | 0 | time->d += (count / 5) * 7; |
259 | | |
260 | | /* Deal with the remainder. */ |
261 | 0 | rem = (count % 5); |
262 | |
|
263 | 0 | if (count > 0) { |
264 | 0 | if (rem == 0) { |
265 | | /* Head back to Friday if we stop on the weekend. */ |
266 | 0 | if (dow == 0) { |
267 | 0 | time->d -= 2; |
268 | 0 | } else if (dow == 6) { |
269 | 0 | time->d -= 1; |
270 | 0 | } |
271 | 0 | } else if (dow == 6) { |
272 | | /* We ended up on Saturday, but there's still work to do, so move |
273 | | * to Sunday and continue from there. */ |
274 | 0 | time->d += 1; |
275 | 0 | } else if (dow + rem > 5) { |
276 | | /* We're on a weekday, but we're going past Friday, so skip right |
277 | | * over the weekend. */ |
278 | 0 | time->d += 2; |
279 | 0 | } |
280 | 0 | } else { |
281 | | /* Completely mirror the forward direction. This also covers the 0 |
282 | | * case, since if we start on the weekend, we want to move forward as |
283 | | * if we stopped there while going backwards. */ |
284 | 0 | if (rem == 0) { |
285 | 0 | if (dow == 6) { |
286 | 0 | time->d += 2; |
287 | 0 | } else if (dow == 0) { |
288 | 0 | time->d += 1; |
289 | 0 | } |
290 | 0 | } else if (dow == 0) { |
291 | 0 | time->d -= 1; |
292 | 0 | } else if (dow + rem < 1) { |
293 | 0 | time->d -= 2; |
294 | 0 | } |
295 | 0 | } |
296 | |
|
297 | 0 | time->d += rem; |
298 | 0 | } |
299 | | |
300 | | static void do_adjust_special(timelib_time* time) |
301 | 343 | { |
302 | 343 | if (time->relative.have_special_relative) { |
303 | 0 | switch (time->relative.special.type) { |
304 | 0 | case TIMELIB_SPECIAL_WEEKDAY: |
305 | 0 | do_adjust_special_weekday(time); |
306 | 0 | break; |
307 | 0 | } |
308 | 0 | } |
309 | 343 | timelib_do_normalize(time); |
310 | 343 | memset(&(time->relative.special), 0, sizeof(time->relative.special)); |
311 | 343 | } |
312 | | |
313 | | static void do_adjust_special_early(timelib_time* time) |
314 | 343 | { |
315 | 343 | if (time->relative.have_special_relative) { |
316 | 0 | switch (time->relative.special.type) { |
317 | 0 | case TIMELIB_SPECIAL_DAY_OF_WEEK_IN_MONTH: |
318 | 0 | time->d = 1; |
319 | 0 | time->m += time->relative.m; |
320 | 0 | time->relative.m = 0; |
321 | 0 | break; |
322 | 0 | case TIMELIB_SPECIAL_LAST_DAY_OF_WEEK_IN_MONTH: |
323 | 0 | time->d = 1; |
324 | 0 | time->m += time->relative.m + 1; |
325 | 0 | time->relative.m = 0; |
326 | 0 | break; |
327 | 0 | } |
328 | 0 | } |
329 | 343 | switch (time->relative.first_last_day_of) { |
330 | 0 | case TIMELIB_SPECIAL_FIRST_DAY_OF_MONTH: /* first */ |
331 | 0 | time->d = 1; |
332 | 0 | break; |
333 | 0 | case TIMELIB_SPECIAL_LAST_DAY_OF_MONTH: /* last */ |
334 | 0 | time->d = 0; |
335 | 0 | time->m++; |
336 | 0 | break; |
337 | 343 | } |
338 | 343 | timelib_do_normalize(time); |
339 | 343 | } |
340 | | |
341 | | static void do_adjust_timezone(timelib_time *tz, timelib_tzinfo *tzi) |
342 | 343 | { |
343 | 343 | switch (tz->zone_type) { |
344 | 59 | case TIMELIB_ZONETYPE_OFFSET: |
345 | | |
346 | 59 | tz->is_localtime = 1; |
347 | 59 | tz->sse += -tz->z; |
348 | 59 | return; |
349 | | |
350 | 11 | case TIMELIB_ZONETYPE_ABBR: { |
351 | | |
352 | 11 | tz->is_localtime = 1; |
353 | 11 | tz->sse += (-tz->z - tz->dst * SECS_PER_HOUR); |
354 | 11 | return; |
355 | 0 | } |
356 | | |
357 | 273 | case TIMELIB_ZONETYPE_ID: |
358 | 273 | tzi = tz->tz_info; |
359 | 273 | TIMELIB_BREAK_INTENTIONALLY_MISSING |
360 | | |
361 | 273 | default: { |
362 | | /* No timezone in struct, fallback to reference if possible */ |
363 | 273 | int32_t current_offset = 0; |
364 | 273 | timelib_sll current_transition_time = 0; |
365 | 273 | unsigned int current_is_dst = 0; |
366 | 273 | int32_t after_offset = 0; |
367 | 273 | timelib_sll after_transition_time = 0; |
368 | 273 | timelib_sll adjustment; |
369 | 273 | int in_transition; |
370 | 273 | int32_t actual_offset; |
371 | 273 | timelib_sll actual_transition_time; |
372 | | |
373 | 273 | if (!tzi) { |
374 | 0 | return; |
375 | 0 | } |
376 | | |
377 | 273 | timelib_get_time_zone_offset_info(tz->sse, tzi, ¤t_offset, ¤t_transition_time, ¤t_is_dst); |
378 | 273 | timelib_get_time_zone_offset_info(tz->sse - current_offset, tzi, &after_offset, &after_transition_time, NULL); |
379 | 273 | actual_offset = after_offset; |
380 | 273 | actual_transition_time = after_transition_time; |
381 | 273 | if (current_offset == after_offset && tz->have_zone) { |
382 | | /* Make sure we're not missing a DST change because we don't know the actual offset yet */ |
383 | 16 | if (current_offset >= 0 && tz->dst && !current_is_dst) { |
384 | | /* Timezone or its DST at or east of UTC, so the local time, interpreted as UTC, leaves DST (as defined in the actual timezone) before the actual local time */ |
385 | 4 | int32_t earlier_offset; |
386 | 4 | timelib_sll earlier_transition_time; |
387 | 4 | timelib_get_time_zone_offset_info(tz->sse - current_offset - 7200, tzi, &earlier_offset, &earlier_transition_time, NULL); |
388 | 4 | if ((earlier_offset != after_offset) && (tz->sse - earlier_offset < after_transition_time)) { |
389 | | /* Looking behind a bit clarified the actual offset to use */ |
390 | 0 | actual_offset = earlier_offset; |
391 | 0 | actual_transition_time = earlier_transition_time; |
392 | 0 | } |
393 | 12 | } else if (current_offset <= 0 && current_is_dst && !tz->dst) { |
394 | | /* Timezone west of UTC, so the local time, interpreted as UTC, leaves DST (as defined in the actual timezone) after the actual local time */ |
395 | 0 | int32_t later_offset; |
396 | 0 | timelib_sll later_transition_time; |
397 | 0 | timelib_get_time_zone_offset_info(tz->sse - current_offset + 7200, tzi, &later_offset, &later_transition_time, NULL); |
398 | 0 | if ((later_offset != after_offset) && (tz->sse - later_offset >= later_transition_time)) { |
399 | | /* Looking ahead a bit clarified the actual offset to use */ |
400 | 0 | actual_offset = later_offset; |
401 | 0 | actual_transition_time = later_transition_time; |
402 | 0 | } |
403 | 0 | } |
404 | 16 | } |
405 | | |
406 | 273 | tz->is_localtime = 1; |
407 | | |
408 | 273 | in_transition = ( |
409 | 273 | actual_transition_time != INT64_MIN && |
410 | 5 | ((tz->sse - actual_offset) >= (actual_transition_time + (current_offset - actual_offset))) && |
411 | 5 | ((tz->sse - actual_offset) < actual_transition_time) |
412 | 273 | ); |
413 | | |
414 | 273 | if ((current_offset != actual_offset) && !in_transition) { |
415 | 0 | adjustment = -actual_offset; |
416 | 273 | } else { |
417 | 273 | adjustment = -current_offset; |
418 | 273 | } |
419 | | |
420 | 273 | tz->sse += adjustment; |
421 | 273 | timelib_set_timezone(tz, tzi); |
422 | 273 | return; |
423 | 273 | } |
424 | 343 | } |
425 | 0 | return; |
426 | 343 | } |
427 | | |
428 | | timelib_sll timelib_epoch_days_from_time(timelib_time *time) |
429 | 702 | { |
430 | 702 | timelib_sll y = time->y; // Make copy, as we don't want to change the original one |
431 | 702 | timelib_sll era, year_of_era, day_of_year, day_of_era; |
432 | | |
433 | 702 | y -= time->m <= 2; |
434 | 702 | era = (y >= 0 ? y : y - 399) / YEARS_PER_ERA; |
435 | 702 | year_of_era = y - era * YEARS_PER_ERA; // [0, 399] |
436 | 702 | day_of_year = (153 * (time->m + (time->m > 2 ? -3 : 9)) + 2)/5 + time->d - 1; // [0, 365] |
437 | 702 | day_of_era = year_of_era * DAYS_PER_YEAR + year_of_era / 4 - year_of_era / 100 + day_of_year; // [0, 146096] |
438 | | |
439 | 702 | return era * DAYS_PER_ERA + day_of_era - HINNANT_EPOCH_SHIFT; |
440 | 702 | } |
441 | | |
442 | | void timelib_update_ts(timelib_time* time, timelib_tzinfo* tzi) |
443 | 343 | { |
444 | 343 | do_adjust_special_early(time); |
445 | 343 | do_adjust_relative(time); |
446 | 343 | do_adjust_special(time); |
447 | | |
448 | | /* You might be wondering, why this code does this in two steps. This is because |
449 | | * timelib_epoch_days_from_time(time) * SECS_PER_DAY with the lowest limit of |
450 | | * timelib_epoch_days_from_time() is less than the range of an int64_t. This then overflows. In |
451 | | * order to be able to still allow for any time in that day that only halfly fits in the int64_t |
452 | | * range, we add the time element first, which is always positive, and then twice half the value |
453 | | * of the earliest day as expressed as unix timestamp. */ |
454 | 343 | time->sse = timelib_hms_to_seconds(time->h, time->i, time->s); |
455 | 343 | time->sse += timelib_epoch_days_from_time(time) * (SECS_PER_DAY / 2); |
456 | 343 | time->sse += timelib_epoch_days_from_time(time) * (SECS_PER_DAY / 2); |
457 | | |
458 | | // This modifies time->sse, if needed |
459 | 343 | do_adjust_timezone(time, tzi); |
460 | | |
461 | 343 | time->sse_uptodate = 1; |
462 | 343 | time->have_relative = time->relative.have_weekday_relative = time->relative.have_special_relative = time->relative.first_last_day_of = 0; |
463 | 343 | } |
464 | | |
465 | | #if 0 |
466 | | int main(void) |
467 | | { |
468 | | timelib_sll res; |
469 | | timelib_time time; |
470 | | |
471 | | time = timelib_strtotime("10 Feb 2005 06:07:03 PM CET"); /* 1108055223 */ |
472 | | printf ("%04d-%02d-%02d %02d:%02d:%02d.%-5d %+04d %1d", |
473 | | time.y, time.m, time.d, time.h, time.i, time.s, time.f, time.z, time.dst); |
474 | | if (time.have_relative) { |
475 | | printf ("%3dY %3dM %3dD / %3dH %3dM %3dS", |
476 | | time.relative.y, time.relative.m, time.relative.d, time.relative.h, time.relative.i, time.relative.s); |
477 | | } |
478 | | if (time.have_weekday_relative) { |
479 | | printf (" / %d", time.relative.weekday); |
480 | | } |
481 | | res = time2unixtime(&time); |
482 | | printf("%Ld\n", res); |
483 | | |
484 | | return 0; |
485 | | } |
486 | | #endif |