[ci] Test Tcl bindings for dragonfly/freebsd
[xapian.git] / xapian-applications / omega / date.cc
bloba76db8d76b99ec248c3db75180d0765e51bdea63
1 /** @file
2 * @brief date range parsing routines for omega
3 */
4 /* Copyright 1999,2000,2001 BrightStation PLC
5 * Copyright 2001 James Aylett
6 * Copyright 2001,2002 Ananova Ltd
7 * Copyright 2002 Intercede 1749 Ltd
8 * Copyright 2002,2003,2006,2014,2016,2017,2018,2024 Olly Betts
10 * This program is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU General Public License as
12 * published by the Free Software Foundation; either version 2 of the
13 * License, or (at your option) any later version.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
23 * USA
26 #include <config.h>
28 #include "date.h"
30 #include <vector>
32 #include <cstdlib>
33 #include <ctime>
34 #include "parseint.h"
35 #include "timegm.h"
37 using namespace std;
39 static int
40 last_day(int y, int m)
42 static const int l[13] = {
43 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
45 if (m != 2) return l[m];
46 return 28 + (y % 4 == 0 && (y % 100 != 0 || y % 400 == 0));
49 // Write exactly w chars to buffer p representing integer v.
51 // The result is left padded with zeros if v < pow(10, w - 1).
53 // If v >= pow(10, w), then the output will show v % pow(10, w) (i.e. the
54 // most significant digits are lost).
55 static void
56 format_int_fixed_width(char * p, int v, int w)
58 while (--w >= 0) {
59 p[w] = '0' + (v % 10);
60 v /= 10;
64 static Xapian::Query
65 date_range_filter(int y1, int m1, int d1, int y2, int m2, int d2)
67 if (y1 > y2 || (y1 == y2 && (m1 > m2 || (m1 == m2 && d1 > d2)))) {
68 // Start is after end.
69 return Xapian::Query::MatchNothing;
71 char buf[10];
72 format_int_fixed_width(buf + 1, y1, 4);
73 format_int_fixed_width(buf + 5, m1, 2);
74 vector<Xapian::Query> v;
76 int d_last = last_day(y1, m1);
77 int d_end = d_last;
78 if (d1 == 1 && m1 == 1 && y1 != y2) {
79 --y1;
80 goto whole_year_at_start;
82 if (y1 == y2 && m1 == m2 && d2 < d_last) {
83 d_end = d2;
85 // Deal with any initial partial month
86 if (d1 > 1 || d_end < d_last) {
87 buf[0] = 'D';
88 for ( ; d1 <= d_end; ++d1) {
89 format_int_fixed_width(buf + 7, d1, 2);
90 v.push_back(Xapian::Query(string_view(buf, 9)));
92 } else {
93 buf[0] = 'M';
94 v.push_back(Xapian::Query(string_view(buf, 7)));
97 if (y1 == y2 && m1 == m2) {
98 return Xapian::Query(Xapian::Query::OP_OR, v.begin(), v.end());
102 int m_last = (y1 < y2) ? 12 : m2 - 1;
103 while (++m1 <= m_last) {
104 format_int_fixed_width(buf + 5, m1, 2);
105 buf[0] = 'M';
106 v.push_back(Xapian::Query(string_view(buf, 7)));
110 if (y1 < y2) {
111 whole_year_at_start:
112 while (++y1 < y2) {
113 format_int_fixed_width(buf + 1, y1, 4);
114 buf[0] = 'Y';
115 v.push_back(Xapian::Query(string_view(buf, 5)));
117 format_int_fixed_width(buf + 1, y2, 4);
118 if (m2 == 12 && d2 >= 31) {
119 v.push_back(Xapian::Query(string_view(buf, 5)));
120 goto whole_year_at_end;
122 buf[0] = 'M';
123 for (m1 = 1; m1 < m2; ++m1) {
124 format_int_fixed_width(buf + 5, m1, 2);
125 v.push_back(Xapian::Query(string_view(buf, 7)));
129 format_int_fixed_width(buf + 5, m2, 2);
131 // Deal with any final partial month
132 if (d2 < last_day(y2, m2)) {
133 buf[0] = 'D';
134 for (d1 = 1; d1 <= d2; ++d1) {
135 format_int_fixed_width(buf + 7, d1, 2);
136 v.push_back(Xapian::Query(string_view(buf, 9)));
138 } else {
139 buf[0] = 'M';
140 v.push_back(Xapian::Query(string_view(buf, 7)));
143 whole_year_at_end:
144 return Xapian::Query(Xapian::Query::OP_OR, v.begin(), v.end());
147 static int DIGIT(char ch) { return ch - '0'; }
149 static int DIGIT2(const char *p) {
150 return DIGIT(p[0]) * 10 + DIGIT(p[1]);
153 static int DIGIT4(const char *p) {
154 return DIGIT2(p) * 100 + DIGIT2(p + 2);
157 static void
158 parse_date(const string & date, int *y, int *m, int *d, bool start)
160 // Support YYYYMMDD, YYYYMM and YYYY.
161 if (date.size() < 4) {
162 // We default to the start of 1970 when START isn't specified, so it
163 // seems logical to here do that here too.
164 *y = 1970;
165 } else {
166 *y = DIGIT4(date.c_str());
168 if (date.size() < 6) {
169 if (start) {
170 *m = 1;
171 *d = 1;
172 } else {
173 *m = 12;
174 *d = 31;
176 return;
178 *m = DIGIT2(date.c_str() + 4);
179 if (date.size() < 8) {
180 if (start) {
181 *d = 1;
182 } else {
183 *d = last_day(*y, *m);
185 return;
187 *d = DIGIT2(date.c_str() + 6);
190 static int
191 ymd_to_days(int y, int m, int d)
193 static const int m_to_d[12] = {
194 0 + 365,
195 31 + 365,
198 120,
199 151,
200 181,
201 212,
202 243,
203 273,
204 304,
207 if (m < 3)
208 --y;
209 return d + (y * 365) + m_to_d[m - 1] + (y / 4) - (y / 100) + (y / 400);
212 static void
213 days_to_ymd(int days, int& y, int& m, int& d)
215 // Clamp to avoid negative years.
216 if (days < 0) days = 0;
217 days += 146037;
218 int g = days / 146097 - 1;
219 int dg = days % 146097;
220 int c = (dg / 36524 + 1) * 3 / 4;
221 int dc = dg - c * 36524;
222 int b = dc / 1461;
223 int db = dc % 1461;
224 int a = (db / 365 + 1) * 3 / 4;
225 int da = db - a * 365;
226 int Y = g * 400 + c * 100 + b * 4 + a;
227 int M = (da * 5 + 308) / 153;
228 y = Y + M / 12;
229 m = M % 12 + 1;
230 d = da - (M + 2) * 153 / 5 + 123;
233 Xapian::Query
234 date_range_filter(const string & date_start, const string & date_end,
235 const string & date_span)
237 int y1, m1, d1, y2, m2, d2;
238 if (!date_span.empty()) {
239 unsigned int days;
240 if (!parse_unsigned(date_span.c_str(), days)) {
241 throw "Datespan value must be >= 0";
243 if (!date_end.empty()) {
244 parse_date(date_end, &y2, &m2, &d2, false);
245 int then = ymd_to_days(y2, m2, d2) - days;
246 days_to_ymd(then, y1, m1, d1);
247 } else if (!date_start.empty()) {
248 parse_date(date_start, &y1, &m1, &d1, true);
249 int end = ymd_to_days(y1, m1, d1) + days;
250 days_to_ymd(end, y2, m2, d2);
251 } else {
252 time_t end = time(NULL);
253 struct tm *t = localtime(&end);
254 y2 = t->tm_year + 1900;
255 m2 = t->tm_mon + 1;
256 d2 = t->tm_mday;
257 parse_date(date_end, &y2, &m2, &d2, false);
258 int then = ymd_to_days(y2, m2, d2) - days;
259 days_to_ymd(then, y1, m1, d1);
261 } else {
262 if (date_start.empty()) {
263 y1 = 1970;
264 m1 = 1;
265 d1 = 1;
266 } else {
267 parse_date(date_start, &y1, &m1, &d1, true);
269 if (date_end.empty()) {
270 time_t now = time(NULL);
271 struct tm *t = localtime(&now);
272 y2 = t->tm_year + 1900;
273 m2 = t->tm_mon + 1;
274 d2 = t->tm_mday;
275 } else {
276 parse_date(date_end, &y2, &m2, &d2, false);
279 return date_range_filter(y1, m1, d1, y2, m2, d2);