kopia lustrzana https://github.com/weetmuts/wmbusmeters
Formulas can now calculate using dates. E.g. '2022-02-03' + 2 month
rodzic
addff11eb7
commit
e064c678a6
|
@ -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.",
|
||||
|
|
272
src/formula.cc
272
src/formula.cc
|
@ -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()));
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
{
|
||||
|
|
184
src/units.cc
184
src/units.cc
|
@ -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!";
|
||||
|
||||
|
|
42
src/units.h
42
src/units.h
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
Ładowanie…
Reference in New Issue