FitnessKit / FitDataProtocol

Swift Implementation the Garmin Flexible and Interoperable Data Transfer Protocol.
https://fitnesskit.github.io/FitDataProtocol/
MIT License
51 stars 15 forks source link

FitFieldTime property wrapper is not using local flag for wrapped value getter. #27

Closed antokne closed 1 year ago

antokne commented 1 year ago

Hi,

In FitFieldTime.swift wrappedValue getter I believe it should be passing local flag to the FitTime decode method e.g change:

let value = FitTime.decode(data: data,
                           base: base,
                           arch: owner?.architecture ?? .little)

to this:


let value = FitTime.decode(data: data,
                           base: base,
                           arch: owner?.architecture ?? .little,
                           isLocal: local)

otherwise I think it will incorrectly decode the local date time in the Activity Message, for example.

khoogheem commented 1 year ago

I updated the master brach will look at bump this weekend..

antokne commented 1 year ago

Thanks @khoogheem.

I pulled this into my fork and have been testing against a Garmin fit file and am still not 100% sure how this should actually work.

The problem is if the field in the Activity message called localTimeStamp is decoded as a local time and uses TimeZone.current, it does not generate a date with the correct offset, unless the activity happened to be recorded in the current timezone too.

What I think is needed here, is to calculate the time interval offset between the timestamp and the localTimeStamp of the activity message. Then you can use this offset to create the correct TimeZone.

I have a fit file that was recorded in Denver so this should be -6hrs. But this only works if the localTimeStamp is decoded using Date.antEPOCH, if I use Date.localAntEPOCH then it decodes it against my current timezone (in NZ) and results in an offset of -19hrs (difference between Denver and NZ).

This means that instead of being able to show a start time of when the activity was recorded in "it's" time zone you show the time the activity was recorded in the current time zone. In the case of viewing the Denver activity it was showing a start time at e.g. 9:30am the next day.

So in summary after all this I think that the localTimeStamp should not be using Date.localAntEPOCH for encoding and decoding just use the antEPOC one like all other timestamps.

Hopes this makes sense, kind of gone round in circles on this a few times.

Ants

khoogheem commented 1 year ago

The file that you have from Denver what encoded it.. ?? I don't think I have any examples that used local timestamp as it is not a required field.. so If you have examples of FIT files from other sources that encoded it (not this sdk) that would be great

Just FYI - I don't have a lot of time to do updates on this SDK anymore but will look at this if you can get me some FIT files..

Thanks

antokne commented 1 year ago

Hey,

It was recorded with an Edge 830.

here's an example file https://www.dropbox.com/scl/fi/njttq30quwqv6piuckvcs/2023-09-20-14-42-34.fit?rlkey=1i9l2d5hqtp0morl828e7r3m4&dl=0

I've mostly completed my project and even have dev data fields working on encoding now too. Just trying to figure out these dates and how they should be written so when I upload to the site mybiketraffic.com the start date is correct and in the correct timezone.

khoogheem commented 1 year ago

Ants - What time are we expecting for it in Denver on that activity 10:02 AM?

timeStamp: FitTime(recordDate: Optional(2023-09-20 22:02:13 +0000), secondSincePowerUp: nil, isLocal: false) localTimeStamp: FitTime(recordDate: Optional(2023-09-20 16:02:13 +0000), secondSincePowerUp: nil, isLocal: true)

khoogheem commented 1 year ago

So if I do something like the below I can get back to the 10:02 in Denver I have updated Master to just use AntEpoch on decode so it should be good to use the local to offset the timestamps

if let activityTime = message.timeStamp?.recordDate, let local = message.localTimeStamp?.recordDate {
    print("time: \(activityTime)")
    print("time: \(local)")
    let diffComponents = Calendar.current.dateComponents([.hour], from: activityTime, to: local)
    let hours = diffComponents.hour

    if let hours {
        if #available(macOS 12.0, *) {
            var format = Date.FormatStyle.dateTime
            format.timeZone = TimeZone(secondsFromGMT: hours * 60 * 60)!
            print("Timestamp offset local  \(activityTime.formatted(format))")
            print("Local Time  \(local.formatted(format))")
        } else {
            // Fallback on earlier versions
        }
    }

}
khoogheem commented 1 year ago

Also looks like Garmin now allows anyone to get the SDK where prior it was just locked members https://developer.garmin.com/fit/example-projects/objc/

antokne commented 1 year ago

expected start time in Denver was 14:42:34.