@@ -2,6 +2,7 @@ import { OtfHttpClient } from '../client/http-client';
22import { StatsTime , EquipmentType , ChallengeCategory } from '../types/workout-enums' ;
33import { components } from '../generated/types' ;
44import { BodyCompositionData } from '../models/body-composition' ;
5+ import { formatDateToPythonISO , formatDateForPythonParity , safeDateFormat } from '../utils/datetime' ;
56
67type Workout = components [ 'schemas' ] [ 'Workout' ] ;
78type BookingV2 = components [ 'schemas' ] [ 'BookingV2' ] ;
@@ -171,8 +172,8 @@ export class WorkoutsApi {
171172 private filterEquipmentData ( equipmentData : any ) : any {
172173 if ( ! equipmentData ) return equipmentData ;
173174
174- // Create a deep copy to avoid mutation
175- const filtered = JSON . parse ( JSON . stringify ( equipmentData ) ) ;
175+ // Create a deep copy to avoid mutation without converting dates
176+ const filtered = this . deepCopyPreservingDates ( equipmentData ) ;
176177
177178 // Remove max_power to match Python structure
178179 delete filtered . max_power ;
@@ -185,14 +186,18 @@ export class WorkoutsApi {
185186 }
186187
187188 /**
188- * Formats date to match Python's ISO format exactly
189- * Python: "2025-07-29T12:00:00+00:00"
190- * JavaScript default: "2025-07-29T12:00:00.000Z"
189+ * Deep copy that preserves date strings without converting them back to .000Z format
191190 */
192- private formatDateToPythonISO ( date : Date ) : string {
193- // Get ISO string and convert Z format to +00:00 format
194- // Remove milliseconds (.000) and replace Z with +00:00
195- return date . toISOString ( ) . replace ( / \. \d { 3 } Z $ / , '+00:00' ) ;
191+ private deepCopyPreservingDates ( obj : any ) : any {
192+ if ( obj === null || typeof obj !== 'object' ) return obj ;
193+ if ( obj instanceof Date ) return obj ; // Keep Date objects as is
194+ if ( Array . isArray ( obj ) ) return obj . map ( item => this . deepCopyPreservingDates ( item ) ) ;
195+
196+ const result : any = { } ;
197+ for ( const [ key , value ] of Object . entries ( obj ) ) {
198+ result [ key ] = this . deepCopyPreservingDates ( value ) ;
199+ }
200+ return result ;
196201 }
197202
198203 /**
@@ -483,7 +488,7 @@ export class WorkoutsApi {
483488 // Python returns: { class_history_uuid, class_start_time, max_hr, member_uuid, performance_summary_id, window_size, zones, telemetry: [...] }
484489 return {
485490 class_history_uuid : response . classHistoryUuid || performanceSummaryId ,
486- class_start_time : response . classStartTime || null ,
491+ class_start_time : response . classStartTime ? formatDateToPythonISO ( new Date ( response . classStartTime ) ) : null ,
487492 max_hr : response . maxHr || 0 ,
488493 member_uuid : response . memberUuid || this . memberUuid ,
489494 performance_summary_id : response . classHistoryUuid || performanceSummaryId ,
@@ -506,8 +511,8 @@ export class WorkoutsApi {
506511 apiType : 'default' ,
507512 path : `/member/members/${ this . memberUuid } /out-of-studio-workout` ,
508513 params : {
509- startDate : startDate . toISOString ( ) ,
510- endDate : endDate . toISOString ( ) ,
514+ startDate : formatDateToPythonISO ( startDate ) ,
515+ endDate : formatDateToPythonISO ( endDate ) ,
511516 } ,
512517 } ) ;
513518
@@ -893,7 +898,7 @@ export class WorkoutsApi {
893898
894899 const enhancedItem = { ...item } ;
895900 const absoluteTime = new Date ( classStart . getTime ( ) + ( item . relative_timestamp * 1000 ) ) ; // Convert seconds to milliseconds
896- enhancedItem . timestamp = this . formatDateToPythonISO ( absoluteTime ) ;
901+ enhancedItem . timestamp = formatDateToPythonISO ( absoluteTime ) ;
897902
898903 return enhancedItem ;
899904 } ) ;
@@ -904,7 +909,7 @@ export class WorkoutsApi {
904909 // Return the complete telemetry object structure to match Python
905910 return {
906911 class_history_uuid : telemetry . classHistoryUuid ,
907- class_start_time : telemetry . classStartTime ,
912+ class_start_time : telemetry . classStartTime ? formatDateToPythonISO ( new Date ( telemetry . classStartTime ) ) : null ,
908913 max_hr : telemetry . maxHr ,
909914 member_uuid : telemetry . memberUuid ,
910915 performance_summary_id : telemetry . classHistoryUuid , // Same as class_history_uuid
@@ -930,18 +935,18 @@ export class WorkoutsApi {
930935 // Match Python logic exactly
931936 switch ( classType ) {
932937 case 'ORANGE_60' :
933- return this . formatDateToPythonISO ( new Date ( start . getTime ( ) + ( 60 * 60 * 1000 ) ) ) ; // 60 minutes
938+ return formatDateToPythonISO ( new Date ( start . getTime ( ) + ( 60 * 60 * 1000 ) ) ) ; // 60 minutes
934939 case 'ORANGE_90' :
935- return this . formatDateToPythonISO ( new Date ( start . getTime ( ) + ( 90 * 60 * 1000 ) ) ) ; // 90 minutes
940+ return formatDateToPythonISO ( new Date ( start . getTime ( ) + ( 90 * 60 * 1000 ) ) ) ; // 90 minutes
936941 case 'STRENGTH_50' :
937942 case 'TREAD_50' :
938- return this . formatDateToPythonISO ( new Date ( start . getTime ( ) + ( 50 * 60 * 1000 ) ) ) ; // 50 minutes
943+ return formatDateToPythonISO ( new Date ( start . getTime ( ) + ( 50 * 60 * 1000 ) ) ) ; // 50 minutes
939944 case 'OTHER' :
940945 console . warn ( `Class type ${ classType } does not have defined length, returning start time plus 60 minutes` ) ;
941- return this . formatDateToPythonISO ( new Date ( start . getTime ( ) + ( 60 * 60 * 1000 ) ) ) ; // Default 60 minutes
946+ return formatDateToPythonISO ( new Date ( start . getTime ( ) + ( 60 * 60 * 1000 ) ) ) ; // Default 60 minutes
942947 default :
943948 console . warn ( `Class type ${ classType } is not recognized, returning start time plus 60 minutes` ) ;
944- return this . formatDateToPythonISO ( new Date ( start . getTime ( ) + ( 60 * 60 * 1000 ) ) ) ; // Default 60 minutes
949+ return formatDateToPythonISO ( new Date ( start . getTime ( ) + ( 60 * 60 * 1000 ) ) ) ; // Default 60 minutes
945950 }
946951 }
947952
0 commit comments