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