Expand some query-related testcases
[xapian.git] / xapian-applications / omega / datevalue.cc
blobb53a1cbc3aa35b20ab4a82ba328535500e7d95d5
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 "timegm.h"
32 #include "values.h"
34 using namespace std;
36 class DateRangeLimit {
37 struct tm tm;
39 static int DIGIT(char ch) { return ch - '0'; }
41 static int DIGIT2(const char *p) {
42 return DIGIT(p[0]) * 10 + DIGIT(p[1]);
45 static int DIGIT4(const char *p) {
46 return DIGIT2(p) * 100 + DIGIT2(p + 2);
49 int is_leap_year() const {
50 int y = tm.tm_year;
51 return (y % 4 == 0 && (y % 100 != 0 || y % 400 == 100));
54 int month_length() const {
55 static const int usual_month_length[12] = {
56 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
58 if (tm.tm_mon == 1 && is_leap_year()) return 29;
59 return usual_month_length[tm.tm_mon];
62 public:
63 DateRangeLimit() { tm.tm_sec = -1; }
65 DateRangeLimit(const string & str, bool start);
67 explicit DateRangeLimit(time_t secs) {
68 #ifdef HAVE_GMTIME_R
69 if (gmtime_r(&secs, &tm) == NULL) {
70 tm.tm_sec = -1;
72 #else
73 // Not thread-safe, but that isn't important for our uses here.
74 struct tm * r = gmtime(&secs);
75 if (r) {
76 tm = *r;
77 } else {
78 tm.tm_sec = -1;
80 #endif
83 bool is_set() const { return tm.tm_sec >= 0; }
85 DateRangeLimit operator-(int span) {
86 if (!is_set()) return *this;
87 return DateRangeLimit(timegm(&tm) - span);
90 DateRangeLimit operator+(int span) {
91 if (!is_set()) return *this;
92 return DateRangeLimit(timegm(&tm) + span);
95 string format(bool start) const;
97 string bin4(bool start) {
98 if (!is_set()) {
99 return start ? string() : string(4, '\xff');
101 time_t s = timegm(&tm);
102 if (s <= 0) {
103 return string();
105 if (sizeof(time_t) > 4 && s >= 0xffffffff) {
106 return string(4, '\xff');
108 return int_to_binary_string(uint32_t(s));
112 DateRangeLimit::DateRangeLimit(const string & str, bool start)
114 if (str.empty()) {
115 tm.tm_sec = -1;
116 return;
119 memset(&tm, 0, sizeof(struct tm));
120 if (start) {
121 tm.tm_mday = 1;
122 } else {
123 tm.tm_mon = 11;
124 tm.tm_hour = 23;
125 tm.tm_min = 59;
126 tm.tm_sec = 59;
128 const char * p = str.data();
129 tm.tm_year = DIGIT4(p) - 1900;
131 if (str.size() >= 6) {
132 tm.tm_mon = DIGIT2(p + 4) - 1;
133 if (str.size() >= 8) {
134 tm.tm_mday = DIGIT2(p + 6);
135 if (str.size() >= 10) {
136 tm.tm_hour = DIGIT2(p + 8);
137 if (str.size() >= 12) {
138 tm.tm_min = DIGIT2(p + 10);
139 if (str.size() >= 14) {
140 tm.tm_sec = DIGIT2(p + 12);
144 return;
148 if (!start) tm.tm_mday = month_length();
151 string
152 DateRangeLimit::format(bool start) const
154 if (!is_set()) {
155 return start ? string() : string(1, '~');
158 char fmt[] = "%Y%m%d%H%M%S";
159 size_t fmt_len = sizeof(fmt) - 1;
161 if (start) {
162 if (tm.tm_sec == 0) {
163 if (tm.tm_min == 0) {
164 if (tm.tm_hour == 0) {
165 if (tm.tm_mday <= 1) {
166 if (tm.tm_mon == 0) {
167 fmt_len = 2;
168 } else {
169 fmt_len = 4;
171 } else {
172 fmt_len = 6;
174 } else {
175 fmt_len = 8;
177 } else {
178 fmt_len = 10;
181 } else {
182 // If there's a leap second right after a range end, it'll just get
183 // treated as being in the range. This doesn't seem like a big
184 // issue, and if we worry about getting that case right, we can never
185 // contract a range unless it ends on second 60, or we know when all
186 // the leap seconds are.
187 if (tm.tm_sec >= 59) {
188 if (tm.tm_min >= 59) {
189 if (tm.tm_hour >= 23) {
190 if (tm.tm_mday >= month_length()) {
191 if (tm.tm_mon >= 11) {
192 fmt_len = 2;
193 } else {
194 fmt_len = 4;
196 } else {
197 fmt_len = 6;
199 } else {
200 fmt_len = 8;
202 } else {
203 fmt_len = 10;
207 fmt[fmt_len] = '\0';
208 char buf[15];
209 size_t len = strftime(buf, sizeof(buf), fmt, &tm);
210 if (start) {
211 while (len && buf[len - 1] == '0') --len;
212 } else {
213 while (len && buf[len - 1] == '9') --len;
214 if (len < 14) {
215 // Append a character that will sort after any valid extra
216 // precision.
217 buf[len++] = '~';
220 return string(buf, len);
223 Xapian::Query
224 date_value_range(bool as_time_t,
225 Xapian::valueno slot,
226 const std::string & date_start,
227 const std::string & date_end,
228 const std::string & date_span)
230 DateRangeLimit start(date_start, true);
231 DateRangeLimit end(date_end, false);
233 if (!date_span.empty()) {
234 time_t span = atoi(date_span.c_str()) * (24 * 60 * 60) - 1;
235 if (end.is_set()) {
236 // If START, END and SPAN are all set, we (somewhat arbitrarily)
237 // ignore START.
238 start = end - span;
239 } else if (start.is_set()) {
240 end = start + span;
241 } else {
242 // Only SPAN is set, so go back from now.
243 time_t now = time(NULL);
244 end = DateRangeLimit(now);
245 start = end - span;
249 if (as_time_t) {
250 return Xapian::Query(Xapian::Query::OP_VALUE_RANGE, slot,
251 start.bin4(true), end.bin4(false));
254 return Xapian::Query(Xapian::Query::OP_VALUE_RANGE, slot,
255 start.format(true), end.format(false));