Skip to content

Instantly share code, notes, and snippets.

@invidian
Last active February 26, 2021 11:37
Show Gist options
  • Save invidian/175d8a635302917bdcb89be08d100482 to your computer and use it in GitHub Desktop.
Save invidian/175d8a635302917bdcb89be08d100482 to your computer and use it in GitHub Desktop.
C unit tests time formatting
// Run using 'gcc main.c -g -O2 -pedantic -o main $(pkg-config gtk+-3.0 --cflags) && ./main'
#include <gdk/gdk.h>
#include <time.h>
#include <assert.h>
#include <limits.h>
static int hview_time_to_string(char *str, size_t maxsize, time_t time) {
struct tm *tm = gmtime(&time);
return strftime(str, maxsize, "%H:%M", tm);
}
// Using less than that may cause output to be truncated.
const int HVIEW_TIMES_TO_SPAN_MIN_BUF_SIZE = 14;
static void hview_times_to_span(char *time_span, size_t maxsize, time_t start_time, time_t end_time) {
char *ptr = time_span;
size_t charsWritten;
charsWritten = hview_time_to_string(ptr, maxsize, start_time);
maxsize -= charsWritten;
ptr += charsWritten;
int dataWritten = snprintf(ptr, maxsize, " - ");
if (dataWritten > 0 && dataWritten < maxsize) {
maxsize -= dataWritten;
ptr += dataWritten;
}
if(end_time)
{
charsWritten = hview_time_to_string(ptr, maxsize, end_time);
maxsize -= charsWritten;
ptr += charsWritten;
}
// Terminate generated string after last character, if we have some buffer remaining, it will
// be done after last character, if not, last character in the buffer will be replaced and output
// will be truncated.
ptr -= maxsize;
ptr = '\0';
}
static void hview_times_to_span_formats_both_start_and_end_time() {
char span[HVIEW_TIMES_TO_SPAN_MIN_BUF_SIZE];
time_t start_time = 0;
time_t end_time = 86400-1;
hview_times_to_span(span, sizeof(span), start_time, end_time);
assert(strcmp(span, "00:00 - 23:59") == 0);
}
static void hview_times_to_span_only_prints_start_time_when_end_time_is_zero() {
char span[HVIEW_TIMES_TO_SPAN_MIN_BUF_SIZE];
time_t start_time = 1;
time_t end_time = 0;
hview_times_to_span(span, sizeof(span), start_time, end_time);
assert(strcmp(span, "00:00 - ") == 0);
}
static void hview_times_to_span_writes_at_most_specified_amount_of_bytes() {
char span[HVIEW_TIMES_TO_SPAN_MIN_BUF_SIZE-1];
time_t start_time = 0;
time_t end_time = 86400-1;
int maxLength = sizeof(span);
hview_times_to_span(span, maxLength, start_time, end_time);
assert(strcmp(span, "00:00 - 23:59") != 0);
assert(strlen(span) < maxLength);
}
static void hview_time_to_string_writes_at_most_specified_amount_of_bytes() {
char time_text[6] = "";
time_t time = 86400-1;
int result = hview_time_to_string(time_text, sizeof(time_text)-1, time);
assert(strcmp(time_text, "23:59") != 0);
}
int main(int argc, char *argv[]) {
hview_time_to_string_writes_at_most_specified_amount_of_bytes();
hview_times_to_span_formats_both_start_and_end_time();
hview_times_to_span_only_prints_start_time_when_end_time_is_zero();
hview_times_to_span_writes_at_most_specified_amount_of_bytes();
}
@krnowak
Copy link

krnowak commented Feb 26, 2021

Hm hm hm. This code could be fine if you assume that buffer is always big enough, because:

  • strftime(str, maxsize, "%H:%M", tm) returns a length of a string produced from processing the format, or 0 if str was too small (or rather, maxsize was too low). So it's either all or nothing, basically. So you would need to check if return value is greater than 0 to detect the truncation.
  • snprintf(ptr, maxsize, " - ") will always return 3 (strlen(" - ")), even if maxsize is less than that. Which means you probably would need to check the return value against the maxsize to detect a truncation (see RETURN VALUE in man snprintf).

So you see, string handling in C is a PITA. I'll leave the decision to yout whether to be defensive against truncation or just assume that truncation won't happen. Probably the middle ground would be to assert that strftime's return value is greater than zero and that the snprintf's return value is less than maxsize.

Other than that:

  • ptr = ptr + charsWritten could be written as ptr += charsWritten. :)
  • strftime returns size_t (or gsize), so maybe charsWritten could be also gsize, but on the other hand, snprintf returns an int, so meh. It probably can stay as is.

Which leads me to another option you could consider. Since you are already using GLib, you could just use it:

GDateTime *dt_start = g_date_time_new_from_unix_local (start_time);
const char *s_end = "";
char *s_end_to_free = NULL;
char *s_start = g_date_time_format (dt_start, "%H:%M");
char *result;

g_date_time_unref (dt_start);
if (end_time != 0)
{
  GDateTime *dt_end = g_date_time_new_from_unix_local (end_time);
  s_end_to_free = g_date_time_format (dt_end, "%H:%M");
  s_end = s_end_to_free;
  g_date_time_unref (dt_end);
}
result = g_strdup_printf ("%s - %s", s_start, s_end);
g_free (s_start);
g_free (s_end_to_free);
return result; /* needs to be freed with g_free when not needed any more. */

Whether it's any better is for you to decide. :) Looks like more code and more memory allocations, but no fiddling with pointers and lengths.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment