xref: /netbsd-src/external/mpl/bind/dist/lib/dns/time.c (revision bcda20f65a8566e103791ec395f7f499ef322704)
1 /*	$NetBSD: time.c,v 1.9 2025/01/26 16:25:25 christos Exp $	*/
2 
3 /*
4  * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
5  *
6  * SPDX-License-Identifier: MPL-2.0
7  *
8  * This Source Code Form is subject to the terms of the Mozilla Public
9  * License, v. 2.0. If a copy of the MPL was not distributed with this
10  * file, you can obtain one at https://mozilla.org/MPL/2.0/.
11  *
12  * See the COPYRIGHT file distributed with this work for additional
13  * information regarding copyright ownership.
14  */
15 
16 /*! \file */
17 
18 #include <ctype.h>
19 #include <inttypes.h>
20 #include <stdio.h>
21 #include <time.h>
22 
23 #include <isc/region.h>
24 #include <isc/result.h>
25 #include <isc/serial.h>
26 #include <isc/stdtime.h>
27 #include <isc/string.h>
28 #include <isc/util.h>
29 
30 #include <dns/time.h>
31 
32 static const int days[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
33 
34 isc_result_t
35 dns_time64_totext(int64_t t, isc_buffer_t *target) {
36 	struct tm tm;
37 	char buf[sizeof("!!!!!!YYYY!!!!!!!!MM!!!!!!!!DD!!!!!!!!HH!!!!!!!!MM!!!!"
38 			"!!!!SS")];
39 	int secs;
40 	unsigned int l;
41 	isc_region_t region;
42 
43 /*
44  * Warning. Do NOT use arguments with side effects with these macros.
45  */
46 #define is_leap(y)	 ((((y) % 4) == 0 && ((y) % 100) != 0) || ((y) % 400) == 0)
47 #define year_secs(y)	 ((is_leap(y) ? 366 : 365) * 86400)
48 #define month_secs(m, y) ((days[m] + ((m == 1 && is_leap(y)) ? 1 : 0)) * 86400)
49 
50 	tm.tm_year = 70;
51 	while (t < 0) {
52 		if (tm.tm_year == 0) {
53 			return ISC_R_RANGE;
54 		}
55 		tm.tm_year--;
56 		secs = year_secs(tm.tm_year + 1900);
57 		t += secs;
58 	}
59 	while ((secs = year_secs(tm.tm_year + 1900)) <= t) {
60 		t -= secs;
61 		tm.tm_year++;
62 		if (tm.tm_year + 1900 > 9999) {
63 			return ISC_R_RANGE;
64 		}
65 	}
66 	tm.tm_mon = 0;
67 	while ((secs = month_secs(tm.tm_mon, tm.tm_year + 1900)) <= t) {
68 		t -= secs;
69 		tm.tm_mon++;
70 	}
71 	tm.tm_mday = 1;
72 	while (86400 <= t) {
73 		t -= 86400;
74 		tm.tm_mday++;
75 	}
76 	tm.tm_hour = 0;
77 	while (3600 <= t) {
78 		t -= 3600;
79 		tm.tm_hour++;
80 	}
81 	tm.tm_min = 0;
82 	while (60 <= t) {
83 		t -= 60;
84 		tm.tm_min++;
85 	}
86 	tm.tm_sec = (int)t;
87 	/* yyyy  mm  dd  HH  MM  SS */
88 	snprintf(buf, sizeof(buf), "%04d%02d%02d%02d%02d%02d",
89 		 tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour,
90 		 tm.tm_min, tm.tm_sec);
91 
92 	isc_buffer_availableregion(target, &region);
93 	l = strlen(buf);
94 
95 	if (l > region.length) {
96 		return ISC_R_NOSPACE;
97 	}
98 
99 	memmove(region.base, buf, l);
100 	isc_buffer_add(target, l);
101 	return ISC_R_SUCCESS;
102 }
103 
104 int64_t
105 dns_time64_from32(uint32_t value) {
106 	isc_stdtime_t now = isc_stdtime_now();
107 	int64_t start;
108 	int64_t t;
109 
110 	/*
111 	 * Adjust the time to the closest epoch.  This should be changed
112 	 * to use a 64-bit counterpart to isc_stdtime_now() if one ever
113 	 * is defined, but even the current code is good until the year
114 	 * 2106.
115 	 */
116 
117 	start = (int64_t)now;
118 	if (isc_serial_gt(value, now)) {
119 		t = start + (value - now);
120 	} else {
121 		t = start - (now - value);
122 	}
123 
124 	return t;
125 }
126 
127 isc_result_t
128 dns_time32_totext(uint32_t value, isc_buffer_t *target) {
129 	return dns_time64_totext(dns_time64_from32(value), target);
130 }
131 
132 isc_result_t
133 dns_time64_fromtext(const char *source, int64_t *target) {
134 	int year, month, day, hour, minute, second;
135 	int64_t value;
136 	int secs;
137 	int i;
138 
139 #define RANGE(min, max, value)                      \
140 	do {                                        \
141 		if (value < (min) || value > (max)) \
142 			return ((ISC_R_RANGE));     \
143 	} while (0)
144 
145 	if (strlen(source) != 14U) {
146 		return DNS_R_SYNTAX;
147 	}
148 	/*
149 	 * Confirm the source only consists digits.  sscanf() allows some
150 	 * minor exceptions.
151 	 */
152 	for (i = 0; i < 14; i++) {
153 		if (!isdigit((unsigned char)source[i])) {
154 			return DNS_R_SYNTAX;
155 		}
156 	}
157 	if (sscanf(source, "%4d%2d%2d%2d%2d%2d", &year, &month, &day, &hour,
158 		   &minute, &second) != 6)
159 	{
160 		return DNS_R_SYNTAX;
161 	}
162 
163 	RANGE(0, 9999, year);
164 	RANGE(1, 12, month);
165 	RANGE(1, days[month - 1] + ((month == 2 && is_leap(year)) ? 1 : 0),
166 	      day);
167 #ifdef __COVERITY__
168 	/*
169 	 * Use a simplified range to silence Coverity warning (in
170 	 * arithmetic with day below).
171 	 */
172 	RANGE(1, 31, day);
173 #endif /* __COVERITY__ */
174 
175 	RANGE(0, 23, hour);
176 	RANGE(0, 59, minute);
177 	RANGE(0, 60, second); /* 60 == leap second. */
178 
179 	/*
180 	 * Calculate seconds from epoch.
181 	 * Note: this uses a idealized calendar.
182 	 */
183 	value = second + (60 * minute) + (3600 * hour) + ((day - 1) * 86400);
184 	for (i = 0; i < (month - 1); i++) {
185 		value += days[i] * 86400;
186 	}
187 	if (is_leap(year) && month > 2) {
188 		value += 86400;
189 	}
190 	if (year < 1970) {
191 		for (i = 1969; i >= year; i--) {
192 			secs = (is_leap(i) ? 366 : 365) * 86400;
193 			value -= secs;
194 		}
195 	} else {
196 		for (i = 1970; i < year; i++) {
197 			secs = (is_leap(i) ? 366 : 365) * 86400;
198 			value += secs;
199 		}
200 	}
201 
202 	*target = value;
203 	return ISC_R_SUCCESS;
204 }
205 
206 isc_result_t
207 dns_time32_fromtext(const char *source, uint32_t *target) {
208 	int64_t value64;
209 	isc_result_t result;
210 	result = dns_time64_fromtext(source, &value64);
211 	if (result != ISC_R_SUCCESS) {
212 		return result;
213 	}
214 	*target = (uint32_t)value64;
215 
216 	return ISC_R_SUCCESS;
217 }
218