Make PostList subclasses return PostList* not Internal*
[xapian.git] / xapian-applications / omega / datevalue.cc
blob0c75fdac84b52b3b656e808c2cc7c6fe4acd8e28
1 /** @file datevalue.cc
2 * @brief date filtering using value ranges
3 */
4 /* Copyright (C) 2006,2015 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 return int_to_binary_string(timegm(&tm));
105 DateRangeLimit::DateRangeLimit(const string & str, bool start)
107 if (str.empty()) {
108 tm.tm_sec = -1;
109 return;
112 memset(&tm, 0, sizeof(struct tm));
113 if (start) {
114 tm.tm_mday = 1;
115 } else {
116 tm.tm_mon = 11;
117 tm.tm_hour = 23;
118 tm.tm_min = 59;
119 tm.tm_sec = 59;
121 const char * p = str.data();
122 tm.tm_year = DIGIT4(p) - 1900;
124 if (str.size() >= 6) {
125 tm.tm_mon = DIGIT2(p + 4) - 1;
126 if (str.size() >= 8) {
127 tm.tm_mday = DIGIT2(p + 6);
128 if (str.size() >= 10) {
129 tm.tm_hour = DIGIT2(p + 8);
130 if (str.size() >= 12) {
131 tm.tm_min = DIGIT2(p + 10);
132 if (str.size() >= 14) {
133 tm.tm_sec = DIGIT2(p + 12);
137 return;
141 if (!start) tm.tm_mday = month_length();
144 string
145 DateRangeLimit::format(bool start) const
147 if (!is_set()) {
148 return start ? string() : string(1, '~');
151 char fmt[] = "%Y%m%d%H%M%S";
152 size_t fmt_len = sizeof(fmt) - 1;
154 if (start) {
155 if (tm.tm_sec == 0) {
156 if (tm.tm_min == 0) {
157 if (tm.tm_hour == 0) {
158 if (tm.tm_mday <= 1) {
159 if (tm.tm_mon == 0) {
160 fmt_len = 2;
161 } else {
162 fmt_len = 4;
164 } else {
165 fmt_len = 6;
167 } else {
168 fmt_len = 8;
170 } else {
171 fmt_len = 10;
174 } else {
175 // If there's a leap second right after a range end, it'll just get
176 // treated as being in the range. This doesn't seem like a big
177 // issue, and if we worry about getting that case right, we can never
178 // contract a range unless it ends on second 60, or we know when all
179 // the leap seconds are.
180 if (tm.tm_sec >= 59) {
181 if (tm.tm_min >= 59) {
182 if (tm.tm_hour >= 23) {
183 if (tm.tm_mday >= month_length()) {
184 if (tm.tm_mon >= 11) {
185 fmt_len = 2;
186 } else {
187 fmt_len = 4;
189 } else {
190 fmt_len = 6;
192 } else {
193 fmt_len = 8;
195 } else {
196 fmt_len = 10;
200 fmt[fmt_len] = '\0';
201 char buf[15];
202 size_t len = strftime(buf, sizeof(buf), fmt, &tm);
203 if (start) {
204 while (len && buf[len - 1] == '0') --len;
205 } else {
206 while (len && buf[len - 1] == '9') --len;
207 if (len < 14) {
208 // Append a character that will sort after any valid extra precision.
209 buf[len++] = '~';
212 return string(buf, len);
215 Xapian::Query
216 date_value_range(bool as_time_t,
217 Xapian::valueno slot,
218 const std::string & date_start,
219 const std::string & date_end,
220 const std::string & date_span)
222 DateRangeLimit start(date_start, true);
223 DateRangeLimit end(date_end, false);
225 if (!date_span.empty()) {
226 time_t span = atoi(date_span.c_str()) * (24 * 60 * 60) - 1;
227 if (end.is_set()) {
228 // If START, END and SPAN are all set, we (somewhat arbitrarily)
229 // ignore START.
230 start = end - span;
231 } else if (start.is_set()) {
232 end = start + span;
233 } else {
234 // Only SPAN is set, so go back from now.
235 time_t now = time(NULL);
236 end = DateRangeLimit(now);
237 start = end - span;
241 if (as_time_t) {
242 return Xapian::Query(Xapian::Query::OP_VALUE_RANGE, slot,
243 start.bin4(true), end.bin4(false));
246 return Xapian::Query(Xapian::Query::OP_VALUE_RANGE, slot,
247 start.format(true), end.format(false));