sscanf_P odd behaviour

I am using sscanf_P to parse the system generated compile date/time. This is simply loaded into the real time clock. It is only done if DEVELOPMENT is defined.

TIME defined format is hh:mm:ss
DATE defined format is mmm dd yyyy

sscanf_P(sd, PSTR("%s %u %u"), month, &(tm.Day), &yy); works just fine and returns the month, day, and year as expected.

sscanf_P(st, PSTR("%u:%u:%u"), &tm.Hour, &tm.Minute, &tm.Second) does not work at all. It returns only the last value, ie. tm.Second in this example and the other values are zero. If I use a format string with just two specifiers then it returns tm.Minute, and other values of zero. A format string with one specifier returns just tm.Hour.

I have tried dozens of different variations on this and none of them work. Eventually I gave up and resorted to laboriously tokenizing the string. That worked, but leaves the code less readable.

Here is the routine where the problem occurs. If anyone has any idea what is the problem I would appreciate an answer.

#ifdef _DEVELOPMENT_
#define _TFF_ 10 // Delay in seconds between compiling and running.

// Set the DS3231 date/time to compile date/time.
void setDS3231time() {
    time_t       t;
    tmElements_t tm;

    char st[]    = {__TIME__};                      // System defined format hh:mm:ss
    char sd[]    = {__DATE__};                      // System defined format mmm dd yyyy
    char sm[][4] = {"Jan", "Feb", "Mar", "Apr", 
                    "May", "Jun", "Jul", "Aug", 
                    "Sep", "Oct", "Nov", "Dec"};

    // Parse the time.
    // This bizare mess is because sscanf_P(st, PSTR("%u:%u:%u"), &(tm.Hour), &(tm.Minute), &(tm.Second))
    // refuses to work!!! Why???
    char *pp;
    uint8_t nn[3], n = 0;

    pp = strtok(st, ":");
    while(pp != NULL) {
        nn[n++] = atoi(pp);
        pp = strtok(NULL, ":");
    }
    tm.Hour   = nn[0];
    tm.Minute = nn[1];
    tm.Second = nn[2];

    // Parse the date. Month is returned as a string.
    uint16_t yy;
    char month[4];
    sscanf_P(sd, PSTR("%s %u %u"), month, &(tm.Day), &yy);
    tm.Year = CalendarYrToTm(yy);

    // Search for the month and return its ordinal value (12 on failure).
    tm.Month = 0;
    while(strcmp(sm[tm.Month], month) && (tm.Month < 12)) tm.Month++;

    // Set the RTC and adjust for the delay between compiling and running.
    t = makeTime(tm);
    RTC.set(t);
    setTime(t);
    adjustTime(_TFF_);
}
#endif // _DEVELOPMENT_

/* EOF FRIDGETEMPLOG.INO */

http://www.cplusplus.com/reference/cstdio/scanf/

specify.png

struct tmElements_t {
  byte Month;
  byte Day;
  unsigned int Year;
} tm;

const char monthTable[] PROGMEM = "JanFebMarAprMayJunJulAugSepOctNovDec";

void setup() {
  Serial.begin(250000);
  char sd[] = __DATE__;

  char month[4];
  sscanf_P(sd, PSTR("%s %hhu %u"), month, &tm.Day, &tm.Year);
  for (byte idx = 0; idx < 12; idx++) {
    if (!strncmp_P(month, monthTable + 3 * idx, 3)) {
      tm.Month = idx + 1;
    }
  }
  Serial.print(tm.Day);
  Serial.print(F("."));
  Serial.print(tm.Month);
  Serial.print(F("."));
  Serial.print(tm.Year);
}

void loop() {}
28.5.2017

OK Thanks for your quick reply. It solved my problem of how to specify an array of strings as progmem.
Unfortunately it does not address the problem I asked about.

The sscanf_P operating on the date string works just fine.
It is the sscanf_P operating on the time string that does not work.

The only difference I can see between the two is the presence of ‘’ as the field separator rather than ‘’ as in the case of the date string.

I tried replacing the s with s then parsing it replacing in the format string with and it worked just fine.

So it appears that sscanf_P (and sscanf) take umbrage at the in the format specifier string. I cannot find any documentation that supports this behaviour.

I think you just miss the hh specifier for the byte pointer arguments of scanf. Try

sscanf_P(st, PSTR("%hhu:%hhu:%hhu"), &tm.Hour, &tm.Minute, &tm.Second);

Or in a test context

struct tmElements_t {
  byte Hour;
  byte Minute;
  byte Second;
  byte Month;
  byte Day;
  unsigned int Year;
} tm;

const char monthTable[] PROGMEM = "JanFebMarAprMayJunJulAugSepOctNovDec";

void setup() {
  Serial.begin(250000);
  char sd[] = __DATE__;
  char st[] = __TIME__;

  sscanf_P(st, PSTR("%hhu:%hhu:%hhu"), &tm.Hour, &tm.Minute, &tm.Second);
  Serial.print(tm.Hour);
  Serial.print(F(":"));
  Serial.print(tm.Minute);
  Serial.print(F(":"));
  Serial.println(tm.Second);
  char month[4];
  sscanf_P(sd, PSTR("%s %hhu %u"), month, &tm.Day, &tm.Year);
  for (byte idx = 0; idx < 12; idx++) {
    if (!strncmp_P(month, monthTable + 3 * idx, 3)) {
      tm.Month = idx + 1;
    }
  }
  Serial.print(tm.Day);
  Serial.print(F("."));
  Serial.print(tm.Month);
  Serial.print(F("."));
  Serial.print(tm.Year);
}

void loop() {}
10:45:11
28.5.2017

If you do not tell scanf that the target has a different size, it assumes int (here 2 bytes) as width of the target,
overwriting other variables if the target happens to be a byte.

If you want to scan a long with scanf, you have to specify the 'l' in the format, thats just the same.

Maybe instead of the explicitly putting a ':' in the format statement, you could drop it with %*c something like :

sscanf_P(st, PSTR("%u%*c%u%*c%u"), &(tm.Hour), &(tm.Minute), &(tm.Second))

It is not the colon that creates the problem, but using bytes to store ints.

Whandall:
It is not the colon that creates the problem, but using bytes to store ints.

Good one. Yes. I've just tried it with int and byte. With int, it works perfectly. With byte, it matches exactly the phenomenon reported by the OP:

It returns only the last value, ie. tm.Second in this example

Whandall:
It is not the colon that creates the problem, but using bytes to store ints.

Just the answer I needed. Thank you very much. I knew about length sub-specifiers and have used them a lot in the past. Why I kept overlooking it this time I don't know. Maybe I assumed that sscanf would do the conversion for me.

Same old problem dogging lone developers everywhere. Without the "tea room get-togethers" you just make the same old mistakes over and over. This forum is the "tea room". :grin: