Extract digits and strings from unspecific line format

Viewed 89

i want to extract all the integers and the corresponding time periods from the output below. After that i want to calculate the summa summarum uptime in days. The calculation is not the problem, but how can i extract the information from the bold marked part of the string?

The problem is, that the output can show up in different ways as you can see. I'm quite new in programming and i only have complicated ideas to solve this problem, like storing the data in two different arrays and then do the calculation.

I think there must be a more elegant solution? I not looking for code, i just want to get some ideas.

System uptime is 2 weeks 5 days 9 hours 4 minutes

System uptime is 1 year 25 weeks 11 days 5 hours 4 minutes

System uptime is 9 hours 4 minutes

int bla(const char *line) {
   ndx = strtok(line, " ");
   while (ndx != NULL) {
     // extract and store data
     ...
     ndx = strtok(NULL, " ");
   }
   // calculate sum of days
   return days;
}

Best Regards, Georg

3 Answers

This code below works:

#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>

#define MINUTE 1
#define HOUR   (MINUTE * 60)
#define DAY    (HOUR * 24)
#define WEEK   (DAY * 7)
#define MONTH  (DAY * 30)
#define YEAR   (DAY * 365)

int periodtominute(char *periodunit) {
  if (strstr(periodunit, "year") != NULL){
    return YEAR;
  }
  if (strstr(periodunit, "month") != NULL){
    return MONTH;
  }
  if (strstr(periodunit, "week") != NULL){
    return WEEK;
  }
  if (strstr(periodunit, "day") != NULL){
    return DAY;
  }
  if (strstr(periodunit, "hour") != NULL){
    return HOUR;
  }
  return MINUTE; // The priod unit is `minute`
}

int bla(const char *line) {
  char *linecopy = malloc(strlen(line) + 1);
  memcpy(linecopy, line, strlen(line));

  int totalminutes = 0;
  int periodtime;
  char *ndx = strtok(linecopy, " ");

  while (ndx != NULL) {
    periodtime = atoi(ndx);
    totalminutes += periodtime * periodtominute(strtok(NULL, " "));
    ndx = strtok(NULL, " ");
  }
  free(linecopy);
  return totalminutes;
}

int main() {
  char *first = "1 year 25 weeks 11 days 5 hours 4 minutes";
  int mins = bla(first);
  printf("%d\n", mins);

  char *second = "5 hours 4 minutes";
  mins = bla(second);
  printf("%d\n", mins);

}

Output:

793744
304

I suggest you return the minutes period time unit because you can convert the returned value to any time unit you want.

NOTE: The const char* can't be passed to strtok because strtok accepts the char * to be able to modify the char *. So a method is to copy the line parameter to allocated memory and free it at the end of the function.

Use the following functions for 'c'

long int strtol(const char *str, char **endptr, int base)

char *strstr(const char *haystack, const char *needle)

do a comparison with strstr() against the "days" and "weeks" to see which string has what from the strok() ptr, then pull out the numeric value with strtol()

You can code the conditional according to what is returned.

long days = 0;
char *ptr;

    //source is ptr from strtok

    if( strstr( source, "days" )){
       //                

        days = strtol(source, &ptr, 10);
       // now you have number of days 
       // ptr contains rest of string without numeric value
       // if days is zero there is no number
    }//... and so on


// example of strtol
char str[30] = "2030300 This is test";
   char *ptr;
   long ret;

   ret = strtol(str, &ptr, 10);
   printf("The number(unsigned long integer) is %ld\n", ret);
   printf("String part is |%s|", ptr);

The correct tool for this job is strtol():

  • use variables char *p; int val, seconds;
  • initialize it to point to the string to parse, initialize seconds to 0.
  • in a loop:
    • parse the number with `val = strtol(p, 0, &p);
    • skip the spaces at p: while (*p == ' ') p++;
    • if at the end of the string, return the number of seconds.
    • check for the different units possible, including plural forms, if you find a match:
      • multiply val by the number of seconds in the unit and add that to seconds,
      • skip the unit string
      • continue the loop
    • if no match: complain about the unrecognized unit and return -1.

Here is simple implementation:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static struct unit {
    const char name[8];
    int seconds;
} const units[] = {
    { "year", 365 * 24 * 60 * 60 }, { "years", 365 * 24 * 60 * 60 },
    { "month", 30 * 24 * 60 * 60 }, { "months", 30 * 24 * 60 * 60 },
    { "week", 7 * 24 * 60 * 60 }, { "weeks", 7 * 24 * 60 * 60 },
    { "day", 24 * 60 * 60 }, { "days", 24 * 60 * 60 },
    { "hour", 60 * 60 }, { "hours", 60 * 60 },
    { "minute", 60 }, { "minutes", 60 },
    { "second", 1 }, { "seconds", 1 },
};

int parse_time(const char *p) {
    int seconds = 0;
    while (*p) {
        size_t i, n, len;
        int val = strtol(p, (char **)&p, 0);
        p += strspn(p, " \t\r\n");
        if (*p != '\0' && !(*p >= '0' && *p <= '9')) {
            len = strcspn(p, " \t\r\n");
            for (i = 0, n = sizeof(units) / sizeof(units[0]); i < n; i++) {
                if (strlen(units[i].name) == len && !memcmp(p, units[i].name, len))
                    break;
            }
            if (i == n) {
                printf("unknown unit: %.*s\n", (int)len, p);
                return -1;
            }
            p += len;
            val *= units[i].seconds;
        }
        seconds += val;
    }
    return seconds;
}

int main(int argc, char *argv[]) {
    char buf[256];
    if (argc > 1) {
        while (*++argv)
            printf("%s -> %d seconds\n", *argv, parse_time(*argv));
    } else {
        for (;;) {
            printf("> ");
            fflush(stdout);
            if (!fgets(buf, sizeof buf, stdin))
                break;
            printf(" -> %d seconds\n", parse_time(buf));
        }
    }
    return 0;
}
Related