2 * @brief date range parsing routines for omega
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
39 last_day(int y
, int m
)
41 static const int l
[13] = {
42 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
44 if (m
!= 2) return l
[m
];
45 return 28 + (y
% 4 == 0 && (y
% 100 != 0 || y
% 400 == 0));
48 // Write exactly w chars to buffer p representing integer v.
50 // The result is left padded with zeros if v < pow(10, w - 1).
52 // If v >= pow(10, w), then the output will show v % pow(10, w) (i.e. the
53 // most significant digits are lost).
55 format_int_fixed_width(char * p
, int v
, int w
)
58 p
[w
] = '0' + (v
% 10);
64 date_range_filter(int y1
, int m1
, int d1
, int y2
, int m2
, int d2
)
66 if (y1
> y2
|| (y1
== y2
&& (m1
> m2
|| (m1
== m2
&& d1
> d2
)))) {
67 // Start is after end.
68 return Xapian::Query::MatchNothing
;
71 format_int_fixed_width(buf
+ 1, y1
, 4);
72 format_int_fixed_width(buf
+ 5, m1
, 2);
73 vector
<Xapian::Query
> v
;
75 int d_last
= last_day(y1
, m1
);
77 if (d1
== 1 && m1
== 1 && y1
!= y2
) {
79 goto whole_year_at_start
;
81 if (y1
== y2
&& m1
== m2
&& d2
< d_last
) {
84 // Deal with any initial partial month
85 if (d1
> 1 || d_end
< d_last
) {
87 for ( ; d1
<= d_end
; ++d1
) {
88 format_int_fixed_width(buf
+ 7, d1
, 2);
89 v
.push_back(Xapian::Query(string_view(buf
, 9)));
93 v
.push_back(Xapian::Query(string_view(buf
, 7)));
96 if (y1
== y2
&& m1
== m2
) {
97 return Xapian::Query(Xapian::Query::OP_OR
, v
.begin(), v
.end());
101 int m_last
= (y1
< y2
) ? 12 : m2
- 1;
102 while (++m1
<= m_last
) {
103 format_int_fixed_width(buf
+ 5, m1
, 2);
105 v
.push_back(Xapian::Query(string_view(buf
, 7)));
112 format_int_fixed_width(buf
+ 1, y1
, 4);
114 v
.push_back(Xapian::Query(string_view(buf
, 5)));
116 format_int_fixed_width(buf
+ 1, y2
, 4);
117 if (m2
== 12 && d2
>= 31) {
118 v
.push_back(Xapian::Query(string_view(buf
, 5)));
119 goto whole_year_at_end
;
122 for (m1
= 1; m1
< m2
; ++m1
) {
123 format_int_fixed_width(buf
+ 5, m1
, 2);
124 v
.push_back(Xapian::Query(string_view(buf
, 7)));
128 format_int_fixed_width(buf
+ 5, m2
, 2);
130 // Deal with any final partial month
131 if (d2
< last_day(y2
, m2
)) {
133 for (d1
= 1; d1
<= d2
; ++d1
) {
134 format_int_fixed_width(buf
+ 7, d1
, 2);
135 v
.push_back(Xapian::Query(string_view(buf
, 9)));
139 v
.push_back(Xapian::Query(string_view(buf
, 7)));
143 return Xapian::Query(Xapian::Query::OP_OR
, v
.begin(), v
.end());
146 static int DIGIT(char ch
) { return ch
- '0'; }
148 static int DIGIT2(const char *p
) {
149 return DIGIT(p
[0]) * 10 + DIGIT(p
[1]);
152 static int DIGIT4(const char *p
) {
153 return DIGIT2(p
) * 100 + DIGIT2(p
+ 2);
157 parse_date(const string
& date
, int *y
, int *m
, int *d
, bool start
)
159 // Support YYYYMMDD, YYYYMM and YYYY.
160 if (date
.size() < 4) {
161 // We default to the start of 1970 when START isn't specified, so it
162 // seems logical to here do that here too.
165 *y
= DIGIT4(date
.c_str());
167 if (date
.size() < 6) {
177 *m
= DIGIT2(date
.c_str() + 4);
178 if (date
.size() < 8) {
182 *d
= last_day(*y
, *m
);
186 *d
= DIGIT2(date
.c_str() + 6);
190 date_range_filter(const string
& date_start
, const string
& date_end
,
191 const string
& date_span
)
193 int y1
, m1
, d1
, y2
, m2
, d2
;
194 if (!date_span
.empty()) {
196 if (!parse_unsigned(date_span
.c_str(), days
)) {
197 throw "Datespan value must be >= 0";
199 time_t secs
= days
* (24 * 60 * 60);
200 if (!date_end
.empty()) {
201 parse_date(date_end
, &y2
, &m2
, &d2
, false);
203 t
.tm_year
= y2
- 1900;
207 t
.tm_min
= t
.tm_sec
= 0;
209 time_t then
= mktime(&t
) - secs
;
210 struct tm
*t2
= localtime(&then
);
211 y1
= t2
->tm_year
+ 1900;
214 } else if (!date_start
.empty()) {
215 parse_date(date_start
, &y1
, &m1
, &d1
, true);
217 t
.tm_year
= y1
- 1900;
221 t
.tm_min
= t
.tm_sec
= 0;
223 time_t end
= mktime(&t
) + secs
;
224 struct tm
*t2
= localtime(&end
);
225 y2
= t2
->tm_year
+ 1900;
229 time_t end
= time(NULL
);
230 struct tm
*t
= localtime(&end
);
231 y2
= t
->tm_year
+ 1900;
234 time_t then
= end
- secs
;
235 struct tm
*t2
= localtime(&then
);
236 y1
= t2
->tm_year
+ 1900;
241 if (date_start
.empty()) {
246 parse_date(date_start
, &y1
, &m1
, &d1
, true);
248 if (date_end
.empty()) {
249 time_t now
= time(NULL
);
250 struct tm
*t
= localtime(&now
);
251 y2
= t
->tm_year
+ 1900;
255 parse_date(date_end
, &y2
, &m2
, &d2
, false);
258 return date_range_filter(y1
, m1
, d1
, y2
, m2
, d2
);