Whilst your use of reserve may reduce or eliminate memory fragmentation in the latest versions of the IDE, I am not keen on teaching the use of String classes for beginners, especially if they happen to use earlier versions of the IDE, or take away that you can use String (and not bother with all that "reserve" stuff).
If I may offer an alternative, teach the use of state machines. I have a state machine write-up which goes over the fundamentals. Here is code to solve your particular problem of parsing a date/time string:
// Expected input format:
// YYYY-MM-DDThh:mm:ss
// possible input states
enum { WANT_YEAR,
WANT_MONTH,
WANT_DAY,
WANT_HOUR,
WANT_MINUTE,
WANT_SECOND,
HAVE_ERROR // got error, wait for new line
} state = WANT_YEAR;
unsigned int currentValue;
// show an error, go to error state
void error (const char * why)
{
Serial.print ("ERROR: ");
Serial.println (why);
state = HAVE_ERROR;
} // end of error
unsigned int year, month, day, hour, minute, second;
bool setDate ()
{
// validation
if (year < 1900 || year > 2050)
{
error ("Year out of range 1900 to 2050");
return false;
}
if (month < 1 || month > 12)
{
error ("Month out of range 1 to 12");
return false;
}
if (day < 1 || day > 31)
{
error ("Day out of range 1 to 31");
return false;
}
Serial.print (F("Setting date to: "));
Serial.print (year);
Serial.print (F("-"));
Serial.print (month);
Serial.print (F("-"));
Serial.println (day);
return true; // set date OK
} // end of setDate
void setTime ()
{
// validation
if (hour > 23)
{
error ("Hour greater than 23");
return;
}
if (minute > 59)
{
error ("Minute greater than 59");
return;
}
if (second > 59)
{
error ("Second greater than 59");
return;
}
Serial.print (F("Setting time to: "));
Serial.print (hour);
Serial.print (F(":"));
Serial.print (minute);
Serial.print (F(":"));
Serial.println (second);
} // end of setTime
void processIncomingByte (const byte inByte)
{
// if previous error, discard input until newline
if (state == HAVE_ERROR)
{
if (inByte == '\n')
{
state = WANT_YEAR;
currentValue = 0;
}
return;
} // end of error
// if incoming digit, add to previous value after multiplying it by 10
if (isdigit (inByte))
{
// cannot exceed 65535 after multiply by 10 and adding 9
if (currentValue > 6552)
{
error ("Number too big");
return;
} // end of too large
currentValue *= 10;
currentValue += inByte - '0';
return;
} // end of digit
// not a digit, process possible sequences
switch (inByte)
{
// hyphen
case '-':
switch (state)
{
case WANT_YEAR:
year = currentValue;
currentValue = 0;
state = WANT_MONTH;
break;
case WANT_MONTH:
month = currentValue;
currentValue = 0;
state = WANT_DAY;
break;
default:
error ("Unexpected '-'");
return;
} // end of switch on state
break;
// colon
case ':':
switch (state)
{
case WANT_HOUR:
hour = currentValue;
currentValue = 0;
state = WANT_MINUTE;
break;
case WANT_MINUTE:
minute = currentValue;
currentValue = 0;
state = WANT_SECOND;
break;
default:
error ("Unexpected ':'");
return;
} // end of switch on state
break;
// the letter 'T'
case 'T':
case 't':
if (state == WANT_DAY)
{
day = currentValue;
currentValue = 0;
state = WANT_HOUR;
break;
}
else
{
error ("Unexpected 'T'");
return;
}
break;
// carriage-return
case '\r':
break; // ignore carriage-return
// newline
case '\n':
switch (state)
{
case WANT_DAY:
day = currentValue;
setDate ();
break;
case WANT_MINUTE:
minute = currentValue;
second = 0;
if (setDate ())
setTime ();
break;
case WANT_SECOND:
second = currentValue;
if (setDate ())
setTime ();
break;
default:
error ("Unexpected end of line");
break; // drop back to wanting the year again
} // end of switch on state
currentValue = 0;
state = WANT_YEAR;
break;
// other
default:
error ("Unexpected character");
return;
} // end of switch on inByte
} // end of processIncomingByte
void setup ()
{
Serial.begin (115200);
Serial.println ();
Serial.println ("Starting ...");
} // end of setup
void loop ()
{
while (Serial.available () > 0)
processIncomingByte (Serial.read ());
// do other stuff here
} // end of loop
My code does not use String at all, nor even character arrays. Instead by using a state machine you keep memory use to a minimum. This sort of technique is also handy for parsing things like GPS strings, incoming HTML, etc. where the incoming text might be lengthy (more than would fit in RAM even) but you only want a few salient details from it, like the latitude and longitude.
My view is that this sort of stuff (from your Github page) is confusing:
int nextIntField(char terminator){
int fieldEnd = command.indexOf(terminator, fieldStart);
if(fieldEnd != -1){
field += command.substring(fieldStart,fieldEnd);
fieldStart = fieldEnd + 1;
int value = field.toInt();
field.remove(0);
return value;
}
else{
return -1;
}
}
int remainingIntField(){
field += command.substring(fieldStart);
int value = field.toInt();
field.remove(0);
return value;
}
If this is for beginners, you are using something simple - String - and then throwing in a lot of complex stuff like concatenation, indexOf, substring, toInt, remove, reserve, etc. which effectively destroys the simplicity you are aiming for in the first place.
A state machine, on the other hand, is more like how we humans process things. We look for the year, then the month, then the day and so on, which is what the state machine does.