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
40 last_day(int y
, int m
)
42 static const int l
[13] = {
43 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
45 if (m
!= 2) return l
[m
];
46 return 28 + (y
% 4 == 0 && (y
% 100 != 0 || y
% 400 == 0));
49 // Write exactly w chars to buffer p representing integer v.
51 // The result is left padded with zeros if v < pow(10, w - 1).
53 // If v >= pow(10, w), then the output will show v % pow(10, w) (i.e. the
54 // most significant digits are lost).
56 format_int_fixed_width(char * p
, int v
, int w
)
59 p
[w
] = '0' + (v
% 10);
65 date_range_filter(int y1
, int m1
, int d1
, int y2
, int m2
, int d2
)
67 if (y1
> y2
|| (y1
== y2
&& (m1
> m2
|| (m1
== m2
&& d1
> d2
)))) {
68 // Start is after end.
69 return Xapian::Query::MatchNothing
;
72 format_int_fixed_width(buf
+ 1, y1
, 4);
73 format_int_fixed_width(buf
+ 5, m1
, 2);
74 vector
<Xapian::Query
> v
;
76 int d_last
= last_day(y1
, m1
);
78 if (d1
== 1 && m1
== 1 && y1
!= y2
) {
80 goto whole_year_at_start
;
82 if (y1
== y2
&& m1
== m2
&& d2
< d_last
) {
85 // Deal with any initial partial month
86 if (d1
> 1 || d_end
< d_last
) {
88 for ( ; d1
<= d_end
; ++d1
) {
89 format_int_fixed_width(buf
+ 7, d1
, 2);
90 v
.push_back(Xapian::Query(string_view(buf
, 9)));
94 v
.push_back(Xapian::Query(string_view(buf
, 7)));
97 if (y1
== y2
&& m1
== m2
) {
98 return Xapian::Query(Xapian::Query::OP_OR
, v
.begin(), v
.end());
102 int m_last
= (y1
< y2
) ? 12 : m2
- 1;
103 while (++m1
<= m_last
) {
104 format_int_fixed_width(buf
+ 5, m1
, 2);
106 v
.push_back(Xapian::Query(string_view(buf
, 7)));
113 format_int_fixed_width(buf
+ 1, y1
, 4);
115 v
.push_back(Xapian::Query(string_view(buf
, 5)));
117 format_int_fixed_width(buf
+ 1, y2
, 4);
118 if (m2
== 12 && d2
>= 31) {
119 v
.push_back(Xapian::Query(string_view(buf
, 5)));
120 goto whole_year_at_end
;
123 for (m1
= 1; m1
< m2
; ++m1
) {
124 format_int_fixed_width(buf
+ 5, m1
, 2);
125 v
.push_back(Xapian::Query(string_view(buf
, 7)));
129 format_int_fixed_width(buf
+ 5, m2
, 2);
131 // Deal with any final partial month
132 if (d2
< last_day(y2
, m2
)) {
134 for (d1
= 1; d1
<= d2
; ++d1
) {
135 format_int_fixed_width(buf
+ 7, d1
, 2);
136 v
.push_back(Xapian::Query(string_view(buf
, 9)));
140 v
.push_back(Xapian::Query(string_view(buf
, 7)));
144 return Xapian::Query(Xapian::Query::OP_OR
, v
.begin(), v
.end());
147 static int DIGIT(char ch
) { return ch
- '0'; }
149 static int DIGIT2(const char *p
) {
150 return DIGIT(p
[0]) * 10 + DIGIT(p
[1]);
153 static int DIGIT4(const char *p
) {
154 return DIGIT2(p
) * 100 + DIGIT2(p
+ 2);
158 parse_date(const string
& date
, int *y
, int *m
, int *d
, bool start
)
160 // Support YYYYMMDD, YYYYMM and YYYY.
161 if (date
.size() < 4) {
162 // We default to the start of 1970 when START isn't specified, so it
163 // seems logical to here do that here too.
166 *y
= DIGIT4(date
.c_str());
168 if (date
.size() < 6) {
178 *m
= DIGIT2(date
.c_str() + 4);
179 if (date
.size() < 8) {
183 *d
= last_day(*y
, *m
);
187 *d
= DIGIT2(date
.c_str() + 6);
191 ymd_to_days(int y
, int m
, int d
)
193 static const int m_to_d
[12] = {
209 return d
+ (y
* 365) + m_to_d
[m
- 1] + (y
/ 4) - (y
/ 100) + (y
/ 400);
213 days_to_ymd(int days
, int& y
, int& m
, int& d
)
215 // Clamp to avoid negative years.
216 if (days
< 0) days
= 0;
218 int g
= days
/ 146097 - 1;
219 int dg
= days
% 146097;
220 int c
= (dg
/ 36524 + 1) * 3 / 4;
221 int dc
= dg
- c
* 36524;
224 int a
= (db
/ 365 + 1) * 3 / 4;
225 int da
= db
- a
* 365;
226 int Y
= g
* 400 + c
* 100 + b
* 4 + a
;
227 int M
= (da
* 5 + 308) / 153;
230 d
= da
- (M
+ 2) * 153 / 5 + 123;
234 date_range_filter(const string
& date_start
, const string
& date_end
,
235 const string
& date_span
)
237 int y1
, m1
, d1
, y2
, m2
, d2
;
238 if (!date_span
.empty()) {
240 if (!parse_unsigned(date_span
.c_str(), days
)) {
241 throw "Datespan value must be >= 0";
243 if (!date_end
.empty()) {
244 parse_date(date_end
, &y2
, &m2
, &d2
, false);
245 int then
= ymd_to_days(y2
, m2
, d2
) - days
;
246 days_to_ymd(then
, y1
, m1
, d1
);
247 } else if (!date_start
.empty()) {
248 parse_date(date_start
, &y1
, &m1
, &d1
, true);
249 int end
= ymd_to_days(y1
, m1
, d1
) + days
;
250 days_to_ymd(end
, y2
, m2
, d2
);
252 time_t end
= time(NULL
);
253 struct tm
*t
= localtime(&end
);
254 y2
= t
->tm_year
+ 1900;
257 parse_date(date_end
, &y2
, &m2
, &d2
, false);
258 int then
= ymd_to_days(y2
, m2
, d2
) - days
;
259 days_to_ymd(then
, y1
, m1
, d1
);
262 if (date_start
.empty()) {
267 parse_date(date_start
, &y1
, &m1
, &d1
, true);
269 if (date_end
.empty()) {
270 time_t now
= time(NULL
);
271 struct tm
*t
= localtime(&now
);
272 y2
= t
->tm_year
+ 1900;
276 parse_date(date_end
, &y2
, &m2
, &d2
, false);
279 return date_range_filter(y1
, m1
, d1
, y2
, m2
, d2
);