From e81eca685aed7f5ac14e5d9613d7502070e5cda8 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sun, 16 Jul 2023 11:17:32 -0700 Subject: [PATCH] garmin: add a _third_ time offset field So FIT files seem to have many many different ways to describe time offsets. And I'm not talking about the overall Garmin time offset of 631065600, which is the conversion from the "Unix Epoch" (Jan 1, 1970) to the "Garmin Epoch" (Dec 31, 1989). No, I'm talking just about "device time" to "local time" to "UTC" conversions. The DEVICE_SETTINGS message has two different fields for time offsets: there's a "UTC offset" (presumably this is the timezone the device is set to), and a "time offset" which we actually use to transform the recorded time of the dive into the local time that we report. But the Suunto FIT export doesn't seem to use either of those, and instead Nick Clark points outthe Suunto FAQ: "Timestamp fields are deliberately defined as UTC time so that they may be conveniently displayed in the local time if so desired. In some instances it is useful to know the UTC offset when the file was generated (possibly different from when it is decoded). This can be accomplished by logging a single message containing both a local_timestamp and a timestamp field. This will establish the UTC offset of the file. Presently these fields are predefined for activity and monitoring messages" so to get the actual local time, instead of getting it from the DEVICE_SETTINGS message, we now have to parse the ACTIVITY message, and take the difference between the regular timestamp and the "local_timestamp" field. The great thing about standards is that there are so many to choose from. Signed-off-by: Linus Torvalds --- src/garmin_parser.c | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/garmin_parser.c b/src/garmin_parser.c index 4263d85..d5a2923 100644 --- a/src/garmin_parser.c +++ b/src/garmin_parser.c @@ -65,6 +65,7 @@ struct garmin_sensor { struct record_data { unsigned int pending; unsigned int time; + unsigned int timestamp; // RECORD_DECO int stop_time; @@ -492,6 +493,7 @@ struct field_desc { // Convert to "standard epoch time" by adding 631065600. DECLARE_FIELD(ANY, timestamp, UINT32) { + garmin->record_data.timestamp = data; if (garmin->callback) { dc_sample_value_t sample = {0}; @@ -652,7 +654,11 @@ DECLARE_FIELD(ACTIVITY, num_sessions, UINT16) { } DECLARE_FIELD(ACTIVITY, type, ENUM) { } DECLARE_FIELD(ACTIVITY, event, ENUM) { } DECLARE_FIELD(ACTIVITY, event_type, ENUM) { } -DECLARE_FIELD(ACTIVITY, local_timestamp, UINT32) { } +DECLARE_FIELD(ACTIVITY, local_timestamp, UINT32) +{ + int time_offset = data - garmin->record_data.timestamp; + garmin->dive.time_offset = time_offset; +} DECLARE_FIELD(ACTIVITY, event_group, UINT8) { } // SPORT @@ -1693,10 +1699,22 @@ garmin_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime) { garmin_parser_t *garmin = (garmin_parser_t *) abstract; dc_ticks_t time = 631065600 + (dc_ticks_t) garmin->dive.time; + int timezone = DC_TIMEZONE_NONE; // Show local time (time_offset) dc_datetime_gmtime(datetime, time + garmin->dive.time_offset); - datetime->timezone = DC_TIMEZONE_NONE; + + /* See if we might have a valid timezone offset */ + if (garmin->dive.time_offset || garmin->dive.utc_offset) { + int offset = garmin->dive.time_offset - garmin->dive.utc_offset; + + /* 15-minute (900-second) offsets are real */ + if ((offset % 900) == 0 && + offset >= -12*60*60 && + offset <= 14*60*60) + timezone = offset; + } + datetime->timezone = timezone; return DC_STATUS_SUCCESS; }