1 /********************************************************************/
2 /*                                                                  */
3 /*  tim_rtl.c     Time access using the C capabilities.             */
4 /*  Copyright (C) 1989 - 2015, 2018, 2020  Thomas Mertes            */
5 /*                                                                  */
6 /*  This file is part of the Seed7 Runtime Library.                 */
7 /*                                                                  */
8 /*  The Seed7 Runtime Library is free software; you can             */
9 /*  redistribute it and/or modify it under the terms of the GNU     */
10 /*  Lesser General Public License as published by the Free Software */
11 /*  Foundation; either version 2.1 of the License, or (at your      */
12 /*  option) any later version.                                      */
13 /*                                                                  */
14 /*  The Seed7 Runtime Library is distributed in the hope that it    */
15 /*  will be useful, but WITHOUT ANY WARRANTY; without even the      */
16 /*  implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR */
17 /*  PURPOSE.  See the GNU Lesser General Public License for more    */
18 /*  details.                                                        */
19 /*                                                                  */
20 /*  You should have received a copy of the GNU Lesser General       */
21 /*  Public License along with this program; if not, write to the    */
22 /*  Free Software Foundation, Inc., 51 Franklin Street,             */
23 /*  Fifth Floor, Boston, MA  02110-1301, USA.                       */
24 /*                                                                  */
25 /*  Module: Seed7 Runtime Library                                   */
26 /*  File: seed7/src/tim_rtl.c                                       */
27 /*  Changes: 2009, 2013 - 2015, 2018, 2020  Thomas Mertes           */
28 /*  Content: Time access using the C capabilities.                  */
29 /*                                                                  */
30 /********************************************************************/
31 
32 #define LOG_FUNCTIONS 0
33 #define VERBOSE_EXCEPTIONS 0
34 
35 #include "version.h"
36 
37 #include "stdio.h"
38 #include "time.h"
39 #include "string.h"
40 #include "errno.h"
41 
42 #include "common.h"
43 #include "big_drv.h"
44 #include "tim_drv.h"
45 #include "rtl_err.h"
46 
47 #undef EXTERN
48 #define EXTERN
49 #include "tim_rtl.h"
50 
51 
52 #if TIME_T_SIZE == 32
53 #if TIME_T_SIGNED
54 #define STRUCT_TM_MIN_YEAR 1902
55 #else
56 #define STRUCT_TM_MIN_YEAR 1970
57 #endif
58 #define STRUCT_TM_MAX_YEAR 2037
59 #elif TIME_T_SIZE == 64
60 #if   defined INT64TYPE_SUFFIX_LL
61 #define STRUCT_TM_MIN_YEAR (-292277022655LL)
62 #define STRUCT_TM_MAX_YEAR   292277026595LL
63 #elif defined INT64TYPE_SUFFIX_L
64 #define STRUCT_TM_MIN_YEAR (-292277022655L)
65 #define STRUCT_TM_MAX_YEAR   292277026595L
66 #else
67 #define STRUCT_TM_MIN_YEAR (-292277022655)
68 #define STRUCT_TM_MAX_YEAR   292277026595
69 #endif
70 #endif
71 
72 #define SECONDS_PER_DAY 86400
73 #define SECONDS_PER_YEAR (SECONDS_PER_DAY * 365)
74 /* Days between 0-01-01 and 1900-01-01 */
75 #define DAYS_FROM_0_TO_1900 693595
76 /* Days between 0-01-01 and 1970-01-01 */
77 #define DAYS_FROM_0_TO_1970 719162
78 #define MINIMUM_TIMESTAMP_YEAR -292277022657
79 #define MAXIMUM_TIMESTAMP_YEAR  292277026596
80 
81 static const int monthDays[2][12] = {
82     {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
83     {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}};
84 
85 static const time_t yearDays[2][13] = {
86     {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 366},
87     {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 367}};
88 
89 
90 
91 /**
92  *  Convert a broken down time in '*timeptr' to a timestamp.
93  *  The timestamp is expressed in seconds since the Unix Epoch.
94  *  The Unix Epoch (1970-01-01 00:00:00 UTC) corresponds to 0.
95  *  The date/time in '*timeptr' is not checked for correctness.
96  *  @return the timestamp that corresponds to the time.
97  */
unchecked_mkutc(struct tm * timeptr)98 time_t unchecked_mkutc (struct tm *timeptr)
99 
100   {
101     int real_year;
102     int leap_year;
103     int year_before;
104     time_t timestamp;
105 
106   /* unchecked_mkutc */
107     logFunction(printf("unchecked_mkutc([%d, %d, %d, %d, %d, %d])\n",
108                        timeptr->tm_year, timeptr->tm_mon, timeptr->tm_mday,
109                        timeptr->tm_hour, timeptr->tm_min, timeptr->tm_sec););
110     real_year = 1900 + timeptr->tm_year;
111     if ((real_year % 4 == 0 && real_year % 100 != 0) || real_year % 400 == 0) {
112       leap_year = 1;
113     } else {
114       leap_year = 0;
115     } /* if */
116     year_before = real_year - 1;
117     if (year_before < 0) {
118       /* Year_before is divided and truncated towards minus infinite. */
119       /* This is done with: quotient = (dividend + 1) / divisor - 1;  */
120       timestamp = (time_t) year_before * 365 +
121                   ((time_t) real_year / 4 - 1) -
122                   ((time_t) real_year / 100 - 1) +
123                   ((time_t) real_year / 400 - 1);
124     } else {
125       timestamp = (time_t) year_before * 365 +
126                   (time_t) year_before / 4 -
127                   (time_t) year_before / 100 +
128                   (time_t) year_before / 400;
129     } /* if */
130     timestamp = (((timestamp -
131                 (time_t) DAYS_FROM_0_TO_1970 +
132                 yearDays[leap_year][timeptr->tm_mon] +
133                 (time_t) timeptr->tm_mday - 1) * 24 +
134                 (time_t) timeptr->tm_hour) * 60 +
135                 (time_t) timeptr->tm_min) * 60 +
136                 (time_t) timeptr->tm_sec;
137     logFunction(printf("unchecked_mkutc --> " FMT_T "\n", timestamp););
138     return timestamp;
139   } /* unchecked_mkutc */
140 
141 
142 
143 /**
144  *  Convert a timestamp into a time from the local time zone.
145  *  The timestamp is expressed in seconds since the Unix Epoch.
146  *  The Unix Epoch (1970-01-01 00:00:00 UTC) corresponds to 0.
147  *  @param timestamp Unix timestamp to be converted.
148  *  @return the local time that corresponds to the timestamp.
149  */
timFromTimestamp(time_t timestamp,intType * year,intType * month,intType * day,intType * hour,intType * minute,intType * second,intType * micro_sec,intType * timeZone,boolType * isDst)150 void timFromTimestamp (time_t timestamp,
151     intType *year, intType *month, intType *day, intType *hour,
152     intType *minute, intType *second, intType *micro_sec, intType *timeZone,
153     boolType *isDst)
154 
155   {
156 #if defined USE_LOCALTIME_R || defined USE_LOCALTIME_S
157     struct tm tm_result;
158 #endif
159     struct tm *local_time;
160 
161   /* timFromTimestamp */
162     logFunction(printf("timFromTimestamp(" FMT_T ")\n", timestamp););
163 #if defined USE_LOCALTIME_R
164     local_time = localtime_r(&timestamp, &tm_result);
165 #elif defined USE_LOCALTIME_S
166     if (localtime_s(&tm_result, &timestamp) != 0) {
167       local_time = NULL;
168     } else {
169       local_time = &tm_result;
170     } /* if */
171 #else
172     local_time = localtime(&timestamp);
173 #endif
174     if (unlikely(local_time == NULL)) {
175       logError(printf("timFromTimestamp(" FMT_T "): "
176                       "One of localtime/localtime_r/localtime_s failed:\n"
177                       "errno=%d\nerror: %s\n",
178                       timestamp, errno, strerror(errno)););
179       raise_error(RANGE_ERROR);
180     } else {
181       *year      = local_time->tm_year + 1900;
182       *month     = local_time->tm_mon + 1;
183       *day       = local_time->tm_mday;
184       *hour      = local_time->tm_hour;
185       *minute    = local_time->tm_min;
186       *second    = local_time->tm_sec;
187       *micro_sec = 0;
188       *timeZone  = (intType) (unchecked_mkutc(local_time) - timestamp) / 60;
189       /* Correct timeZone values that are outside of the allowed range. */
190       /* Under Linux this never happens, but Windows has this problem.  */
191       if (unlikely(*timeZone < -12 * 60)) {
192         *timeZone += 24 * 60;
193         if (*day < monthDays[((*year % 4 == 0 && *year % 100 != 0) || *year % 400 == 0)][*month - 1]) {
194           (*day)++;
195         } else if (*month < 12) {
196           (*month)++;
197           *day = 1;
198         } else {
199           (*year)++;
200           *month = 1;
201           *day = 1;
202         } /* if */
203       } else if (unlikely(*timeZone > 14 * 60)) {
204         *timeZone -= 24 * 60;
205         if (*day > 1) {
206           (*day)--;
207         } else if (*month > 1) {
208           (*month)--;
209           *day = monthDays[((*year % 4 == 0 && *year % 100 != 0) || *year % 400 == 0)][*month - 1];
210         } else {
211           (*year)--;
212           *month = 12;
213           *day = 31;
214         } /* if */
215       } /* if */
216       *isDst     = local_time->tm_isdst > 0;
217     } /* if */
218     logFunction(printf("timFromTimestamp(" FMT_T ", "
219                        F_D(04) "-" F_D(02) "-" F_D(02) " "
220                        F_D(02) ":" F_D(02) ":" F_D(02) "." F_D(06) " "
221                        FMT_D " %d) -->\n",
222                        timestamp, *year, *month, *day, *hour, *minute, *second,
223                        *micro_sec, *timeZone, *isDst););
224   } /* timFromTimestamp */
225 
226 
227 
228 /**
229  *  Convert a timestamp into a time from the local time zone.
230  *  The timestamp is expressed in seconds since the Unix Epoch.
231  *  The Unix Epoch (1970-01-01 00:00:00 UTC) corresponds to 0.
232  *  @param timestamp Unix timestamp to be converted.
233  *  @return the local time that corresponds to the timestamp.
234  */
timFromIntTimestamp(intType timestamp,intType * year,intType * month,intType * day,intType * hour,intType * minute,intType * second,intType * micro_sec,intType * timeZone,boolType * isDst)235 void timFromIntTimestamp (intType timestamp,
236     intType *year, intType *month, intType *day, intType *hour,
237     intType *minute, intType *second, intType *micro_sec, intType *timeZone,
238     boolType *isDst)
239 
240   { /* timFromIntTimestamp */
241     if (!inTimeTRange(timestamp)) {
242       logError(printf("timFromIntTimestamp(" FMT_D "): "
243                       "Timestamp not in allowed range of time_t.\n",
244                       timestamp););
245       raise_error(RANGE_ERROR);
246     } else {
247       timFromTimestamp((time_t) timestamp,
248           year, month, day, hour, minute, second, micro_sec, timeZone, isDst);
249     } /* if */
250   } /* timFromIntTimestamp */
251 
252 
253 
254 /**
255  *  Convert a timestamp into an UTC time.
256  *  The timestamp is expressed in seconds since the Unix Epoch.
257  *  The Unix Epoch (1970-01-01 00:00:00 UTC) corresponds to 0.
258  *  @param timestamp Unix timestamp to be converted.
259  */
timUtcFromTimestamp(timeStampType timestamp,intType * year,intType * month,intType * day,intType * hour,intType * minute,intType * second)260 void timUtcFromTimestamp (timeStampType timestamp,
261     intType *year, intType *month, intType *day,
262     intType *hour, intType *minute, intType *second)
263 
264   {
265     int64Type daysSince1970_01_01;
266     int64Type currentYear;
267     int64Type previousYear;
268     int64Type dayJan1;
269     int64Type yearDay;
270     int32Type secondsSinceMidnight;
271     int leapYear;
272     int monthIdx;
273 
274   /* timUtcFromTimestamp */
275     logFunction(printf("timUtcFromTimestamp(" FMT_D64 ")\n", timestamp););
276     if (timestamp < 0) {
277       daysSince1970_01_01 = (timestamp + 1) / SECONDS_PER_DAY - 1;
278       secondsSinceMidnight = (int32Type) (timestamp - daysSince1970_01_01 * SECONDS_PER_DAY);
279       currentYear = 1969 + timestamp / SECONDS_PER_YEAR;
280       do {
281         previousYear = currentYear - 1;
282         if (previousYear < 0) {
283           /* previousYear is divided and truncated towards minus infinite. */
284           /* This is done with: quotient = (dividend + 1) / divisor - 1; */
285           dayJan1 = previousYear * 365 +
286                     (currentYear / 4 - 1) -
287                     (currentYear / 100 - 1) +
288                     (currentYear / 400 - 1) -
289                     DAYS_FROM_0_TO_1970;
290         } else {
291           dayJan1 = previousYear * 365 +
292                     previousYear / 4 -
293                     previousYear / 100 +
294                     previousYear / 400 -
295                     DAYS_FROM_0_TO_1970;
296         } /* if */
297         yearDay = daysSince1970_01_01 - dayJan1;
298         if ((currentYear % 4 == 0 && currentYear % 100 != 0) || currentYear % 400 == 0) {
299           leapYear = 1;
300         } else {
301           leapYear = 0;
302         } /* if */
303         if (yearDay >= (leapYear ? 366 : 365)) {
304           /* Add at least one year. Underestimate the added number */
305           /* of years, by assuming that every year is a leap year. */
306           currentYear += (yearDay - (leapYear ? 366 : 365)) / 366 + 1;
307         } /* if */
308       } while (yearDay >= (leapYear ? 366 : 365));
309     } else {
310       daysSince1970_01_01 = timestamp / SECONDS_PER_DAY;
311       secondsSinceMidnight = (int32Type) (timestamp % SECONDS_PER_DAY);
312       currentYear = 1970 + timestamp / SECONDS_PER_YEAR;
313       do {
314         previousYear = currentYear - 1;
315         dayJan1 = previousYear * 365 +
316                   previousYear / 4 -
317                   previousYear / 100 +
318                   previousYear / 400 -
319                   DAYS_FROM_0_TO_1970;
320         yearDay = daysSince1970_01_01 - dayJan1;
321         if (yearDay < 0) {
322           /* Subtract at least one year. Underestimate the subtracted     */
323           /* number of years, by assuming that every year is a leap year. */
324           currentYear -= (-yearDay - 1) / 366 + 1;
325         } /* if */
326       } while (yearDay < 0);
327     } /* if */
328     if ((currentYear % 4 == 0 && currentYear % 100 != 0) || currentYear % 400 == 0) {
329       leapYear = 1;
330     } else {
331       leapYear = 0;
332     } /* if */
333     for (monthIdx = 0; monthIdx < 12 && yearDay >= yearDays[leapYear][monthIdx]; monthIdx++) ;
334     *year = currentYear;
335     *month = monthIdx;
336     *day = yearDay - yearDays[leapYear][monthIdx - 1] + 1;
337     *hour = secondsSinceMidnight / 3600;
338     *minute = (secondsSinceMidnight / 60) % 60;
339     *second = secondsSinceMidnight % 60;
340     logFunction(printf("timUtcFromTimestamp -> "
341                        F_D(04) "-" F_D(02) "-" F_D(02) " "
342                        F_D(02) ":" F_D(02) ":" F_D(02) "\n",
343                        *year, *month, *day, *hour, *minute, *second););
344   } /* timUtcFromTimestamp */
345 
346 
347 
348 /**
349  *  Convert a time with timeZone to a timestamp.
350  *  The timestamp is expressed in seconds since the Unix Epoch.
351  *  The Unix Epoch (1970-01-01 00:00:00 UTC) corresponds to 0.
352  *  @return the timestamp that corresponds to the time, or
353  *          TIMESTAMPTYPE_MIN if a value is not in the allowed range.
354  */
timToTimestamp(intType year,intType month,intType day,intType hour,intType minute,intType second,intType timeZone)355 timeStampType timToTimestamp (intType year, intType month, intType day,
356     intType hour, intType minute, intType second, intType timeZone)
357 
358   {
359     int leap_year;
360     timeStampType previousYear;
361     timeStampType timestamp;
362 
363   /* timToTimestamp */
364     logFunction(printf("timToTimestamp(" F_D(04) "-" F_D(02) "-" F_D(02) " "
365                                          F_D(02) ":" F_D(02) ":" F_D(02) " "
366                                          FMT_D ")\n",
367                        year, month, day, hour, minute, second, timeZone););
368     if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) {
369       leap_year = 1;
370     } else {
371       leap_year = 0;
372     } /* if */
373     if (unlikely(year <= MINIMUM_TIMESTAMP_YEAR ||
374                  year >= MAXIMUM_TIMESTAMP_YEAR ||
375                  month < 1 || month > 12 || day < 1 ||
376                  day > monthDays[leap_year][month - 1] ||
377                  hour < 0 || hour >= 24 ||
378                  minute < 0 || minute >= 60 ||
379                  second < 0 || second >= 60)) {
380       logError(printf("timToTimestamp(" F_D(04) "-" F_D(02) "-" F_D(02) " "
381                                         F_D(02) ":" F_D(02) ":" F_D(02) " "
382                                         FMT_D "): Not in allowed range.\n",
383                        year, month, day, hour, minute, second, timeZone););
384       timestamp = TIMESTAMPTYPE_MIN;
385     } else {
386       previousYear = (timeStampType) year - 1;
387       if (previousYear < 0) {
388         /* previousYear is divided and truncated towards minus infinite. */
389         /* This is done with: quotient = (dividend + 1) / divisor - 1;  */
390         timestamp = previousYear * 365 +
391                     ((timeStampType) year / 4 - 1) -
392                     ((timeStampType) year / 100 - 1) +
393                     ((timeStampType) year / 400 - 1);
394       } else {
395         timestamp = previousYear * 365 +
396                     previousYear / 4 -
397                     previousYear / 100 +
398                     previousYear / 400;
399         /* printf("timestamp: " FMT_D64 "\n", timestamp - DAYS_FROM_0_TO_1970); */
400       } /* if */
401       timestamp = (((timestamp -
402                   (timeStampType) DAYS_FROM_0_TO_1970 +
403                   yearDays[leap_year][month - 1] +
404                   (timeStampType) day - 1) * 24 +
405                   (timeStampType) hour) * 60 +
406                   (timeStampType) minute) * 60 +
407                   (timeStampType) second;
408       if (unlikely(timeZone < -1500 || timeZone > 1500)) {
409         logError(printf("timToTimestamp("
410                         F_D(04) "-" F_D(02) "-" F_D(02) " "
411                         F_D(02) ":" F_D(02) ":" F_D(02) " " FMT_D
412                         "): timeZone not in allowed range.\n",
413                         year, month, day, hour, minute, second, timeZone););
414         timestamp = TIMESTAMPTYPE_MIN;
415       } else {
416         timestamp -= (timeStampType) timeZone * 60;
417       } /* if */
418     } /* if */
419     logFunction(printf("timToTimestamp --> " FMT_D64 "\n", timestamp););
420     return timestamp;
421   } /* timToTimestamp */
422 
423 
424 
425 #if TIME_T_SIZE != TIMESTAMPTYPE_SIZE || !TIME_T_SIGNED
426 /**
427  *  Convert a time with timeZone to a timestamp.
428  *  The timestamp is expressed in seconds since the Unix Epoch.
429  *  The Unix Epoch (1970-01-01 00:00:00 UTC) corresponds to 0.
430  *  @return the timestamp that corresponds to the time, or
431  *          TIME_T_ERROR if a value is not in the allowed range.
432  */
timToOsTimestamp(intType year,intType month,intType day,intType hour,intType minute,intType second,intType timeZone)433 time_t timToOsTimestamp (intType year, intType month, intType day,
434     intType hour, intType minute, intType second, intType timeZone)
435 
436   {
437     timeStampType timestamp;
438     time_t osTimestamp;
439 
440   /* timToOsTimestamp */
441     logFunction(printf("timToOsTimestamp(" F_D(04) "-" F_D(02) "-" F_D(02) " "
442                                            F_D(02) ":" F_D(02) ":" F_D(02) " "
443                                            FMT_D ")\n",
444                        year, month, day, hour, minute, second, timeZone););
445     timestamp = timToTimestamp(year, month, day, hour, minute, second,
446                                timeZone);
447     if (unlikely(timestamp == TIMESTAMPTYPE_MIN || !inTimeTRange(timestamp))) {
448       osTimestamp = TIME_T_ERROR;
449     } else {
450       osTimestamp = (time_t) timestamp;
451     } /* if */
452     logFunction(printf("timToOsTimestamp --> " FMT_T "\n", timestamp););
453     return osTimestamp;
454   } /* timToOsTimestamp */
455 #endif
456 
457 
458 
459 /**
460  *  Sets timeZone and daylightSavingTime for a given time.
461  *  @return the time in the local time zone.
462  */
timSetLocalTZ(intType year,intType month,intType day,intType hour,intType minute,intType second,intType * timeZone,boolType * isDst)463 void timSetLocalTZ (intType year, intType month, intType day, intType hour,
464     intType minute, intType second, intType *timeZone, boolType *isDst)
465 
466   {
467     time_t timestamp;
468 #if defined USE_LOCALTIME_R || defined USE_LOCALTIME_S
469     struct tm tm_result;
470 #endif
471     struct tm *local_time;
472     time_t timeZoneReference;
473 
474   /* timSetLocalTZ */
475     logFunction(printf("timSetLocalTZ(" F_D(04) "-" F_D(02) "-" F_D(02) " "
476                                         F_D(02) ":" F_D(02) ":" F_D(02) ")\n",
477                        year, month, day, hour, minute, second););
478     timestamp = 0;
479 #if defined USE_LOCALTIME_R
480     local_time = localtime_r(&timestamp, &tm_result);
481 #elif defined USE_LOCALTIME_S
482     if (localtime_s(&tm_result, &timestamp) != 0) {
483       local_time = NULL;
484     } else {
485       local_time = &tm_result;
486     } /* if */
487 #else
488     local_time = localtime(&timestamp);
489 #endif
490     if (unlikely(local_time == NULL)) {
491       logError(printf("timSetLocalTZ: "
492                       "One of localtime/localtime_r/localtime_s(" FMT_T ") failed:\n"
493                       "errno=%d\nerror: %s\n",
494                       timestamp, errno, strerror(errno)););
495       raise_error(RANGE_ERROR);
496     } else {
497       timeZoneReference = unchecked_mkutc(local_time) / 60;
498       /* printf("timeZoneReference: %ld\n", timeZoneReference); */
499       timestamp = timToOsTimestamp(year, month, day, hour, minute, second, 0);
500       if (unlikely(timestamp == TIME_T_ERROR)) {
501         *timeZone = timeZoneReference;
502         *isDst    = 0;
503       } else {
504         /* printf("timestamp: %ld\n", timestamp); */
505         timestamp -= timeZoneReference * 60;
506 #if !LOCALTIME_WORKS_SIGNED
507         if (timestamp < 0) {
508           *timeZone = timeZoneReference;
509           *isDst    = 0;
510         } else {
511 #endif
512 #if defined USE_LOCALTIME_R
513           local_time = localtime_r(&timestamp, &tm_result);
514 #elif defined USE_LOCALTIME_S
515           if (localtime_s(&tm_result, &timestamp) != 0) {
516             local_time = NULL;
517           } else {
518             local_time = &tm_result;
519           } /* if */
520 #else
521           local_time = localtime(&timestamp);
522 #endif
523           if (unlikely(local_time == NULL)) {
524             logError(printf("timSetLocalTZ: One of "
525                             "localtime/localtime_r/localtime_s(" FMT_T ") failed:\n"
526                             "errno=%d\nerror: %s\n",
527                             timestamp, errno, strerror(errno)););
528             raise_error(RANGE_ERROR);
529           } else {
530             *timeZone = (intType) (unchecked_mkutc(local_time) - timestamp) / 60;
531             /* Correct timeZone values that are outside of the allowed range. */
532             /* Under Linux this never happens, but Windows has this problem.  */
533             if (unlikely(*timeZone < -12 * 60)) {
534               *timeZone += 24 * 60;
535             } else if (unlikely(*timeZone > 14 * 60)) {
536               *timeZone -= 24 * 60;
537             } /* if */
538             *isDst    = local_time->tm_isdst > 0;
539           } /* if */
540 #if !LOCALTIME_WORKS_SIGNED
541         } /* if */
542 #endif
543       } /* if */
544     } /* if */
545     logFunction(printf("timSetLocalTZ(" F_D(04) "-" F_D(02) "-" F_D(02) " "
546                        F_D(02) ":" F_D(02) ":" F_D(02) " " FMT_D " %d) -->\n",
547                        year, month, day, hour, minute, second,
548                        *timeZone, *isDst););
549   } /* timSetLocalTZ */
550 
551 
552 
553 /**
554  *  Assign year, month and day from the number of days since 1900-01-01.
555  *  This functions works correct for the whole range of daysSince1900_01_01.
556  */
dateFromDaysSince1900(int32Type daysSince1900_01_01,intType * year,intType * month,intType * day)557 void dateFromDaysSince1900 (int32Type daysSince1900_01_01,
558     intType *year, intType *month, intType *day)
559 
560   {
561     int32Type currentYear;
562     int32Type previousYear;
563     int32Type dayJan1;
564     int32Type yearDay;
565     int leapYear;
566     int monthIdx;
567 
568   /* dateFromDaysSince1900 */
569     logFunction(printf("dateFromDaysSince1900(" FMT_D32 ")\n", daysSince1900_01_01););
570     if (daysSince1900_01_01 < 0) {
571       currentYear = 1899 + daysSince1900_01_01 / 365;
572       do {
573         previousYear = currentYear - 1;
574         if (previousYear < 0) {
575           /* previousYear is divided and truncated towards minus infinite. */
576           /* This is done with: quotient = (dividend + 1) / divisor - 1; */
577           dayJan1 = previousYear * 365 +
578                     (currentYear / 4 - 1) -
579                     (currentYear / 100 - 1) +
580                     (currentYear / 400 - 1) -
581                     DAYS_FROM_0_TO_1900;
582         } else {
583           dayJan1 = previousYear * 365 +
584                     previousYear / 4 -
585                     previousYear / 100 +
586                     previousYear / 400 -
587                     DAYS_FROM_0_TO_1900;
588         } /* if */
589         yearDay = daysSince1900_01_01 - dayJan1;
590         if ((currentYear % 4 == 0 && currentYear % 100 != 0) || currentYear % 400 == 0) {
591           leapYear = 1;
592         } else {
593           leapYear = 0;
594         } /* if */
595         if (yearDay >= (leapYear ? 366 : 365)) {
596           /* Add at least one year. Underestimate the added number */
597           /* of years, by assuming that every year is a leap year. */
598           currentYear += (yearDay - (leapYear ? 366 : 365)) / 366 + 1;
599         } /* if */
600       } while (yearDay >= (leapYear ? 366 : 365));
601     } else {
602       currentYear = 1900 + daysSince1900_01_01 / 365;
603       do {
604         previousYear = currentYear - 1;
605         dayJan1 = previousYear * 365 +
606                   previousYear / 4 -
607                   previousYear / 100 +
608                   previousYear / 400 -
609                   DAYS_FROM_0_TO_1900;
610         yearDay = daysSince1900_01_01 - dayJan1;
611         if (yearDay < 0) {
612           /* Subtract at least one year. Underestimate the subtracted     */
613           /* number of years, by assuming that every year is a leap year. */
614           currentYear -= (-yearDay - 1) / 366 + 1;
615         } /* if */
616       } while (yearDay < 0);
617     } /* if */
618     if ((currentYear % 4 == 0 && currentYear % 100 != 0) || currentYear % 400 == 0) {
619       leapYear = 1;
620     } else {
621       leapYear = 0;
622     } /* if */
623     for (monthIdx = 0; monthIdx < 12 && yearDay >= yearDays[leapYear][monthIdx]; monthIdx++) ;
624     *year = currentYear;
625     *month = monthIdx;
626     *day = yearDay - yearDays[leapYear][monthIdx - 1] + 1;
627     logFunction(printf("dateFromDaysSince1900 -> " F_D(04) "-" F_D(02) "-" F_D(02) "\n",
628                        *year, *month, *day););
629   } /* dateFromDaysSince1900 */
630 
631 
632 
633 /**
634  *  Assign date and time from a string with the given 'isoDate'.
635  *  Possible formats for 'isoData' are "yyyy-mm-dd hh:mm:ss.uuuuuu",
636  *  "yyyy-mm-dd hh:mm:ss", "yyyy-mm-dd hh:mm", "yyyy-mm-dd hh",
637  *  "yyyy-mm-dd", "yyyy-mm", "hh:mm:ss.uuuuuu", "hh:mm:ss" and "hh:mm".
638  *  @param isoDate String with an ISO date/time.
639  */
assignTime(const_cstriType isoDate,intType * year,intType * month,intType * day,intType * hour,intType * minute,intType * second,intType * microSecond,boolType * isTime)640 boolType assignTime (const_cstriType isoDate, intType *year, intType *month,
641     intType *day, intType *hour, intType *minute, intType *second,
642     intType *microSecond, boolType *isTime)
643 
644   {
645     int numAssigned;
646     memSizeType microsecStriLen;
647     char microsecStri[6 + NULL_TERMINATION_LEN];
648     boolType okay = FALSE;
649 
650   /* assignTime */
651     numAssigned = sscanf(isoDate,
652                          FMT_D "-" FMT_U "-" FMT_U " "
653                          FMT_U ":" FMT_U ":" FMT_U ".%6s",
654                          year, month, day, hour, minute, second,
655                          microsecStri);
656     if (numAssigned >= 2) {
657       *isTime = FALSE;
658       if (numAssigned == 7) {
659         microsecStriLen = (memSizeType) strlen(microsecStri);
660         if (microsecStriLen < 6) {
661           memset(&microsecStri[microsecStriLen], '0', 6 - microsecStriLen);
662           microsecStri[6] = '\0';
663         } /* if */
664         okay = sscanf((const char *) microsecStri, FMT_D, microSecond) == 1;
665       } else {
666         okay = TRUE;
667         *microSecond = 0;
668         if (numAssigned <= 5) {
669           *second = 0;
670           if (numAssigned <= 4) {
671             *minute = 0;
672             if (numAssigned <= 3) {
673               *hour = 0;
674               if (numAssigned <= 2) {
675                 *day = 1;
676               } /* if */
677             } /* if */
678           } /* if */
679         } /* if */
680       } /* if */
681     } else {
682       numAssigned = sscanf(isoDate,
683                            FMT_U ":" FMT_U ":" FMT_U ".%6s",
684                            hour, minute, second, microsecStri);
685       if (numAssigned >= 2) {
686         *year        = 0;
687         *month       = 1;
688         *day         = 1;
689         *microSecond = 0;
690         *isTime = TRUE;
691         if (numAssigned == 4) {
692           microsecStriLen = (memSizeType) strlen(microsecStri);
693           if (microsecStriLen < 6) {
694             memset(&microsecStri[microsecStriLen], '0', 6 - microsecStriLen);
695             microsecStri[6] = '\0';
696           } /* if */
697           okay = sscanf((const char *) microsecStri, FMT_D, microSecond) == 1;
698         } else {
699           okay = TRUE;
700           *microSecond = 0;
701           if (numAssigned <= 2) {
702             *second = 0;
703           } /* if */
704         } /* if */
705       } /* if */
706     } /* if */
707     logFunction(printf("assignTime(" F_D(04) "-" F_D(02) "-" F_D(02) " "
708                                      F_D(02) ":" F_D(02) ":" F_D(02) "."
709                                      F_D(06) ", %d) --> %d\n",
710                        *year, *month, *day, *hour, *minute, *second,
711                        *microSecond, *isTime, okay););
712     return okay;
713   } /* assignTime */
714