diff --git a/fittrackee/tests/fixtures/fixtures_workouts.py b/fittrackee/tests/fixtures/fixtures_workouts.py
index 46402c61..e2e11b21 100644
--- a/fittrackee/tests/fixtures/fixtures_workouts.py
+++ b/fittrackee/tests/fixtures/fixtures_workouts.py
@@ -452,6 +452,120 @@ def gpx_file_wo_name() -> str:
)
+@pytest.fixture()
+def gpx_file_with_offset() -> str:
+ return (
+ ''
+ '' # noqa
+ ' '
+ ' '
+ ' '
+ ' '
+ ' 998'
+ ' '
+ ' '
+ ' '
+ ' 998'
+ ' '
+ ' '
+ ' '
+ ' 994'
+ ' '
+ ' '
+ ' '
+ ' 994'
+ ' '
+ ' '
+ ' '
+ ' 994'
+ ' '
+ ' '
+ ' '
+ ' 993'
+ ' '
+ ' '
+ ' '
+ ' 992'
+ ' '
+ ' '
+ ' '
+ ' 992'
+ ' '
+ ' '
+ ' '
+ ' 987'
+ ' '
+ ' '
+ ' '
+ ' 987'
+ ' '
+ ' '
+ ' '
+ ' 987'
+ ' '
+ ' '
+ ' '
+ ' 987'
+ ' '
+ ' '
+ ' '
+ ' 986'
+ ' '
+ ' '
+ ' '
+ ' 986'
+ ' '
+ ' '
+ ' '
+ ' 986'
+ ' '
+ ' '
+ ' '
+ ' 985'
+ ' '
+ ' '
+ ' '
+ ' 980'
+ ' '
+ ' '
+ ' '
+ ' 980'
+ ' '
+ ' '
+ ' '
+ ' 980'
+ ' '
+ ' '
+ ' '
+ ' 979'
+ ' '
+ ' '
+ ' '
+ ' 981'
+ ' '
+ ' '
+ ' '
+ ' 980'
+ ' '
+ ' '
+ ' '
+ ' 979'
+ ' '
+ ' '
+ ' '
+ ' 979'
+ ' '
+ ' '
+ ' '
+ ' 975'
+ ' '
+ ' '
+ ' '
+ ' '
+ ''
+ )
+
+
@pytest.fixture()
def gpx_file_wo_track() -> str:
return (
diff --git a/fittrackee/tests/workouts/test_utils/test_gpx.py b/fittrackee/tests/workouts/test_utils/test_gpx.py
index 06d655a0..167b8bba 100644
--- a/fittrackee/tests/workouts/test_utils/test_gpx.py
+++ b/fittrackee/tests/workouts/test_utils/test_gpx.py
@@ -2,7 +2,7 @@ from unittest.mock import call, patch
import pytest
from flask import Flask
-from gpxpy.gpx import MovingData
+from gpxpy.gpx import IGNORE_TOP_SPEED_PERCENTILES, MovingData
from werkzeug.datastructures import FileStorage
from fittrackee.users.models import User, UserSportPreference
@@ -55,7 +55,12 @@ class TestStoppedSpeedThreshold:
assert gpx_track_segment_mock.call_args_list[0] == call(
stopped_speed_threshold=expected_threshold
)
- gpx_track_segment_mock.assert_called_with(expected_threshold)
+ gpx_track_segment_mock.assert_called_with(
+ expected_threshold, # stopped_speed_threshold
+ False, # raw
+ IGNORE_TOP_SPEED_PERCENTILES, # speed_extreemes_percentiles
+ True, # ignore_nonstandard_distances
+ )
def test_it_calls_get_moving_data_with_threshold_depending_from_user_preference( # noqa
self,
@@ -85,4 +90,9 @@ class TestStoppedSpeedThreshold:
assert gpx_track_segment_mock.call_args_list[0] == call(
stopped_speed_threshold=expected_threshold
)
- gpx_track_segment_mock.assert_called_with(expected_threshold)
+ gpx_track_segment_mock.assert_called_with(
+ expected_threshold, # stopped_speed_threshold
+ False, # raw
+ IGNORE_TOP_SPEED_PERCENTILES, # speed_extreemes_percentiles
+ True, # ignore_nonstandard_distances
+ )
diff --git a/fittrackee/tests/workouts/test_utils/test_workouts.py b/fittrackee/tests/workouts/test_utils/test_workouts.py
index 0e325878..16a90145 100644
--- a/fittrackee/tests/workouts/test_utils/test_workouts.py
+++ b/fittrackee/tests/workouts/test_utils/test_workouts.py
@@ -1,9 +1,27 @@
+from datetime import datetime
from statistics import mean
-from typing import List
+from typing import List, Union
import pytest
+import pytz
+from gpxpy.gpxfield import SimpleTZ
-from fittrackee.workouts.utils.workouts import get_average_speed
+from fittrackee.workouts.utils.workouts import (
+ get_average_speed,
+ get_workout_datetime,
+)
+
+utc_datetime = datetime(
+ year=2022, month=6, day=11, hour=10, minute=23, second=00, tzinfo=pytz.utc
+)
+input_workout_dates = [
+ utc_datetime,
+ utc_datetime.replace(tzinfo=None),
+ utc_datetime.replace(tzinfo=SimpleTZ('Z')),
+ utc_datetime.astimezone(pytz.timezone('Europe/Paris')),
+ utc_datetime.astimezone(pytz.timezone('America/Toronto')),
+ '2022-06-11 12:23:00',
+]
class TestWorkoutAverageSpeed:
@@ -30,3 +48,68 @@ class TestWorkoutAverageSpeed:
assert get_average_speed(
nb_workouts, total_average_speed, workout_average_speed
) == mean(ave_speeds_list)
+
+
+class TestWorkoutGetWorkoutDatetime:
+ @pytest.mark.parametrize('input_workout_date', input_workout_dates)
+ def test_it_returns_naive_datetime(
+ self, input_workout_date: Union[datetime, str]
+ ) -> None:
+ naive_workout_date, _ = get_workout_datetime(
+ workout_date=input_workout_date, user_timezone='Europe/Paris'
+ )
+
+ assert naive_workout_date == datetime(
+ year=2022, month=6, day=11, hour=10, minute=23, second=00
+ )
+
+ def test_it_return_naive_datetime_when_no_user_timezone(self) -> None:
+ naive_workout_date, _ = get_workout_datetime(
+ workout_date='2022-06-11 12:23:00', user_timezone=None
+ )
+
+ assert naive_workout_date == datetime(
+ year=2022, month=6, day=11, hour=12, minute=23, second=00
+ )
+
+ @pytest.mark.parametrize('input_workout_date', input_workout_dates)
+ def test_it_returns_datetime_with_user_timezone(
+ self, input_workout_date: Union[datetime, str]
+ ) -> None:
+ timezone = 'Europe/Paris'
+
+ _, workout_date_with_tz = get_workout_datetime(
+ input_workout_date, user_timezone=timezone, with_timezone=True
+ )
+
+ assert workout_date_with_tz == datetime(
+ year=2022,
+ month=6,
+ day=11,
+ hour=10,
+ minute=23,
+ second=00,
+ tzinfo=pytz.utc,
+ ).astimezone(pytz.timezone(timezone))
+
+ def test_it_does_not_return_datetime_with_user_timezone_when_no_user_tz(
+ self,
+ ) -> None:
+ _, workout_date_with_tz = get_workout_datetime(
+ workout_date='2022-06-11 12:23:00',
+ user_timezone=None,
+ with_timezone=True,
+ )
+
+ assert workout_date_with_tz is None
+
+ def test_it_does_not_return_datetime_with_user_timezone_when_with_timezone_to_false( # noqa
+ self,
+ ) -> None:
+ _, workout_date_with_tz = get_workout_datetime(
+ workout_date='2022-06-11 12:23:00',
+ user_timezone='Europe/Paris',
+ with_timezone=False,
+ )
+
+ assert workout_date_with_tz is None
diff --git a/fittrackee/tests/workouts/test_workouts_api_1_post.py b/fittrackee/tests/workouts/test_workouts_api_1_post.py
index 5e56f003..f75320ec 100644
--- a/fittrackee/tests/workouts/test_workouts_api_1_post.py
+++ b/fittrackee/tests/workouts/test_workouts_api_1_post.py
@@ -2,7 +2,7 @@ import json
import os
from datetime import datetime
from io import BytesIO
-from typing import Dict
+from typing import Dict, Optional
from unittest.mock import Mock
import pytest
@@ -92,9 +92,7 @@ def assert_workout_data_with_gpx_segments(data: Dict) -> None:
assert data['data']['workouts'][0]['descent'] == 23.4
assert data['data']['workouts'][0]['distance'] == 0.3
assert data['data']['workouts'][0]['max_alt'] == 998.0
- assert (
- data['data']['workouts'][0]['max_speed'] is None
- ) # not enough points
+ assert data['data']['workouts'][0]['max_speed'] == 5.25
assert data['data']['workouts'][0]['min_alt'] == 975.0
assert data['data']['workouts'][0]['moving'] == '0:03:55'
assert data['data']['workouts'][0]['pauses'] == '0:00:15'
@@ -114,7 +112,7 @@ def assert_workout_data_with_gpx_segments(data: Dict) -> None:
assert segment['descent'] == 11.0
assert segment['distance'] == 0.113
assert segment['max_alt'] == 998.0
- assert segment['max_speed'] is None
+ assert segment['max_speed'] == 5.25
assert segment['min_alt'] == 987.0
assert segment['moving'] == '0:01:30'
assert segment['pauses'] is None
@@ -128,28 +126,33 @@ def assert_workout_data_with_gpx_segments(data: Dict) -> None:
assert segment['descent'] == 12.4
assert segment['distance'] == 0.186
assert segment['max_alt'] == 987.0
- assert segment['max_speed'] is None
+ assert segment['max_speed'] == 5.12
assert segment['min_alt'] == 975.0
assert segment['moving'] == '0:02:25'
assert segment['pauses'] is None
records = data['data']['workouts'][0]['records']
- assert len(records) == 3
+ assert len(records) == 4
assert records[0]['sport_id'] == 1
assert records[0]['workout_id'] == data['data']['workouts'][0]['id']
- assert records[0]['record_type'] == 'LD'
+ assert records[0]['record_type'] == 'MS'
assert records[0]['workout_date'] == 'Tue, 13 Mar 2018 12:44:45 GMT'
- assert records[0]['value'] == '0:03:55'
+ assert records[0]['value'] == 5.25
assert records[1]['sport_id'] == 1
assert records[1]['workout_id'] == data['data']['workouts'][0]['id']
- assert records[1]['record_type'] == 'FD'
+ assert records[1]['record_type'] == 'LD'
assert records[1]['workout_date'] == 'Tue, 13 Mar 2018 12:44:45 GMT'
- assert records[1]['value'] == 0.3
+ assert records[1]['value'] == '0:03:55'
assert records[2]['sport_id'] == 1
assert records[2]['workout_id'] == data['data']['workouts'][0]['id']
- assert records[2]['record_type'] == 'AS'
+ assert records[2]['record_type'] == 'FD'
assert records[2]['workout_date'] == 'Tue, 13 Mar 2018 12:44:45 GMT'
- assert records[2]['value'] == 4.59
+ assert records[2]['value'] == 0.3
+ assert records[3]['sport_id'] == 1
+ assert records[3]['workout_id'] == data['data']['workouts'][0]['id']
+ assert records[3]['record_type'] == 'AS'
+ assert records[3]['workout_date'] == 'Tue, 13 Mar 2018 12:44:45 GMT'
+ assert records[3]['value'] == 4.59
def assert_workout_data_wo_gpx(data: Dict) -> None:
@@ -315,6 +318,41 @@ class TestPostWorkoutWithGpx(ApiTestCaseMixin, CallArgsMixin):
)
assert_workout_data_with_gpx(data)
+ @pytest.mark.parametrize('input_user_timezone', [None, 'Europe/Paris'])
+ def test_it_adds_a_workout_with_gpx_with_offset(
+ self,
+ app: Flask,
+ user_1: User,
+ sport_1_cycling: Sport,
+ gpx_file_with_offset: str,
+ input_user_timezone: Optional[str],
+ ) -> None:
+ user_1.timezone = input_user_timezone
+ client, auth_token = self.get_test_client_and_auth_token(
+ app, user_1.email
+ )
+
+ response = client.post(
+ '/api/workouts',
+ data=dict(
+ file=(
+ BytesIO(str.encode(gpx_file_with_offset)),
+ 'example.gpx',
+ ),
+ data='{"sport_id": 1}',
+ ),
+ headers=dict(
+ content_type='multipart/form-data',
+ Authorization=f'Bearer {auth_token}',
+ ),
+ )
+
+ data = json.loads(response.data.decode())
+ assert response.status_code == 201
+ assert 'created' in data['status']
+ assert len(data['data']['workouts']) == 1
+ assert_workout_data_with_gpx(data)
+
@pytest.mark.parametrize(
'input_description,input_notes',
[
diff --git a/fittrackee/workouts/utils/gpx.py b/fittrackee/workouts/utils/gpx.py
index f549014f..367c1c3c 100644
--- a/fittrackee/workouts/utils/gpx.py
+++ b/fittrackee/workouts/utils/gpx.py
@@ -1,5 +1,5 @@
-from datetime import timedelta
-from typing import Any, Dict, List, Optional, Tuple
+from datetime import datetime, timedelta, timezone
+from typing import Any, Dict, List, Optional, Tuple, Union
import gpxpy.gpx
@@ -16,9 +16,9 @@ def open_gpx_file(gpx_file: str) -> Optional[gpxpy.gpx.GPX]:
def get_gpx_data(
- parsed_gpx: gpxpy.gpx,
+ parsed_gpx: Union[gpxpy.gpx.GPX, gpxpy.gpx.GPXTrackSegment],
max_speed: float,
- start: int,
+ start: Union[datetime, None],
stopped_time_between_seg: timedelta,
stopped_speed_threshold: float,
) -> Dict:
@@ -32,7 +32,8 @@ def get_gpx_data(
duration = parsed_gpx.get_duration()
gpx_data['duration'] = (
- timedelta(seconds=duration) + stopped_time_between_seg
+ timedelta(seconds=duration if duration else 0)
+ + stopped_time_between_seg
)
ele = parsed_gpx.get_elevation_extremes()
@@ -43,18 +44,24 @@ def get_gpx_data(
gpx_data['uphill'] = hill.uphill
gpx_data['downhill'] = hill.downhill
- mv = parsed_gpx.get_moving_data(
+ moving_data = parsed_gpx.get_moving_data(
stopped_speed_threshold=stopped_speed_threshold
)
- gpx_data['moving_time'] = timedelta(seconds=mv.moving_time)
- gpx_data['stop_time'] = (
- timedelta(seconds=mv.stopped_time) + stopped_time_between_seg
- )
- distance = mv.moving_distance + mv.stopped_distance
- gpx_data['distance'] = distance / 1000
+ if moving_data:
+ gpx_data['moving_time'] = timedelta(seconds=moving_data.moving_time)
+ gpx_data['stop_time'] = (
+ timedelta(seconds=moving_data.stopped_time)
+ + stopped_time_between_seg
+ )
+ distance = moving_data.moving_distance + moving_data.stopped_distance
+ gpx_data['distance'] = distance / 1000
- average_speed = distance / mv.moving_time if mv.moving_time > 0 else 0
- gpx_data['average_speed'] = (average_speed / 1000) * 3600
+ average_speed = (
+ distance / moving_data.moving_time
+ if moving_data.moving_time > 0
+ else 0
+ )
+ gpx_data['average_speed'] = (average_speed / 1000) * 3600
return gpx_data
@@ -72,9 +79,9 @@ def get_gpx_info(
if gpx is None:
raise WorkoutGPXException('not found', 'No gpx file')
- gpx_data = {'name': gpx.tracks[0].name, 'segments': []}
- max_speed = 0
- start = 0
+ gpx_data: Dict = {'name': gpx.tracks[0].name, 'segments': []}
+ max_speed = 0.0
+ start: Optional[datetime] = None
map_data = []
weather_data = []
segments_nb = len(gpx.tracks[0].segments)
@@ -83,14 +90,15 @@ def get_gpx_info(
stopped_time_between_seg = no_stopped_time
for segment_idx, segment in enumerate(gpx.tracks[0].segments):
- segment_start = 0
+ segment_start: Optional[datetime] = None
segment_points_nb = len(segment.points)
for point_idx, point in enumerate(segment.points):
if point_idx == 0:
+ segment_start = point.time
# first gpx point => get weather
- if start == 0:
+ if start is None:
start = point.time
- if update_weather_data:
+ if point.time and update_weather_data:
weather_data.append(get_weather(point))
# if a previous segment exists, calculate stopped time between
@@ -108,13 +116,19 @@ def get_gpx_info(
if update_map_data:
map_data.append([point.longitude, point.latitude])
- calculated_max_speed = segment.get_moving_data(
+ moving_data = segment.get_moving_data(
stopped_speed_threshold=stopped_speed_threshold
- ).max_speed
- segment_max_speed = calculated_max_speed if calculated_max_speed else 0
+ )
+ if moving_data:
+ calculated_max_speed = moving_data.max_speed
+ segment_max_speed = (
+ calculated_max_speed if calculated_max_speed else 0
+ )
- if segment_max_speed > max_speed:
- max_speed = segment_max_speed
+ if segment_max_speed > max_speed:
+ max_speed = segment_max_speed
+ else:
+ segment_max_speed = 0.0
segment_data = get_gpx_data(
segment,
@@ -137,12 +151,16 @@ def get_gpx_info(
if update_map_data:
bounds = gpx.get_bounds()
- gpx_data['bounds'] = [
- bounds.min_latitude,
- bounds.min_longitude,
- bounds.max_latitude,
- bounds.max_longitude,
- ]
+ gpx_data['bounds'] = (
+ [
+ bounds.min_latitude,
+ bounds.min_longitude,
+ bounds.max_latitude,
+ bounds.max_longitude,
+ ]
+ if bounds
+ else []
+ )
return gpx_data, map_data, weather_data
@@ -222,7 +240,11 @@ def get_chart_data(
'latitude': point.latitude,
'longitude': point.longitude,
'speed': speed,
- 'time': point.time,
+ # workaround
+ # https://github.com/tkrajina/gpxpy/issues/209
+ 'time': point.time.replace(
+ tzinfo=timezone(point.time.utcoffset())
+ ),
}
)
previous_point = point
diff --git a/fittrackee/workouts/utils/weather.py b/fittrackee/workouts/utils/weather.py
index 8d580ad7..79a22932 100644
--- a/fittrackee/workouts/utils/weather.py
+++ b/fittrackee/workouts/utils/weather.py
@@ -3,18 +3,22 @@ from typing import Dict, Optional
import forecastio
import pytz
-from gpxpy.gpx import GPXRoutePoint
+from gpxpy.gpx import GPXTrackPoint
from fittrackee import appLog
API_KEY = os.getenv('WEATHER_API_KEY')
-def get_weather(point: GPXRoutePoint) -> Optional[Dict]:
- if not API_KEY or API_KEY == '':
+def get_weather(point: GPXTrackPoint) -> Optional[Dict]:
+ if not API_KEY or not point.time:
return None
try:
- point_time = pytz.utc.localize(point.time)
+ point_time = (
+ pytz.utc.localize(point.time)
+ if point.time.tzinfo is None
+ else point.time.astimezone(pytz.utc)
+ )
forecast = forecastio.load_forecast(
API_KEY,
point.latitude,
diff --git a/fittrackee/workouts/utils/workouts.py b/fittrackee/workouts/utils/workouts.py
index e9159423..dea4c946 100644
--- a/fittrackee/workouts/utils/workouts.py
+++ b/fittrackee/workouts/utils/workouts.py
@@ -22,31 +22,42 @@ from .gpx import get_gpx_info
from .maps import generate_map, get_map_hash
-def get_datetime_with_tz(
- timezone: str, workout_date: datetime, gpx_data: Optional[Dict] = None
-) -> Tuple[Optional[datetime], datetime]:
+def get_workout_datetime(
+ workout_date: Union[datetime, str],
+ user_timezone: Optional[str],
+ date_str_format: Optional[str] = None,
+ with_timezone: bool = False,
+) -> Tuple[datetime, Optional[datetime]]:
"""
- Return naive datetime and datetime with user timezone
+ Return naive datetime and datetime with user timezone if with_timezone
"""
- workout_date_tz = None
- if timezone:
- user_tz = pytz.timezone(timezone)
- utc_tz = pytz.utc
- if gpx_data:
- # workout date in gpx is in UTC, but in naive datetime
- fmt = '%Y-%m-%d %H:%M:%S'
- workout_date_string = workout_date.strftime(fmt)
- workout_date_tmp = utc_tz.localize(
- datetime.strptime(workout_date_string, fmt)
- )
- workout_date_tz = workout_date_tmp.astimezone(user_tz)
- else:
- workout_date_tz = user_tz.localize(workout_date)
- workout_date = workout_date_tz.astimezone(utc_tz)
- # make datetime 'naive' like in gpx file
- workout_date = workout_date.replace(tzinfo=None)
+ workout_date_with_user_tz = None
- return workout_date_tz, workout_date
+ # workout w/o gpx
+ if isinstance(workout_date, str):
+ if not date_str_format:
+ date_str_format = '%Y-%m-%d %H:%M:%S'
+ workout_date = datetime.strptime(workout_date, date_str_format)
+ if user_timezone:
+ workout_date = pytz.timezone(user_timezone).localize(workout_date)
+
+ if workout_date.tzinfo is None:
+ naive_workout_date = workout_date
+ if user_timezone and with_timezone:
+ pytz.utc.localize(naive_workout_date)
+ workout_date_with_user_tz = pytz.utc.localize(
+ naive_workout_date
+ ).astimezone(pytz.timezone(user_timezone))
+ else:
+ naive_workout_date = workout_date.astimezone(pytz.utc).replace(
+ tzinfo=None
+ )
+ if user_timezone and with_timezone:
+ workout_date_with_user_tz = workout_date.astimezone(
+ pytz.timezone(user_timezone)
+ )
+
+ return naive_workout_date, workout_date_with_user_tz
def get_datetime_from_request_args(
@@ -57,25 +68,32 @@ def get_datetime_from_request_args(
date_from_str = params.get('from')
if date_from_str:
- date_from = datetime.strptime(date_from_str, '%Y-%m-%d')
- _, date_from = get_datetime_with_tz(user.timezone, date_from)
+ date_from, _ = get_workout_datetime(
+ workout_date=date_from_str,
+ user_timezone=user.timezone,
+ date_str_format='%Y-%m-%d',
+ )
date_to_str = params.get('to')
if date_to_str:
- date_to = datetime.strptime(
- f'{date_to_str} 23:59:59', '%Y-%m-%d %H:%M:%S'
+ date_to, _ = get_workout_datetime(
+ workout_date=f'{date_to_str} 23:59:59',
+ user_timezone=user.timezone,
)
- _, date_to = get_datetime_with_tz(user.timezone, date_to)
return date_from, date_to
+def _remove_microseconds(delta: timedelta) -> timedelta:
+ return delta - timedelta(microseconds=delta.microseconds)
+
+
def update_workout_data(
workout: Union[Workout, WorkoutSegment], gpx_data: Dict
) -> Union[Workout, WorkoutSegment]:
"""
Update workout or workout segment with data from gpx file
"""
- workout.pauses = gpx_data['stop_time']
- workout.moving = gpx_data['moving_time']
+ workout.pauses = _remove_microseconds(gpx_data['stop_time'])
+ workout.moving = _remove_microseconds(gpx_data['moving_time'])
workout.min_alt = gpx_data['elevation_min']
workout.max_alt = gpx_data['elevation_max']
workout.descent = gpx_data['downhill']
@@ -92,17 +110,17 @@ def create_workout(
Create Workout from data entered by user and from gpx if a gpx file is
provided
"""
- workout_date = (
- gpx_data['start']
+ workout_date, workout_date_tz = get_workout_datetime(
+ workout_date=gpx_data['start']
if gpx_data
- else datetime.strptime(workout_data['workout_date'], '%Y-%m-%d %H:%M')
- )
- workout_date_tz, workout_date = get_datetime_with_tz(
- user.timezone, workout_date, gpx_data
+ else workout_data['workout_date'],
+ date_str_format=None if gpx_data else '%Y-%m-%d %H:%M',
+ user_timezone=user.timezone,
+ with_timezone=True,
)
duration = (
- gpx_data['duration']
+ _remove_microseconds(gpx_data['duration'])
if gpx_data
else timedelta(seconds=workout_data['duration'])
)
@@ -202,11 +220,10 @@ def edit_workout(
workout.notes = workout_data.get('notes')
if not workout.gpx:
if workout_data.get('workout_date'):
- workout_date = datetime.strptime(
- workout_data['workout_date'], '%Y-%m-%d %H:%M'
- )
- _, workout.workout_date = get_datetime_with_tz(
- auth_user.timezone, workout_date
+ workout.workout_date, _ = get_workout_datetime(
+ workout_date=workout_data.get('workout_date', ''),
+ date_str_format='%Y-%m-%d %H:%M',
+ user_timezone=auth_user.timezone,
)
if workout_data.get('duration'):
diff --git a/fittrackee/workouts/workouts.py b/fittrackee/workouts/workouts.py
index b5d90a32..a6152189 100644
--- a/fittrackee/workouts/workouts.py
+++ b/fittrackee/workouts/workouts.py
@@ -1111,7 +1111,8 @@ def post_workout_no_gpx(
"status": "success"
}
- :=3.7.4.3", markers = "python_version < \"3.8\""
[[package]]
name = "gpxpy"
-version = "1.3.4"
+version = "1.5.0"
description = "GPX file parser and GPS track manipulation library"
category = "main"
optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+python-versions = ">=3.6"
[[package]]
name = "greenlet"
@@ -1466,7 +1466,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-
[metadata]
lock-version = "1.1"
python-versions = "^3.7"
-content-hash = "fa5be5cc59de72ae3ddb18acb2c0291a7d77296de6427492b46c40d794800335"
+content-hash = "40b8491c8b82a7b29f57a2845209965c6351b527aebb7d504f5f8ec0ace4141e"
[metadata.files]
alabaster = [
@@ -1727,7 +1727,7 @@ gitpython = [
{file = "GitPython-3.1.27.tar.gz", hash = "sha256:1c885ce809e8ba2d88a29befeb385fcea06338d3640712b59ca623c220bb5704"},
]
gpxpy = [
- {file = "gpxpy-1.3.4.tar.gz", hash = "sha256:4a0f072ae5bdf9270c7450e452f93a6c5c91d888114e8d78868a8f163b0dbb15"},
+ {file = "gpxpy-1.5.0.tar.gz", hash = "sha256:e6993a8945eae07a833cd304b88bbc6c3c132d63b2bf4a9b0a5d9097616b8708"},
]
greenlet = [
{file = "greenlet-1.1.2-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:58df5c2a0e293bf665a51f8a100d3e9956febfbf1d9aaf8c0677cf70218910c6"},
diff --git a/pyproject.toml b/pyproject.toml
index 1dcfdfff..6d13c3b5 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -29,7 +29,7 @@ flask = "^2.1"
flask-bcrypt = "^1.0"
flask-dramatiq = "^0.6.0"
flask-migrate = "^3.1"
-gpxpy = "=1.3.4"
+gpxpy = "=1.5.0"
gunicorn = "^20.1"
humanize = "^4.1"
psycopg2-binary = "^2.9"