3

From /usr/include/time.h:

/* Used by other time functions. */
struct tm
{
int tm_sec;. . . /* Seconds..[0-60] (1 leap second) */
int tm_min;. . . /* Minutes..[0-59] */
int tm_hour;. . . /* Hours.. [0-23] */
int tm_mday;. . . /* Day... [1-31] */
int tm_mon;. . . /* Month.. [0-11] */
int tm_year;. . . /* Year.- 1900. */
int tm_wday;. . . /* Day of week..[0-6] */
int tm_yday;. . . /* Days in year.[0-365].*/
int tm_isdst;.. . /* DST... [-1/0/1]*/

#ifdef. __USE_BSD
long int tm_gmtoff;. . /* Seconds east of UTC. */
__const char* tm_zone;. / Timezone abbreviation. */
#else
long int __tm_gmtoff;.. /* Seconds east of UTC. */
__const char* __tm_zone;. / Timezone abbreviation. */
#endif
};

If you want to write this structure to a file, and you want your program to read it back and to have multi-arch support (ie 32 bit version writes it, 64 bit version reads it), you'll have to do some hacks to make sure that it's the same size for each system. Does anyone know of a better way to keep time stamps that are architecture independent? Eg, I want to be able to write some structure eg, time_t, or struct tm to a file and read it back for any architecture. Does anyone have any experience or advice for this? Is struct tm even the best way to store a time stamp in C/C++? I realize there's a fair bit of overhead using this structure.

I currently have redefined the structure to the following:

//Force 32 bit format and space requirements
struct tm32
{
int tm_sec;. . . /* Seconds..[0-60] (1 leap second) */
int tm_min;. . . /* Minutes..[0-59] */
int tm_hour;. . . /* Hours.. [0-23] */
int tm_mday;. . . /* Day... [1-31] */
int tm_mon;. . . /* Month.. [0-11] */
int tm_year;. . . /* Year.- 1900. */
int tm_wday;. . . /* Day of week..[0-6] */
int tm_yday;. . . /* Days in year.[0-365].*/
int tm_isdst;.. . /* DST... [-1/0/1]*/

int tm_gmtoff; // this does nothing but hold space
int tm_zone; // this does nothing but hold space
};

Since the functions (mktime(), time(), gmtime(), strftime()) I'm using to manipulate the struct tm don't seem to even look at the last two fields in the struct tm structure, I simply use integers as place holders in their positions so that the size will always be the same. However, I'm still looking for a better solution to this...

EDIT: The int types I used in the above fix could be int32_t or whatever fixed width type chosen.

jkrez
  • 43
  • 6

4 Answers4

5

Ensuring that data saved is written in a way that can be read back on the desired platforms would not qualify as a "hack", in my opinion. Blindly saving out a (probably packed) structure in binary format and expecting to read it back easily and portably would, however.

You need to handle each field separately, since the compiler might add padding between the fields which would appear in a "binary dump" of the struct, but that is completely compiler-dependent and thus very bad for interoperability between platforms.

I would probably decide on a reasonable precision for the native int fields of the structure, say 32 bits, and write something like this:

void tm_serialize(FILE *out, const struct tm *tm)
{
  int32_serialize(out, tm->tm_sec);
  int32_serialize(out, tm->tm_min);
  /* and so on */
}

struct tm tm_deserialize(FILE *in)
{
  struct tm tm;
  tm.tm_sec = int32_deserialize(in);
  tm.tm_min = int32_deserialize(in);
  /* and so on */
  return tm;
}

Where, of course, int32_serialize() and int32_deserialize() simply write (and read) a binary representation of the 32-bit integer in a known (such as big-endian) format which is well-defined and easy to read back on any platform.

UPDATE: Serializing strings can of course be done in the exact same way, one popular format is the same as C's in-memory layout, i.e. 0-terminated char array. Then you'd just have:

void string_serialize(FILE *out, const char* string);
int  string_deserialize(FILE *in, char *string, size_t max);

Here, the string_deserialize() function must have a buffer size limit so it doesn't overflow anything by readin too much. I imagined the return value to indicate success/failure, so the calling code can take whatever measures it wants to.

In the above, I wasn't aiming for a space-minimized serialization, as a commenter pointed out many of the fields that are int at run-time don't really need all that precision, so they could be serialized into something smaller. If you wanted to do that, it would of course be trivial to invent appropriate functions like int8_serialize() and so on, and use the right one for each field.

unwind
  • 391,730
  • 64
  • 469
  • 606
  • +1—this is the generic way to serialize a structure. Nitpick: if you're going to pack it, the appropriate size for almost all of those fields is `uint8_t`. `tm_year` may need more (or even a signed type), and `tm_gmtoff` needs larger and signed. – derobert Dec 07 '11 at 09:12
  • This solves the issue of storing the integer fields, but I'll still need to create standard fixed width fields for the char * and long int members (which could range from 32bits - 64bits). – jkrez Dec 07 '11 at 09:21
1

struct tm is normally used with the C/POSIX time functions. Assuming you're not using it outside the ranges allowed by those functions, there are a couple of obvious ways:

  • use strftime to write it, strptime to read it. Advantage: very easy to understand the output data—its written in a normal time format (use e.g., ISO format)
  • call mktime to convert it to an time_t, then write it as binary or ASCII. Other direction is either localtime or gmtime. Advantage: small, typically 4 or 8 bytes for binary. Not much larger for ASCII.
derobert
  • 49,731
  • 15
  • 94
  • 124
  • Using strftime and strptime sounds like a decent solution without modification to existing data structures. I can't see any cases where my use would go beyond the expected uses of the C/POSIX time functions. Imma give this a shot. – jkrez Dec 07 '11 at 09:19
0

A sure way to make the file completely platform-independent is to use a text file.

Otherwise include <stdint.h> and use types like uint32_t in your converted structure.

Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
  • 1
    `uint32_t` won't actually protect you, as one architecture could be big-endian while another is little-endian. Or there could always be different padding. – derobert Dec 07 '11 at 09:07
0

gettimeofday(2) will return the number of seconds and microseconds since the epoch; since the time is of type time_t, it might be 32-bits (which place the end of the world at 2038) or it might be larger, so it would be wise to cast the time_t to a uint64_t. Store the 64-bit integer in a specified byte order using a tool such as htobe64(3).

Community
  • 1
  • 1
sarnold
  • 102,305
  • 22
  • 181
  • 238