summaryrefslogtreecommitdiff
path: root/navit/util.c
diff options
context:
space:
mode:
Diffstat (limited to 'navit/util.c')
-rw-r--r--navit/util.c1433
1 files changed, 928 insertions, 505 deletions
diff --git a/navit/util.c b/navit/util.c
index 2cceae460..944dc2a21 100644
--- a/navit/util.c
+++ b/navit/util.c
@@ -38,91 +38,343 @@ typedef int ssize_t ;
#include "debug.h"
#include "config.h"
-void
-strtoupper(char *dest, const char *src)
-{
- while (*src)
- *dest++=toupper(*src++);
- *dest='\0';
+void strtoupper(char *dest, const char *src) {
+ while (*src)
+ *dest++=toupper(*src++);
+ *dest='\0';
}
-void
-strtolower(char *dest, const char *src)
-{
- while (*src)
- *dest++=tolower(*src++);
- *dest='\0';
+void strtolower(char *dest, const char *src) {
+ while (*src)
+ *dest++=tolower(*src++);
+ *dest='\0';
}
-int
-navit_utf8_strcasecmp(const char *s1, const char *s2)
-{
- char *s1_folded,*s2_folded;
- int cmpres;
- s1_folded=g_utf8_casefold(s1,-1);
- s2_folded=g_utf8_casefold(s2,-1);
- cmpres=strcmp(s1_folded,s2_folded);
- dbg(lvl_debug,"Compared %s with %s, got %d\n",s1_folded,s2_folded,cmpres);
- g_free(s1_folded);
- g_free(s2_folded);
- return cmpres;
+/**
+ * @brief Fast compute of square root for unsigned ints
+ *
+ * @param n The input number
+ * @return sqrt(n)
+ */
+unsigned int uint_sqrt(unsigned int n) {
+ unsigned int h, p= 0, q= 1, r= n;
+
+ /* avoid q rollover */
+ if(n >= (1<<(sizeof(n)*8-2))) {
+ q = 1<<(sizeof(n)*8-2);
+ } else {
+ while ( q <= n ) {
+ q <<= 2;
+ }
+ q >>= 2;
+ }
+
+ while ( q != 0 ) {
+ h = p + q;
+ p >>= 1;
+ if ( r >= h ) {
+ p += q;
+ r -= h;
+ }
+ q >>= 2;
+ }
+ return p;
+}
+
+int navit_utf8_strcasecmp(const char *s1, const char *s2) {
+ char *s1_folded,*s2_folded;
+ int cmpres;
+ s1_folded=g_utf8_casefold(s1,-1);
+ s2_folded=g_utf8_casefold(s2,-1);
+ cmpres=strcmp(s1_folded,s2_folded);
+ dbg(lvl_debug,"Compared %s with %s, got %d",s1_folded,s2_folded,cmpres);
+ g_free(s1_folded);
+ g_free(s2_folded);
+ return cmpres;
+}
+
+/**
+ * @brief Trims all leading and trailing whitespace characters from a string.
+ *
+ * Whitespace characters are all up to and including 0x20.
+ *
+ * This function operates in-place, i.e. `s` will be modified.
+ *
+ * @param s The string to trim
+ */
+static void strtrim(char *s) {
+ char *tmp = g_strdup(s);
+ char *in = tmp;
+ while (strlen(in) && (in[0] <= 0x20))
+ in++;
+ while (strlen(in) && (in[strlen(in) - 1] <= 0x20))
+ in[strlen(in) - 1] = 0;
+ strcpy(s, in);
+ g_free(tmp);
+}
+
+/**
+ * @brief Parser states for `parse_for_systematic_comparison()`.
+ */
+enum parse_state {
+ parse_state_whitespace,
+ parse_state_numeric,
+ parse_state_alpha,
+};
+
+/**
+ * @brief Parses a string for systematic comparison.
+ *
+ * This is a helper function for `compare_name_systematic()`.
+ *
+ * The string is broken down into numeric and non-numeric parts. Whitespace characters are discarded
+ * unless they are surrounded by string characters, in which case the whole unit is treated as one
+ * string part. All strings are converted to lowercase and leading zeroes stripped from numbers.
+ *
+ * @param s The string to parse
+ *
+ * @return A buffer containing the parsed string, parts delimited by a null character, the last part
+ * followed by a double null character.
+ */
+static char * parse_for_systematic_comparison(const char *s) {
+ char *ret = g_malloc0(strlen(s) * 2 + 1);
+ const char *in = s;
+ char *out = ret;
+ char *part;
+ enum parse_state state = parse_state_whitespace;
+ int i = 0;
+ char c;
+
+ dbg(lvl_debug, "enter\n");
+
+ while (i < strlen(in)) {
+ c = in[i];
+ if ((c <= 0x20) || (c == ',') || (c == '-') || (c == '.') || (c == '/')) {
+ /* whitespace */
+ if (state == parse_state_numeric) {
+ part = g_malloc0(i + 1);
+ strncpy(part, in, i);
+ sprintf(part, "%d", atoi(part));
+ strcpy(out, part);
+ out += strlen(part) + 1;
+ dbg(lvl_debug, "part='%s'\n", part);
+ g_free(part);
+ in += i;
+ i = 1;
+ state = parse_state_whitespace;
+ } else
+ i++;
+ } else if ((c >= '0') && (c <= '9')) {
+ /* numeric */
+ if (state == parse_state_alpha) {
+ part = g_malloc0(i + 1);
+ strncpy(part, in, i);
+ strtrim(part);
+ strcpy(out, part);
+ out += strlen(part) + 1;
+ dbg(lvl_debug, "part='%s'\n", part);
+ g_free(part);
+ in += i;
+ i = 1;
+ } else
+ i++;
+ state = parse_state_numeric;
+ } else {
+ /* alpha */
+ if (state == parse_state_numeric) {
+ part = g_malloc0(i + 1);
+ strncpy(part, in, i);
+ sprintf(part, "%d", atoi(part));
+ strcpy(out, part);
+ out += strlen(part) + 1;
+ dbg(lvl_debug, "part='%s'\n", part);
+ g_free(part);
+ in += i;
+ i = 1;
+ } else
+ i++;
+ state = parse_state_alpha;
+ }
+ }
+
+ if (strlen(in) > 0) {
+ if (state == parse_state_numeric) {
+ part = g_malloc0(strlen(in) + 1);
+ strcpy(part, in);
+ sprintf(part, "%d", atoi(part));
+ strcpy(out, part);
+ dbg(lvl_debug, "part='%s'\n", part);
+ g_free(part);
+ } else if (state == parse_state_alpha) {
+ part = g_malloc0(strlen(in) + 1);
+ strcpy(part, in);
+ strtrim(part);
+ strcpy(out, part);
+ dbg(lvl_debug, "part='%s'\n", part);
+ g_free(part);
+ }
+ }
+
+ return ret;
+}
+
+/**
+ * @brief Compares two name_systematic strings.
+ *
+ * A name_systematic string is typically used for road reference numbers (A 4, I-51, SP526). This
+ * function performs a fuzzy comparison: Each string is broken down into numeric and non-numeric parts.
+ * Then both strings are compared part by part. The following rules apply:
+ *
+ * \li Semicolons denote sequences of strings, and the best match between any pair of strings from `s1` and `s2` is
+ * returned.
+ * \li Whitespace bordering on a number is discarded.
+ * \li Whitespace surrounded by string characters is treated as one string with the surrounding characters.
+ * \li If one string has more parts than the other, the shorter string is padded with null parts.
+ * \li null equals null.
+ * \li null does not equal non-null.
+ * \li Numeric parts are compared as integers, hence `'042'` equals `'42'`.
+ * \li Comparison of string parts is case-insensitive.
+ *
+ * Partial matches are currently determined by determining each part of one string with each part of the other. Each
+ * part of one string that is matched by at least one part of the other increases the score. Order is currently not
+ * taken into account, i.e. `'42A'` and `'A-42A'` are both considered full (not partial) matches for `'A42'`. Future
+ * versions may change this.
+ *
+ * @param s1 The first string
+ * @param s2 The second string
+ *
+ * @return 0 if both strings match, nonzero if they do not. `MAX_MISMATCH` indicates a complete mismatch; values in
+ * between indicate partial matches (lower values correspond to better matches).
+ */
+int compare_name_systematic(const char *s1, const char *s2) {
+ int ret = MAX_MISMATCH;
+ int tmp;
+ int elements = 0, matches = 0;
+ char *l = NULL, *r = NULL, *l0, *r0;
+
+ if (!s1 || !s1[0]) {
+ if (!s2 || !s2[0])
+ return 0;
+ else
+ return MAX_MISMATCH;
+ } else if (!s2 || !s2[0])
+ return MAX_MISMATCH;
+
+ /* break up strings at semicolons and parse each separately, return 0 if any two match */
+ if (strchr(s1, ';')) {
+ l = g_strdup(s1);
+ for (l0 = strtok(l, ";"); l0; l0 = strtok(NULL, ";")) {
+ tmp = compare_name_systematic(l0, s2);
+ if (tmp < ret)
+ ret = tmp;
+ if (!ret)
+ break;
+ }
+ g_free(l);
+ return ret;
+ } else if (strchr(s2, ';')) {
+ r = g_strdup(s2);
+ for (r0 = strtok(r, ";"); r0; r0 = strtok(NULL, ";")) {
+ tmp = compare_name_systematic(s1, r0);
+ if (tmp < ret)
+ ret = tmp;
+ if (!ret)
+ break;
+ }
+ g_free(r);
+ return ret;
+ }
+
+ /* s1 and s2 are single strings (no semicolons) */
+ l0 = parse_for_systematic_comparison(s1);
+ r0 = parse_for_systematic_comparison(s2);
+
+ /* count left-hand elements and all left-hand elements matched by a right-hand element */
+ for (l = l0; l[0]; l += strlen(l) + 1) {
+ elements++;
+ for (r = r0; r[0]; r += strlen(r) + 1) {
+ if (atoi(l) || (l[0] == '0')) {
+ if ((atoi(r) || (r[0] == '0')) && (atoi(l) == atoi(r))) {
+ matches++;
+ break;
+ }
+ } else if (!strcasecmp(l, r)) {
+ matches++;
+ break;
+ }
+ }
+ }
+
+ /* same in the opposite direction */
+ for (r = r0; r[0]; r += strlen(r) + 1) {
+ elements++;
+ for (l = l0; l[0]; l += strlen(l) + 1) {
+ if (atoi(l) || (l[0] == '0')) {
+ if ((atoi(r) || (r[0] == '0')) && (atoi(l) == atoi(r))) {
+ matches++;
+ break;
+ }
+ } else if (!strcasecmp(l, r)) {
+ matches++;
+ break;
+ }
+ }
+ }
+
+ g_free(l0);
+ g_free(r0);
+
+ ret = ((elements - matches) * MAX_MISMATCH) / elements;
+
+ dbg(lvl_debug, "'%s' %s '%s', ret=%d",
+ s1, ret ? (ret == MAX_MISMATCH ? "does NOT match" : "PARTIALLY matches") : "matches", s2, ret);
+
+ return ret;
}
-static void
-hash_callback(gpointer key, gpointer value, gpointer user_data)
-{
- GList **l=user_data;
- *l=g_list_prepend(*l, value);
+static void hash_callback(gpointer key, gpointer value, gpointer user_data) {
+ GList **l=user_data;
+ *l=g_list_prepend(*l, value);
}
-GList *
-g_hash_to_list(GHashTable *h)
-{
- GList *ret=NULL;
- g_hash_table_foreach(h, hash_callback, &ret);
+GList *g_hash_to_list(GHashTable *h) {
+ GList *ret=NULL;
+ g_hash_table_foreach(h, hash_callback, &ret);
- return ret;
+ return ret;
}
-static void
-hash_callback_key(gpointer key, gpointer value, gpointer user_data)
-{
- GList **l=user_data;
- *l=g_list_prepend(*l, key);
+static void hash_callback_key(gpointer key, gpointer value, gpointer user_data) {
+ GList **l=user_data;
+ *l=g_list_prepend(*l, key);
}
-GList *
-g_hash_to_list_keys(GHashTable *h)
-{
- GList *ret=NULL;
- g_hash_table_foreach(h, hash_callback_key, &ret);
+GList *g_hash_to_list_keys(GHashTable *h) {
+ GList *ret=NULL;
+ g_hash_table_foreach(h, hash_callback_key, &ret);
- return ret;
+ return ret;
}
-gchar *
-g_strconcat_printf(gchar *buffer, gchar *fmt, ...)
-{
- gchar *str,*ret;
- va_list ap;
+gchar *g_strconcat_printf(gchar *buffer, gchar *fmt, ...) {
+ gchar *str,*ret;
+ va_list ap;
- va_start(ap, fmt);
- str=g_strdup_vprintf(fmt, ap);
- va_end(ap);
- if (! buffer)
- return str;
- ret=g_strconcat(buffer, str, NULL);
- g_free(buffer);
- g_free(str);
- return ret;
+ va_start(ap, fmt);
+ str=g_strdup_vprintf(fmt, ap);
+ va_end(ap);
+ if (! buffer)
+ return str;
+ ret=g_strconcat(buffer, str, NULL);
+ g_free(buffer);
+ g_free(str);
+ return ret;
}
#ifndef HAVE_GLIB
int g_utf8_strlen_force_link(gchar *buffer, int max);
-int
-g_utf8_strlen_force_link(gchar *buffer, int max)
-{
- return g_utf8_strlen(buffer, max);
+int g_utf8_strlen_force_link(gchar *buffer, int max) {
+ return g_utf8_strlen(buffer, max);
}
#endif
@@ -132,33 +384,30 @@ g_utf8_strlen_force_link(gchar *buffer, int max)
#endif
#if defined(_WIN32) || defined(__CEGCC__) || defined (__APPLE__) || defined(HAVE_API_ANDROID)
-char *stristr(const char *String, const char *Pattern)
-{
- char *pptr, *sptr, *start;
-
- for (start = (char *)String; *start != (int)NULL; start++)
- {
- /* find start of pattern in string */
- for ( ; ((*start!=(int)NULL) && (toupper(*start) != toupper(*Pattern))); start++)
- ;
- if ((int)NULL == *start)
- return NULL;
-
- pptr = (char *)Pattern;
- sptr = (char *)start;
-
- while (toupper(*sptr) == toupper(*pptr))
- {
- sptr++;
- pptr++;
-
- /* if end of pattern then pattern was found */
-
- if ((int)NULL == *pptr)
- return (start);
- }
- }
- return NULL;
+char *stristr(const char *String, const char *Pattern) {
+ char *pptr, *sptr, *start;
+
+ for (start = (char *)String; *start != (int)NULL; start++) {
+ /* find start of pattern in string */
+ for ( ; ((*start!=(int)NULL) && (toupper(*start) != toupper(*Pattern))); start++)
+ ;
+ if ((int)NULL == *start)
+ return NULL;
+
+ pptr = (char *)Pattern;
+ sptr = (char *)start;
+
+ while (toupper(*sptr) == toupper(*pptr)) {
+ sptr++;
+ pptr++;
+
+ /* if end of pattern then pattern was found */
+
+ if ((int)NULL == *pptr)
+ return (start);
+ }
+ }
+ return NULL;
}
#ifndef SIZE_MAX
@@ -185,128 +434,177 @@ char *stristr(const char *String, const char *Pattern)
#ifndef HAVE_GETDELIM
/**
* @brief Reads the part of a file up to a delimiter to a string.
- * <p>
+ * <p>
* Read up to (and including) a DELIMITER from FP into *LINEPTR (and NUL-terminate it).
*
* @param lineptr Pointer to a pointer returned from malloc (or NULL), pointing to a buffer. It is
* realloc'ed as necessary and will receive the data read.
- * @param n Size of the buffer.
+ * @param n Size of the buffer.
*
* @return Number of characters read (not including the null terminator), or -1 on error or EOF.
*/
-ssize_t
-getdelim (char **lineptr, size_t *n, int delimiter, FILE *fp)
-{
- int result;
- size_t cur_len = 0;
+ssize_t getdelim (char **lineptr, size_t *n, int delimiter, FILE *fp) {
+ int result;
+ size_t cur_len = 0;
- if (lineptr == NULL || n == NULL || fp == NULL)
- {
- return -1;
+ if (lineptr == NULL || n == NULL || fp == NULL) {
+ return -1;
}
- flockfile (fp);
+ flockfile (fp);
- if (*lineptr == NULL || *n == 0)
- {
- *n = 120;
- *lineptr = (char *) realloc (*lineptr, *n);
- if (*lineptr == NULL)
- {
- result = -1;
- goto unlock_return;
- }
+ if (*lineptr == NULL || *n == 0) {
+ *n = 120;
+ *lineptr = (char *) realloc (*lineptr, *n);
+ if (*lineptr == NULL) {
+ result = -1;
+ goto unlock_return;
+ }
}
- for (;;)
- {
- int i;
-
- i = getc (fp);
- if (i == EOF)
- {
- result = -1;
- break;
- }
-
- /* Make enough space for len+1 (for final NUL) bytes. */
- if (cur_len + 1 >= *n)
- {
- size_t needed_max=SIZE_MAX;
- size_t needed = 2 * *n + 1; /* Be generous. */
- char *new_lineptr;
- if (needed_max < needed)
- needed = needed_max;
- if (cur_len + 1 >= needed)
- {
- result = -1;
- goto unlock_return;
- }
-
- new_lineptr = (char *) realloc (*lineptr, needed);
- if (new_lineptr == NULL)
- {
- result = -1;
- goto unlock_return;
- }
-
- *lineptr = new_lineptr;
- *n = needed;
- }
-
- (*lineptr)[cur_len] = i;
- cur_len++;
-
- if (i == delimiter)
- break;
- }
- (*lineptr)[cur_len] = '\0';
- result = cur_len ? cur_len : result;
-
- unlock_return:
- funlockfile (fp); /* doesn't set errno */
-
- return result;
+ for (;;) {
+ int i;
+
+ i = getc (fp);
+ if (i == EOF) {
+ result = -1;
+ break;
+ }
+
+ /* Make enough space for len+1 (for final NUL) bytes. */
+ if (cur_len + 1 >= *n) {
+ size_t needed_max=SIZE_MAX;
+ size_t needed = 2 * *n + 1; /* Be generous. */
+ char *new_lineptr;
+ if (needed_max < needed)
+ needed = needed_max;
+ if (cur_len + 1 >= needed) {
+ result = -1;
+ goto unlock_return;
+ }
+
+ new_lineptr = (char *) realloc (*lineptr, needed);
+ if (new_lineptr == NULL) {
+ result = -1;
+ goto unlock_return;
+ }
+
+ *lineptr = new_lineptr;
+ *n = needed;
+ }
+
+ (*lineptr)[cur_len] = i;
+ cur_len++;
+
+ if (i == delimiter)
+ break;
+ }
+ (*lineptr)[cur_len] = '\0';
+ result = cur_len ? cur_len : result;
+
+unlock_return:
+ funlockfile (fp); /* doesn't set errno */
+
+ return result;
}
#endif
#ifndef HAVE_GETLINE
-ssize_t
-getline (char **lineptr, size_t *n, FILE *stream)
-{
- return getdelim (lineptr, n, '\n', stream);
+ssize_t getline (char **lineptr, size_t *n, FILE *stream) {
+ return getdelim (lineptr, n, '\n', stream);
}
#endif
#if defined(_UNICODE)
-wchar_t* newSysString(const char *toconvert)
-{
- int newstrlen = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, toconvert, -1, 0, 0);
- wchar_t *newstring = g_new(wchar_t,newstrlen);
- MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, toconvert, -1, newstring, newstrlen) ;
- return newstring;
+wchar_t* newSysString(const char *toconvert) {
+ int newstrlen = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, toconvert, -1, 0, 0);
+ wchar_t *newstring = g_new(wchar_t,newstrlen);
+ MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, toconvert, -1, newstring, newstrlen) ;
+ return newstring;
}
#else
-char * newSysString(const char *toconvert)
-{
- return g_strdup(toconvert);
+char * newSysString(const char *toconvert) {
+ return g_strdup(toconvert);
}
#endif
#endif
+/**
+ * @brief Optimizes the format of a string, adding carriage returns so that when displayed, the result text zone is roughly as wide as high
+ *
+ * @param[in,out] s The string to proces (will be modified by this function, but length will be unchanged)
+ */
+void square_shape_str(char *s) {
+ char *c;
+ char *last_break;
+ unsigned int max_cols = 0;
+ unsigned int cur_cols = 0;
+ unsigned int max_rows = 0;
+ unsigned int surface;
+ unsigned int target_cols;
+
+ if (!s)
+ return;
+ for (c=s; *c!='\0'; c++) {
+ if (*c==' ') {
+ if (max_cols < cur_cols)
+ max_cols = cur_cols;
+ cur_cols = 0;
+ max_rows++;
+ } else
+ cur_cols++;
+ }
+ if (max_cols < cur_cols)
+ max_cols = cur_cols;
+ if (cur_cols) /* If last line does not end with CR, add it to line numbers anyway */
+ max_rows++;
+ /* Give twice more room for rows (hence the factor 2 below)
+ * This will render as a rectangular shape, taking more horizontal space than vertical */
+ surface = max_rows * 2 * max_cols;
+ target_cols = uint_sqrt(surface);
+
+ if (target_cols < max_cols)
+ target_cols = max_cols;
+
+ target_cols = target_cols + target_cols/10; /* Allow 10% extra on columns */
+ dbg(lvl_debug, "square_shape_str(): analyzing input text=\"%s\". max_rows=%u, max_cols=%u, surface=%u, target_cols=%u",
+ s, max_rows, max_cols, max_rows * 2 * max_cols, target_cols);
+
+ cur_cols = 0;
+ last_break = NULL;
+ for (c=s; *c!='\0'; c++) {
+ if (*c==' ') {
+ if (cur_cols>=target_cols) { /* This line is too long, break at the previous non alnum character */
+ if (last_break) {
+ *last_break =
+ '\n'; /* Replace the previous non alnum character with a line break, this creates a new line and prevents the previous line from being too long */
+ cur_cols = c-last_break;
+ }
+ }
+ last_break = c; /* Record this position as a candidate to insert a line break */
+ }
+ cur_cols++;
+ }
+ if (cur_cols>=target_cols && last_break) {
+ *last_break =
+ '\n'; /* Replace the previous non alnum character with a line break, this creates a new line and prevents the previous line from being too long */
+ }
+
+ dbg(lvl_debug, "square_shape_str(): output text=\"%s\"", s);
+}
+
#if defined(_MSC_VER) || (!defined(HAVE_GETTIMEOFDAY) && defined(HAVE_API_WIN32_BASE))
/**
* Impements a simple incomplete version of gettimeofday. Only usefull for messuring
* time spans, not the real time of day.
*/
-int gettimeofday(struct timeval *time, void *local)
-{
- int milliseconds = GetTickCount();
+int gettimeofday(struct timeval *time, void *local) {
+ int milliseconds = GetTickCount();
- time->tv_sec = milliseconds/1000;
- time->tv_usec = (milliseconds - (time->tv_sec * 1000)) * 1000;
+ time->tv_sec = milliseconds/1000;
+ time->tv_usec = (milliseconds - (time->tv_sec * 1000)) * 1000;
- return 0;
+ return 0;
}
#endif
/**
@@ -316,32 +614,160 @@ int gettimeofday(struct timeval *time, void *local)
*
* @return The number of seconds elapsed since January 1, 1970, 00:00:00 UTC.
*/
-unsigned int
-iso8601_to_secs(char *iso8601)
-{
- int a,b,d,val[6],i=0;
- char *start=iso8601,*pos=iso8601;
- while (*pos && i < 6) {
- if (*pos < '0' || *pos > '9') {
- val[i++]=atoi(start);
- pos++;
- start=pos;
- }
- if(*pos)
- pos++;
- }
-
- a=val[0]/100;
- b=2-a+a/4;
-
- if (val[1] < 2) {
- val[0]--;
- val[1]+=12;
- }
-
- d=1461*(val[0]+4716)/4+306001*(val[1]+1)/10000+val[2]+b-2442112;
-
- return ((d*24+val[3])*60+val[4])*60+val[5];
+unsigned int iso8601_to_secs(char *iso8601) {
+ int a,b,d,val[6],i=0;
+ char *start=iso8601,*pos=iso8601;
+ while (*pos && i < 6) {
+ if (*pos < '0' || *pos > '9') {
+ val[i++]=atoi(start);
+ pos++;
+ start=pos;
+ }
+ if(*pos)
+ pos++;
+ }
+
+ a=val[0]/100;
+ b=2-a+a/4;
+
+ if (val[1] < 2) {
+ val[0]--;
+ val[1]+=12;
+ }
+
+ d=1461*(val[0]+4716)/4+306001*(val[1]+1)/10000+val[2]+b-2442112;
+
+ return ((d*24+val[3])*60+val[4])*60+val[5];
+}
+
+/**
+ * @brief Converts a `tm` structure to `time_t`
+ *
+ * Returns the value of type `time_t` that represents the UTC time described by the `tm` structure
+ * pointed to by `pt` (which may be modified).
+ *
+ * This function performs the reverse translation that `gmtime()` does. As this functionality is absent
+ * in the standard library, it is emulated by calling `mktime()`, converting its output into both GMT
+ * and local time, comparing the results and calling `mktime()` again with an input adjusted for the
+ * offset in the opposite direction. This ensures maximum portability.
+ *
+ * The values of the `tm_wday` and `tm_yday` members of `pt` are ignored, and the values of the other
+ * members are interpreted even if out of their valid ranges (see `struct tm`). For example, `tm_mday`
+ * may contain values above 31, which are interpreted accordingly as the days that follow the last day
+ * of the selected month.
+ *
+ * A call to this function automatically adjusts the values of the members of `pt` if they are off-range
+ * or—in the case of `tm_wday` and `tm_yday`—if their values are inconsistent with the other members.
+ *
+ */
+time_t mkgmtime(struct tm * pt) {
+ time_t ret;
+
+ /* Input, GMT and local time */
+ struct tm * pti, * pgt, * plt;
+
+ pti = g_memdup(pt, sizeof(struct tm));
+
+ ret = mktime(pti);
+
+ pgt = g_memdup(gmtime(&ret), sizeof(struct tm));
+ plt = g_memdup(localtime(&ret), sizeof(struct tm));
+
+ pti->tm_year = pt->tm_year - pgt->tm_year + plt->tm_year;
+ pti->tm_mon = pt->tm_mon - pgt->tm_mon + plt->tm_mon;
+ pti->tm_mday = pt->tm_mday - pgt->tm_mday + plt->tm_mday;
+ pti->tm_hour = pt->tm_hour - pgt->tm_hour + plt->tm_hour;
+ pti->tm_min = pt->tm_min - pgt->tm_min + plt->tm_min;
+ pti->tm_sec = pt->tm_sec - pgt->tm_sec + plt->tm_sec;
+
+ ret = mktime(pti);
+
+ dbg(lvl_debug, "time %ld (%02d-%02d-%02d %02d:%02d:%02d)\n", ret, pti->tm_year, pti->tm_mon, pti->tm_mday,
+ pti->tm_hour, pti->tm_min, pti->tm_sec);
+
+ g_free(pti);
+ g_free(pgt);
+ g_free(plt);
+
+ return ret;
+}
+
+/**
+ * @brief Converts an ISO 8601-style time string into `time_t`.
+ */
+time_t iso8601_to_time(char * iso8601) {
+ /* Date/time fields (YYYY-MM-DD-hh-mm-ss) */
+ int val[8];
+
+ int i = 0;
+
+ /* Start of next integer portion and current position */
+ char *start = iso8601, *pos = iso8601;
+
+ /* Time struct */
+ struct tm tm;
+
+ memset(&tm, 0, sizeof(struct tm));
+
+ while (*pos && i < 6) {
+ if (*pos < '0' || *pos > '9') {
+ val[i++] = atoi(start);
+ if (i == 6)
+ break;
+ pos++;
+ start = pos;
+ }
+ if (*pos)
+ pos++;
+ }
+ val[6] = 0;
+ val[7] = 0;
+ if (*pos && i == 6) {
+ if (pos[1] && pos[2] && (!pos[3] || pos[3] == ':')) {
+ val[6] = atoi(pos);
+ if (pos[3] == ':') {
+ pos += 3;
+ val[7] = (val[6] < 0) ? -atoi(pos) : atoi(pos);
+ }
+ } else if (pos[1] && pos[2] && pos[3] && pos[4]) {
+ val[6] = atoi(pos) / 100;
+ val[7] = atoi(pos) % 100;
+ }
+ }
+
+ tm.tm_year = val[0] - 1900;
+ tm.tm_mon = val[1] - 1;
+ tm.tm_mday = val[2];
+ tm.tm_hour = val[3] - val[6];
+ tm.tm_min = val[4] - val[7];
+ tm.tm_sec = val[5];
+
+ dbg(lvl_debug, "time %s (%02d-%02d-%02d %02d:%02d:%02d)\n", iso8601, tm.tm_year, tm.tm_mon, tm.tm_mday,
+ tm.tm_hour, tm.tm_min, tm.tm_sec);
+
+ return mkgmtime(&tm);
+}
+
+/**
+ * @brief Converts time to ISO8601 format.
+ *
+ * The caller is responsible for freeing the return value of this function when it is no longer needed.
+ *
+ * @param time The time, as returned by `time()` and related functions
+ *
+ * @return Time in ISO8601 format
+ */
+char * time_to_iso8601(time_t time) {
+ char *timep=NULL;
+ char buffer[32];
+ struct tm *tm;
+
+ tm = gmtime(&time);
+ if (tm) {
+ strftime(buffer, sizeof(buffer), "%Y-%m-%dT%TZ", tm);
+ timep=g_strdup(buffer);
+ }
+ return timep;
}
/**
@@ -349,35 +775,27 @@ iso8601_to_secs(char *iso8601)
*
* @return Time in ISO 8601 format
*/
-char *
-current_to_iso8601(void)
-{
- char *timep=NULL;
+char *current_to_iso8601(void) {
#ifdef HAVE_API_WIN32_BASE
- SYSTEMTIME ST;
- GetSystemTime(&ST);
- timep=g_strdup_printf("%d-%02d-%02dT%02d:%02d:%02dZ",ST.wYear,ST.wMonth,ST.wDay,ST.wHour,ST.wMinute,ST.wSecond);
+ char *timep=NULL;
+ SYSTEMTIME ST;
+ GetSystemTime(&ST);
+ timep=g_strdup_printf("%d-%02d-%02dT%02d:%02d:%02dZ",ST.wYear,ST.wMonth,ST.wDay,ST.wHour,ST.wMinute,ST.wSecond);
+ return timep;
#else
- char buffer[32];
- time_t tnow;
- struct tm *tm;
- tnow = time(0);
- tm = gmtime(&tnow);
- if (tm) {
- strftime(buffer, sizeof(buffer), "%Y-%m-%dT%TZ", tm);
- timep=g_strdup(buffer);
- }
+ time_t tnow;
+ tnow = time(0);
+ return time_to_iso8601(tnow);
#endif
- return timep;
}
struct spawn_process_info {
#ifdef HAVE_API_WIN32_BASE
- PROCESS_INFORMATION pr;
+ PROCESS_INFORMATION pr;
#else
- pid_t pid; // = -1 if non-blocking spawn isn't supported
- int status; // exit status if non-blocking spawn isn't supported
+ pid_t pid; // = -1 if non-blocking spawn isn't supported
+ int status; // exit status if non-blocking spawn isn't supported
#endif
};
@@ -388,95 +806,91 @@ struct spawn_process_info {
* @param in arg string to escape
* @returns escaped string
*/
-char *
-shell_escape(char *arg)
-{
- char *r;
- int arglen=strlen(arg);
- int i,j,rlen;
+char *shell_escape(char *arg) {
+ char *r;
+ int arglen=strlen(arg);
+ int i,j,rlen;
#ifdef HAVE_API_WIN32_BASE
- {
- int bscount=0;
- rlen=arglen+3;
- r=g_new(char,rlen);
- r[0]='"';
- for(i=0,j=1;i<arglen;i++) {
- if(arg[i]=='\\') {
- bscount++;
- if(i==(arglen-1)) {
- // Most special case - last char is
- // backslash. We can't escape it inside
- // quoted string due to Win unescaping
- // rules so quote should be closed
- // before backslashes and these
- // backslashes shouldn't be doubled
- rlen+=bscount;
- r=g_realloc(r,rlen);
- r[j++]='"';
- memset(r+j,'\\',bscount);
- j+=bscount;
- }
- } else {
- //Any preceeding backslashes will be doubled.
- bscount*=2;
- // Double quote needs to be preceeded by
- // at least one backslash
- if(arg[i]=='"')
- bscount++;
- if(bscount>0) {
- rlen+=bscount;
- r=g_realloc(r,rlen);
- memset(r+j,'\\',bscount);
- j+=bscount;
- bscount=0;
- }
- r[j++]=arg[i];
- if(i==(arglen-1)) {
- r[j++]='"';
- }
- }
- }
- r[j++]=0;
- }
+ {
+ int bscount=0;
+ rlen=arglen+3;
+ r=g_new(char,rlen);
+ r[0]='"';
+ for(i=0,j=1; i<arglen; i++) {
+ if(arg[i]=='\\') {
+ bscount++;
+ if(i==(arglen-1)) {
+ // Most special case - last char is
+ // backslash. We can't escape it inside
+ // quoted string due to Win unescaping
+ // rules so quote should be closed
+ // before backslashes and these
+ // backslashes shouldn't be doubled
+ rlen+=bscount;
+ r=g_realloc(r,rlen);
+ r[j++]='"';
+ memset(r+j,'\\',bscount);
+ j+=bscount;
+ }
+ } else {
+ //Any preceeding backslashes will be doubled.
+ bscount*=2;
+ // Double quote needs to be preceeded by
+ // at least one backslash
+ if(arg[i]=='"')
+ bscount++;
+ if(bscount>0) {
+ rlen+=bscount;
+ r=g_realloc(r,rlen);
+ memset(r+j,'\\',bscount);
+ j+=bscount;
+ bscount=0;
+ }
+ r[j++]=arg[i];
+ if(i==(arglen-1)) {
+ r[j++]='"';
+ }
+ }
+ }
+ r[j++]=0;
+ }
#else
- {
- // Will use hard quoting for the whole string
- // and replace each singular quote found with a '\'' sequence.
- rlen=arglen+3;
- r=g_new(char,rlen);
- r[0]='\'';
- for(i=0,j=1;i<arglen;i++) {
- if(arg[i]=='\'') {
- rlen+=3;
- r=g_realloc(r,rlen);
- g_strlcpy(r+j,"'\\''",rlen-j);
- } else {
- r[j++]=arg[i];
- }
- }
- r[j++]='\'';
- r[j++]=0;
- }
+ {
+ // Will use hard quoting for the whole string
+ // and replace each singular quote found with a '\'' sequence.
+ rlen=arglen+3;
+ r=g_new(char,rlen);
+ r[0]='\'';
+ for(i=0,j=1; i<arglen; i++) {
+ if(arg[i]=='\'') {
+ rlen+=3;
+ r=g_realloc(r,rlen);
+ g_strlcpy(r+j,"'\\''",rlen-j);
+ } else {
+ r[j++]=arg[i];
+ }
+ }
+ r[j++]='\'';
+ r[j++]=0;
+ }
#endif
- return r;
+ return r;
}
#ifndef _POSIX_C_SOURCE
-static char*
-spawn_process_compose_cmdline(char **argv)
-{
- int i,j;
- char *cmdline=shell_escape(argv[0]);
- for(i=1,j=strlen(cmdline);argv[i];i++) {
- char *arg=shell_escape(argv[i]);
- int arglen=strlen(arg);
- cmdline[j]=' ';
- cmdline=g_realloc(cmdline,j+1+arglen+1);
- memcpy(cmdline+j+1,arg,arglen+1);
- g_free(arg);
- j=j+1+arglen;
- }
- return cmdline;
+static char* spawn_process_compose_cmdline(char **argv) {
+ int i,j;
+ char *cmdline=shell_escape(argv[0]);
+ for(i=1,j=strlen(cmdline); argv[i]; i++) {
+ char *arg=shell_escape(argv[i]);
+ int arglen=strlen(arg);
+ cmdline[j]=' ';
+ cmdline=g_realloc(cmdline,j+1+arglen+1);
+ memcpy(cmdline+j+1,arg,arglen+1);
+ g_free(arg);
+ j=j+1+arglen;
+ }
+ return cmdline;
}
#endif
@@ -501,82 +915,81 @@ GList *spawn_process_children=NULL;
* @returns 0 - success, >0 - return code, -1 - error
*/
struct spawn_process_info*
-spawn_process(char **argv)
-{
- struct spawn_process_info*r=g_new(struct spawn_process_info,1);
+spawn_process(char **argv) {
+ struct spawn_process_info*r=g_new(struct spawn_process_info,1);
#ifdef _POSIX_C_SOURCE
- {
- pid_t pid;
-
- sigset_t set, old;
- dbg(lvl_debug,"spawning process for '%s'\n", argv[0]);
- sigemptyset(&set);
- sigaddset(&set,SIGCHLD);
- spawn_process_sigmask(SIG_BLOCK,&set,&old);
- pid=fork();
- if(pid==0) {
- execvp(argv[0], argv);
- /*Shouldn't reach here*/
- exit(1);
- } else if(pid>0) {
- r->status=-1;
- r->pid=pid;
- spawn_process_children=g_list_prepend(spawn_process_children,r);
- } else {
- dbg(lvl_error,"fork() returned error.");
- g_free(r);
- r=NULL;
- }
- spawn_process_sigmask(SIG_SETMASK,&old,NULL);
- return r;
- }
+ {
+ pid_t pid;
+
+ sigset_t set, old;
+ dbg(lvl_debug,"spawning process for '%s'", argv[0]);
+ sigemptyset(&set);
+ sigaddset(&set,SIGCHLD);
+ spawn_process_sigmask(SIG_BLOCK,&set,&old);
+ pid=fork();
+ if(pid==0) {
+ execvp(argv[0], argv);
+ /*Shouldn't reach here*/
+ exit(1);
+ } else if(pid>0) {
+ r->status=-1;
+ r->pid=pid;
+ spawn_process_children=g_list_prepend(spawn_process_children,r);
+ } else {
+ dbg(lvl_error,"fork() returned error.");
+ g_free(r);
+ r=NULL;
+ }
+ spawn_process_sigmask(SIG_SETMASK,&old,NULL);
+ return r;
+ }
#else
#ifdef HAVE_API_WIN32_BASE
- {
- char *cmdline;
- DWORD dwRet;
-
- // For [desktop] Windows it's adviceable not to use
- // first CreateProcess parameter because PATH is not used
- // if it is defined.
- //
- // On WinCE 6.0 I was unable to launch anything
- // without first CreateProcess parameter, also it seems that
- // no WinCE program has support for quoted strings in arguments.
- // So...
+ {
+ char *cmdline;
+ DWORD dwRet;
+
+ // For [desktop] Windows it's adviceable not to use
+ // first CreateProcess parameter because PATH is not used
+ // if it is defined.
+ //
+ // On WinCE 6.0 I was unable to launch anything
+ // without first CreateProcess parameter, also it seems that
+ // no WinCE program has support for quoted strings in arguments.
+ // So...
#ifdef HAVE_API_WIN32_CE
- LPWSTR cmd,args;
- cmdline=g_strjoinv(" ",argv+1);
- args=newSysString(cmdline);
- cmd = newSysString(argv[0]);
- dwRet=CreateProcess(cmd, args, NULL, NULL, 0, 0, NULL, NULL, NULL, &(r->pr));
- dbg(lvl_debug, "CreateProcess(%s,%s), PID=%i\n",argv[0],cmdline,r->pr.dwProcessId);
- g_free(cmd);
+ LPWSTR cmd,args;
+ cmdline=g_strjoinv(" ",argv+1);
+ args=newSysString(cmdline);
+ cmd = newSysString(argv[0]);
+ dwRet=CreateProcess(cmd, args, NULL, NULL, 0, 0, NULL, NULL, NULL, &(r->pr));
+ dbg(lvl_debug, "CreateProcess(%s,%s), PID=%i",argv[0],cmdline,r->pr.dwProcessId);
+ g_free(cmd);
#else
- TCHAR* args;
- STARTUPINFO startupInfo;
- memset(&startupInfo, 0, sizeof(startupInfo));
- startupInfo.cb = sizeof(startupInfo);
- cmdline=spawn_process_compose_cmdline(argv);
- args=newSysString(cmdline);
- dwRet=CreateProcess(NULL, args, NULL, NULL, 0, 0, NULL, NULL, &startupInfo, &(r->pr));
- dbg(lvl_debug, "CreateProcess(%s), PID=%i\n",cmdline,r->pr.dwProcessId);
+ TCHAR* args;
+ STARTUPINFO startupInfo;
+ memset(&startupInfo, 0, sizeof(startupInfo));
+ startupInfo.cb = sizeof(startupInfo);
+ cmdline=spawn_process_compose_cmdline(argv);
+ args=newSysString(cmdline);
+ dwRet=CreateProcess(NULL, args, NULL, NULL, 0, 0, NULL, NULL, &startupInfo, &(r->pr));
+ dbg(lvl_debug, "CreateProcess(%s), PID=%i",cmdline,r->pr.dwProcessId);
#endif
- g_free(cmdline);
- g_free(args);
- return r;
- }
+ g_free(cmdline);
+ g_free(args);
+ return r;
+ }
#else
- {
- char *cmdline=spawn_process_compose_cmdline(argv);
- int status;
- dbg(lvl_error,"Unblocked spawn_process isn't availiable on this platform.\n");
- status=system(cmdline);
- g_free(cmdline);
- r->status=status;
- r->pid=0;
- return r;
- }
+ {
+ char *cmdline=spawn_process_compose_cmdline(argv);
+ int status;
+ dbg(lvl_error,"Unblocked spawn_process isn't availiable on this platform.");
+ status=system(cmdline);
+ g_free(cmdline);
+ r->status=status;
+ r->pid=0;
+ return r;
+ }
#endif
#endif
}
@@ -586,154 +999,164 @@ spawn_process(char **argv)
*
* @param in *pi pointer to spawn_process_info structure
* @param in block =0 do not block =1 block until child terminated
- * @returns -1 - still running, >=0 program exited,
+ * @returns -1 - still running, >=0 program exited,
* =255 trminated abnormally or wasn't run at all.
- *
+ *
*/
-int spawn_process_check_status(struct spawn_process_info *pi, int block)
-{
- if(pi==NULL) {
- dbg(lvl_error,"Trying to get process status of NULL, assuming process is terminated.\n");
- return 255;
- }
+int spawn_process_check_status(struct spawn_process_info *pi, int block) {
+ if(pi==NULL) {
+ dbg(lvl_error,"Trying to get process status of NULL, assuming process is terminated.");
+ return 255;
+ }
#ifdef HAVE_API_WIN32_BASE
- {int failcount=0;
- while(1){
- DWORD dw;
- if(GetExitCodeProcess(pi->pr.hProcess,&dw)) {
- if(dw!=STILL_ACTIVE) {
- return dw;
- break;
- }
- } else {
- dbg(lvl_error,"GetExitCodeProcess failed. Assuming the process is terminated.");
- return 255;
- }
- if(!block)
- return -1;
-
- dw=WaitForSingleObject(pi->pr.hProcess,INFINITE);
- if(dw==WAIT_FAILED && failcount++==1) {
- dbg(lvl_error,"WaitForSingleObject failed twice. Assuming the process is terminated.");
- return 0;
- break;
- }
- }
- }
+ {
+ int failcount=0;
+ while(1) {
+ DWORD dw;
+ if(GetExitCodeProcess(pi->pr.hProcess,&dw)) {
+ if(dw!=STILL_ACTIVE) {
+ return dw;
+ break;
+ }
+ } else {
+ dbg(lvl_error,"GetExitCodeProcess failed. Assuming the process is terminated.");
+ return 255;
+ }
+ if(!block)
+ return -1;
+
+ dw=WaitForSingleObject(pi->pr.hProcess,INFINITE);
+ if(dw==WAIT_FAILED && failcount++==1) {
+ dbg(lvl_error,"WaitForSingleObject failed twice. Assuming the process is terminated.");
+ return 0;
+ break;
+ }
+ }
+ }
#else
#ifdef _POSIX_C_SOURCE
- if(pi->status!=-1) {
- return pi->status;
- }
- while(1) {
- int status;
- pid_t w=waitpid(pi->pid,&status,block?0:WNOHANG);
- if(w>0) {
- if(WIFEXITED(status))
- pi->status=WEXITSTATUS(status);
- return pi->status;
- if(WIFSTOPPED(status)) {
- dbg(lvl_debug,"child is stopped by %i signal\n",WSTOPSIG(status));
- } else if (WIFSIGNALED(status)) {
- dbg(lvl_debug,"child terminated by signal %i\n",WEXITSTATUS(status));
- pi->status=255;
- return 255;
- }
- if(!block)
- return -1;
- } else if(w==0) {
- if(!block)
- return -1;
- } else {
- if(pi->status!=-1) // Signal handler has changed pi->status while in this function
- return pi->status;
- dbg(lvl_error,"waitpid() indicated error, reporting process termination.\n");
- return 255;
- }
- }
+ if(pi->status!=-1) {
+ return pi->status;
+ }
+ while(1) {
+ int status;
+ pid_t w=waitpid(pi->pid,&status,block?0:WNOHANG);
+ if(w>0) {
+ if(WIFEXITED(status))
+ pi->status=WEXITSTATUS(status);
+ return pi->status;
+ if(WIFSTOPPED(status)) {
+ dbg(lvl_debug,"child is stopped by %i signal",WSTOPSIG(status));
+ } else if (WIFSIGNALED(status)) {
+ dbg(lvl_debug,"child terminated by signal %i",WEXITSTATUS(status));
+ pi->status=255;
+ return 255;
+ }
+ if(!block)
+ return -1;
+ } else if(w==0) {
+ if(!block)
+ return -1;
+ } else {
+ if(pi->status!=-1) // Signal handler has changed pi->status while in this function
+ return pi->status;
+ dbg(lvl_error,"waitpid() indicated error, reporting process termination.");
+ return 255;
+ }
+ }
#else
- dbg(lvl_error, "Non-blocking spawn_process isn't availiable for this platform, repoting process exit status.\n");
- return pi->status;
+ dbg(lvl_error, "Non-blocking spawn_process isn't availiable for this platform, repoting process exit status.");
+ return pi->status;
#endif
#endif
}
-void spawn_process_info_free(struct spawn_process_info *pi)
-{
- if(pi==NULL)
- return;
+void spawn_process_info_free(struct spawn_process_info *pi) {
+ if(pi==NULL)
+ return;
#ifdef HAVE_API_WIN32_BASE
- CloseHandle(pi->pr.hProcess);
- CloseHandle(pi->pr.hThread);
+ CloseHandle(pi->pr.hProcess);
+ CloseHandle(pi->pr.hThread);
#endif
#ifdef _POSIX_C_SOURCE
- {
- sigset_t set, old;
- sigemptyset(&set);
- sigaddset(&set,SIGCHLD);
- spawn_process_sigmask(SIG_BLOCK,&set,&old);
- spawn_process_children=g_list_remove(spawn_process_children,pi);
- spawn_process_sigmask(SIG_SETMASK,&old,NULL);
- }
+ {
+ sigset_t set, old;
+ sigemptyset(&set);
+ sigaddset(&set,SIGCHLD);
+ spawn_process_sigmask(SIG_BLOCK,&set,&old);
+ spawn_process_children=g_list_remove(spawn_process_children,pi);
+ spawn_process_sigmask(SIG_SETMASK,&old,NULL);
+ }
#endif
- g_free(pi);
+ g_free(pi);
}
#ifdef _POSIX_C_SOURCE
-static void spawn_process_sigchld(int sig)
-{
- int status;
- pid_t pid;
- while ((pid=waitpid(-1, &status, WNOHANG)) > 0) {
- GList *el=g_list_first(spawn_process_children);
- while(el) {
- struct spawn_process_info *p=el->data;
- if(p->pid==pid) {
- p->status=status;
- }
- el=g_list_next(el);
- }
- }
+static void spawn_process_sigchld(int sig) {
+ int status;
+ pid_t pid;
+ while ((pid=waitpid(-1, &status, WNOHANG)) > 0) {
+ GList *el=g_list_first(spawn_process_children);
+ while(el) {
+ struct spawn_process_info *p=el->data;
+ if(p->pid==pid) {
+ p->status=status;
+ }
+ el=g_list_next(el);
+ }
+ }
}
#endif
-void spawn_process_init()
-{
+void spawn_process_init() {
#ifdef _POSIX_C_SOURCE
- struct sigaction act;
- act.sa_handler=spawn_process_sigchld;
- act.sa_flags=0;
- sigemptyset(&act.sa_mask);
- sigaction(SIGCHLD, &act, NULL);
+ struct sigaction act;
+ act.sa_handler=spawn_process_sigchld;
+ act.sa_flags=0;
+ sigemptyset(&act.sa_mask);
+ sigaction(SIGCHLD, &act, NULL);
#endif
- return;
-}
-
-/** Get printable compass direction from an angle. */
-void
-get_compass_direction(char *buffer, int angle, int mode)
-{
- angle=angle%360;
- switch (mode) {
- case 0:
- sprintf(buffer,"%d",angle);
- break;
- case 1:
- if (angle < 69 || angle > 291)
- *buffer++='N';
- if (angle > 111 && angle < 249)
- *buffer++='S';
- if (angle > 22 && angle < 158)
- *buffer++='E';
- if (angle > 202 && angle < 338)
- *buffer++='W';
- *buffer++='\0';
- break;
- case 2:
- angle=(angle+15)/30;
- if (! angle)
- angle=12;
- sprintf(buffer,"%d H", angle);
- break;
- }
+ return;
+}
+
+/**
+ * @brief Get printable compass direction from an angle.
+ *
+ * This function supports three different modes:
+ *
+ * In mode 0, the angle in degrees is output as a string.
+ *
+ * In mode 1, the angle is output as a cardinal direction (N, SE etc.).
+ *
+ * In mode 2, the angle is output in analog clock notation (6 o'clock).
+ *
+ * @param buffer Buffer to hold the result string (up to 5 characters, including the terminating null
+ * character, may be required)
+ * @param angle The angle to convert
+ * @param mode The conversion mode, see description
+ */
+void get_compass_direction(char *buffer, int angle, int mode) {
+ angle=angle%360;
+ switch (mode) {
+ case 0:
+ sprintf(buffer,"%d",angle);
+ break;
+ case 1:
+ if (angle < 69 || angle > 291)
+ *buffer++='N';
+ if (angle > 111 && angle < 249)
+ *buffer++='S';
+ if (angle > 22 && angle < 158)
+ *buffer++='E';
+ if (angle > 202 && angle < 338)
+ *buffer++='W';
+ *buffer++='\0';
+ break;
+ case 2:
+ angle=(angle+15)/30;
+ if (! angle)
+ angle=12;
+ sprintf(buffer,"%d H", angle);
+ break;
+ }
}