diff --git a/src/Makefile.am b/src/Makefile.am index 5ff5deae..1028c34b 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -60,6 +60,8 @@ libmu_la_SOURCES= \ mu-contacts.h \ mu-container.c \ mu-container.h \ + mu-date.c \ + mu-date.h \ mu-index.c \ mu-index.h \ mu-log.c \ diff --git a/src/mu-date.c b/src/mu-date.c new file mode 100644 index 00000000..6c1e91df --- /dev/null +++ b/src/mu-date.c @@ -0,0 +1,265 @@ +/* +** Copyright (C) 2011 +** +** This program is free software; you can redistribute it and/or modify it +** under the terms of the GNU General Public License as published by the +** Free Software Foundation; either version 3, or (at your option) any +** later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software Foundation, +** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +** +*/ + +#include +#include + +#include "mu-util.h" +#include "mu-date.h" + + +const char* +mu_date_str_s (const char* frm, time_t t) +{ + struct tm *tmbuf; + static char buf[128]; + static int is_utf8 = -1; + + if (G_UNLIKELY(is_utf8 == -1)) + is_utf8 = mu_util_locale_is_utf8 () ? 1 : 0; + + g_return_val_if_fail (frm, NULL); + + tmbuf = localtime(&t); + strftime (buf, sizeof(buf), frm, tmbuf); + + if (!is_utf8) { + /* charset is _not_ utf8, so we need to convert it, so + * the date could contain locale-specific characters*/ + gchar *conv; + GError *err; + err = NULL; + conv = g_locale_to_utf8 (buf, -1, NULL, NULL, &err); + if (err) { + g_warning ("conversion failed: %s", err->message); + g_error_free (err); + strcpy (buf, ""); + } else + strncpy (buf, conv, sizeof(buf)); + + g_free (conv); + } + + return buf; +} + +char* +mu_date_str (const char *frm, time_t t) +{ + return g_strdup (mu_date_str_s(frm, t)); +} + + +const char* +mu_date_display_s (time_t t) +{ + time_t now; + static const time_t SECS_IN_DAY = 24 * 60 * 60; + + now = time (NULL); + + if (ABS(now - t) > SECS_IN_DAY) + return mu_date_str_s ("%x", t); + else + return mu_date_str_s ("%X", t); +} + + + +time_t +mu_date_parse_hdwmy (const char *nptr) +{ + long int num; + char *endptr; + time_t now, delta; + time_t never = (time_t)-1; + + g_return_val_if_fail (nptr, never); + + num = strtol (nptr, &endptr, 10); + if (num <= 0 || num > 9999) + return never; + + if (endptr == NULL || *endptr == '\0') + return never; + + switch (endptr[0]) { + case 'h': /* hour */ + delta = num * 60 * 60; break; + case 'd': /* day */ + delta = num * 24 * 60 * 60; break; + case 'w': /* week */ + delta = num * 7 * 24 * 60 * 60; break; + case 'm': /* month */ + delta = num * 30 * 24 * 60 * 60; break; + case 'y': /* year */ + delta = num * 365 * 24 * 60 * 60; break; + default: + return never; + } + + now = time(NULL); + return delta <= now ? now - delta : never; +} + + +const char* +mu_date_complete_s (const char *date, gboolean is_begin) +{ + static char fulldate[14 + 1]; + + static const char* full_begin = "00000101000000"; + static const char* full_end = "99991231235959"; + + g_return_val_if_fail (date, NULL); + + strncpy (fulldate, is_begin ? full_begin : full_end, + sizeof(fulldate)); + memcpy (fulldate, date, strlen(date)); + + return fulldate; +} + + +char* +mu_date_complete (const char *date, gboolean is_begin) +{ + const char *s; + + g_return_val_if_fail (date, NULL); + + s = mu_date_complete_s (date, is_begin); + return s ? g_strdup (s) : NULL; +} + + +const char* +mu_date_interpret_s (const char *datespec, gboolean is_begin) +{ + static char fulldate[14 + 1]; + time_t now; + + g_return_val_if_fail (datespec, NULL); + + now = time(NULL); + if (strcmp (datespec, "today") == 0) { + strftime(fulldate, sizeof(fulldate), + is_begin ? "%Y%m%d000000" : "%Y%m%d235959", + localtime(&now)); + return fulldate; + } + + if (strcmp (datespec, "now") == 0) { + strftime(fulldate, sizeof(fulldate), "%Y%m%d%H%M%S", + localtime(&now)); + return fulldate; + } + + { + time_t t; + t = mu_date_parse_hdwmy (datespec); + if (t != (time_t)-1) { + strftime(fulldate, sizeof(fulldate), "%Y%m%d%H%M%S", + localtime(&t)); + return fulldate; + } + } + + return datespec; /* nothing changed */ +} + + +char* +mu_date_interpret (const char *datespec, gboolean is_begin) +{ + char *s; + + g_return_val_if_fail (datespec, NULL); + + s = mu_date_interpret (datespec, is_begin); + return s ? g_strdup(s) : NULL; +} + + +time_t +mu_date_str_to_time_t (const char* date, gboolean local) +{ + struct tm tm; + char mydate[14 + 1]; /* YYYYMMDDHHMMSS */ + time_t t; + const char *tz; + + memset (&tm, 0, sizeof(struct tm)); + strncpy (mydate, date, 15); + mydate[sizeof(mydate)-1]='\0'; + + g_return_val_if_fail (date, (time_t)-1); + + tm.tm_sec = atoi (mydate + 12); mydate[12] = '\0'; + tm.tm_min = atoi (mydate + 10); mydate[10] = '\0'; + tm.tm_hour = atoi (mydate + 8); mydate[8] = '\0'; + tm.tm_mday = atoi (mydate + 6); mydate[6] = '\0'; + tm.tm_mon = atoi (mydate + 4) - 1; mydate[4] = '\0'; + tm.tm_year = atoi (mydate) - 1900; + tm.tm_isdst = -1; /* figure out the dst */ + + if (!local) { /* temporalily switch to UTC */ + tz = getenv ("TZ"); + setenv ("TZ", "", 1); + tzset (); + } + + t = mktime (&tm); + + if (!local) { /* switch back */ + if (tz) + setenv("TZ", tz, 1); + else + unsetenv("TZ"); + tzset(); + } + + return t; +} + +const char* +mu_date_time_t_to_str_s (time_t t, gboolean local) +{ + /* static char datestr[14 + 1]; /\* YYYYMMDDHHMMSS *\/ */ + static char datestr[14+1]; /* YYYYMMDDHHMMSS */ + + static const char *frm = "%Y%m%d%H%M%S"; + + strftime (datestr, sizeof(datestr), frm, + local ? localtime (&t) : gmtime(&t)); + + return datestr; +} + + +char* +mu_date_time_t_to_str (time_t t, gboolean local) +{ + const char* str; + + str = mu_date_time_t_to_str_s (t, local); + + return str ? g_strdup(str): NULL; +} + diff --git a/src/mu-date.h b/src/mu-date.h new file mode 100644 index 00000000..7e5e6692 --- /dev/null +++ b/src/mu-date.h @@ -0,0 +1,147 @@ +/* +** Copyright (C) 2011 Dirk-Jan C. Binnema +** +** This program is free software; you can redistribute it and/or modify it +** under the terms of the GNU General Public License as published by the +** Free Software Foundation; either version 3, or (at your option) any +** later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software Foundation, +** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +** +*/ + +#include + +#ifndef __MU_DATE_H__ +#define __MU_DATE_H__ + +G_BEGIN_DECLS + + +/** + * get a string for a given time_t + * + * mu_date_str_s returns a ptr to a static buffer, + * while mu_date_str returns dynamically allocated + * memory that must be freed after use. + * + * @param frm the format of the string (in strftime(3) format) + * @param t the time as time_t + * + * @return a string representation of the time; see above for what to + * do with it. Lenght is max. 128 bytes, inc. the ending \0. if the + * format is too long, the value will be truncated. in practice this + * should not happen. + */ +const char* mu_date_str_s (const char* frm, time_t t) G_GNUC_CONST; +char* mu_date_str (const char* frm, time_t t) G_GNUC_WARN_UNUSED_RESULT; + + + +/** + * get a display string for a given time_t; if the given is less than + * 24h from the current time, we display the time, otherwise the date, + * using the preferred date/time for the current locale + * + * mu_str_display_date_s returns a ptr to a static buffer, + * + * @param t the time as time_t + * + * @return a string representation of the time/date + */ +const char* mu_date_display_s (time_t t); + +/** + * + * parse strings like 1h, 3w, 2m to mean '1 hour before now', '3 weeks + * before now' and '2 * 30 days before now' + * + * the format is (h|d|w|m|y), where is an integer > 0, and + * h=hour, d=day, w=week, m=30 days, year=365 days. function returns + * *now* minus this value as time_t (UTC) + * + * if the number cannot be parsed, return (time_t)-1 + * + * @param str a str + * + * @return the time_t of the point in time indicated by 'now' minus + * the value, or (time_t)-1 otherwise + */ +time_t mu_date_parse_hdwmy (const char* str); + + + +/** + * complete a date (a string of the form YYYYMMDDHHMMSS with [0..14] + * of the rightmost characters missing) to the the full YYYYMMDDHHMMSS. + * + * if is_begin is TRUE, add to the 'floor' (e.g, + * 20110101=>20110101000000), otherwise go to the 'ceiling', + * e.g. 2009=>20091231235050) + * + * @param date a date string (assumed to have the beginning of the + * date, this is not checked + * @param is_begin if TRUE go to floor (as described), otherwise to + * the ceiling + * + * @return mu_date_complete: return a newly allocated string (free + * with g_free) with the full, 14-char date; mu_date_complete_s: + * return a statically allocated string. NOT REENTRANT. + */ +char* mu_date_complete (const char *date, gboolean is_begin); +const char* mu_date_complete_s (const char *date, gboolean is_begin); + + + +/** + * + * + * @param datespec + * @param is_begin + * + * @return + */ +const char* mu_date_interpret_s (const char *datespec, gboolean is_begin); +char* mu_date_interpret (const char *datespec, gboolean is_begin); + + + +/** + * convert a date of the form 'YYYYMMDDHHMMSS' into time_t + * + * @param date a date str of the form 'YYYYMMDDHHMMSS' + * @param local if TRUE, source is assumed to bin in local time, UTC otherwise + * + * @return the corresponding time_t, or (time_t)-1 in case of error + */ +time_t mu_date_str_to_time_t (const char* date, gboolean local); + + + + +/** + * convert a time_t value into a date string of the form + * 'YYYYMMDDHHMMSS'; assume UTC + * + * @param t a time_t value + * @param local if TRUE, convert to local time, otherwise use UTC + * + * @return mu_date_time_t_to_str_s: a static string (don't modify, + * non-reentrant) of the form 'YYYYMMDDHHMMSS'; mu_date_time_t_to_str: + * return a newly allocated string with the same. + */ +const char* mu_date_time_t_to_str_s (time_t t, gboolean local); +char* mu_date_time_t_to_str (time_t t, gboolean local); + + + +G_END_DECLS + +#endif /*__MU_DATE_H__*/ diff --git a/src/tests/Makefile.am b/src/tests/Makefile.am index c25add06..c4195d74 100644 --- a/src/tests/Makefile.am +++ b/src/tests/Makefile.am @@ -86,6 +86,10 @@ TEST_PROGS += test-mu-threads test_mu_threads_SOURCES= test-mu-threads.c dummy.cc test_mu_threads_LDADD= libtestmucommon.la +TEST_PROGS += test-mu-date +test_mu_date_SOURCES= test-mu-date.c dummy.cc +test_mu_date_LDADD= libtestmucommon.la + # TEST_PROGS += test-mu-msg-file # test_mu_msg_file_SOURCES= test-mu-msg-file.c dummy.cc diff --git a/src/tests/test-mu-date.c b/src/tests/test-mu-date.c new file mode 100644 index 00000000..1ffe509e --- /dev/null +++ b/src/tests/test-mu-date.c @@ -0,0 +1,153 @@ +/* -*-mode: c; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-*/ + +/* +** Copyright (C) 2008-2010 Dirk-Jan C. Binnema +** +** This program is free software; you can redistribute it and/or modify it +** under the terms of the GNU General Public License as published by the +** Free Software Foundation; either version 3, or (at your option) any +** later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software Foundation, +** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +** +*/ + +#if HAVE_CONFIG_H +#include "config.h" +#endif /*HAVE_CONFIG_H*/ + +#include +#include +#include +#include + +#include + +#include "test-mu-common.h" +#include "src/mu-date.h" + + +static void +test_mu_date_str (void) +{ + struct tm *tmbuf; + char buf[64]; + gchar *tmp; + time_t some_time; + + some_time = 1234567890; + tmbuf = localtime (&some_time); + strftime (buf, 64, "%x", tmbuf); + + /* $ date -ud@1234567890; Fri Feb 13 23:31:30 UTC 2009 */ + g_assert_cmpstr (mu_date_str_s ("%x", some_time), ==, buf); + + /* date -ud@987654321 Thu Apr 19 04:25:21 UTC 2001 */ + some_time = 987654321; + tmbuf = localtime (&some_time); + strftime (buf, 64, "%c", tmbuf); + tmp = mu_date_str ("%c", some_time); + + g_assert_cmpstr (tmp, ==, buf); + g_free (tmp); +} + + + +static void +test_mu_date_parse_hdwmy (void) +{ + time_t diff; + + diff = time(NULL) - mu_date_parse_hdwmy ("3h"); + g_assert (diff > 0); + g_assert_cmpuint (3 * 60 * 60 - diff, <=, 1); + + diff = time(NULL) - mu_date_parse_hdwmy ("5y"); + g_assert (diff > 0); + g_assert_cmpuint (5 * 365 * 24 * 60 * 60 - diff, <=, 1); + + diff = time(NULL) - mu_date_parse_hdwmy ("3m"); + g_assert (diff > 0); + g_assert_cmpuint (3 * 30 * 24 * 60 * 60 - diff, <=, 1); + + diff = time(NULL) - mu_date_parse_hdwmy ("21d"); + g_assert (diff > 0); + g_assert_cmpuint (21 * 24 * 60 * 60 - diff, <=, 1); + + diff = time(NULL) - mu_date_parse_hdwmy ("2w"); + g_assert (diff > 0); + g_assert_cmpuint (2 * 7 * 24 * 60 * 60 - diff, <=, 1); + + + g_assert_cmpint (mu_date_parse_hdwmy("-1y"),==, (time_t)-1); +} + + +static void +test_mu_date_complete_begin (void) +{ + g_assert_cmpstr (mu_date_complete_s("2000", TRUE), ==, + "20000101000000"); + g_assert_cmpstr (mu_date_complete_s("2", TRUE), ==, + "20000101000000"); + g_assert_cmpstr (mu_date_complete_s ("", TRUE), ==, + "00000101000000"); + g_assert_cmpstr (mu_date_complete_s ("201007", TRUE), ==, + "20100701000000"); + g_assert_cmpstr (mu_date_complete_s ("19721214", TRUE), ==, + "19721214000000"); + g_assert_cmpstr (mu_date_complete_s ("19721214234512", TRUE), ==, + "19721214234512"); +} + + +static void +test_mu_date_complete_end (void) +{ + g_assert_cmpstr (mu_date_complete_s ("2000", FALSE), ==, + "20001231235959"); + g_assert_cmpstr (mu_date_complete_s ("2", FALSE), ==, + "29991231235959"); + g_assert_cmpstr (mu_date_complete_s ("", FALSE), ==, + "99991231235959"); + g_assert_cmpstr (mu_date_complete_s ("201007", FALSE), ==, + "20100731235959"); + g_assert_cmpstr (mu_date_complete_s ("19721214", FALSE), ==, + "19721214235959"); + g_assert_cmpstr (mu_date_complete_s ("19721214234512", FALSE), ==, + "19721214234512"); +} + + + + +int +main (int argc, char *argv[]) +{ + g_test_init (&argc, &argv, NULL); + + /* mu_str_date */ + g_test_add_func ("/mu-str/mu-date-str", + test_mu_date_str); + + g_test_add_func ("/mu-str/mu_date_parse_hdwmy", + test_mu_date_parse_hdwmy); + g_test_add_func ("/mu-str/mu_date_complete_begin", + test_mu_date_complete_begin); + g_test_add_func ("/mu-str/mu_date_complete_end", + test_mu_date_complete_end); + + g_log_set_handler (NULL, + G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL| G_LOG_FLAG_RECURSION, + (GLogFunc)black_hole, NULL); + + return g_test_run (); +}