Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle FitFile versions without lossy float #135

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 62 additions & 2 deletions fitparse/base.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#!/usr/bin/env python

import io
import logging
import os
import struct
import warnings
Expand Down Expand Up @@ -217,8 +218,8 @@ def _parse_file_header(self):
header_size, protocol_ver_enc, profile_ver_enc, data_size = self._read_struct('2BHI4x', data=header_data)

# Decode the same way the SDK does
self.protocol_version = float("%d.%d" % (protocol_ver_enc >> 4, protocol_ver_enc & ((1 << 4) - 1)))
self.profile_version = float("%d.%d" % (profile_ver_enc / 100, profile_ver_enc % 100))
self.protocol_version = FitVersion("%d.%d" % (protocol_ver_enc >> 4, protocol_ver_enc & ((1 << 4) - 1)))
self.profile_version = FitVersion("%d.%d" % (profile_ver_enc / 100, profile_ver_enc % 100))

# Consume extra header information
extra_header_size = header_size - 12
Expand Down Expand Up @@ -626,6 +627,65 @@ class FitFile(CacheMixin, UncachedFitFile):
pass


class FitVersion():
def __init__(self, version=None):
self._major = 0
self._minor = 0
if isinstance(version, float):
# Legacy match
logging.warning("Legacy matching with float versions, provide a string or FitVersion object for precision")
# Cast to a string and fall through
version = str(version)
if isinstance(version, str):
version_fields = version.split('.')
self._major = int(version_fields[0])
self._minor = int(version_fields[1])
else:
raise ValueError(f"Unknown quantity {version} cannot be parsed as a version string/float.")

@property
def major(self):
return self._major

@property
def minor(self):
return self._minor

def __str__(self):
return f"{self.major}.{self.minor}"

def __eq__(self, other):
if isinstance(other, FitVersion):
return self.major == other.major and self.minor == other.minor
else:
return self == FitVersion(other)

def __gt__(self, other):
if isinstance(other, FitVersion):
return self.major > other.major or self.major == other.major and self.minor > other.minor
else:
return self > FitVersion(other)

def __lt__(self, other):
if isinstance(other, FitVersion):
return self.major < other.major or self.major == other.major and self.minor < other.minor
else:
return self > FitVersion(other)

def __ge__(self, other):
if isinstance(other, FitVersion):
return self.major > other.major or self.major == other.major and self.minor >= other.minor
else:
return self > FitVersion(other)

def __le__(self, other):
if isinstance(other, FitVersion):
return self.major < other.major or self.major == other.major and self.minor <= other.minor
else:
return self > FitVersion(other)




# TODO: Create subclasses like Activity and do per-value monkey patching
# for example local_timestamp to adjust timestamp on a per-file basis
45 changes: 45 additions & 0 deletions tests/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import warnings

from fitparse import FitFile
from fitparse.base import FitVersion
from fitparse.processors import UTC_REFERENCE, StandardUnitsDataProcessor
from fitparse.records import BASE_TYPES, Crc
from fitparse.utils import FitEOFError, FitCRCError, FitHeaderError, FitParseError
Expand Down Expand Up @@ -77,6 +78,50 @@ def testfile(filename):
return os.path.join(os.path.dirname(os.path.realpath(__file__)), 'files', filename)


class FitVersionTestCase(unittest.TestCase):
def test_version_parse(self):
v10 = FitVersion("1.0")
v11 = FitVersion("1.1")
v12 = FitVersion(1.2)

self.assertEqual(v10, "1.0")
self.assertEqual(v11, "1.1")
self.assertEqual(v12, "1.2")

def test_version_parse_errors(self):
with self.assertRaises(ValueError):
FitVersion(1)
with self.assertRaises(ValueError):
FitVersion([1, 2])

def test_version_equality(self):
v10 = FitVersion("1.0")
v10_ = FitVersion("1.0")
v11 = FitVersion("1.1")

self.assertEqual(v10, v10_)
self.assertEqual(v10_, v10)
self.assertNotEqual(v10, v11)

self.assertGreaterEqual(v10, v10_)
self.assertLessEqual(v10, v10_)

def test_version_inequality(self):
v10 = FitVersion("1.0")
v11 = FitVersion("1.1")
v20 = FitVersion("2.0")

self.assertGreater(v11, v10)
self.assertGreater(v20, v11)
self.assertGreaterEqual(v11, v10)
self.assertGreaterEqual(v20, v11)

self.assertLess(v10, v11)
self.assertLess(v11, v20)
self.assertLessEqual(v10, v11)
self.assertLessEqual(v11, v20)


class FitFileTestCase(unittest.TestCase):
def test_basic_file_with_one_record(self, endian='<'):
f = FitFile(generate_fitfile(endian=endian))
Expand Down