char *feedtime = "0:0";
String interval = "6";
char* convert(char* x, String y){
int hour;
int minute;
sscanf(x, "%d:%d", &hour, &minute);
char buf[5];
if (y == "6"){
if (hour > 17){
hour = (hour+6)%24;
snprintf(buf, 5, "%d%s", hour, ":0");
}
if (hour < 18){
hour = hour + 6;
snprintf(buf, 5, "%d%s", hour, ":0\0");
}
}
buf [5] = '\0';
return buf;
}
When I execute convert(time, interval);
the serial monitor returns the correct value but adds a ' or another symbol to it.
Any ideas why?
I updated my code from what people said, however I still get the same issue?
You are returning a pointer to a stack variable. This is wrong. Once the function exits the stack space used by 'buf' is undefined.
You need an extra character in your buffer. You only have a 4 character array, but you need 5 characters (2 for the hour, 2 for the :0, and 1 for the trailing 0). You also need to null terminate the string when you are done.
and what jcopenha says.
Your strings aren't properly zeroterminated. Increase the size of buf.
Related
I'm working on a program that performs calculations given a char array that represents a time in the format HH:MM:SS. It has to parse the individual time units.
Here's a cut down version of my code, just focusing on the hours:
unsigned long parseTime(const char *time)
{
int base = 10; //base 10
long hours = 60; //defaults to something out of range
char localTime[BUFSIZ] //declares a local array
strncpy(localTime, time, BUFSIZ); //copies parameter array to local
errno = 0; //sets errno to 0
char *par; //pointer
par = strchr(localTime, ':'); //parses to the nearest ':'
localTime[par - localTime] = '\0'; //sets the ':' to null character
hours = strtol(localTime, &par, base); //updates hours to parsed numbers in the char array
printf("errno is: %d\n", errno); //checks errno
errno = 0; //resets errno to 0
par++; //moves pointer past the null character
}
The problem is that if the input is invalid (e.g. aa:13:13), strtol() apparently doesn't detect an error because it's not updating errno to 1, so I can't do error handling. What am I getting wrong?
strtol is not required to produce an error code when no conversion can be performed. Instead you should use the second argument which stores the final position after conversion and compare it to the initial position.
BTW there are numerous other errors in your code that do not affect the problem you're seeing but which should also be fixed, such as incorrect use of strncpy.
As others have explained, strtol may not update errno in case it cannot perform any conversion. The C Standard only documents that errnor be set to ERANGE in case the converted value does not fit in a long integer.
Your code has other issues:
Copying the string with strncpy is incorrect: in case the source string is longer than BUFSIZ, localTime will not be null terminated. Avoid strncpy, a poorly understood function that almost never fits the purpose.
In this case, you no not need to clear the : to '\0', strtol will stop at the first non digit character. localTime[par - localTime] = '\0'; is a complicated way to write *par = '\0';
A much simpler version is this:
long parseTime(const char *time) {
char *par;
long hours;
if (!isdigit((unsigned char)*time) {
/* invalid format */
return -1;
}
errno = 0;
hours = strtol(time, &par, 10);
if (errno != 0) {
/* overflow */
return -2;
}
/* you may want to check that hour is within a decent range... */
if (*par != ':') {
/* invalid format */
return -3;
}
par++;
/* now you can parse further fields... */
return hours;
}
I changed the return type to long so you can easily check for invalid format and even determine which error from a negative return value.
For an even simpler alternative, use sscanf:
long parseTime(const char *time) {
unsigned int hours, minutes, seconds;
char c;
if (sscanf(time, "%u:%u:%u%c", &hours, &minutes, &seconds, &c) != 3) {
/* invalid format */
return -1;
}
if (hours > 1000 || minutes > 59 || seconds > 59) {
/* invalid values */
return -2;
}
return hours * 3600L + minutes * 60 + seconds;
}
This approach still accepts incorrect strings such as 1: 1: 1 or 12:00000002:1. Parsing the string by hand seem the most concise and efficient solution.
A useful trick with sscanf() is that code can do multiple passes to detect errant input:
// HH:MM:SS
int parseTime(const char *hms, unsigned long *secs) {
int n = 0;
// Check for valid text
sscanf(hms "%*[0-2]%*[0-9]:%*[0-5]%*[0-9]:%*[0-5]%*[0-9]%n", &n);
if (n == 0) return -1; // fail
// Scan and convert to integers
unsigned h,m,s;
sscanf(hms "%u:%u:%u", &h, &m, &s);
// Range checks as needed
if (h >= 24 || m >= 60 || s >= 60) return -1;
*sec = (h*60 + m)*60L + s;
return 0;
}
After hours = strtol(localTime, &par, base); statement you have to first save the value of errno. Because after this statement you are going to call printf() statement that also set errno accordingly.
printf("errno is: %d\n", errno);
So in this statement "errno" gives the error indication for printf() not for strtol()... To do so save "errno" before calling any library function because most of the library function interact with "errno".
The correct use is :
hours = strtol(localTime, &par, base);
int saved_error = errno; // Saving the error...
printf("errno is: %d\n", saved_error);
Now check it. It will give correct output surely...And one more thing to convert this errno to some meaningful string to represent error use strerror() function as :
printf("Error is: %s\n", strerror(saved_error));
I'm using C to read in an external text file. The input is not great and would look like;
0PAUL 22 ACACIA AVENUE 02/07/1986RN666
As you can see I have no obvious delimeter, and sometimes the values have no space between them. However I do know how long in character length each value should be when split. Which is as follows,
id = 1
name = 20
house number = 5
street name = 40
date of birth = 10
reference = 5
I've set up a structure I want to hold this information in, and have tried using fscanf to read in the file.
However I find something along the lines of just isn't doing what I need,
fscanf(file_in, "%1d, %20s", person.id[i], person.name[i]);
(The actual line I use attempts to grab all input but you should see where I'm going...)
The long term intention is to reformat the input file into another output file which would be made a little easier on the eye.
I appreciate I'm probably going about this all the wrong way, but I would hugely appreciate it if somebody could set me on the right path. If you're able to take it easy on me in regard to an obvious lack of understanding, I'd appreciate that also.
Thanks for reading
Use fgets to read each line at a time, then extract each field from the input line. Warning: no range checks is performed on buffers, so attention must be kept to resize buffers opportunely.
For example something like this (I don't compile it, so maybe some errors exist):
void copy_substr(const char * pBuffer, int content_size, int start_idx, int end_idx, char * pOutBuffer)
{
end_idx = end_idx > content_size ? content_size : end_idx;
int j = 0;
for (int i = start_idx; i < end_idx; i++)
pOutBuffer[j++] = pBuffer[i];
pOutBuffer[j] = 0;
return;
}
void test_solution()
{
char buffer_char[200];
fgets(buffer_char,sizeof(buffer_char),stdin); // use your own FILE handle instead of stdin
int len = strlen(buffer_char);
char temp_buffer[100];
// Reading first field: str[0..1), so only the char 0 (len=1)
int field_size = 1;
int filed_start_ofs = 0;
copy_substr(buffer_char, len, filed_start_ofs, filed_start_ofs + field_size, temp_buffer);
}
scanf is a good way to do it, you just need to use a buffer and call sscanf multiple times and give the good offsets.
For example :
char buffer[100];
fscanf(file_in, "%s",buffer);
sscanf(buffer, "%1d", person.id[i]);
sscanf(buffer+1, "%20s", person.name[i]);
sscanf(buffer+1+20, "%5d", person.street_number[i]);
and so on.
I feel like it is the easiest way to do it.
Please also consider using an array of your struct instead of a struct of arrays, it just feels wrong to have person.id[i] and not person[i].id
If you have fixed column widths, you can use pointer arithmetic to access substrings of your string str. if you have a starting index begin,
printf("%s", str + begin) ;
will print the substring beginning at begin and up to the end. If you want to print a string of a certain length, you can use printf's precision specifier .*, which takes a maximum length as additional argument:
printf("%.*s", length, str + begin) ;
If you want to copy the string to a temporary buffer, you could use strncpy, which will generate a null terminated string if the buffer is larger than the substring length. You could also use snprintf according to the above pattern:
char buf[length + 1];
snprintf(buf, sizeof(buf), "%.*s", length, str + begin) ;
This will extract leading and trailing spaces, which is probably not what you want. You could write a function to strip the unwanted whitespace; there should be plenty of examples here on SO.
You could also strip the whitespace when copying the substring. The example code below does this with the isspace function/macro from <ctype.h>:
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
int extract(char *buf, const char *str, int len)
{
const char *end = str + len;
int tail = -1;
int i = 0;
// skip leading white space;
while (str < end && *str && isspace(*str)) str++;
// copy string
while (str < end && *str) {
if (!isspace(*str)) tail = i + 1;
buf[i++] = *str++;
}
if (tail < 0) tail= i;
buf[tail] = '\0';
return tail;
}
int main()
{
char str[][80] = {
"0PAUL 22 ACACIA AVENUE 02/07/1986RN666",
"1BOB 1 POLK ST 01/04/1988RN802",
"2ALICE 99 WEST HIGHLAND CAUSEWAY 28/06/1982RN774"
};
int i;
for (i = 0; i < 3; i++) {
char *p = str[i];
char id[2];
char name[20];
char number[6];
char street[35];
char bday[11];
char ref[11];
extract(id, p + 0, 1);
extract(name, p + 1, 19);
extract(number, p + 20, 5);
extract(street, p + 25, 34);
extract(bday, p + 59, 10);
extract(ref, p + 69, 10);
printf("<person id='%s'>\n", id);
printf(" <name>%s</name>\n", name);
printf(" <house>%s</house>\n", number);
printf(" <street>%s</street>\n", street);
printf(" <birthday>%s</birthday>\n", bday);
printf(" <reference>%s</reference>\n", ref);
printf("</person>\n\n");
}
return 0;
}
There's a danger here, however: When you access a string at a certain position str + pos you should make sure that you don't go beyond the actual string length. For example, you string may be terminated after the name. When you access the birthday, you access valid memory, but it might contain garbage.
You can avoid this problem by padding the full string with spaces.
I'm new to low level programming and have been stuck with the following problem:
I get a string in the following format:
?cmd=ASET&<hour>&<minute>
where the hour and minute values always consist of 2 decimal numbers.
So an example of the string that can be received is:
"?cmd=ASET&08&30"
I am trying to write an if statement that recognizes that the string starts with "?cmd=ASET" and changes two global variables called minute and hour to the values in the String. I've been trying to do this with strtok(), but have not had any luck so far. So the global layout of my if statement would be:
if (String starts with "?cmd=ASET") {
minute = value found in the string;
hour = value found in the string;
}
I would really appreciate any help.
Try something like this, where cmd is a char * or char [] type variable. Note, strncmp() is safer than strcmp(). In general, in C programming, you want to use the variants of functions that limit the length, to avoid stack overflow attacks and other security risks. And since string to numeric functions can fail if given bad input it is better to use a form where you can check their status, which is why atoi() and atol() are not recommended. sscanf() allows status to be checked as does strtol() so they are both acceptable.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int
main()
{
char *string = "?cmd=ASET&12&30";
#define ASET_CMD "?cmd=ASET"
int hour = 0, minute = 0;
if (strncmp(string, ASET_CMD, strlen(ASET_CMD)) == 0) {
if (sscanf(string + strlen(ASET_CMD), "&%d&%d", &hour, &minute) != 2) {
printf("Couldn't parse input string");
exit(EXIT_FAILURE);
}
}
printf("hour: %d, minute: %d\n", hour, minute);
return(EXIT_SUCCESS);
}
$ cc -o prog prog.c
$ ./prog
hour: 12, minute: 30
If sscanf() is available for OP, consider:
unsigned hour, minute;
int n;
int cnt = sscanf(buffer, "?cmd=ASET&%2u&%2u%n", &hour, &minute, &n);
if (cnt == 2 && buffer[n] == 0) Success();
else Failure();
cnt will have the value of 2 if the prefix matches and 2 numbers where found.
The n detects if any addtional characters exist in the string.
there is a function atoi() which returns integer from string.
You can parse string like that
char string[16];
strcpy(string, "?cmd=ASET&08&30");
if (String starts with "?cmd=ASET") {
hour = atoi(string+10);
minute = atoi(string+13);
}
where +10 is position for hour and +13 for minute.
here is a working example:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main ()
{
int hour, minute;
char string[16];
strcpy(string, "?cmd=ASET&08&30");
/* Check if string contains ?cmd=ASET */
if (strstr(string, "?cmd=ASET") != NULL)
{
/* Parse minute and hour */
hour = atoi(string+10);
minute = atoi(string+13);
/* Print output */
printf("Minute: %02d \nHour: %02d\n", minute, hour);
}
}
Say I have a time like this
12:34:56
How would I go about storing the parsed integers into a char array?(this is a requirement)
argv[2] is the time passed in as a argument in the terminal.
This is what I have so far:
char *semi;
semi = strchr(argv[2],':')
&semi = '\0';
while(argv[2] != null){
By the way, this is in C. I'm aware other languages would make this easier.
Why not simply use sscanf?
Like
char hh[3], mm[3], ss[3];
const char time[] = "12:34:56";
sscanf(time, "%2s:%2s:%2s", hh, mm, ss);
You should check the return value from sscanf for validation of the input.
You can simply put the null byte '\0' in place of the character ':' in the string argv[2] and save pointers to the beginning of the hours, minutes and seconds part in the string.
// pointer to the start of the hour part
char *hh = argv[2];
char *mm, *ss;
char *temp = strchr(hh, ':');
// pointer to the start of the minute part
mm = temp + 1;
// null-terminate the hour part
*temp = '\0';
temp = strchr(mm, ':');
// pointer to the start of the second part
ss = temp + 1;
// null-terminate the minute part
*temp = '\0';
// print the hours, minutes and seconds part
printf("%s\n", hh);
printf("%s\n", mm);
printf("%s\n", ss);
argv[2] is a string, i.e., it is null-terminated, so the seconds part is already null-terminated.
I'm trying to make a program in c to read in from text a large database of ice rink activities. Does anyone know how to check for something that is not in the format
ie the text document will have something like this
sample
---------------------------------------------
date startT endT END
_______________________________________________
Ice Rink 1
1/13/2014 1:50 3:50 PM Public Skating
1/13/2014 1:50 3:50 PM Game
ice rink 2
1/13/2014 1:50 3:50 PM OPEN
I can already successfully read in one line of the event, date time and description
but how do I skip or detect the lines that don't match my scan in style of
fscanf(ifp,"%d/%d/%d\t%d:%d%s\t%d:%d%s\t\t %20c",
&e1[i].month,&e1[i].day,&e1[i].year,&e1[i].startH,&e1[i].startM,e1[i].MER1,&e1[i].endH,&e1[i].endM,e1[i].MER2,e1[i].event);
In short: how do detect cases that don't match this exactly?
Thanks in advance
As others have already said, you can check the return value of fscanf to find out whether a line is in the given format. That isn't the ideal approach, however. First, your data is organised line-wise, but fscanf treats the newline character like any other whitespace. You could read the line with fgets first and then apply sscanf on the line, but you'd still have one big monolithic format specifier that is easy to lose track of.
I'd like to propose another approach. Yor data lines seem to be organised in fields, which are separated from each other with tab characters. You could read the lines with fgets, then split them with strtok and finally scan the separate fields with sscanf. If you write custom wrapper functions to your sscanf statements, you can run a sanity check on the data when it's read.
/*
* Return true if str has format "hh:min AM/PM"
*/
int scan_time(const char *str, int *hh, int *mm)
{
char buf[4] = {0};
int n;
char c;
n = sscanf(str, "%d:%d%4s %c", hh, mm, buf, &c);
if (n == 4) return 0; /* trailing extra chars */
if (n < 2) return 0; /* missing minutes */
if (n == 3) {
int key = (buf[0] << 16) + (buf[1] << 8) + buf[2];
#define KEY(a, b) ((a << 16) + (b << 8))
switch (key) {
case KEY('a', 'm'):
case KEY('A', 'M'):
break;
case KEY('p', 'm'):
case KEY('P', 'M'):
*hh += 12;
break;
default:
return 0; /* invalid am/pm spec */
}
}
if (*hh < 0 || *hh >= 24) return 0; /* invalid hours */
if (*mm < 0 || *mm >= 60) return 0; /* invalid minutes */
return 1;
}
/*
* Return true, if str has format "mm/dd/year"
*/
int scan_date(const char *str, int *yy, int *mm, int *dd)
{
static const int mdays[] = {
0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};
int n;
char c;
n = sscanf(str, "%d/%d/%d %c", mm, dd, yy, &c);
if (n == 4) return 0; /* trailing extra chars */
if (n < 2) return 0; /* missing day */
if (n == 2) *yy = 2014; /* set default value */
if (*yy < 100) *yy += 2000; /* allow 1/1/14 */
if (*mm < 1 || *mm > 12) return 0; /* invalid month */
if (*dd < 1 || *dd > mdays[*mm]) return 0;
if (*mm == 2 && *dd == 29 % *yy % 4) return 0;
/* invalid day */
return 1;
}
/*
* Return true if line is "date \t time \t time \t text"
*/
int scan_line(char *str, struct Event *ev)
{
char *token;
token = strtok(str, "\t");
if (token == NULL) return 0;
if (!scan_date(token, &ev->year, &ev->month, &ev->day)) return 0;
token = strtok(NULL, "\t");
if (token == NULL) return 0;
if (!scan_time(token, &ev->startH, &ev->startM)) return 0;
token = strtok(NULL, "\t");
if (token == NULL) return 0;
if (!scan_time(token, &ev->endH, &ev->endM)) return 0;
token = strtok(NULL, "\t");
if (token == NULL) return 0;
strncpy(ev->event, token, 40);
return 1;
}
/*
* Remove trailing newline
*/
void chomp(char *str)
{
int l = strlen(str);
if (l && str[l - 1] == '\n') str[l - 1] = '\0';
}
/*
* Scan file with events
*/
int scan_file(const char *fn)
{
FILE *f = fopen(fn, "r");
if (f == NULL) return -1;
for (;;) {
struct Event ev;
char line[200];
if (fgets(line, 200, f) == NULL) break;
chomp(line);
if (scan_line(line, &ev)) {
printf("%s on %d/%d/%d\n",
ev.event, ev.month, ev.day, ev.year);
}
}
return 0;
}
Here, the scan_xxx functions scan a piece of data, check the format, assign the data and run a basic check on the data, so that yo'll never get an event on the 32nd of January or at 35:00h.
This makes the scanning functions more complicated than a single call to sscanf, but there are some benefits. First, the checks are done when reading the format. That means you don't have to check your data in the client code, because you can rely on sensible values. That also means that you don't have to duplicate code: Note how the checks for the time are coded only once, namely in scan_time, although the are applied twice per line, for the start and end times.
Treating the data field-wise in encapsulated functions allows you to change the format. For example, you could allow "1pm" as valid shortcut for "1:00 pm". You'd just have to re-scanf your time field with a second format string when the first format fails. You can also do that with your long single-line format, but since you have two time fields, that wouldn't be so easy.
Also note how the code above accepts 14 as shortcut for 2014 and interprets a missing year as 2014. All this might seem a bit too complicated for a simple data scanning tool, but you can re-use your functions in similar projects. Also, writing these tidy functions is more fun than wrangling longish scanf formats.
You can check the return of fscanf: "On success, the function returns the number of items of the argument list successfully filled. This count can match the expected number of items or be less (even zero) due to a matching failure, a reading error, or the reach of the end-of-file." If you know how many items you want to match, you can check how many were matched successfully. Now realize this, a subsequent one will pick up where the first one stopped. Meaning the next fscanf start where the other one stopped, either at the completion of a full fscanf, of the first time it encountered something not within the format.
Just brain storming but what you can do to get around this is use some form of fgets to get the line until a '\n' appears and do nothing with that line.
Note that whatever you do, because you have more than "type" of input line, you aren't going to be fscanfing the data straight into variables in your program. You have to know whether you have a rink name or an activity entry, before you can decide what to do with the line.
So you will first read a whole line in, then process it (and dump empty lines as you go)
You can use sscanf to see if the line is of an acceptable format. You will want to test it against the format of the activity entry first, because you will conclude that if the first element doesn't match (the first digit of a time) then you must have a rink-name. Then see if you can scan the result into a suitable rink name (you might want to check something about these).
If the sscanf for the activity entry fails on anything other than the first entry, you can tell your user which one it was and thus what it is that is wrong (IE if sscanf returns 3, then you know that the date didn't scan in properly).