/* * Copyright (C) 2006, Jamie McCracken * Copyright (C) 2008-2010, Nokia * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "config.h" /* For timegm usage on __GLIBC__ we need _GNU_SOURCE, should be * defined in config.h based on configure checks... */ #include #include #include #include #include #include #include #include "tracker-date-time.h" #include "tracker-type-utils.h" GQuark tracker_date_error_quark (void) { return g_quark_from_static_string ("tracker_date_error-quark"); } gdouble tracker_string_to_date (const gchar *date_string, gint *offset_p, GError **error) { /* TODO Add more checks. */ static GRegex *regex = NULL; GMatchInfo *match_info; gchar *match; struct tm tm; gdouble t; gint offset; gboolean timezoned; if (!date_string) { g_set_error (error, TRACKER_DATE_ERROR, TRACKER_DATE_ERROR_EMPTY, "Empty date string"); return -1; } /* We should have a valid iso 8601 date in format * YYYY-MM-DDThh:mm:ss with optional TZ */ if (!regex) { GError *e = NULL; regex = g_regex_new ("^(-?[0-9][0-9][0-9][0-9])-([0-9][0-9])-([0-9][0-9])T([0-9][0-9]):([0-9][0-9]):([0-9][0-9])(\\.[0-9]+)?(Z|(\\+|-)([0-9][0-9]):?([0-9][0-9]))?$", 0, 0, &e); if (e) { g_error ("%s", e->message); } } if (!g_regex_match (regex, date_string, 0, &match_info)) { g_match_info_free (match_info); g_set_error (error, TRACKER_DATE_ERROR, TRACKER_DATE_ERROR_INVALID_ISO8601, "Not a ISO 8601 date string. Allowed form is [-]CCYY-MM-DDThh:mm:ss[Z|(+|-)hh:mm]"); return -1; } memset (&tm, 0, sizeof (struct tm)); /* year */ match = g_match_info_fetch (match_info, 1); tm.tm_year = atoi (match) - 1900; g_free (match); /* month */ match = g_match_info_fetch (match_info, 2); tm.tm_mon = atoi (match) - 1; g_free (match); /* day of month */ match = g_match_info_fetch (match_info, 3); tm.tm_mday = atoi (match); g_free (match); /* hour */ match = g_match_info_fetch (match_info, 4); tm.tm_hour = atoi (match); g_free (match); /* minute */ match = g_match_info_fetch (match_info, 5); tm.tm_min = atoi (match); g_free (match); /* second */ match = g_match_info_fetch (match_info, 6); tm.tm_sec = atoi (match); g_free (match); match = g_match_info_fetch (match_info, 8); timezoned = (match && strlen (match) > 0); g_free (match); if (timezoned) { /* timezoned */ /* mktime() always assumes that "tm" is in locale time but we * want to keep control on time, so we go to UTC */ #if !(defined(__FreeBSD__) || defined(__OpenBSD__) || defined (__GLIBC__)) t = mktime (&tm); t -= timezone; #else t = timegm (&tm); #endif offset = 0; match = g_match_info_fetch (match_info, 9); if (match && strlen (match) > 0) { /* non-UTC timezone */ gboolean positive_offset; positive_offset = (match[0] == '+'); g_free (match); match = g_match_info_fetch (match_info, 10); offset = atoi (match) * 3600; g_free (match); match = g_match_info_fetch (match_info, 11); offset += atoi (match) * 60; g_free (match); if (!positive_offset) { offset = -offset; } if (offset < -14 * 3600 || offset > 14 * 3600) { g_set_error (error, TRACKER_DATE_ERROR, TRACKER_DATE_ERROR_OFFSET, "UTC offset too large: %d seconds", offset); g_match_info_free (match_info); return -1; } t -= offset; } } else { time_t t2; /* local time */ tm.tm_isdst = -1; t = mktime (&tm); /* calculate UTC offset, requires timegm for correct result with past times when timezone had different UTC offset */ #if !(defined(__FreeBSD__) || defined(__OpenBSD__) || defined (__GLIBC__)) offset = -timezone + (tm.tm_isdst > 0 ? 3600 : 0); #else t2 = timegm (&tm); offset = t2 - (time_t) t; #endif } match = g_match_info_fetch (match_info, 7); if (match && strlen (match) > 0) { char milliseconds[4] = "000\0"; /* first character of match is decimal point we're interested in a maximum of 3 decimal places (milliseconds) */ memcpy (milliseconds, match + 1, MIN (3, strlen (match + 1))); t += (gdouble) atoi (milliseconds) / 1000; } g_free (match); g_match_info_free (match_info); if (offset_p) { *offset_p = offset; } return t; } gchar * tracker_date_to_string (gdouble date_time) { gchar buffer[30]; time_t seconds; gint64 total_milliseconds; gint milliseconds; struct tm utc_time; size_t count; memset (buffer, '\0', sizeof (buffer)); memset (&utc_time, 0, sizeof (struct tm)); total_milliseconds = (gint64) round (date_time * 1000); milliseconds = total_milliseconds % 1000; if (milliseconds < 0) { milliseconds += 1000; } seconds = (time_t) ((total_milliseconds - milliseconds) / 1000); gmtime_r (&seconds, &utc_time); /* Output is ISO 8601 format : "YYYY-MM-DDThh:mm:ss" */ count = strftime (buffer, sizeof (buffer), "%FT%T", &utc_time); /* Append milliseconds (if non-zero) and time zone */ if (milliseconds > 0) { snprintf (buffer + count, sizeof (buffer) - count, ".%03dZ", milliseconds); } else { buffer[count] = 'Z'; } return count > 0 ? g_strdup (buffer) : NULL; } static void date_time_value_init (GValue *value) { value->data[0].v_double = 0; value->data[1].v_int = 0; } static void date_time_value_copy (const GValue *src_value, GValue *dest_value) { dest_value->data[0].v_double = src_value->data[0].v_double; dest_value->data[1].v_int = src_value->data[1].v_int; } GType tracker_date_time_get_type (void) { static GType tracker_date_time_type_id = 0; if (G_UNLIKELY (tracker_date_time_type_id == 0)) { static const GTypeValueTable value_table = { date_time_value_init, NULL, date_time_value_copy }; static const GTypeInfo type_info = { 0, NULL, NULL, NULL, NULL, NULL, 0, 0, NULL, &value_table }; static const GTypeFundamentalInfo fundamental_info = { 0 }; tracker_date_time_type_id = g_type_register_fundamental ( g_type_fundamental_next (), "TrackerDateTime", &type_info, &fundamental_info, 0); } return tracker_date_time_type_id; } void tracker_date_time_set (GValue *value, gdouble time, gint offset) { g_return_if_fail (G_VALUE_HOLDS (value, TRACKER_TYPE_DATE_TIME)); g_return_if_fail (offset >= -14 * 3600 && offset <= 14 * 3600); value->data[0].v_double = time; value->data[1].v_int = offset; } void tracker_date_time_set_from_string (GValue *value, const gchar *date_time_string, GError **error) { gdouble time; gint offset; GError *new_error = NULL; g_return_if_fail (G_VALUE_HOLDS (value, TRACKER_TYPE_DATE_TIME)); g_return_if_fail (date_time_string != NULL); time = tracker_string_to_date (date_time_string, &offset, &new_error); if (new_error != NULL) { g_propagate_error (error, new_error); return; } tracker_date_time_set (value, time, offset); } gdouble tracker_date_time_get_time (const GValue *value) { g_return_val_if_fail (G_VALUE_HOLDS (value, TRACKER_TYPE_DATE_TIME), 0); /* UTC timestamp */ return value->data[0].v_double; } gint tracker_date_time_get_offset (const GValue *value) { g_return_val_if_fail (G_VALUE_HOLDS (value, TRACKER_TYPE_DATE_TIME), 0); /* UTC offset */ return value->data[1].v_int; } gint tracker_date_time_get_local_date (const GValue *value) { gdouble local_timestamp; g_return_val_if_fail (G_VALUE_HOLDS (value, TRACKER_TYPE_DATE_TIME), 0); /* return number of days since epoch */ local_timestamp = tracker_date_time_get_time (value) + tracker_date_time_get_offset (value); return (gint) (local_timestamp / 3600 / 24); } gint tracker_date_time_get_local_time (const GValue *value) { gdouble local_timestamp; g_return_val_if_fail (G_VALUE_HOLDS (value, TRACKER_TYPE_DATE_TIME), 0); /* return local time of day */ local_timestamp = tracker_date_time_get_time (value) + tracker_date_time_get_offset (value); return (int) local_timestamp % (24 * 3600); }