Search code examples
pythondatetimetimestampbit-manipulationgarmin

how to handle timestamp_16 in Garmin devices?


I own a Garmin Vivosport which I use to track my activities and my sleep, and I want to run some analysis on my own heart rate data taken from the FIT files I download via Garmin Connect.

The problem is that I do not manage to align timestamps properly.

A similar question is asked here but I didn't find an answer, nor could comment to add my question.

As explained here, in the FIT files the timestamp information is split into two different variables: a timestamp which appears every now and then, and a timestamp_16 which is attached to every individual measurement. According to what they say, timestamp_16 contains the lower 16 bits of the actual timestamp, so it should be combined with the higher 16 bits of the previous timestamp.

I am using data recorded on May 1st 2019. Files cover the 24h of the day, so the first data point is at 00:00 (I checked this in the graphic interface of Garmin Connect and things match). In this file I find this to be the first data entry of interest:

monitoring
 * activity_type: sedentary
 * current_activity_type_intensity: (8,)
 * intensity: 0
 * timestamp: 2019-04-30 22:00:00

[some other lines in between]

monitoring
 * heart_rate: 72
 * timestamp_16: 31132

the first monitoring object in the above snipped si the last one containing a timestamp before the one containing the heart rate, so it matches what is written in the linked instructions.

With these informations, I tried a few solution but with none of them I managed to get the actual timestamp of the first data point in the file to be at 00:00 on May 1st 2019 (nor within a few minutes).

If I follow the instructions given in the above link, I get:

mesgTimestamp = timestamp
mesgTimestamp += ( timestamp_16 - ( mesgTimestamp & 0xFFFF ) ) & 0xFFFF

but the outcome is: 2019-05-01 12:49:00

I also tried to replace by hand the lower 16 bits of timestamp with timestamp_16, but again no success:

timestamp    :   0b1011100110010001011111001011000
timestamp_16 :                   0b111100110011100
result       :   0b1011100110010000111100110011100

The datetime value corresponding to the above result is datetime.datetime(2019, 4, 30, 18, 36, 44).

Here is the code with all my attempts. Here is a github issue where this is being discussed.

As you can see above I cannot get the right result, i.e. May 1st 2019 00:00. In addition to this, if I apply Garmin's recipe by hand at the bit level I get a different result compared to what I get applying the formula they give.

Also, the results that I get are off by an amount of time having non zero minutes and seconds, and this makes me believe that this is not a timezone issue (I also tried to play with it with no success).

Has anyone found a stable solution to this? Could you please share something, if you have it (also in other languages, I am interested in the logic here)?

I am trying to get this right since a few months (working on this during my spare time), but this lack of result is really depressing :\


Solution

  • Take the special Garmin epoch into account, it's 631065600 seconds later than the Unix timestamp epoch, the calculations need to happen in that special time (if it was just a normal offset that wouldn't be the case, but these offsets are not strictly additive so the "absolute value" of the time matters). Just subtract 631065600:

    dt_offset = datetime.datetime(2019,4,30,22,0,0,0)
    timestamp = int(datetime.datetime.timestamp(dt_offset)) - 631065600
    timestamp_16 = 31132
    

    Apply the time delta in any reasonable way, for example:

    mesgTimestamp = timestamp
    mesgTimestamp += ( timestamp_16 - ( mesgTimestamp & 0xFFFF ) ) & 0xFFFF
    

    Or:

    mesgTimestamp = timestamp + ((timestamp_16 - timestamp) & 0xffff)
    

    Or:

    mesgTimestamp = (timestamp & 0xffff0000) | timestamp_16
    if mesgTimestamp < timestamp:
      mesgTimestamp += 0x10000
    

    Apply the Garmin epoch-offset again when using the resulting timestamp:

    print('New:', datetime.datetime.fromtimestamp(mesgTimestamp + 631065600, pytz.timezone('Europe/Zurich')))
    

    Result: New: 2019-05-01 00:01:00+02:00 Close enough?