Implement date: date range filter/searches. Remove restriction on pure negative queries
This commit is contained in:
+290
-2
@@ -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++) {
|
||||
{
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user