/src/htslib/hts_time_funcs.h
Line | Count | Source (jump to first uncovered line) |
1 | | /* hts_time_funcs.h -- Implementations of non-standard time functions |
2 | | |
3 | | Copyright (C) 2022 Genome Research Ltd. |
4 | | |
5 | | Author: Rob Davies <rmd@sanger.ac.uk> |
6 | | |
7 | | Permission is hereby granted, free of charge, to any person obtaining a copy |
8 | | of this software and associated documentation files (the "Software"), to deal |
9 | | in the Software without restriction, including without limitation the rights |
10 | | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
11 | | copies of the Software, and to permit persons to whom the Software is |
12 | | furnished to do so, subject to the following conditions: |
13 | | |
14 | | The above copyright notice and this permission notice shall be included in |
15 | | all copies or substantial portions of the Software. |
16 | | |
17 | | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
18 | | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
19 | | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
20 | | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
21 | | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
22 | | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
23 | | DEALINGS IN THE SOFTWARE. */ |
24 | | |
25 | | /* |
26 | | This mainly exists because timegm() is not a standard function, and so |
27 | | Cannot be used in portable code. Unfortunately the standard one (mktime) |
28 | | always takes the local timezone into accout so doing a UTC conversion |
29 | | with it involves changing the TZ environment variable, which is rather |
30 | | messy and not likely to go well with threaded code. |
31 | | |
32 | | The code here is a much simplified version of the BSD timegm() implementation. |
33 | | It currently rejects dates before 1970, avoiding problems with -ve time_t. |
34 | | It also works strictly in UTC, so doesn't have to worry about tm_isdst |
35 | | which makes the calculation much easier. |
36 | | |
37 | | Some of this is derived from BSD sources, for example |
38 | | https://github.com/NetBSD/src/blob/trunk/lib/libc/time/localtime.c |
39 | | which state: |
40 | | |
41 | | ** This file is in the public domain, so clarified as of |
42 | | ** 1996-06-05 by Arthur David Olson. |
43 | | |
44 | | Non-derived code is copyright as above. |
45 | | */ |
46 | | |
47 | | #include <stdint.h> |
48 | | #include <limits.h> |
49 | | #include <errno.h> |
50 | | #include <time.h> |
51 | | |
52 | 0 | static inline int hts_time_normalise(int *tens, int *units, int base) { |
53 | 0 | if (*units < 0 || *units >= base) { |
54 | 0 | int delta = *units >= 0 ? *units / base : (-1 - (-1 - *units) / base); |
55 | 0 | int64_t tmp = (int64_t) (*tens) + delta; |
56 | 0 | if (tmp < INT_MIN || tmp > INT_MAX) return 1; |
57 | 0 | *tens = tmp; |
58 | 0 | *units -= delta * base; |
59 | 0 | } |
60 | 0 | return 0; |
61 | 0 | } |
62 | | |
63 | 0 | static inline int hts_year_is_leap(int64_t year) { |
64 | 0 | return ((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0); |
65 | 0 | } |
66 | | |
67 | | // Number of leap years to start of year |
68 | | // Only works for year >= 1. |
69 | 0 | static inline int64_t hts_leaps_to_year_start(int64_t year) { |
70 | 0 | --year; |
71 | 0 | return year / 4 - year / 100 + year / 400; |
72 | 0 | } |
73 | | |
74 | | static inline int hts_time_normalise_tm(struct tm *t) |
75 | 0 | { |
76 | 0 | const int days_per_mon[2][12] = { |
77 | 0 | { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }, |
78 | 0 | { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 } |
79 | 0 | }; |
80 | 0 | const int year_days[2] = { 365, 366 }; |
81 | 0 | int overflow = 0; |
82 | 0 | int64_t year; |
83 | |
|
84 | 0 | if (t->tm_sec > 62) { |
85 | 0 | overflow |= hts_time_normalise(&t->tm_min, &t->tm_sec, 60); |
86 | 0 | } |
87 | 0 | overflow |= hts_time_normalise(&t->tm_hour, &t->tm_min, 60); |
88 | 0 | overflow |= hts_time_normalise(&t->tm_mday, &t->tm_hour, 24); |
89 | 0 | overflow |= hts_time_normalise(&t->tm_year, &t->tm_mon, 12); |
90 | 0 | if (overflow) |
91 | 0 | return 1; |
92 | | |
93 | 0 | year = (int64_t) t->tm_year + 1900LL; |
94 | 0 | while (t->tm_mday <= 0) { |
95 | 0 | --year; |
96 | 0 | t->tm_mday += year_days[hts_year_is_leap(year + (1 < t->tm_mon))]; |
97 | 0 | } |
98 | 0 | while (t->tm_mday > 366) { |
99 | 0 | t->tm_mday -= year_days[hts_year_is_leap(year + (1 < t->tm_mon))]; |
100 | 0 | ++year; |
101 | 0 | } |
102 | 0 | for (;;) { |
103 | 0 | int mdays = days_per_mon[hts_year_is_leap(year)][t->tm_mon]; |
104 | 0 | if (t->tm_mday <= mdays) |
105 | 0 | break; |
106 | 0 | t->tm_mday -= mdays; |
107 | 0 | t->tm_mon++; |
108 | 0 | if (t->tm_mon >= 12) { |
109 | 0 | year++; |
110 | 0 | t->tm_mon = 0; |
111 | 0 | } |
112 | 0 | } |
113 | 0 | year -= 1900; |
114 | 0 | if (year != t->tm_year) { |
115 | 0 | if (year < INT_MIN || year > INT_MAX) |
116 | 0 | return 1; |
117 | 0 | t->tm_year = year; |
118 | 0 | } |
119 | 0 | return 0; |
120 | 0 | } |
121 | | |
122 | | /** |
123 | | * Convert broken-down time to an equivalent time_t value |
124 | | * @param target Target broken-down time structure |
125 | | * @return Equivalent time_t value on success; -1 on failure |
126 | | * |
127 | | * This function first normalises the time in @p target so that the |
128 | | * structure members are in the valid range. It then calculates the |
129 | | * number of seconds (ignoring leap seconds) between midnight Jan 1st 1970 |
130 | | * and the target date. |
131 | | * |
132 | | * If @p target is outside the range that can be represented in a time_t, |
133 | | * or tm_year is less than 70 (which would return a negative value) then |
134 | | * it returns -1 and sets errno to EOVERFLOW. |
135 | | */ |
136 | | |
137 | | static inline time_t hts_time_gm(struct tm *target) |
138 | 0 | { |
139 | 0 | int month_start[2][12] = { |
140 | 0 | { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 }, |
141 | 0 | { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335 } |
142 | 0 | }; |
143 | 0 | int years_from_epoch, leaps, days; |
144 | 0 | int64_t secs; |
145 | |
|
146 | 0 | if (hts_time_normalise_tm(target) != 0) |
147 | 0 | goto overflow; |
148 | | |
149 | 0 | if (target->tm_year < 70) |
150 | 0 | goto overflow; |
151 | | |
152 | 0 | years_from_epoch = target->tm_year - 70; |
153 | 0 | leaps = (hts_leaps_to_year_start(target->tm_year + 1900) |
154 | 0 | - hts_leaps_to_year_start(1970)); |
155 | 0 | days = ((365 * (years_from_epoch - leaps) + 366 * leaps) |
156 | 0 | + month_start[hts_year_is_leap(target->tm_year + 1900)][target->tm_mon] |
157 | 0 | + target->tm_mday - 1); |
158 | 0 | secs = ((int64_t) days * 86400LL |
159 | 0 | + target->tm_hour * 3600 |
160 | 0 | + target->tm_min * 60 |
161 | 0 | + target->tm_sec); |
162 | 0 | if (sizeof(time_t) < 8 && secs > INT_MAX) |
163 | 0 | goto overflow; |
164 | | |
165 | 0 | return (time_t) secs; |
166 | | |
167 | 0 | overflow: |
168 | 0 | errno = EOVERFLOW; |
169 | 0 | return (time_t) -1; |
170 | 0 | } |