Formulas can now calculate using dates. E.g. '2022-02-03' + 2 month

pull/708/head
Fredrik Öhrström 2022-11-26 14:15:14 +01:00
rodzic addff11eb7
commit e064c678a6
8 zmienionych plików z 605 dodań i 134 usunięć

Wyświetl plik

@ -140,6 +140,21 @@ namespace
.set(StorageNr(8),StorageNr(19))
);
/*
addNumericFieldWithExtractor(
"history_{storage_counter-7counter}",
"The historic date #.",
PrintProperty::JSON,
Quantity::PointInTime,
VifScaling::Auto,
FieldMatcher::build()
.set(MeasurementType::Instantaneous)
.set(VIFRange::Volume)
.set(StorageNr(8),StorageNr(19)),
Unit::Date
);
*/
addStringFieldWithExtractor(
"device_date_time",
"Date and time when the meter sent the telegram.",

Wyświetl plik

@ -38,7 +38,9 @@ NumericFormulaSquareRoot::~NumericFormulaSquareRoot() { }
double NumericFormulaConstant::calculate(SIUnit to)
{
return siunit().convertTo(constant_, to);
double r {};
siunit().convertTo(constant_, to, &r);
return r;
}
double NumericFormulaMeterField::calculate(SIUnit to_si_unit)
@ -52,7 +54,9 @@ double NumericFormulaMeterField::calculate(SIUnit to_si_unit)
const SIUnit& field_si_unit = toSIUnit(field_unit);
return field_si_unit.convertTo(val, to_si_unit);
double r {};
field_si_unit.convertTo(val, to_si_unit, &r);
return r;
}
double NumericFormulaDVEntryField::calculate(SIUnit to_si_unit)
@ -63,31 +67,66 @@ double NumericFormulaDVEntryField::calculate(SIUnit to_si_unit)
const SIUnit& counter_si_unit = toSIUnit(Unit::COUNTER);
return counter_si_unit.convertTo(val, to_si_unit);
double r {};
counter_si_unit.convertTo(val, to_si_unit, &r);
return r;
}
double NumericFormulaAddition::calculate(SIUnit to)
double NumericFormulaAddition::calculate(SIUnit to_siunit)
{
double l = left_->calculate(to);
double r = right_->calculate(to);
double l = left_->calculate(left_->siunit());
double r = right_->calculate(right_->siunit());
return l+r;
double v {};
SIUnit v_siunit(Unit::COUNTER);
left_->siunit().mathOpTo(MathOp::ADD, l, r, right_->siunit(), &v_siunit, &v);
double result {};
v_siunit.convertTo(v, to_siunit, &result);
if (isDebugEnabled())
{
debug("(formula) ADD %g (%s) %g (%s) --> %g %s --> %g %s\n",
l, left_->siunit().info().c_str(),
r, right_->siunit().info().c_str(),
v, v_siunit.info().c_str(),
result, siunit().info().c_str());
}
return result;
}
double NumericFormulaSubtraction::calculate(SIUnit to)
double NumericFormulaSubtraction::calculate(SIUnit to_siunit)
{
double l = left_->calculate(to);
double r = right_->calculate(to);
double l = left_->calculate(left_->siunit());
double r = right_->calculate(right_->siunit());
return l-r;
double v {};
SIUnit v_siunit(Unit::COUNTER);
left_->siunit().mathOpTo(MathOp::SUB, l, r, right_->siunit(), &v_siunit, &v);
double result {};
v_siunit.convertTo(v, to_siunit, &result);
if (isDebugEnabled())
{
debug("(formula) SUB %g (%s) %g (%s) --> %g %s --> %g %s\n",
l, left_->siunit().info().c_str(),
r, right_->siunit().info().c_str(),
v, v_siunit.info().c_str(),
result, siunit().info().c_str());
}
return result;
}
double NumericFormulaMultiplication::calculate(SIUnit to)
double NumericFormulaMultiplication::calculate(SIUnit to_siunit)
{
double l = left_->calculate(left_->siunit());
double r = right_->calculate(right_->siunit());
double m = l*r;
double v = siunit().convertTo(m, to);
double v {};
siunit().convertTo(m, to_siunit, &v);
if (isDebugEnabled())
{
@ -95,17 +134,18 @@ double NumericFormulaMultiplication::calculate(SIUnit to)
l, left_->siunit().info().c_str(),
r, right_->siunit().info().c_str(),
m,
v, to.info().c_str());
v, to_siunit.info().c_str());
}
return v;
}
double NumericFormulaDivision::calculate(SIUnit to)
double NumericFormulaDivision::calculate(SIUnit to_siunit)
{
double l = left_->calculate(left_->siunit());
double r = right_->calculate(right_->siunit());
double d = l/r;
double v = siunit().convertTo(d, to);
double v {};
siunit().convertTo(d, to_siunit, &v);
if (isDebugEnabled())
{
@ -114,28 +154,30 @@ double NumericFormulaDivision::calculate(SIUnit to)
r, right_->siunit().info().c_str(),
d,
v,
to.info().c_str());
to_siunit.info().c_str());
}
return v;
}
double NumericFormulaExponentiation::calculate(SIUnit to)
double NumericFormulaExponentiation::calculate(SIUnit to_siunit)
{
double l = left_->calculate(to);
double r = right_->calculate(to);
double l = left_->calculate(to_siunit);
double r = right_->calculate(to_siunit);
double p = pow(l,r);
double v = siunit().convertTo(p, to);
double v {};
siunit().convertTo(p, to_siunit, &v);
debug("(formula) %g <-- %g <-- pow %g ^ %g\n", v, p, l, r);
return v;
}
double NumericFormulaSquareRoot::calculate(SIUnit to)
double NumericFormulaSquareRoot::calculate(SIUnit to_siunit)
{
double i = inner_->calculate(inner_->siunit());
double s = sqrt(i);
double v = siunit().convertTo(s, to);
double v {};
siunit().convertTo(s, to_siunit, &v);
if (isDebugEnabled())
{
@ -143,7 +185,7 @@ double NumericFormulaSquareRoot::calculate(SIUnit to)
i, inner_->siunit().info().c_str(),
s,
v,
to.info().c_str());
to_siunit.info().c_str());
}
return v;
}
@ -153,6 +195,8 @@ const char *toString(TokenType tt)
switch (tt) {
case TokenType::SPACE: return "SPACE";
case TokenType::NUMBER: return "NUMBER";
case TokenType::DATETIME: return "DATETIME";
case TokenType::TIME: return "TIME";
case TokenType::LPAR: return "LPAR";
case TokenType::RPAR: return "RPAR";
case TokenType::PLUS: return "PLUS";
@ -176,7 +220,30 @@ string Token::str(const string &s)
double Token::val(const string &s)
{
string v = s.substr(start, len);
return atof(v.c_str());
if (type == TokenType::NUMBER)
{
return atof(v.c_str());
}
else if (type == TokenType::DATETIME)
{
struct tm time {};
strptime(v.c_str(), "'%Y-%m-%d %H:%M:%S'", &time);
time_t epoch = mktime(&time);
double result = (double)epoch;
return result;
}
else if (type == TokenType::TIME)
{
int h = 0;
int m = 0;
int s = 0;
sscanf(v.c_str(), "'%02d:%02d:%02d'", &h, &m, &s);
double result = h*3600+m*60+s;
return result;
}
assert(0);
return 0;
}
string Token::vals(const string &s)
@ -250,6 +317,76 @@ size_t FormulaImplementation::findNumber(size_t i)
return len;
}
size_t FormulaImplementation::findDateTime(size_t i)
{
// A datetime is converted into a unix timestamp.
// Patterns: '2222-22-22 11:11:00'
// '2222-22-22 11:11'
// '2222-22-22'
struct tm time {};
const char *start = &formula_[i];
const char *end;
memset(&time, 0, sizeof(time));
if (i+20 < formula_.length())
{
end = strptime(start, "'%Y-%m-%d %H:%M:%S'", &time);
if (distance(start, end) == 21) return 21;
}
if (i+17 < formula_.length())
{
end = strptime(start, "'%Y-%m-%d %H:%M'", &time);
if (distance(start, end) == 18) return 18;
}
if (i+11 < formula_.length())
{
end = strptime(start, "'%Y-%m-%d'", &time);
if (distance(start, end) == 12) return 12;
}
return 0;
}
size_t FormulaImplementation::findTime(size_t i)
{
// A time is converted into seconds 10:11:12 is 10*3600+11*60+12 seconds.
// Patterns: '11:22:15'
// '11:22'
if (i+9 < formula_.length() &&
'\'' == formula_[i+0] &&
isdigit(formula_[i+1]) &&
isdigit(formula_[i+2]) &&
':' == formula_[i+3] &&
isdigit(formula_[i+4]) &&
isdigit(formula_[i+5]) &&
':' == formula_[i+6] &&
isdigit(formula_[i+7]) &&
isdigit(formula_[i+8]) &&
'\'' == formula_[i+9])
{
return 10;
}
if (i+6 < formula_.length() &&
'\'' == formula_[i+0] &&
isdigit(formula_[i+1]) &&
isdigit(formula_[i+2]) &&
':' == formula_[i+3] &&
isdigit(formula_[i+4]) &&
isdigit(formula_[i+5]) &&
'\'' == formula_[i+6])
{
return 7;
}
return 0;
}
size_t FormulaImplementation::findPlus(size_t i)
{
if (i >= formula_.length()) return 0;
@ -387,6 +524,12 @@ bool FormulaImplementation::tokenize()
len = findSpace(i);
if (len > 0) { i+=len; continue; } // No token added for whitespace.
len = findDateTime(i);
if (len > 0) { tokens_.push_back(Token(TokenType::DATETIME, i, len)); i+=len; continue; }
len = findTime(i);
if (len > 0) { tokens_.push_back(Token(TokenType::TIME, i, len)); i+=len; continue; }
len = findNumber(i);
if (len > 0) { tokens_.push_back(Token(TokenType::NUMBER, i, len)); i+=len; continue; }
@ -424,7 +567,13 @@ bool FormulaImplementation::tokenize()
}
// Interrupted early, thus there was an error tokenizing.
if (i < formula_.length()) return false;
if (i < formula_.length())
{
Token tok(TokenType::SPACE, i, 0);
errors_.push_back(tostrprintf("Unknown token!\n"+tok.withMarker(formula_)));
valid_ = false;
return false;
}
return true;
}
@ -441,6 +590,18 @@ size_t FormulaImplementation::parseOps(size_t i)
return i+1;
}
if (tok->type == TokenType::DATETIME)
{
handleUnixTimestamp(tok);
return i+1;
}
if (tok->type == TokenType::TIME)
{
handleSeconds(tok);
return i+1;
}
if (tok->type == TokenType::PLUS)
{
size_t next = parseOps(i+1);
@ -553,12 +714,30 @@ void FormulaImplementation::handleConstant(Token *number, Token *unit)
doConstant(u, c);
}
void FormulaImplementation::handleUnixTimestamp(Token *number)
{
double c = number->val(formula_);
doConstant(Unit::UnixTimestamp, c);
}
void FormulaImplementation::handleSeconds(Token *number)
{
double c = number->val(formula_);
Unit u = Unit::Second;
doConstant(u, c);
}
void FormulaImplementation::handleAddition(Token *tok)
{
SIUnit right_siunit = topOp()->siunit();
SIUnit left_siunit = top2Op()->siunit();
SIUnit to_siunit(Unit::COUNTER);
if (!left_siunit.canConvertTo(right_siunit))
bool ok = left_siunit.mathOpTo(MathOp::ADD, 0, 0, right_siunit, &to_siunit, NULL);
if (!ok)
{
string lsis = left_siunit.str();
string rsis = right_siunit.str();
@ -570,7 +749,7 @@ void FormulaImplementation::handleAddition(Token *tok)
return;
}
doAddition();
doAddition(to_siunit);
}
void FormulaImplementation::handleSubtraction(Token *tok)
@ -578,11 +757,15 @@ void FormulaImplementation::handleSubtraction(Token *tok)
SIUnit right_siunit = topOp()->siunit();
SIUnit left_siunit = top2Op()->siunit();
if (!left_siunit.canConvertTo(right_siunit))
SIUnit v_siunit(Unit::COUNTER);
bool ok = left_siunit.mathOpTo(MathOp::SUB, 0, 0, right_siunit, &v_siunit, NULL);
if (!ok)
{
string lsis = left_siunit.str();
string rsis = right_siunit.str();
errors_.push_back(tostrprintf("Cannot subtract %s to %s!\n%s",
errors_.push_back(tostrprintf("Cannot subtract %s from %s!\n%s",
left_siunit.info().c_str(),
right_siunit.info().c_str(),
tok->withMarker(formula_).c_str()));
@ -590,7 +773,7 @@ void FormulaImplementation::handleSubtraction(Token *tok)
return;
}
doSubtraction();
doSubtraction(v_siunit);
}
void FormulaImplementation::handleMultiplication(Token *tok)
@ -601,7 +784,7 @@ void FormulaImplementation::handleMultiplication(Token *tok)
void FormulaImplementation::handleDivision(Token *tok)
{
// Any two units can be multiplied! You might not like the answer thought....
// Any two units can be divided! You might not like the answer thought....
doDivision();
}
@ -750,38 +933,24 @@ void FormulaImplementation::doConstant(Unit u, double c)
pushOp(new NumericFormulaConstant(this, u, c));
}
void FormulaImplementation::doAddition()
void FormulaImplementation::doAddition(const SIUnit &to_siunit)
{
assert(op_stack_.size() >= 2);
SIUnit right_siunit = topOp()->siunit();
unique_ptr<NumericFormula> right_node = popOp();
SIUnit left_siunit = topOp()->siunit();
unique_ptr<NumericFormula> left_node = popOp();
pushOp(new NumericFormulaAddition(this, left_siunit, left_node, right_node));
assert(left_siunit.canConvertTo(right_siunit));
pushOp(new NumericFormulaAddition(this, to_siunit, left_node, right_node));
}
void FormulaImplementation::doSubtraction()
void FormulaImplementation::doSubtraction(const SIUnit &to_siunit)
{
assert(op_stack_.size() >= 2);
SIUnit right_siunit = topOp()->siunit();
unique_ptr<NumericFormula> right_node = popOp();
SIUnit left_siunit = topOp()->siunit();
unique_ptr<NumericFormula> left_node = popOp();
pushOp(new NumericFormulaSubtraction(this, left_siunit, left_node, right_node));
assert(left_siunit.canConvertTo(right_siunit));
pushOp(new NumericFormulaSubtraction(this, to_siunit, left_node, right_node));
}
void FormulaImplementation::doMultiplication()
@ -863,7 +1032,8 @@ void FormulaImplementation::doMeterField(Unit u, FieldInfo *fi)
{
SIUnit from_si_unit = toSIUnit(fi->defaultUnit());
SIUnit to_si_unit = toSIUnit(u);
assert(from_si_unit.canConvertTo(to_si_unit));
assert(from_si_unit.convertTo(0, to_si_unit, NULL));
pushOp(new NumericFormulaMeterField(this, u, fi->vname(), fi->xuantity()));
}

Wyświetl plik

@ -194,6 +194,8 @@ enum class TokenType
LPAR,
RPAR,
NUMBER,
DATETIME,
TIME,
PLUS,
MINUS,
TIMES,
@ -241,11 +243,11 @@ struct FormulaImplementation : public Formula
// Pushes a dve entry field read on the formula builder stack.
void doDVEntryField(Unit u, DVEntryCounterType ct);
// Pops the two top nodes of the formula builder stack and pushes an addition on the formula builder stack.
// The target unit will be the first unit of the two operands.
void doAddition();
// The target unit will be the supplied to unit.
void doAddition(const SIUnit &to);
// Pops the two top nodes of the formula builder stack and pushes a subtraction on the formula builder stack.
// The target unit will be the first unit of the two operands.
void doSubtraction();
// The target unit will be the supplied to unit.
void doSubtraction(const SIUnit &to);
// Pops the two top nodes of the formula builder stack and pushes a multiplication on the formula builder stack.
// The target unit will be multiplication of the SI Units.
void doMultiplication();
@ -265,6 +267,8 @@ struct FormulaImplementation : public Formula
bool go();
size_t findSpace(size_t i);
size_t findNumber(size_t i);
size_t findDateTime(size_t i);
size_t findTime(size_t i);
size_t findUnit(size_t i);
size_t findPlus(size_t i);
size_t findMinus(size_t i);
@ -281,6 +285,8 @@ struct FormulaImplementation : public Formula
size_t parsePar(size_t i);
void handleConstant(Token *number, Token *unit);
void handleSeconds(Token *number);
void handleUnixTimestamp(Token *number);
void handleAddition(Token *add);
void handleSubtraction(Token *add);
void handleMultiplication(Token *add);

Wyświetl plik

@ -62,7 +62,9 @@ bool verbose_ = false;
X(si_units_siexp) \
X(si_units_basic) \
X(si_units_conversion) \
X(formulas_building) \
X(formulas_building_consts) \
X(formulas_building_meters) \
X(formulas_datetimes) \
X(formulas_parsing_1) \
X(formulas_parsing_2) \
X(formulas_multiply_constants) \
@ -862,7 +864,7 @@ void test_device_parsing()
void test_month(int y, int m, int day, int mdiff, string from, string to)
{
struct tm date;
struct tm date {};
date.tm_year = y-1900;
date.tm_mon = m-1;
date.tm_mday = day;
@ -1597,7 +1599,6 @@ void test_units_extraction()
test_unit("work_kvarh", true, "work", Unit::KVARH);
test_unit("current_power_consumption_phase1_kw", true, "current_power_consumption_phase1", Unit::KW);
}
void test_expected_failed_si_convert(Unit from_unit,
@ -1613,7 +1614,7 @@ void test_expected_failed_si_convert(Unit from_unit,
{
printf("ERROR! Not the expected quantities!\n");
}
if (from_si_unit.canConvertTo(to_si_unit))
if (from_si_unit.convertTo(0, to_si_unit, NULL))
{
printf("ERROR! Should not be able to convert from %s to %s !\n", fu.c_str(), tu.c_str());
}
@ -1628,6 +1629,10 @@ void test_si_convert(double from_value, double expected_value,
set<Unit> *from_set,
set<Unit> *to_set)
{
debug("test_si_convert from %.17g %s to %.17g %s\n",
from_value, expected_from_unit.c_str(),
expected_value, expected_to_unit.c_str());
string evs = tostrprintf("%.15g", expected_value);
SIUnit from_si_unit(from_unit);
@ -1638,7 +1643,8 @@ void test_si_convert(double from_value, double expected_value,
from_set->erase(from_unit);
to_set->erase(to_unit);
double e = from_si_unit.convertTo(from_value, to_si_unit);
double e {};
from_si_unit.convertTo(from_value, to_si_unit, &e);
string es = tostrprintf("%.15g", e);
if (canConvert(from_unit, to_unit))
@ -1788,15 +1794,15 @@ LIST_OF_QUANTITIES
test_si_convert(3600.0, 1.0/24.0, Unit::Second, "s", Unit::Day, "d", Quantity::Time, &from_set, &to_set);
// 1 min is 60 seconds.
test_si_convert(1.0, 60.0, Unit::Minute, "min", Unit::Second, "s", Quantity::Time, &from_set, &to_set);
// 1 day is 1/365.2425 year
test_si_convert(1.0, 1.0/365.2425, Unit::Day, "d", Unit::Year, "y", Quantity::Time, &from_set, &to_set);
// 1 month is 30.437 days
test_si_convert(2.0, 30.437*2, Unit::Month, "month", Unit::Day, "d", Quantity::Time, &from_set, &to_set);
test_si_convert(30.437*2, 2.0, Unit::Day, "d", Unit::Month, "month", Quantity::Time, &from_set, &to_set);
// 1 day is 24 hours
test_si_convert(1.0, 24, Unit::Day, "d", Unit::Hour, "h", Quantity::Time, &from_set, &to_set);
// 1 month is 1 month.
test_si_convert(1.0, 1.0, Unit::Month, "month", Unit::Month, "month", Quantity::Time, &from_set, &to_set);
test_si_convert(1.0, 1.0, Unit::Year, "y", Unit::Year, "y", Quantity::Time, &from_set, &to_set);
// 100 hours is 100/24 days.
test_si_convert(100.0, 100.0/24.0, Unit::Hour, "h", Unit::Day, "d", Quantity::Time, &from_set, &to_set);
// 1 year is 365.2425 days.
test_si_convert(1.0, 365.2425, Unit::Year, "y", Unit::Day, "d", Quantity::Time, &from_set, &to_set);
// test_si_convert(1.0, 365.2425, Unit::Year, "y", Unit::Day, "d", Quantity::Time, &from_set, &to_set);
check_units_tested(from_set, to_set, Quantity::Time);
@ -2043,19 +2049,22 @@ LIST_OF_QUANTITIES
check_quantities_tested(q_set);
}
void test_formulas_building()
void test_formulas_building_consts()
{
unique_ptr<FormulaImplementation> f = unique_ptr<FormulaImplementation>(new FormulaImplementation());
double v; // , expected;
f->doConstant(Unit::KWH, 17);
f->doConstant(Unit::KWH, 1);
f->doAddition();
double v = f->calculate(Unit::KWH);
f->doAddition(SI_KWH);
v = f->calculate(Unit::KWH);
if (v != 18.0)
{
printf("ERROR in test formula 1 expected 18.0 but got %lf\n", v);
}
/*
f->clear();
f->doConstant(Unit::KWH, 10);
v = f->calculate(Unit::MJ);
@ -2067,7 +2076,7 @@ void test_formulas_building()
f->clear();
f->doConstant(Unit::GJ, 10);
f->doConstant(Unit::MJ, 10);
f->doAddition();
f->doAddition(SI_GJ);
v = f->calculate(Unit::GJ);
if (v != 10.01)
{
@ -2077,15 +2086,53 @@ void test_formulas_building()
f->clear();
f->doConstant(Unit::C, 10);
f->doConstant(Unit::C, 20);
f->doAddition();
f->doAddition(SI_C);
f->doConstant(Unit::C, 22);
f->doAddition();
f->doAddition(SI_C);
v = f->calculate(Unit::C);
if (v != 52)
{
printf("ERROR in test formula 4 expected 52 but got %lf\n", v);
}
f->clear();
f->doConstant(Unit::Month, 2);
f->doConstant(Unit::COUNTER, 3);
f->doMultiplication();
v = f->calculate(Unit::Month);
if (v != 6)
{
printf("ERROR in test formula 5 expected 6 but got %g\n", v);
}
f->clear();
f->doConstant(Unit::UnixTimestamp, 3600*24*11); // mon 12 jan 1970 01:00:00 CET
f->doConstant(Unit::UnixTimestamp, 9);
f->doAddition(SIUnit(Unit::UnixTimestamp));
v = f->calculate(Unit::UnixTimestamp);
expected = 3600*24*11+9;
if (v != expected)
{
printf("ERROR in test formula 6 expected %g but got %g\n", expected, v);
}
f->clear();
f->doConstant(Unit::UnixTimestamp, 3600*24*11); // mon 12 jan 1970 01:00:00 CET
f->doConstant(Unit::Month, 1);
f->doAddition(SIUnit(Unit::UnixTimestamp));
v = f->calculate(Unit::UnixTimestamp);
expected = 3600*24*(31+11); // mon 12 feb 1970 01:00:00 CET
if (v != expected)
{
printf("ERROR in test formula 7 expected %g but got %g\n", expected, v);
}
*/
}
void test_formulas_building_meters()
{
unique_ptr<FormulaImplementation> f = unique_ptr<FormulaImplementation>(new FormulaImplementation());
////////////////////////////////////////////////////////////////////////////////////////////////////
{
@ -2113,7 +2160,7 @@ void test_formulas_building()
f->setMeter(meter.get());
f->doMeterField(Unit::C, fi_flow);
v = f->calculate(Unit::C);
double v = f->calculate(Unit::C);
if (v != 31)
{
printf("ERROR in test formula 5 expected 31 but got %lf\n", v);
@ -2124,7 +2171,7 @@ void test_formulas_building()
f->doMeterField(Unit::C, fi_flow);
f->doMeterField(Unit::C, fi_ext);
f->doAddition();
f->doAddition(SIUnit(Unit::C));
v = f->calculate(Unit::C);
if (v != 50)
{
@ -2167,11 +2214,11 @@ void test_formulas_building()
f->doMeterField(Unit::KW, fi_p1);
f->doMeterField(Unit::KW, fi_p2);
f->doAddition();
f->doAddition(SI_KW);
f->doMeterField(Unit::KW, fi_p3);
f->doAddition();
f->doAddition(SI_KW);
v = f->calculate(Unit::KW);
double v = f->calculate(Unit::KW);
if (v != 0.21679)
{
printf("ERROR in test formula 7 expected 0.21679 but got %lf\n", v);
@ -2223,6 +2270,110 @@ void test_formula_error(FormulaImplementation *f, Meter *m, string formula, Unit
assert(!ok);
}
double totime(int year, int month = 1, int day = 1, int hour = 0, int min = 0, int sec = 0)
{
struct tm date {};
date.tm_year = year-1900;
date.tm_mon = month-1;
date.tm_mday = day;
date.tm_hour = hour;
date.tm_min = min;
date.tm_sec = sec;
// This t timestamp is dependent on the local time zone.
time_t t = mktime(&date);
/*
// Extract the local time zone.
struct tm tz_adjust {};
localtime_r(&t, &tz_adjust);
// if tm_gmtoff is zero, then we are in Greenwich!
// if tm_gmtoff is 3600 then we are in Stockholm!
// Now adjust the t timestamp so that we execute this this, as if we are in Greenwich.
// This way, the test will work wherever in the world we run it.
t -= tz_adjust.tm_gmtoff;
*/
return (double)t;
}
void test_datetime(FormulaImplementation *f, string formula, int year, int month=1, int day=1, int hour=0, int min=0, int sec=0)
{
f->clear();
double expected = totime(year,month,day,hour,min,sec);
f->parse(NULL, formula);
if (!f->valid())
{
printf("%s\n", f->errors().c_str());
}
double v = f->calculate(Unit::UnixTimestamp);
if (v != expected)
{
time_t t = v;
struct tm time {};
localtime_r(&t, &time);
string gs = strdatetimesec(&time);
printf("ERROR Expected datetime %.17g %04d-%02d-%02d %02d:%02d:%02d "
"but got %.17g (%s) when testing \"%s\"\n",
expected, year, month, day, hour, min, sec,
v, gs.c_str(), formula.c_str());
}
}
void test_time(FormulaImplementation *f, string formula, int hour=0, int min=0, int sec=0)
{
f->clear();
double expected = hour*3600+min*60+sec;
f->parse(NULL, formula);
if (!f->valid())
{
printf("%s\n", f->errors().c_str());
}
double v = f->calculate(Unit::Second);
if (v != expected)
{
printf("ERROR Expected time %.17g but got %.17g when testing %s %02d:%02d.%02d\n",
expected, v, formula.c_str(), hour, min, sec);
}
}
void test_formulas_datetimes()
{
unique_ptr<FormulaImplementation> f = unique_ptr<FormulaImplementation>(new FormulaImplementation());
test_datetime(f.get(), "'2022-02-02'", 2022, 02, 02);
test_datetime(f.get(), "'2021-02-28'", 2021, 02, 28);
test_datetime(f.get(), "'1970-01-01 01:00:00'", 1970, 01, 01, 01, 00, 00);
test_datetime(f.get(), "'1970-01-01 01:00'", 1970, 01, 01, 01, 00);
test_datetime(f.get(), "'1970-01-01'", 1970, 01, 01);
test_time(f.get(), "'00:15'", 0, 15, 0);
test_time(f.get(), "'00:00:16'", 0, 0, 16);
test_datetime(f.get(), "'2022-01-01 00:00:00' + 1s", 2022,1,1,0,0,1);
test_datetime(f.get(), "'1971-10-01 02:17' +7d+1h+2min+1s", 1971, 10, 8, 3, 19, 1);
test_datetime(f.get(), "'2000-01-01' + 1month", 2000, 2, 1);
test_datetime(f.get(), "'2020-12-31' + 2month", 2021, 2, 28);
test_datetime(f.get(), "'2020-12-31' - 10month", 2020, 2, 29);
test_datetime(f.get(), "'2021-01-31' - 1month", 2020, 12,31);
test_datetime(f.get(), "'2021-01-31' - 2month", 2020, 11, 30);
test_datetime(f.get(), "'2021-01-31' - 24month", 2019, 1, 31);
test_datetime(f.get(), "'2021-01-31' + 24month", 2023, 1, 31);
test_datetime(f.get(), "'2021-01-31' + 22month", 2022, 11, 30);
// 2020 was a leap year.
test_datetime(f.get(), "'2021-02-28' -12month", 2020,2,29);
// 2000 was a leap year %100=0 but %400=0 overrides.
test_datetime(f.get(), "'2001-02-28' -12month", 2000, 2, 29);
// 2100 is not a leap year since %100=0 and not overriden %400 != 0.
test_datetime(f.get(), "'2000-02-29' +(12month * 100counter)", 2100,2,28);
}
void test_formulas_parsing_1()
{
MeterInfo mi;
@ -2322,6 +2473,13 @@ void test_formulas_sqrt_constants()
test_formula_value(&fi, NULL, "sqrt((2 kwh * 2 kwh) + (3 kvarh * 3 kvarh))", 3.6055512754639891, Unit::KVAH);
}
void test_formulas_date_constants()
{
FormulaImplementation fi;
// test_formula_value(&fi, NULL, "2022-01-01 + 1 month", "2022-02-01");
}
void test_formulas_errors()
{
{

Wyświetl plik

@ -93,18 +93,23 @@ using namespace std;
X(Minute, 60.0, SIExp().s(1)) \
X(Hour, 3600.0, SIExp().s(1)) \
X(Day, 3600.0*24, SIExp().s(1)) \
X(Month, 3600.0*24*30.437, SIExp().s(1)) \
X(Year, 3600.0*24*365.2425, SIExp().s(1)) \
X(DateTimeUT, 1.0, SIExp().s(1)) \
X(DateTimeUTC, 1.0, SIExp().s(1)) \
X(DateTimeLT, 1.0, SIExp().s(1)) \
\
X(Month, 1, SIExp().month(1)) \
X(Year, 1, SIExp().year(1)) \
X(UnixTimestamp,1.0, SIExp().unixTimestamp(1)) \
X(DateTimeUTC, 0.0, SIExp().unixTimestamp(1)) \
X(DateTimeLT, 0.0, SIExp().unixTimestamp(1)) \
X(DateLT, 0.0, SIExp().unixTimestamp(1)) \
X(TimeLT, 0.0, SIExp().unixTimestamp(1)) \
\
X(RH, 1.0, SIExp()) \
X(HCA, 1.0, SIExp()) \
X(COUNTER, 1.0, SIExp()) \
X(TXT, 1.0, SIExp()) \
// 3600.0*24*365.2425
// 3600.0*24*30.437
#define X(cname,lcname,hrname,quantity,explanation) const SIUnit SI_##cname(Unit::cname);
LIST_OF_UNITS
#undef X
@ -150,26 +155,6 @@ LIST_OF_CONVERSIONS
return 0;
}
bool SIUnit::canConvertTo(const SIUnit &uto) const
{
// Same exponents! Then we can always convert!
if (exponents_ == uto.exponents_) return true;
// Now the special cases. K-C-F
if ((exponents_ == SI_K.exponents_ ||
exponents_ == SI_C.exponents_ ||
exponents_ == SI_F.exponents_) &&
(uto.exponents_ == SI_K.exponents_ ||
uto.exponents_ == SI_C.exponents_ ||
uto.exponents_ == SI_F.exponents_))
{
// We are converting between the K,C,F temperatures only!
return true;
}
return false;
}
bool isKCF(const SIExp &e)
{
return
@ -178,6 +163,15 @@ bool isKCF(const SIExp &e)
e == SI_F.exp();
}
bool is_S_MONTH_YEAR_UT(const SIExp &e)
{
return
e == SI_Second.exp() ||
e == SI_UnixTimestamp.exp() ||
e == SI_Month.exp() ||
e == SI_Year.exp();
}
void getScaleOffset(const SIExp &e, double *scale, double *offset)
{
if (e == SI_K.exp())
@ -201,15 +195,16 @@ void getScaleOffset(const SIExp &e, double *scale, double *offset)
assert(0);
}
double SIUnit::convertTo(double val, const SIUnit &uto) const
bool SIUnit::convertTo(double left, const SIUnit &out_siunit, double *out) const
{
if (exp() == uto.exp())
if (exp() == out_siunit.exp())
{
return (val*scale_)/uto.scale_;
if (out != NULL) *out = (left*scale_)/out_siunit.scale_;
return true;
}
// Now the special cases. K-C-F
if (isKCF(exp()) && isKCF(uto.exp()))
if (isKCF(exp()) && isKCF(out_siunit.exp()))
{
double from_scale {};
double from_offset {};
@ -220,13 +215,97 @@ double SIUnit::convertTo(double val, const SIUnit &uto) const
double to_offset {};
double to_scale {};
getScaleOffset(uto.exp(), &to_scale, &to_offset);
to_scale *= uto.scale();
getScaleOffset(out_siunit.exp(), &to_scale, &to_offset);
to_scale *= out_siunit.scale();
return ((val+from_offset)*from_scale)/to_scale-to_offset;
if (out != NULL) *out = ((left+from_offset)*from_scale)/to_scale-to_offset;
return true;
}
return std::numeric_limits<double>::quiet_NaN();
if (out != NULL) *out = std::numeric_limits<double>::quiet_NaN();
return false;
}
bool forbidden_op(MathOp op, const SIExp &a, const SIExp &b)
{
// Two unix timestamps cannot be added together. They can be subtracted though!
if (op == MathOp::ADD && a == SI_UnixTimestamp.exp() && b == SI_UnixTimestamp.exp()) return true;
return false;
}
double do_op(MathOp op, double left, double right)
{
if (op == MathOp::ADD) return left+right;
if (op == MathOp::SUB) return left-right;
assert(0);
}
bool SIUnit::mathOpTo(MathOp op, double left, double right, const SIUnit &right_siunit, SIUnit *out_siunit, double *out) const
{
// Adding all values with the same units.
if (exp() == right_siunit.exp())
{
if (forbidden_op(op, exp(), right_siunit.exp()))
{
if (out_siunit != NULL) *out_siunit = SI_COUNTER;
if (out != NULL) *out = std::numeric_limits<double>::quiet_NaN();
return false;
}
double left_converted {};
convertTo(left, right_siunit, &left_converted);
double result = do_op(op, left_converted, right);
if (out_siunit != NULL) *out_siunit = right_siunit;
if (out != NULL) *out = result;
return true;
}
// Adding temperatures.
if (isKCF(exp()) && isKCF(right_siunit.exp()))
{
double left_converted {};
convertTo(left, right_siunit, &left_converted);
double result = do_op(op, left_converted, right);
if (out_siunit != NULL) *out_siunit = right_siunit;
if (out != NULL) *out = result;
return true;
}
// Operating on unix timestamps
if (exp() == SI_UnixTimestamp.exp() || right_siunit.exp() == SI_UnixTimestamp.exp())
{
if (right_siunit.exp() == SI_UnixTimestamp.exp())
{
// The timestamp is right, flip the arguments.
return right_siunit.mathOpTo(op, right, left, *this, out_siunit, out);
}
assert(exp() == SI_UnixTimestamp.exp() && right_siunit.exp() != SI_UnixTimestamp.exp());
// The timestamp is left. Lets handle all permitted additions to UnixTimestamp.
if (right_siunit.exp() == SI_Second.exp())
{
// Move right argument (day, hour, min, s) to seconds.
double right_converted {};
right_siunit.convertTo(right, SI_Second, &right_converted);
// Add the seconds to the unix timestamp.
double result = do_op(op, left, right_converted);
if (out_siunit != NULL) *out_siunit = SI_UnixTimestamp;
if (out != NULL) *out = result;
return true;
}
if (right_siunit.exp() == SI_Month.exp())
{
// Move right argument (day, hour, min, s) to seconds.
if (op == MathOp::SUB) right = -right;
double result = addMonths(left, right);
if (out_siunit != NULL) *out_siunit = SI_UnixTimestamp;
if (out != NULL) *out = result;
return true;
}
}
// Oups, should not get here....
return false;
}
SIUnit SIUnit::mul(const SIUnit &m) const
@ -385,15 +464,6 @@ string strWithUnitLowerCase(double v, Unit u)
return r;
}
Unit replaceWithConversionUnit(Unit u, vector<Unit> cs)
{
for (Unit c : cs)
{
if (canConvert(u, c)) return c;
}
return u;
}
string valueToString(double v, Unit u)
{
if (isnan(v))
@ -597,6 +667,11 @@ int8_t SIExp::safe_div2(int8_t a)
return d;
}
bool SIExp::operator!=(const SIExp &e) const
{
return ! (*this == e);
}
bool SIExp::operator==(const SIExp &e) const
{
return
@ -608,7 +683,10 @@ bool SIExp::operator==(const SIExp &e) const
cd_ == e.cd_ &&
k_ == e.k_ &&
c_ == e.c_ &&
f_ == e.f_;
f_ == e.f_ &&
month_ == e.month_ &&
year_ == e.year_ &&
unix_timestamp_ == e.unix_timestamp_;
}
SIExp SIExp::mul(const SIExp &e) const
@ -623,7 +701,10 @@ SIExp SIExp::mul(const SIExp &e) const
.cd(ee.safe_add(cd(),e.cd()))
.k(ee.safe_add(k(),e.k()))
.c(ee.safe_add(c(),e.c()))
.f(ee.safe_add(f(),e.f()));
.f(ee.safe_add(f(),e.f()))
.month(ee.safe_add(month(),e.month()))
.year(ee.safe_add(year(),e.year()))
.unixTimestamp(ee.safe_add(unixTimestamp(),e.unixTimestamp()));
return ee;
}
@ -639,7 +720,10 @@ SIExp SIExp::div(const SIExp &e) const
.cd(ee.safe_sub(cd(),e.cd()))
.k(ee.safe_sub(k(),e.k()))
.c(ee.safe_sub(c(),e.c()))
.f(ee.safe_sub(f(),e.f()));
.f(ee.safe_sub(f(),e.f()))
.month(ee.safe_sub(month(),e.month()))
.year(ee.safe_sub(year(),e.year()))
.unixTimestamp(ee.safe_sub(unixTimestamp(),e.unixTimestamp()));
return ee;
}
@ -656,7 +740,10 @@ SIExp SIExp::sqrt() const
.cd(ee.safe_div2(cd()))
.k(ee.safe_div2(k()))
.c(ee.safe_div2(c()))
.f(ee.safe_div2(f()));
.f(ee.safe_div2(f()))
.month(ee.safe_div2(month()))
.year(ee.safe_div2(year()))
.unixTimestamp(ee.safe_div2(unixTimestamp()));
return ee;
}
@ -676,6 +763,9 @@ string SIExp::str() const
DO_UNIT_SIEXP(f_, f);
DO_UNIT_SIEXP(s_, s);
DO_UNIT_SIEXP(a_, a);
DO_UNIT_SIEXP(month_, month);
DO_UNIT_SIEXP(year_, year);
DO_UNIT_SIEXP(unix_timestamp_, ut);
if (invalid_) r = "!"+r+"-Invalid!";

Wyświetl plik

@ -104,9 +104,11 @@ LIST_OF_QUANTITIES
X(Day,d,"d",Time,"day") \
X(Month,month,"month",Time,"month") \
X(Year,y,"y",Time,"year") \
X(DateTimeUT,ut,"ut",PointInTime,"unix timestamp") \
X(UnixTimestamp,ut,"ut",PointInTime,"unix timestamp") \
X(DateTimeUTC,utc,"utc",PointInTime,"coordinated universal time") \
X(DateTimeLT,lt,"lt",PointInTime,"local time") \
X(DateTimeLT,datetime,"datetime",PointInTime,"local time") \
X(DateLT,date,"date",PointInTime,"local date") \
X(TimeLT,time,"time",PointInTime,"local time") \
\
X(RH,rh,"RH",RelativeHumidity,"relative humidity") \
X(HCA,hca,"hca",HCA,"heat cost allocation") \
@ -150,6 +152,9 @@ struct SIExp
SIExp &k(int8_t i) { k_ = i; if (k_ != 0 && (c_ != 0 || f_ != 0)) { invalid_ = true; } return *this; }
SIExp &c(int8_t i) { c_ = i; if (c_ != 0 && (k_ != 0 || f_ != 0)) { invalid_ = true; } return *this; }
SIExp &f(int8_t i) { f_ = i; if (f_ != 0 && (k_ != 0 || c_ != 0)) { invalid_ = true; } return *this; }
SIExp &month(int8_t i) { month_ = i; return *this; }
SIExp &year(int8_t i) { year_ = i; return *this; }
SIExp &unixTimestamp(int8_t i) { unix_timestamp_ = i; return *this; }
int8_t s() const { return s_; }
int8_t m() const { return m_; }
@ -160,6 +165,10 @@ struct SIExp
int8_t k() const { return k_; }
int8_t c() const { return c_; }
int8_t f() const { return f_; }
int8_t month() const { return month_; }
int8_t year() const { return year_; }
int8_t unixTimestamp() const { return unix_timestamp_; }
SIExp mul(const SIExp &e) const;
SIExp div(const SIExp &e) const;
SIExp sqrt() const;
@ -167,6 +176,7 @@ struct SIExp
int8_t safe_sub(int8_t a, int8_t b);
int8_t safe_div2(int8_t a);
bool operator==(const SIExp &e) const;
bool operator!=(const SIExp &e) const;
std::string str() const;
@ -190,11 +200,21 @@ private:
// But kw*h can be translated into kw*s since scaling (*3600) can be done on the
// calculated value without knowing the h value. Therefore we have to
// treat k, c and f as distinct units. I.e. you cannot add m3*k+m3*f+m3*c.
int8_t month_ {}; // Why month and year here instead of using s? Because they vary in length and thus cannot
int8_t year_ {}; // be auto-converted to seconds, ie you cannot add/subtract months/years without knowing what you add to.
int8_t unix_timestamp_ {}; // Why not s? Because point in time is referenced agains an offset. UT is 1970-01-01 01:00.
// If exponents have over/underflowed or if multiple of (k,c,f) (month,year,unix_timestamp) are set,
// then the SIExp is not valid any more!
// If exponents have over/underflowed or if multiple of k,c,f are set, then the SIExp is not valid any more!
bool invalid_ = false;
};
enum class MathOp
{
ADD, SUB
};
struct SIUnit
{
// Transform a double,double,uint64_t into an SIUnit.
@ -220,11 +240,12 @@ struct SIUnit
// Return a detailed string like: kwh[3.6⁶s⁻²m²kg]Energy
std::string info() const;
// Check if the exponents (ie units) are the same.
bool sameExponents(SIUnit &to) const { return exponents_ == to.exponents_; }
// Check if this unit can be converted to the other unit.
bool canConvertTo(const SIUnit &to) const;
// Convert value from this unit to another unit.
double convertTo(double val, const SIUnit &to) const;
bool sameExponents(SIUnit &to_siunit) const { return exponents_ == to_siunit.exponents_; }
// Convert value from this unit to another unit and store it in out. Return false if conversion is impossible!
bool convertTo(double left, const SIUnit &out_siunit, double *out) const;
// Do a math op. Store the resulting unit and value into the destination pointers.
// Return false if the addion cannot be performed.
bool mathOpTo(MathOp op, double left, double right, const SIUnit &right_siunit, SIUnit *out_siunit, double *out) const;
// Multiply this unit with another unit.
SIUnit mul(const SIUnit &m) const ;
// Dividethis unit with another unit.
@ -257,9 +278,10 @@ std::string unitToStringLowerCase(Unit u);
std::string unitToStringUpperCase(Unit u);
std::string valueToString(double v, Unit u);
Unit replaceWithConversionUnit(Unit u, std::vector<Unit> cs);
bool extractUnit(const std::string &s, std::string *vname, Unit *u);
#define X(cname,lcname,hrname,quantity,explanation) extern const SIUnit SI_##cname;
LIST_OF_UNITS
#undef X
#endif

Wyświetl plik

@ -1405,6 +1405,15 @@ int get_days_in_month(int year, int month)
return days;
}
double addMonths(double t, int months)
{
time_t ut = (time_t)t;
struct tm time;
localtime_r(&ut, &time);
addMonths(&time, months);
return (double)mktime(&time);
}
void addMonths(struct tm *date, int months)
{
bool is_last_day_in_month = date->tm_mday == get_days_in_month(date->tm_year, date->tm_mon);

Wyświetl plik

@ -80,6 +80,7 @@ std::string strdatetime(struct tm *date);
// Return for example: 2010-03-21 15:22:03
std::string strdatetimesec(struct tm *date);
void addMonths(struct tm* date, int m);
double addMonths(double t, int m);
bool stringFoundCaseIgnored(const std::string& haystack, const std::string& needle);