C - putting current date in a filename - c

I have 4 values: A,B,C,D. After doing a set of computations with those values, I want my code to output the results in a file of the form ABCD_MM.DD.YY.txt, to keep track of when it was done.
I'm not quite sure on the best way to do this in C. I have a "working" version using itoa(), which isn't a standard C function and will go (and has gone) unrecognized on machines other than mine when compiling.
This is the code I have for doing this, could someone help with a better (and universally accepted) way? The char array name was defined with a global scope.
void setFileName(){
time_t now;
struct tm *today;
char date[9];
//get current date
time(&now);
today = localtime(&now);
//print it in DD.MM.YY format.
strftime(date, 15, "%d.%m.%Y", today);
char buff[20];
char vars[20];
//put together a string of the form:
//"ABCD_DD.MM.YY.txt"
strcpy(vars, itoa(A, buff, 20));
strcat(vars, itoa(B, buff, 20));
strcat(vars, itoa(C, buff, 20));
strcat(vars, itoa(D, buff, 20));
strcpy(name, vars);
strcat(name, "_");
strcat(name, date);
strcat(name, ".txt");
}

char filename [ FILENAME_MAX ];
snprintf(filename, FILENAME_MAX, "%d%d%d%d_%s.txt", A, B, C, D, date);

Related

How do I create a JSON object in C?

I currently have a string in C that I have coded like this:
#define MESSAGE_STRING "{\"deviceName\":string, \"Telemetry\":{\"voltage\":12,\"current\":0.12}}"
As I am sending it to a cloud service that processes it as a JSON message and receives this:
But I want to do this in an easier way as this could get a lot more complex when I am sending more complicated JSON messages. I was wondering if there was any easy way to create these JSON objects within C easier?
Cheers
Write your JSON in a text file, and replace every parameter with a %d, %f, %s placeholder.
Then read this file, and fill in the values with sprintf()
But pay attention to the order of your parameters.
json.txt:
{
"device_name": "%s",
"Telemetry": {
"voltage": %d,
"current": %f
}
}
main.c
#include <stdio.h>
int main(void)
{
char json_format[4096];
char message_string[4096];
FILE* fp;
// Read in json format
fp = fopen("json.txt", "r");
int len = fread(json_format, 1, 4096, fp);
json_format[len] = '\0';
fclose(fp);
// Fill in parameters
const char* device_name = "string";
int voltage = 12;
float current = 0.12;
sprintf(message_string, json_format, device_name, voltage, current);
printf("%s", message_string);
}
The code is only for understanding the concept.
It works, but don't use it, because it doesn't check for erorrs and the memory management is bad too.

What function in C can I use to get the date alone separate from time?

I am building a C program and I need to use time separately and also the date separately. I want to do this without using the structure struct tm since I have to send the time and the date to different columns in my database table called Logs:
int main(){
duration = clock() - duration;
double Duration = ((double)duration)/CLOCKS_PER_SEC;
char* IPaddr = inet_ntoa(newAddr.sin_addr);
int PortNo = ntohs(newAddr.sin_port);
printf("\n%s %s %d %f\n", task, IPaddr, PortNo, Duration);
printf("now: %d-%d-%d %d:%d:%d\n",tm.tm_mday,tm.tm_mon+1,tm.tm_year+1900,tm.tm_hour,tm.tm_min,tm.tm_sec);
if (mysql_query(con, "INSERT INTO Logs(ClientIP, ClientPort, Job_type, SubmissionTime, SubmissionDate, Duration)
VALUES(%s, %d, %s, %, %, %f)")) {
finish_with_error(con);
}
return 0;
}
You might want to declare two character arrays, one for the date string and one for the time string, and call strftime twice to construct them.
Something like this:
char SubmissionDate[20], SubmissionTime[20];
strftime(SubmissionDate, sizeof(SubmissionDate), "%Y-%m-%d", &tm);
strftime(SubmissionTime, sizeof(SubmissionTime), "%H:%M:%S", &tm);

Adding time and date to a filename

I'm trying to write a source file, that would take in the filename/directory and then add current date & time to the end of the file name. So far I've found out that we can use the time() & localtime() functions. However, I'm not quite sure on where to start.
Could someone give me some instructions/steps on the path I could follow to get there?
Thanks! :D
Use time() and localtime() to get the current time
Use strftime() to format it to the format you want.
Use snprintf() to combine the formatted time with the original file name.
Use rename() to do the actual renaming.
Note that all of the above can be done in one line of shell script, so ask yourself whether you really need to do it in C, as opposed to relegating it to sh.
To get the date/time you have to include time.h.
Then you can use the localtime function like this:
time_t t = time(NULL);
struct tm tm = *localtime(&t);
The struct tm contains the desired information. You can access the day of the month by tm.tm_mday and so on.
You can use sprintf to write all the date information to a string like this:
char datum[128];
sprintf(datum, "%d-%d-%dT%d:%d:%d", tm.tm_year+1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
printf("%s\n", datum);
would give something like
2013-10-31T20:26:42
You can append this string to your filename by using strcat
This code will work.
char timestr[50];
time_t now = time(NULL);
struct tm *t = localtime(&now);
strftime(timestr, sizeof(timestr)-1, "%m-%d-%Y", t);
timestr[49] = 0;
if((filename = malloc(strlen(argv[2])+strlen(timestr)+1) != NULL) {
filename[0] = '\0';
strcat(filename,argv[2];
strcat(filename,"_");
strcat(filename,timestr);
strcat(filename,".log");
}
Just change the argv[2] as per your code argc.

ASN1_TIME_print functionality without BIO?

As described in this question: Openssl C++ get expiry date, there is the possibility to write an ASN1 time into a BIO buffer and then read it back into a custom buffer buf:
BIO *bio;
int write = 0;
bio = BIO_new(BIO_s_mem());
if (bio) {
if (ASN1_TIME_print(bio, tm))
write = BIO_read(bio, buf, len-1);
BIO_free(bio);
}
buf[write]='\0';
return write;
How could this be achieved without using BIO at all? The ASN1_TIME_print function is only present when OPENSSL_NO_BIO is not defined. Is there a way to write the time directly into a given buffer?
You can try the sample code below. It doesn't use BIO, but should give you the same output as the OP's example. If you don't trust the ASN1_TIME string, you'll want to add some error checking for:
notBefore->data is > 10 chars
each char value is between '0' and '9'
values for year, month, day, hour, minute, second
type
You should test for the type (i.e. UTC), if you expect multiple types.
You should also test whether or not the date/time is GMT and add that to the string if you want the output to match exactly as if using BIOs. see:
openssl/crypto/asn1/t_x509.c - ASN1_UTCTIME_print or ASN1_GENERALIZEDTIME_print
ASN1_TIME* notBefore = NULL;
int len = 32;
char buf[len];
struct tm tm_time;
notBefore = X509_get_notBefore(x509_cert);
// Format ASN1_TIME with type UTC into a tm struct
if(notBefore->type == V_ASN1_UTCTIME){
strptime((const char*)notBefore->data, "%y%m%d%H%M%SZ" , &tm_time);
strftime(buf, sizeof(char) * len, "%h %d %H:%M:%S %Y", &tm_time);
}
// Format ASN1_TIME with type "Generalized" into a tm struct
if(notBefore->type == V_ASN1_GENERALIZEDTIME){
// I didn't look this format up, but it shouldn't be too difficult
}
I think this should be possible, at least in terms of writing the time directly into a given buffer -- but you'll still need to use BIOs.
Ideally, BIO_new_mem_buf would suit, given that it creates an in-memory BIO using a given buffer as the source. Unfortunately, that function treats the given buffer as read-only, which is not what we want. However, we can create our own function (let's call it BIO_new_mem_buf2), based on the BIO_new_mem_buf source code:
BIO *BIO_new_mem_buf2(void *buf, int len)
{
BIO *ret;
BUF_MEM *b;
size_t sz;
if (!buf) {
BIOerr(BIO_F_BIO_NEW_MEM_BUF, BIO_R_NULL_PARAMETER);
return NULL;
}
sz = (size_t)len;
if (!(ret = BIO_new(BIO_s_mem())))
return NULL;
b = (BUF_MEM *)ret->ptr;
b->data = buf;
b->length = sz;
b->max = sz;
return ret;
}
This is just like BIO_new_mem_buf, except that a) the len argument must indicate the size of the given buffer, and b) the BIO is not marked "readonly".
With the above, you should now be able to call:
ASN1_TIME_print(bio, tm)
and have the time appear in your given buffer.
Note that I have not tested the above code, so YMMV. Hope this helps!

Smart variadic expansion based on format string

I have a daemon that reads a configuration file in order to know where to write something. In the configuration file, a line like this exists:
output = /tmp/foo/%d/%s/output
Or, it may look like this:
output = /tmp/foo/%s/output/%d
... or simply like this:
output = /tmp/foo/%s/output
... or finally:
output = /tmp/output
I have that line as cfg->pathfmt within my program. What I am trying to do now is to come up with some clever way of using it.
A little more explanation, the path can contain up to two components to be formatted. %d will be expanded as a job ID (int), %s as a job name (string). The user may want to use one, both or none in the configuration file. I need to know what they want and in what order before I finally pass it to snprintf(). I can kind of narrow it down, but I keep wanting to talk to strtok() and that seems ugly.
I want to give users this kind of flexibility, however I'm getting lost looking for a sensible, portable way to implement it. I'm also at a complete and total loss for how to begin searching for this.
I'd be very happy if:
Someone could help me narrow down the search phrase to find good examples
Someone could post a link to some OSS project implementing this
Someone could post some psuedo code
I don't want the code written for me, I'm just really stuck on what (I think) should be something very simple and need some help taking the first bite. I really feel like I'm over thinking and overlooking the obvious.
The end result should be a boolean function like this:
bool output_sugar(const char *fmt, int jobid, const char *jobname, struct job *j);
It would then call snprintf() (sensibly) on j->outpath, returning false if some kind of garbage (i.e. % followed by something not s, d or %) is in the config line (or its null). The sanity checks are easy, I'm just having a bit of a time getting the number (and order) of arguments to format correct.
Thanks in advance. Also, feel free to edit this title if you have the reputation to do so, as I said, I'm not quite sure how to ask the question in a single line. I think what I need is a parser, but it feels awkward using a full blown lexer / parser to handle one simple string.
Yes, you need a parser of some sort. It need not be complex, though:
void format_filename(const char *fmt, int jobid, const char *jobname,
char *buffer, size_t buflen)
{
char *end = buffer + buflen - 1;
const char *src = fmt;
char *dst = buffer;
char c;
assert(buffer != 0 && fmt != 0 && buflen != 0 && jobname != 0);
while ((c = *src++) != '\0')
{
if (dst >= end)
err_exit("buffer overflow in %s(): format = %s\n",
__func__, fmt);
else if (c != '%')
*dst++ = c;
else if ((c = *src++) == '\0' || c == '%')
{
*dst++ = '%';
if (c == '\0')
break;
}
else if (c == 's')
{
size_t len = strlen(jobname);
if (len > end - dst)
err_exit("buffer overflow on jobname in %s(): format = %s\n",
__func__, fmt);
else
{
strcpy(dst, jobname);
dst += len;
}
}
else if (c == 'd')
{
int nchars = snprintf(dst, end - dst, "%d", jobid);
if (nchars < 0 || nchars >= end - dst)
err_exit("format error on jobid in %s(); format = %s\n",
__func__, fmt);
dst += nchars;
}
else
err_exit("invalid format character %d in %s(): format = %s\n",
c, __func__, fmt);
}
*dst = '\0';
}
Now tested code. Note that it supports the '%%' notation to allow the user to embed a single '%' in the output. Also, it treats a single '%' at the end of the string as valid and equivalent to '%%'. It calls err_exit() on error; you can choose alternative error strategies as suits your system. I simply assume you have included <assert.h>, <stdio.h> and <string.h> and the header for the err_exit() (variadic) function.
Test code...
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <assert.h>
static void err_exit(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
vfprintf(stderr, fmt, args);
va_end(args);
exit(1);
}
... then format_filename() as above, then ...
#define DIM(x) (sizeof(x)/sizeof(*(x)))
static const char *format[] =
{
"/tmp/%d/name/%s",
"/tmp/%s/number/%d",
"/tmp/%s.%d%%",
"/tmp/%",
};
int main(void)
{
char buffer[64];
size_t i;
for (i = 0; i < DIM(format); i++)
{
format_filename(format[i], 1234, "job-name", buffer, sizeof(buffer));
printf("fmt = %-20s; name = %s\n", format[i], buffer);
}
return(0);
}
Using strtok is a error prone. You can treat your variables as a mini language using (fl)lex and yacc. There is simple tutorial here
%{
#include <stdio.h>
%}
%%
%d printf("%04d",jobid);
%s printf("%s",stripspaces(dirname));
%%
I made an ODBC wrapper that would let you do stuff like dbprintf("insert into blah values %s %D %T %Y", stuff here...); But it was many years ago and I bit it and parsed the format string using strtok.
If the number of options is small and you don't otherwise want/need the extra flexibility and complexity of a parser, you could simply search for each potential replacement substring using strstr().
If you have only the two options, you could tolerably create a four-branched if/else structure (only A, only B, both with A before B, both with B before A) in which to call sprintf() with the correctly ordered arguments. Otherwise, make multiple sprintf() calls, each of which replaces only the first replacement-marker in the format string. (This implies building a list of which replacements are needed and sorting them in appearance-order...)

Resources