Implement date: date range filter/searches. Remove restriction on pure negative queries

This commit is contained in:
Jean-Francois Dockes
2010-09-11 12:07:53 +02:00
parent 3a6b34d24b
commit ceb996c8fb
8 changed files with 551 additions and 55 deletions
+290 -2
View File
@@ -29,6 +29,7 @@ static char rcsid[] = "@(#$Id: smallut.cpp,v 1.35 2008-11-19 10:06:49 dockes Exp
#include <sys/stat.h>
#include <string>
#include <iostream>
#include "smallut.h"
#include "utf8iter.h"
@@ -281,6 +282,7 @@ template <class T> bool stringToStrings(const string &s, T &tokens,
}
return true;
}
bool stringToStrings(const string &s, list<string> &tokens,
const string& as)
{
@@ -707,10 +709,253 @@ float Chrono::secs(int frozen)
gettime(CLOCK_REALTIME, &tv);
float secs = (float)(frozen?frozen_tv.tv_sec:tv.tv_sec - m_secs);
float nsecs = (float)(frozen?frozen_tv.tv_nsec:tv.tv_nsec - m_nsecs);
//fprintf(stderr, "secs %.2f nsecs %.2f\n", secs, nsecs);
return secs + nsecs * 1e-9;
}
// Date is Y[-M[-D]]
static bool parsedate(vector<string>::const_iterator& it,
vector<string>::const_iterator end, DateInterval *dip)
{
dip->y1 = dip->m1 = dip->d1 = dip->y2 = dip->m2 = dip->d2 = 0;
if (it == end || sscanf(it++->c_str(), "%d", &dip->y1) != 1) {
return false;
}
if (it == end || *it == "/")
return true;
if (*it++ != "-") {
return false;
}
if (it == end || sscanf(it++->c_str(), "%d", &dip->m1) != 1) {
return false;
}
if (it == end || *it == "/")
return true;
if (*it++ != "-") {
return false;
}
if (it == end || sscanf(it++->c_str(), "%d", &dip->d1) != 1) {
return -1;
}
return true;
}
// Called with the 'P' already processed. Period ends at end of string
// or at '/'. We dont' do a lot effort at validation and will happily
// accept 10Y1Y4Y (the last wins)
static bool parseperiod(vector<string>::const_iterator& it,
vector<string>::const_iterator end, DateInterval *dip)
{
dip->y1 = dip->m1 = dip->d1 = dip->y2 = dip->m2 = dip->d2 = 0;
while (it != end) {
int value;
if (sscanf(it++->c_str(), "%d", &value) != 1) {
return false;
}
if (it == end || it->empty())
return false;
switch (it->at(0)) {
case 'Y': case 'y': dip->y1 = value;break;
case 'M': case 'm': dip->m1 = value;break;
case 'D': case 'd': dip->d1 = value;break;
default: return false;
}
it++;
if (it == end)
return true;
if (*it == "/") {
return true;
}
}
return true;
}
static void cerrdip(const string& s, DateInterval *dip)
{
cerr << s << dip->y1 << "-" << dip->m1 << "-" << dip->d1 << "/"
<< dip->y2 << "-" << dip->m2 << "-" << dip->d2
<< endl;
}
// Compute date + period. Won't work out of the unix era.
// or pre-1970 dates. Just convert everything to unixtime and
// seconds (with average durations for months/years), add and convert
// back
static bool addperiod(DateInterval *dp, DateInterval *pp)
{
struct tm tm;
// Create a struct tm with possibly non normalized fields and let
// timegm sort it out
memset(&tm, 0, sizeof(tm));
tm.tm_year = dp->y1 - 1900 + pp->y1;
tm.tm_mon = dp->m1 + pp->m1 -1;
tm.tm_mday = dp->d1 + pp->d1;
time_t tres = timegm(&tm);
// Convert back to normalized tm, then output
gmtime_r(&tres, &tm);
dp->y1 = tm.tm_year + 1900;
dp->m1 = tm.tm_mon + 1;
dp->d1 = tm.tm_mday;
//cerrdip("Addperiod return", dp);
return true;
}
int monthdays(int mon, int year)
{
switch (mon) {
case 2: return (year % 4) == 0 ? 29 : 28;
case 1:case 3:case 5:case 7: case 8:case 10:case 12: return 31;
default: return 30;
}
}
bool parsedateinterval(const string& s, DateInterval *dip)
{
vector<string> vs;
dip->y1 = dip->m1 = dip->d1 = dip->y2 = dip->m2 = dip->d2 = 0;
DateInterval p1, p2, d1, d2;
p1 = p2 = d1 = d2 = *dip;
bool hasp1 = false, hasp2 = false, hasd1 = false, hasd2 = false,
hasslash = false;
if (!stringToStrings(s, vs, "PYMDpymd-/")) {
return false;
}
if (vs.empty())
return false;
vector<string>::const_iterator it = vs.begin();
if (*it == "P" || *it == "p") {
it++;
if (!parseperiod(it, vs.end(), &p1)) {
return false;
}
hasp1 = true;
//cerrdip("p1", &p1);
p1.y1 = -p1.y1;
p1.m1 = -p1.m1;
p1.d1 = -p1.d1;
} else if (*it == "/") {
hasslash = true;
goto secondelt;
} else {
if (!parsedate(it, vs.end(), &d1)) {
return false;
}
hasd1 = true;
}
// Got one element and/or /
secondelt:
if (it != vs.end()) {
if (*it != "/") {
return false;
}
hasslash = true;
it++;
if (it == vs.end()) {
// ok
} else if (*it == "P" || *it == "p") {
it++;
if (!parseperiod(it, vs.end(), &p2)) {
return false;
}
hasp2 = true;
} else {
if (!parsedate(it, vs.end(), &d2)) {
return false;
}
hasd2 = true;
}
}
// 2 periods dont' make sense
if (hasp1 && hasp2) {
return false;
}
// Nothing at all doesn't either
if (!hasp1 && !hasd1 && !hasp2 && !hasd2) {
return false;
}
// Empty part means today IF other part is period, else means
// forever (stays at 0)
time_t now = time(0);
struct tm *tmnow = gmtime(&now);
if ((!hasp1 && !hasd1) && hasp2) {
d1.y1 = 1900 + tmnow->tm_year;
d1.m1 = tmnow->tm_mon + 1;
d1.d1 = tmnow->tm_mday;
hasd1 = true;
} else if ((!hasp2 && !hasd2) && hasp1) {
d2.y1 = 1900 + tmnow->tm_year;
d2.m1 = tmnow->tm_mon + 1;
d2.d1 = tmnow->tm_mday;
hasd2 = true;
}
// Incomplete dates have different meanings depending if there is
// a period or not (actual or infinite indicated by a / + empty)
//
// If there is no explicit period, an incomplete date indicates a
// period of the size of the uncompleted elements. Ex: 1999
// actually means 1999/P12M
//
// If there is a period, the incomplete date should be extended
// to the beginning or end of the unspecified portion. Ex: 1999/
// means 1999-01-01/ and /1999 means /1999-12-31
if (hasd1) {
if (!(hasslash || hasp2)) {
if (d1.m1 == 0) {
p2.m1 = 12;
d1.m1 = 1;
d1.d1 = 1;
} else if (d1.d1 == 0) {
d1.d1 = 1;
p2.d1 = monthdays(d1.m1, d1.y1);
}
hasp2 = true;
} else {
if (d1.m1 == 0) {
d1.m1 = 1;
d1.d1 = 1;
} else if (d1.d1 == 0) {
d1.d1 = 1;
}
}
}
// if hasd2 is true we had a /
if (hasd2) {
if (d2.m1 == 0) {
d2.m1 = 12;
d2.d1 = 31;
} else if (d2.d1 == 0) {
d2.d1 = monthdays(d2.m1, d2.y1);
}
}
if (hasp1) {
// Compute d1
d1 = d2;
if (!addperiod(&d1, &p1)) {
return false;
}
} else if (hasp2) {
// Compute d2
d2 = d1;
if (!addperiod(&d2, &p2)) {
return false;
}
}
dip->y1 = d1.y1;
dip->m1 = d1.m1;
dip->d1 = d1.d1;
dip->y2 = d2.y1;
dip->m2 = d2.m1;
dip->d2 = d2.d1;
return true;
}
#else
#include <string>
@@ -750,13 +995,33 @@ struct spair suffpairs[] = {
};
int nsuffpairs = sizeof(suffpairs) / sizeof(struct spair);
// Periods test strings
const char* periods[] = {
"2001", // Year 2001
"2001/", // 2001 or later
"2001/P3Y", // 2001 -> 2004 or 2005, ambiguous
"2001-01-01/P3Y", // 01-2001 -> 01 2004
"2001-03-03/2001-05-01", // Explicit one
"P3M/", // 3 months ago to now
"P1Y1M/2001-03-01", // 2000-02-01/2001-03-01
"/2001", // From the epoch to the end of 2001
};
const int nperiods = sizeof(periods) / sizeof(char*);
const char *thisprog;
static void cerrdip(const string& s, DateInterval *dip)
{
cerr << s << dip->y1 << "-" << dip->m1 << "-" << dip->d1 << "/"
<< dip->y2 << "-" << dip->m2 << "-" << dip->d2
<< endl;
}
int main(int argc, char **argv)
{
thisprog = *argv++;argc--;
#if 1
#if 0
if (argc <=0 ) {
cerr << "Usage: smallut <stringtosplit>" << endl;
exit(1);
@@ -771,6 +1036,29 @@ int main(int argc, char **argv)
cerr << "[" << *it << "] ";
cerr << endl;
exit(0);
#elif 0
if (argc <=0 ) {
cerr << "Usage: smallut <dateinterval>" << endl;
exit(1);
}
string s = *argv++;argc--;
DateInterval di;
if (!parsedateinterval(s, &di)) {
cerr << "Parse failed" << endl;
exit(1);
}
cerrdip("", &di);
exit(0);
#elif 1
DateInterval di;
for (int i = 0; i < nperiods; i++) {
if (!parsedateinterval(periods[i], &di)) {
cerr << "Parsing failed for [" << periods[i] << "]" << endl;
} else {
cerrdip(string(periods[i]).append(" : "), &di);
}
}
exit(0);
#elif 0
for (int i = 0; i < npairs; i++) {
{
+17
View File
@@ -56,6 +56,23 @@ extern int stringisuffcmp(const string& s1, const string& s2);
// Compare charset names, removing the more common spelling variations
extern bool samecharset(const string &cs1, const string &cs2);
// Parse date interval specifier into pair of y,m,d dates. The format
// for the time interval is based on a subset of iso 8601 with
// the addition of open intervals, and removal of all time indications.
// 'P' is the Period indicator, it's followed by a length in
// years/months/days (or any subset thereof)
// Dates: YYYY-MM-DD YYYY-MM YYYY
// Periods: P[nY][nM][nD] where n is an integer value.
// At least one of YMD must be specified
// The separator for the interval is /. Interval examples
// YYYY/ (from YYYY) YYYY-MM-DD/P3Y (3 years after date) etc.
// This returns a pair of y,m,d dates.
struct DateInterval {
int y1;int m1;int d1; int y2;int m2;int d2;
};
bool parsedateinterval(const string&s, DateInterval *di);
int monthdays(int mon, int year);
/**
* Parse input string into list of strings.
*