Hi, Arduino fans
FYI, Just started with C++ and Arduino+ESP32 last month, experienced in C#,C,VB etc.
The problem: I run into what looks as strange behavior to me, trying to assemble a date string in the US format (and later on in different Europian formats like Dutch) as : DayOfWeek, Month Day YYYY.
The code below is condensed to the relevant part, using TimeLib.h for the called time functions like dayStr().
The sketch, originally developed/running under Arduino IDE, now under VS2019-vMicro. The problem:
The std::string returned (today) is : "July, July 27 2021"
Where it should read: "Tuesday, July 27 2021"
If I print out the result of dayStr(weekday(timenow)) separately it is: Tuesday My question:
Why is the result of monthStr(month(timenow)) also assigned to the day part in sprintf? And how to avoid this ofcourse. Use separate vars for each argument?
std::string FormatTimeString(time_t timenow, bool fulldate, bool onlydate = false, bool secsinclude = false) {
const int sizBuf = 100;
char spart[sizBuf];
int n;
std::string fstring = "";
// Optional
if (fulldate || onlydate)
{
n = sprintf(spart, "%s, %s %d %d", dayStr(weekday(timenow)), monthStr(month(timenow)), day(timenow), year(timenow));
Serial.println(spart);
Serial.println(dayStr(weekday(timenow)));
if (n > 0) {}
else {
perror("Error:");
}
//Serial.printf("Date string is %d characters long: %s \n", n, spart);
fstring = std::string(spart);
if (onlydate) { return fstring; }
}
// Additional time fields…
}
The Time library keeps the strings in PROGMEM so it can't return a pointer to the string directly. It also can't return a pointer to a local variable since that variable will go out-of-scope when the function returns and the memory may be re-used.
Their solution was to have a single buffer to copy the string into. All of the functions that look up strings use the some buffer.
Copy the strings into local variables before you use them.
Thanks for this John.
The thing is, I had the time parts in different vars originally but this looked a bit silly (to me) in terms of compact programming, so I switched to the all-inline solution.
Reuse of a buffer was indeed what I thought to see.
I assumed (wrongly) that each argument of sprintf - the function call(s) - were executed one by one, result stored in each own temp location, and then passed on to the executing sprintf. I assumed again wrongly that storing of the intermediate results is a task of the executing sprintf - argument parsing.
This looks valid for base types like int but not for string pointers.
Correct me if I'm wrong.
So separate vars(buffers) for the strings.
that was a correct assumption.
...but what you get back from dayStr() is a pointer to a place in memory that is reused by the next call monthStr() so sprintf() gets actually twice the same pointer and when it goes read the cString to render the final result, of course same pointer = same content twice.
using strncat() and building your spart buffer in chucks should work
(alternatively you could build that up directly into fstring using std::string methods)
As a closing remark see below a working version, serial output shown first. Thanks for the advice on my first posting. The project is a Weather Station( Rui Santos), showing temp, humidity, time, date.
The String function takes care of copying the buffer content via pointer (char *) to a new mem location.
Johan
Serial output
23.80 -> temperature
64.50 -> humidity
09:36 -> time
Date string is 23 characters long: Wednesday, July 28 2021
if spart is sized in the right way for never overflowing, that will work. (the parameters are known and you know the max size of each component, so it's just adding the max sizes plus the spaces and other things you want in that string)
if you are unsure or could modify the formatting string in the future, you could use snprintf() or slprintf() to ensure you don't overflow.
PS: it's good to store the result of sprintf() in a variable, could be useful to check all went fine, but if you don't check it...
PS': if you return fstring anyway (local variable - returning a copy will be what happens), why don't you build it in fstring directly ?
dpart.c_str() interesting because I used that before the current String solution. dpart and mpart where then declared as char *. The result was 2 strings with nonsense, a string conversion of the pointer...
Same with strcpy().
That's the reason I ultimately was drawn to String. Apparently String class/constructor does the copying and errorchecking, see Wstring.h and Wstring.cpp: (lots of code for just copying a string to another mem location...). Extract below.
Wstring.h
public:
// constructors
// creates a copy of the initial value.
// if the initial value is null or invalid, or if memory allocation
// fails, the string will be marked as invalid (i.e. "if (s)" will
// be false).
String(const char *cstr = "");
WString.cpp
String::String(const char *cstr) {
init();
if (cstr)
copy(cstr, strlen(cstr));
}
I meant just use std::string (and std::ostringstream because I don't think the ESP32 has toString() implemented). I don't know why you would use String on top and the cString functions like sprintf()....