diff --git a/configure.ac b/configure.ac index 695a86b..17bd5f9 100644 --- a/configure.ac +++ b/configure.ac @@ -158,9 +158,14 @@ AC_CHECK_HEADERS([sys/param.h]) # Checks for global variable declarations. AC_CHECK_DECLS([optreset]) +# Checks for structures. +AC_CHECK_MEMBERS([struct tm.tm_gmtoff],,,[ +#include +]) + # Checks for library functions. AC_FUNC_STRERROR_R -AC_CHECK_FUNCS([localtime_r gmtime_r]) +AC_CHECK_FUNCS([localtime_r gmtime_r timegm _mkgmtime]) AC_CHECK_FUNCS([getopt_long]) # Versioning. diff --git a/examples/output_xml.c b/examples/output_xml.c index 2baa329..86a3957 100644 --- a/examples/output_xml.c +++ b/examples/output_xml.c @@ -230,9 +230,16 @@ dctool_xml_output_write (dctool_output_t *abstract, dc_parser_t *parser, const u goto cleanup; } - fprintf (output->ostream, "%04i-%02i-%02i %02i:%02i:%02i\n", - dt.year, dt.month, dt.day, - dt.hour, dt.minute, dt.second); + if (dt.timezone == DC_TIMEZONE_NONE) { + fprintf (output->ostream, "%04i-%02i-%02i %02i:%02i:%02i\n", + dt.year, dt.month, dt.day, + dt.hour, dt.minute, dt.second); + } else { + fprintf (output->ostream, "%04i-%02i-%02i %02i:%02i:%02i %+03i:%02i\n", + dt.year, dt.month, dt.day, + dt.hour, dt.minute, dt.second, + dt.timezone / 3600, (dt.timezone % 3600) / 60); + } // Parse the divetime. message ("Parsing the divetime.\n"); diff --git a/include/libdivecomputer/datetime.h b/include/libdivecomputer/datetime.h index d8f596b..768883d 100644 --- a/include/libdivecomputer/datetime.h +++ b/include/libdivecomputer/datetime.h @@ -26,6 +26,8 @@ extern "C" { #endif /* __cplusplus */ +#define DC_TIMEZONE_NONE 0x80000000 + #if defined (_WIN32) && !defined (__GNUC__) typedef __int64 dc_ticks_t; #else @@ -39,6 +41,7 @@ typedef struct dc_datetime_t { int hour; int minute; int second; + int timezone; } dc_datetime_t; dc_ticks_t diff --git a/src/atomics_cobalt_parser.c b/src/atomics_cobalt_parser.c index e43243c..e16077b 100644 --- a/src/atomics_cobalt_parser.c +++ b/src/atomics_cobalt_parser.c @@ -122,6 +122,7 @@ atomics_cobalt_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *dateti datetime->hour = p[0x18]; datetime->minute = p[0x19]; datetime->second = 0; + datetime->timezone = DC_TIMEZONE_NONE; } return DC_STATUS_SUCCESS; diff --git a/src/citizen_aqualand_parser.c b/src/citizen_aqualand_parser.c index f89eacf..319dcf7 100644 --- a/src/citizen_aqualand_parser.c +++ b/src/citizen_aqualand_parser.c @@ -95,6 +95,7 @@ citizen_aqualand_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *date datetime->hour = bcd2dec(p[0x0A]); datetime->minute = bcd2dec(p[0x0B]); datetime->second = bcd2dec(p[0x0C]); + datetime->timezone = DC_TIMEZONE_NONE; } return DC_STATUS_SUCCESS; diff --git a/src/cochran_commander_parser.c b/src/cochran_commander_parser.c index 33f3b28..12e8b42 100644 --- a/src/cochran_commander_parser.c +++ b/src/cochran_commander_parser.c @@ -435,6 +435,7 @@ cochran_commander_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *dat datetime->day = data[layout->datetime + 2]; datetime->month = data[layout->datetime + 5]; datetime->year = data[layout->datetime + 4] + (data[layout->datetime + 4] > 91 ? 1900 : 2000); + datetime->timezone = DC_TIMEZONE_NONE; break; case DATE_ENCODING_SMHDMY: datetime->second = data[layout->datetime + 0]; @@ -443,6 +444,7 @@ cochran_commander_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *dat datetime->day = data[layout->datetime + 3]; datetime->month = data[layout->datetime + 4]; datetime->year = data[layout->datetime + 5] + (data[layout->datetime + 5] > 91 ? 1900 : 2000); + datetime->timezone = DC_TIMEZONE_NONE; break; case DATE_ENCODING_TICKS: ts = array_uint32_le(data + layout->datetime) + COCHRAN_EPOCH; diff --git a/src/cressi_edy_parser.c b/src/cressi_edy_parser.c index c9e1d91..eaf55c3 100644 --- a/src/cressi_edy_parser.c +++ b/src/cressi_edy_parser.c @@ -115,6 +115,7 @@ cressi_edy_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime) datetime->hour = bcd2dec (p[14]); datetime->minute = bcd2dec (p[15]); datetime->second = 0; + datetime->timezone = DC_TIMEZONE_NONE; } return DC_STATUS_SUCCESS; diff --git a/src/cressi_leonardo_parser.c b/src/cressi_leonardo_parser.c index b8c47e0..d7cf0de 100644 --- a/src/cressi_leonardo_parser.c +++ b/src/cressi_leonardo_parser.c @@ -100,6 +100,7 @@ cressi_leonardo_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datet datetime->hour = p[11]; datetime->minute = p[12]; datetime->second = 0; + datetime->timezone = DC_TIMEZONE_NONE; } return DC_STATUS_SUCCESS; diff --git a/src/datetime.c b/src/datetime.c index 9d07f8c..3ceed42 100644 --- a/src/datetime.c +++ b/src/datetime.c @@ -61,6 +61,47 @@ dc_gmtime_r (const time_t *t, struct tm *tm) #endif } +static time_t +dc_timegm (struct tm *tm) +{ +#if defined(HAVE_TIMEGM) + return timegm (tm); +#elif defined (HAVE__MKGMTIME) + return _mkgmtime (tm); +#else + static const unsigned int mdays[] = { + 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 + }; + + if (tm == NULL || + tm->tm_mon < 0 || tm->tm_mon > 11 || + tm->tm_mday < 1 || tm->tm_mday > 31 || + tm->tm_hour < 0 || tm->tm_hour > 23 || + tm->tm_min < 0 || tm->tm_min > 59 || + tm->tm_sec < 0 || tm->tm_sec > 60) + return (time_t) -1; + + /* Number of leap days since 1970-01-01. */ + int year = tm->tm_year + 1900 - (tm->tm_mon < 2); + int leapdays = + ((year / 4) - (year / 100) + (year / 400)) - + ((1969 / 4) - (1969 / 100) + (1969 / 400)); + + time_t result = 0; + result += (tm->tm_year - 70) * 365; + result += leapdays; + result += mdays[tm->tm_mon]; + result += tm->tm_mday - 1; + result *= 24; + result += tm->tm_hour; + result *= 60; + result += tm->tm_min; + result *= 60; + result += tm->tm_sec; + return result; +#endif +} + dc_ticks_t dc_datetime_now (void) { @@ -72,11 +113,23 @@ dc_datetime_localtime (dc_datetime_t *result, dc_ticks_t ticks) { time_t t = ticks; + int offset = 0; struct tm tm; if (dc_localtime_r (&t, &tm) == NULL) return NULL; +#ifdef HAVE_STRUCT_TM_TM_GMTOFF + offset = tm.tm_gmtoff; +#else + struct tm tmp = tm; + time_t t_local = dc_timegm (&tmp); + if (t_local == (time_t) -1) + return NULL; + + offset = t_local - t; +#endif + if (result) { result->year = tm.tm_year + 1900; result->month = tm.tm_mon + 1; @@ -84,6 +137,7 @@ dc_datetime_localtime (dc_datetime_t *result, result->hour = tm.tm_hour; result->minute = tm.tm_min; result->second = tm.tm_sec; + result->timezone = offset; } return result; @@ -106,6 +160,7 @@ dc_datetime_gmtime (dc_datetime_t *result, result->hour = tm.tm_hour; result->minute = tm.tm_min; result->second = tm.tm_sec; + result->timezone = 0; } return result; @@ -124,7 +179,15 @@ dc_datetime_mktime (const dc_datetime_t *dt) tm.tm_hour = dt->hour; tm.tm_min = dt->minute; tm.tm_sec = dt->second; - tm.tm_isdst = -1; + tm.tm_isdst = 0; - return mktime (&tm); + time_t t = dc_timegm (&tm); + if (t == (time_t) -1) + return t; + + if (dt->timezone != DC_TIMEZONE_NONE) { + t -= dt->timezone; + } + + return t; } diff --git a/src/diverite_nitekq_parser.c b/src/diverite_nitekq_parser.c index 6c7a05f..dce40d8 100644 --- a/src/diverite_nitekq_parser.c +++ b/src/diverite_nitekq_parser.c @@ -118,6 +118,7 @@ diverite_nitekq_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datet datetime->hour = p[3]; datetime->minute = p[4]; datetime->second = p[5]; + datetime->timezone = DC_TIMEZONE_NONE; } return DC_STATUS_SUCCESS; diff --git a/src/hw_ostc_parser.c b/src/hw_ostc_parser.c index b23e6d3..f1c656f 100644 --- a/src/hw_ostc_parser.c +++ b/src/hw_ostc_parser.c @@ -394,6 +394,7 @@ hw_ostc_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime) dt.hour = p[3]; dt.minute = p[4]; dt.second = 0; + dt.timezone = DC_TIMEZONE_NONE; if (version == 0x24) { if (datetime) @@ -405,8 +406,10 @@ hw_ostc_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime) ticks -= divetime; - if (!dc_datetime_localtime (datetime, ticks)) + if (!dc_datetime_gmtime (datetime, ticks)) return DC_STATUS_DATAFORMAT; + + datetime->timezone = DC_TIMEZONE_NONE; } return DC_STATUS_SUCCESS; diff --git a/src/mares_darwin_parser.c b/src/mares_darwin_parser.c index 1201ec6..ba0382f 100644 --- a/src/mares_darwin_parser.c +++ b/src/mares_darwin_parser.c @@ -118,6 +118,7 @@ mares_darwin_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime datetime->hour = p[4]; datetime->minute = p[5]; datetime->second = 0; + datetime->timezone = DC_TIMEZONE_NONE; } return DC_STATUS_SUCCESS; diff --git a/src/mares_iconhd_parser.c b/src/mares_iconhd_parser.c index 0c45a15..76324f3 100644 --- a/src/mares_iconhd_parser.c +++ b/src/mares_iconhd_parser.c @@ -329,6 +329,7 @@ mares_iconhd_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime datetime->day = array_uint16_le (p + 4); datetime->month = array_uint16_le (p + 6) + 1; datetime->year = array_uint16_le (p + 8) + 1900; + datetime->timezone = DC_TIMEZONE_NONE; } return DC_STATUS_SUCCESS; diff --git a/src/mares_nemo_parser.c b/src/mares_nemo_parser.c index 9a6cbfd..c0ab03b 100644 --- a/src/mares_nemo_parser.c +++ b/src/mares_nemo_parser.c @@ -200,6 +200,7 @@ mares_nemo_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime) datetime->hour = p[3]; datetime->minute = p[4]; datetime->second = 0; + datetime->timezone = DC_TIMEZONE_NONE; } return DC_STATUS_SUCCESS; diff --git a/src/oceanic_atom2_parser.c b/src/oceanic_atom2_parser.c index 9133984..19abadb 100644 --- a/src/oceanic_atom2_parser.c +++ b/src/oceanic_atom2_parser.c @@ -324,6 +324,7 @@ oceanic_atom2_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetim break; } datetime->second = 0; + datetime->timezone = DC_TIMEZONE_NONE; // Convert to a 24-hour clock. datetime->hour %= 12; diff --git a/src/oceanic_veo250_parser.c b/src/oceanic_veo250_parser.c index ba69723..a3acf57 100644 --- a/src/oceanic_veo250_parser.c +++ b/src/oceanic_veo250_parser.c @@ -121,6 +121,7 @@ oceanic_veo250_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *dateti datetime->hour = p[3]; datetime->minute = p[2]; datetime->second = 0; + datetime->timezone = DC_TIMEZONE_NONE; if (parser->model == VEO200 || parser->model == VEO250) datetime->year += 3; diff --git a/src/oceanic_vtpro_parser.c b/src/oceanic_vtpro_parser.c index 9c85854..894b626 100644 --- a/src/oceanic_vtpro_parser.c +++ b/src/oceanic_vtpro_parser.c @@ -135,6 +135,7 @@ oceanic_vtpro_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetim } datetime->minute = bcd2dec (p[0]); datetime->second = 0; + datetime->timezone = DC_TIMEZONE_NONE; // Convert to a 24-hour clock. datetime->hour %= 12; diff --git a/src/shearwater_predator_parser.c b/src/shearwater_predator_parser.c index c923153..4217f54 100644 --- a/src/shearwater_predator_parser.c +++ b/src/shearwater_predator_parser.c @@ -202,6 +202,8 @@ shearwater_predator_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *d if (!dc_datetime_gmtime (datetime, ticks)) return DC_STATUS_DATAFORMAT; + datetime->timezone = DC_TIMEZONE_NONE; + return DC_STATUS_SUCCESS; } diff --git a/src/suunto_d9_parser.c b/src/suunto_d9_parser.c index 68e79f9..6c96fa0 100644 --- a/src/suunto_d9_parser.c +++ b/src/suunto_d9_parser.c @@ -317,6 +317,7 @@ suunto_d9_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime) datetime->month = p[5]; datetime->day = p[6]; } + datetime->timezone = DC_TIMEZONE_NONE; } return DC_STATUS_SUCCESS; diff --git a/src/suunto_eon_parser.c b/src/suunto_eon_parser.c index 066c997..2106c69 100644 --- a/src/suunto_eon_parser.c +++ b/src/suunto_eon_parser.c @@ -178,6 +178,7 @@ suunto_eon_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime) datetime->minute = bcd2dec (p[4]); } datetime->second = 0; + datetime->timezone = DC_TIMEZONE_NONE; } return DC_STATUS_SUCCESS; diff --git a/src/suunto_eonsteel_parser.c b/src/suunto_eonsteel_parser.c index baa0319..0ebee01 100644 --- a/src/suunto_eonsteel_parser.c +++ b/src/suunto_eonsteel_parser.c @@ -1114,7 +1114,11 @@ suunto_eonsteel_parser_get_datetime(dc_parser_t *parser, dc_datetime_t *datetime if (parser->size < 4) return DC_STATUS_UNSUPPORTED; - dc_datetime_gmtime(datetime, array_uint32_le(parser->data)); + if (!dc_datetime_gmtime(datetime, array_uint32_le(parser->data))) + return DC_STATUS_DATAFORMAT; + + datetime->timezone = DC_TIMEZONE_NONE; + return DC_STATUS_SUCCESS; } diff --git a/src/suunto_vyper_parser.c b/src/suunto_vyper_parser.c index 0ca8eaa..80f7d54 100644 --- a/src/suunto_vyper_parser.c +++ b/src/suunto_vyper_parser.c @@ -223,6 +223,7 @@ suunto_vyper_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime datetime->hour = p[3]; datetime->minute = p[4]; datetime->second = 0; + datetime->timezone = DC_TIMEZONE_NONE; } return DC_STATUS_SUCCESS; diff --git a/src/uwatec_smart_parser.c b/src/uwatec_smart_parser.c index 99bfa28..54bdee5 100644 --- a/src/uwatec_smart_parser.c +++ b/src/uwatec_smart_parser.c @@ -710,6 +710,8 @@ uwatec_smart_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime if (!dc_datetime_gmtime (datetime, ticks)) return DC_STATUS_DATAFORMAT; + + datetime->timezone = utc_offset * 900; } else { // For devices without timezone support, the current timezone of // the host system is used.