2 * @brief date filtering using value ranges
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
23 #include "datevalue.h"
37 class DateRangeLimit
{
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 {
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
];
64 DateRangeLimit() { tm
.tm_sec
= -1; }
66 DateRangeLimit(const string
& str
, bool start
);
68 explicit DateRangeLimit(time_t secs
) {
70 if (gmtime_r(&secs
, &tm
) == NULL
) {
74 // Not thread-safe, but that isn't important for our uses here.
75 struct tm
* r
= gmtime(&secs
);
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
) {
100 return start
? string() : string(4, '\xff');
102 time_t s
= timegm(&tm
);
104 // This encoding can't represent dates before the epoch, so treat
105 // the range as having an open start.
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
)
128 memset(&tm
, 0, sizeof(struct tm
));
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);
157 if (!start
) tm
.tm_mday
= month_length();
161 DateRangeLimit::format(bool start
) const
164 return start
? string() : string(1, '~');
167 char fmt
[] = "%Y%m%d%H%M%S";
168 size_t fmt_len
= sizeof(fmt
) - 1;
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) {
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) {
218 size_t len
= strftime(buf
, sizeof(buf
), fmt
, &tm
);
220 while (len
&& buf
[len
- 1] == '0') --len
;
222 while (len
&& buf
[len
- 1] == '9') --len
;
224 // Append a character that will sort after any valid extra
229 return string(buf
, len
);
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()) {
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;
249 // If START, END and SPAN are all set, we (somewhat arbitrarily)
252 } else if (start
.is_set()) {
255 // Only SPAN is set, so go back from now.
256 time_t now
= time(NULL
);
257 end
= DateRangeLimit(now
);
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));