[ci] Test Tcl bindings for dragonfly/freebsd
[xapian.git] / xapian-applications / omega / datevalue.cc
blobac81d88e5364259bd831de9a910e7adccb774f66
1 /** @file
2 * @brief date filtering using value ranges
3 */
4 /* Copyright (C) 2006,2015,2021 Olly Betts
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
21 #include <config.h>
23 #include "datevalue.h"
25 #include <cstdlib>
26 #include <cstring>
27 #include <ctime>
29 #include <xapian.h>
31 #include "parseint.h"
32 #include "timegm.h"
33 #include "values.h"
35 using namespace std;
37 class DateRangeLimit {
38 struct tm tm;
40 static int DIGIT(char ch) { return ch - '0'; }
42 static int DIGIT2(const char *p) {
43 return DIGIT(p[0]) * 10 + DIGIT(p[1]);
46 static int DIGIT4(const char *p) {
47 return DIGIT2(p) * 100 + DIGIT2(p + 2);
50 int is_leap_year() const {
51 int y = tm.tm_year;
52 return (y % 4 == 0 && (y % 100 != 0 || y % 400 == 100));
55 int month_length() const {
56 static const int usual_month_length[12] = {
57 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
59 if (tm.tm_mon == 1 && is_leap_year()) return 29;
60 return usual_month_length[tm.tm_mon];
63 public:
64 DateRangeLimit() { tm.tm_sec = -1; }
66 DateRangeLimit(const string & str, bool start);
68 explicit DateRangeLimit(time_t secs) {
69 #ifdef HAVE_GMTIME_R
70 if (gmtime_r(&secs, &tm) == NULL) {
71 tm.tm_sec = -1;
73 #else
74 // Not thread-safe, but that isn't important for our uses here.
75 struct tm * r = gmtime(&secs);
76 if (r) {
77 tm = *r;
78 } else {
79 tm.tm_sec = -1;
81 #endif
84 bool is_set() const { return tm.tm_sec >= 0; }
86 DateRangeLimit operator-(int span) {
87 if (!is_set()) return *this;
88 return DateRangeLimit(timegm(&tm) - span);
91 DateRangeLimit operator+(int span) {
92 if (!is_set()) return *this;
93 return DateRangeLimit(timegm(&tm) + span);
96 string format(bool start) const;
98 string bin4(bool start) {
99 if (!is_set()) {
100 return start ? string() : string(4, '\xff');
102 time_t s = timegm(&tm);
103 if (s <= 0) {
104 // This encoding can't represent dates before the epoch, so treat
105 // the range as having an open start.
106 return string();
108 if constexpr(sizeof(time_t) > 4) {
109 if (s >= 0xffffffff) {
110 // This encoding can't represent dates after 0xffffffff, so
111 // clamp the range end to that and Xapian's matcher will see
112 // the range end is >= the slot upper bound and optimise this
113 // to an open upper range end.
114 return string(4, '\xff');
117 return int_to_binary_string(uint32_t(s));
121 DateRangeLimit::DateRangeLimit(const string & str, bool start)
123 if (str.empty()) {
124 tm.tm_sec = -1;
125 return;
128 memset(&tm, 0, sizeof(struct tm));
129 if (start) {
130 tm.tm_mday = 1;
131 } else {
132 tm.tm_mon = 11;
133 tm.tm_hour = 23;
134 tm.tm_min = 59;
135 tm.tm_sec = 59;
137 const char * p = str.data();
138 tm.tm_year = DIGIT4(p) - 1900;
140 if (str.size() >= 6) {
141 tm.tm_mon = DIGIT2(p + 4) - 1;
142 if (str.size() >= 8) {
143 tm.tm_mday = DIGIT2(p + 6);
144 if (str.size() >= 10) {
145 tm.tm_hour = DIGIT2(p + 8);
146 if (str.size() >= 12) {
147 tm.tm_min = DIGIT2(p + 10);
148 if (str.size() >= 14) {
149 tm.tm_sec = DIGIT2(p + 12);
153 return;
157 if (!start) tm.tm_mday = month_length();
160 string
161 DateRangeLimit::format(bool start) const
163 if (!is_set()) {
164 return start ? string() : string(1, '~');
167 char fmt[] = "%Y%m%d%H%M%S";
168 size_t fmt_len = sizeof(fmt) - 1;
170 if (start) {
171 if (tm.tm_sec == 0) {
172 if (tm.tm_min == 0) {
173 if (tm.tm_hour == 0) {
174 if (tm.tm_mday <= 1) {
175 if (tm.tm_mon == 0) {
176 fmt_len = 2;
177 } else {
178 fmt_len = 4;
180 } else {
181 fmt_len = 6;
183 } else {
184 fmt_len = 8;
186 } else {
187 fmt_len = 10;
190 } else {
191 // If there's a leap second right after a range end, it'll just get
192 // treated as being in the range. This doesn't seem like a big
193 // issue, and if we worry about getting that case right, we can never
194 // contract a range unless it ends on second 60, or we know when all
195 // the leap seconds are.
196 if (tm.tm_sec >= 59) {
197 if (tm.tm_min >= 59) {
198 if (tm.tm_hour >= 23) {
199 if (tm.tm_mday >= month_length()) {
200 if (tm.tm_mon >= 11) {
201 fmt_len = 2;
202 } else {
203 fmt_len = 4;
205 } else {
206 fmt_len = 6;
208 } else {
209 fmt_len = 8;
211 } else {
212 fmt_len = 10;
216 fmt[fmt_len] = '\0';
217 char buf[15];
218 size_t len = strftime(buf, sizeof(buf), fmt, &tm);
219 if (start) {
220 while (len && buf[len - 1] == '0') --len;
221 } else {
222 while (len && buf[len - 1] == '9') --len;
223 if (len < 14) {
224 // Append a character that will sort after any valid extra
225 // precision.
226 buf[len++] = '~';
229 return string(buf, len);
232 Xapian::Query
233 date_value_range(bool as_time_t,
234 Xapian::valueno slot,
235 const std::string & date_start,
236 const std::string & date_end,
237 const std::string & date_span)
239 DateRangeLimit start(date_start, true);
240 DateRangeLimit end(date_end, false);
242 if (!date_span.empty()) {
243 unsigned int days;
244 if (!parse_unsigned(date_span.c_str(), days)) {
245 throw "Datespan value must be >= 0";
247 time_t span = days * (24 * 60 * 60) - 1;
248 if (end.is_set()) {
249 // If START, END and SPAN are all set, we (somewhat arbitrarily)
250 // ignore START.
251 start = end - span;
252 } else if (start.is_set()) {
253 end = start + span;
254 } else {
255 // Only SPAN is set, so go back from now.
256 time_t now = time(NULL);
257 end = DateRangeLimit(now);
258 start = end - span;
262 if (as_time_t) {
263 return Xapian::Query(Xapian::Query::OP_VALUE_RANGE, slot,
264 start.bin4(true), end.bin4(false));
267 return Xapian::Query(Xapian::Query::OP_VALUE_RANGE, slot,
268 start.format(true), end.format(false));