3

How can I size the char array for strftime without trial and error? Using mktime, the timestamp size N in the example has to be greater 86, otherwise I get arbitrary dates back. e.g.

N = 86 : 2013-07-13 02:41

N = 82 : 1979-05-18 13:23

How do I efficiently scale N without prior knowledge of the date? The check >0 does not help.

#include <iostream>
#include <cstring>
#include <ctime>

#define N 86

using namespace std;

int main(void)
{
time_t t;
struct tm ts;
char timestamp[N] ;

ts.tm_min    = 41;     
ts.tm_hour   = 2;     
ts.tm_mday   = 13;
ts.tm_mon    = 7 - 1;      
ts.tm_year   = 13 - 1900 + 2000;         

t = mktime(&ts);

if (strftime(timestamp, sizeof(timestamp)-1, "%Y-%m-%d %H:%M", &ts) > 0)
    cout << timestamp;
else {
    cerr << "strftime failed." <<endl;
    return 1;
}

return 0;
}
Community
  • 1
  • 1
smartmic
  • 661
  • 5
  • 15
  • 2
    This probably has more to do with uninitialized members in `ts` that confuse `mktime`. Try to `memset(&ts, 0, sizeof(ts));` before setting the members you want, so that `tm_sec` et al are guaranteed to be `0`. – Wintermute May 15 '15 at 22:31
  • ...or just initialize it - `tm ts{};` (This is C++ so no need for `struct`) – Captain Obvlious May 15 '15 at 22:47
  • Not sure why you think you need a variable length for this. Based on your format string all dates/times will be exactly the same length. – Retired Ninja May 15 '15 at 23:21
  • good points, initializing solves the problem. I just wonder why the correct result in case of not-initialized `ts` members depends then on the string length? – smartmic May 16 '15 at 18:21
  • All of the variables are local on the stack. Changing the size of one may shift the others so that whatever random data is in those addresses doesn't cause an issue. – Retired Ninja May 17 '15 at 06:45

2 Answers2

1

From the documentation for strftime:

If the length of the resulting C string, including the terminating null-character, doesn't exceed maxsize, the function returns the total number of characters copied to ptr (not including the terminating null-character). Otherwise, it returns zero, and the contents of the array pointed by ptr are indeterminate.

That means if you don't know the size and can dynamically allocate a string you can do something along the lines of:

int size = N; //  Some starting size
char *timestamp = malloc(size);

//  Your time stuff

int result = strftime(timestamp, size - 1, "%Y-%m-%d %H:%M", &ts);

// While there isn't enough room to store the result
while (result == 0)
{
  free(timestamp);  //  Free old data
  size *= 2;        //  Double the size (should be more than enough)
  timestamp = malloc(size); //  Allocate the new size. You can check for failed allocations here as well.

  //  Retry
  result = strftime(timestamp, size - 1, "%Y-%m-%d %H:%M", &ts);
}

std::cout << timestamp;
ozdrgnaDiies
  • 1,909
  • 1
  • 19
  • 34
  • `malloc` instead of `std::string`? Tsk. – Captain Obvlious May 15 '15 at 22:48
  • I am sticking to OP's use of `char[]`. Using an `std::string` is a different answer which you are free to post. Since OP is using C++ he might appreciate that answer more. – ozdrgnaDiies May 15 '15 at 22:50
  • I'm curious: how would you use `std::string` here? `std::string::c_str()` and `std::string::data()` are const. I'm using `std::unique_ptr timestamp(new char[size]);`. – chrisdembia Dec 05 '19 at 19:14
  • Note that `strftime` may return 0 even if there are no errors. E.g. in many locales `%p` results to an empty string and 0 is returned. This is documented on the man page. – bitsmanent Aug 26 '22 at 21:53
0

Because you tagged this as C++, perhaps you might consider the following.

--> Note that there is no struggle with the string size here.


// function to create a timestamp style string 
std::string   yyDmmDdd_hhCmmGet(time_t tt)
{
   std::stringstream ss;
   // goal - something like:  "%Y-%m-%d %H:%M"
   {
      struct tm mybdtod; // linux api: my broken down time of day

      // the following is a relatively slow function
      ::localtime_r (&tt, &mybdtod);
      // linux api - convert time_t to tm as local time

      ss << std::setw(4) << (mybdtod.tm_year+1900) 
         << "-"
         << std::setfill('0') << std::setw(2) << mybdtod.tm_mon+1 
         << "-"
         << std::setfill('0') << std::setw(2) <<  mybdtod.tm_mday   
         << " ";

      ss << std::dec << std::setfill('0')  << std::setw(2) 
         << mybdtod.tm_hour 
         << ":"
         << std::setfill('0')  << std::setw(2) 
         << mybdtod.tm_min;
   }
   return(ss.str());
}


int t186(void)
{
   struct tm ts; // linux api: time struct
   ::memset(&ts, 0, sizeof(tm));

   ts.tm_min    = 41;
   ts.tm_hour   = 3-1;
   ts.tm_mday   = 13;
   ts.tm_mon    = 7 - 1;
   ts.tm_year   = 13 - 1900 + 2000;

   time_t tt = mktime(&ts); // linux api: Convert tm struct to time_t

   // format time_t to string
   std::string s = yyDmmDdd_hhCmmGet(tt); // timestamp style

   std::cout << "\n" << s 
             << "\n            s.size(): " 
             << s.size() << " chars" << std::endl;

   // now we know how many chars timestamp needs 

   // add 1 to size because ?strftime seems to need it?
   char timestamp[s.size()+1];  

   (void)strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M", &ts);
   // linux api: format time_t to string

   std::cout << "\n" << timestamp << std::endl;
   std::cout << "   sizeof(timestamp): " 
             << sizeof(timestamp) << " chars" << std::endl;

   return(0);
 }
2785528
  • 5,438
  • 2
  • 18
  • 20