Skip to content
This repository was archived by the owner on May 8, 2025. It is now read-only.

Commit 5d183d6

Browse files
committed
convert date_time and local_date_time types to Unix time
1 parent b4f8174 commit 5d183d6

File tree

6 files changed

+86
-16
lines changed

6 files changed

+86
-16
lines changed

README.md

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -114,18 +114,20 @@ In addition, public functions provide a short-hand way to access commonly used e
114114
- sport()
115115

116116
###Optional Parameters
117-
There are three optional parameters that can be passed as an associative array when the phpFITFileAnalysis object is instantiated. These are:
117+
There are four optional parameters that can be passed as an associative array when the phpFITFileAnalysis object is instantiated. These are:
118118

119119
- fix_data
120120
- units
121121
- pace
122+
- garmin_timestamps
122123

123124
For example:
124125
```php
125126
$options = [
126-
'fix_data' => ['cadence', 'distance'],
127-
'units' => 'statute',
128-
'pace' => true
127+
'fix_data' => ['cadence', 'distance'],
128+
'units' => 'statute',
129+
'pace' => true,
130+
'garmin_timestamps' => true
129131
];
130132
$pFFA = new adriangibbons\phpFITFileAnalysis('my_fit_file.fit', $options);
131133
```
@@ -263,6 +265,18 @@ foreach ($pFFA->data_mesgs['record']['speed'] as $key => $value) {
263265
```
264266
Note that if 'raw' units are requested then this parameter has no effect on the speed data, as it is left untouched from what was read-in from the file.
265267

268+
####Timestamps
269+
Unix time is the number of seconds since UTC 00:00:00 Jan 01 1970, however the FIT standard specifies that fields of type date_time and local_date_time (e.g. timestamps) are unsigned long integers representing seconds since UTC 00:00 Dec 31 1989.
270+
271+
The difference (in seconds) between FIT and Unix timestamps is 631,065,600:
272+
```php
273+
$date_FIT = new DateTime('1989-12-31 00:00:00', new DateTimeZone('UTC'));
274+
$date_UNIX = new DateTime('1970-01-01 00:00:00', new DateTimeZone('UTC'));
275+
$diff = $date_FIT->getTimestamp() - $date_UNIX->getTimestamp();
276+
echo 'The difference (in seconds) between FIT and Unix timestamps is '. number_format($diff);
277+
```
278+
By default, fields of type date_time and local_date_time read from FIT files will have this delta added to them so that they can be treated as Unix time. If the FIT timestamp is required, the 'garmin_timestamps' option can be set to true.
279+
266280
##Analysis
267281
The following functions return arrays of that could be used to create tables/charts:
268282
```php

demo/mountain-biking.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,8 @@
5656

5757

5858
// Google Time Zone API
59-
$date = new DateTime('1989-12-31', new DateTimeZone('UTC')); // FIT timestamps are seconds since UTC 00:00:00 Dec 31 1989, source FIT SDK
60-
$date_s = $date->getTimestamp() + $pFFA->data_mesgs['session']['start_time'];
59+
$date = new DateTime('now', new DateTimeZone('UTC'));
60+
$date_s = $pFFA->data_mesgs['session']['start_time'];
6161

6262
$url_tz = 'https://maps.googleapis.com/maps/api/timezone/json?location='.$LatLng_start.'&timestamp='.$date_s.'&key=AIzaSyDlPWKTvmHsZ-X6PGsBPAvo0nm1-WdwuYE';
6363

demo/power-analysis.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@
1717
$pFFA = new adriangibbons\phpFITFileAnalysis(__DIR__ . $file, $options);
1818

1919
// Google Time Zone API
20-
$date = new DateTime("1989-12-31", new DateTimeZone("UTC")); // timestamp[s]: seconds since UTC 00:00:00 Dec 31 1989
21-
$date_s = $date->getTimestamp() + $pFFA->data_mesgs['session']['start_time'];
20+
$date = new DateTime('now', new DateTimeZone('UTC'));
21+
$date_s = $pFFA->data_mesgs['session']['start_time'];
2222

2323
$url_tz = "https://maps.googleapis.com/maps/api/timezone/json?location=".reset($pFFA->data_mesgs['record']['position_lat']).','.reset($pFFA->data_mesgs['record']['position_long'])."&timestamp=".$date_s."&key=AIzaSyDlPWKTvmHsZ-X6PGsBPAvo0nm1-WdwuYE";
2424

demo/quadrant-analysis.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@
1717
$pFFA = new adriangibbons\phpFITFileAnalysis(__DIR__ . $file, $options);
1818

1919
// Google Time Zone API
20-
$date = new DateTime("1989-12-31", new DateTimeZone("UTC")); // timestamp[s]: seconds since UTC 00:00:00 Dec 31 1989
21-
$date_s = $date->getTimestamp() + $pFFA->data_mesgs['session']['start_time'];
20+
$date = new DateTime('now', new DateTimeZone('UTC'));
21+
$date_s = $pFFA->data_mesgs['session']['start_time'];
2222

2323
$url_tz = "https://maps.googleapis.com/maps/api/timezone/json?location=".reset($pFFA->data_mesgs['record']['position_lat']).','.reset($pFFA->data_mesgs['record']['position_long'])."&timestamp=".$date_s."&key=AIzaSyDlPWKTvmHsZ-X6PGsBPAvo0nm1-WdwuYE";
2424

@@ -33,7 +33,7 @@
3333
$ftp = 329;
3434
$selected_cadence = 90;
3535

36-
$json = $pFFA->getJSON($crank_length, $ftp, $selected_cadence);
36+
$json = $pFFA->getJSON($crank_length, $ftp, ['all'], $selected_cadence);
3737

3838
} catch (Exception $e) {
3939
echo 'caught exception: '.$e->getMessage();

demo/swim.php

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,12 @@
1919
echo 'caught exception: '.$e->getMessage();
2020
die();
2121
}
22-
23-
$units = 'm';
24-
$pool_length = $pFFA->data_mesgs['session']['pool_length'];
25-
$total_distance = number_format(max($pFFA->data_mesgs['record']['distance']));
22+
$units = 'm';
23+
$pool_length = $pFFA->data_mesgs['session']['pool_length'];
24+
$total_distance = number_format($pFFA->data_mesgs['record']['distance']);
2625
if ($pFFA->enumData('display_measure', $pFFA->data_mesgs['session']['pool_length_unit']) == 'statute') {
2726
$pool_length = round($pFFA->data_mesgs['session']['pool_length'] * 1.0936133);
28-
$total_distance = number_format(max($pFFA->data_mesgs['record']['distance']) * 1.0936133);
27+
$total_distance = number_format($pFFA->data_mesgs['record']['distance'] * 1.0936133);
2928
$units = 'yd';
3029
}
3130
?>

src/phpFITFileAnalysis.php

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,15 @@
2323
define('DATA_MESSAGE', 0);
2424
}
2525

26+
/*
27+
* This is the number of seconds difference between FIT and Unix timestamps.
28+
* FIT timestamps are seconds since UTC 00:00:00 Dec 31 1989 (source FIT SDK)
29+
* Unix time is the number of seconds since UTC 00:00:00 Jan 01 1970
30+
*/
31+
if (!defined('FIT_UNIX_TS_DIFF')) {
32+
define('FIT_UNIX_TS_DIFF', 631065600);
33+
}
34+
2635
class phpFITFileAnalysis
2736
{
2837
public $data_mesgs = []; // Used to store the data read from the file in associative arrays.
@@ -35,6 +44,7 @@ class phpFITFileAnalysis
3544
private $file_header = []; // Contains information about the FIT file such as the Protocol version, Profile version, and Data Size.
3645
private $php_trader_ext_loaded = false; // Is the PHP Trader extension loaded? Use $this->sma() algorithm if not available.
3746
private $types = null; // Set by $endianness depending on architecture in Definition Message.
47+
private $garmin_timestamps = false; // By default the constant FIT_UNIX_TS_DIFF will be added to timestamps.
3848

3949
// Enumerated data looked up by enumData().
4050
// Values from 'Profile.xls' contained within the FIT SDK.
@@ -856,6 +866,9 @@ public function __construct($file_path, $options = null)
856866
throw new \Exception('phpFITFileAnalysis->__construct(): file \''.$file_path.'\' does not exist!');
857867
}
858868
$this->options = $options;
869+
if (isset($options['garmin_timestamps']) && $options['garmin_timestamps'] == true) {
870+
$this->garmin_timestamps = true;
871+
}
859872
$this->php_trader_ext_loaded = extension_loaded('trader');
860873

861874
/**
@@ -993,6 +1006,11 @@ private function readDataRecords()
9931006
// Check if it's an invalid value for the type
9941007
$tmp_value = unpack($this->types[$field_defn['base_type']], substr($this->file_contents, $this->file_pointer, $field_defn['size']))['tmp'];
9951008
if ($tmp_value !== $this->invalid_values[$field_defn['base_type']]) {
1009+
// If it's a timestamp, compensate between different in FIT and Unix timestamp epochs
1010+
if ($field_defn['field_definition_number'] === 253 && !$this->garmin_timestamps) {
1011+
$tmp_value += FIT_UNIX_TS_DIFF;
1012+
}
1013+
9961014
// If it's a Record data message, store all the pieces in the temporary array as the timestamp may not be first...
9971015
if ($this->defn_mesgs[$local_mesg_type]['global_mesg_num'] === 20) {
9981016
$tmp_record_array[$this->data_mesg_info[$this->defn_mesgs[$local_mesg_type]['global_mesg_num']]['field_defns'][$field_defn['field_definition_number']]['field_name']] = $tmp_value / $this->data_mesg_info[$this->defn_mesgs[$local_mesg_type]['global_mesg_num']]['field_defns'][$field_defn['field_definition_number']]['scale'] - $this->data_mesg_info[$this->defn_mesgs[$local_mesg_type]['global_mesg_num']]['field_defns'][$field_defn['field_definition_number']]['offset'];
@@ -1035,6 +1053,45 @@ private function readDataRecords()
10351053
*/
10361054
private function fixData($options)
10371055
{
1056+
// By default the constant FIT_UNIX_TS_DIFF will be added to timestamps, which have field type of date_time (or local_date_time).
1057+
// Timestamp fields (field number == 253) converted after being unpacked in $this->readDataRecords().
1058+
if (!$this->garmin_timestamps) {
1059+
$date_times = [
1060+
['message_name' => 'activity', 'field_name' => 'local_timestamp'],
1061+
['message_name' => 'course_point', 'field_name' => 'timestamp'],
1062+
['message_name' => 'file_id', 'field_name' => 'time_created'],
1063+
['message_name' => 'goal', 'field_name' => 'end_date'],
1064+
['message_name' => 'goal', 'field_name' => 'start_date'],
1065+
['message_name' => 'lap', 'field_name' => 'start_time'],
1066+
['message_name' => 'length', 'field_name' => 'start_time'],
1067+
['message_name' => 'monitoring', 'field_name' => 'local_timestamp'],
1068+
['message_name' => 'monitoring_info', 'field_name' => 'local_timestamp'],
1069+
['message_name' => 'obdii_data', 'field_name' => 'start_timestamp'],
1070+
['message_name' => 'schedule', 'field_name' => 'scheduled_time'],
1071+
['message_name' => 'schedule', 'field_name' => 'time_created'],
1072+
['message_name' => 'segment_lap', 'field_name' => 'start_time'],
1073+
['message_name' => 'session', 'field_name' => 'start_time'],
1074+
['message_name' => 'timestamp_correlation', 'field_name' => 'local_timestamp'],
1075+
['message_name' => 'timestamp_correlation', 'field_name' => 'system_timestamp'],
1076+
['message_name' => 'training_file', 'field_name' => 'time_created'],
1077+
['message_name' => 'video_clip', 'field_name' => 'end_timestamp'],
1078+
['message_name' => 'video_clip', 'field_name' => 'start_timestamp']
1079+
];
1080+
1081+
foreach ($date_times as $date_time) {
1082+
if (isset($this->data_mesgs[$date_time['message_name']][$date_time['field_name']])) {
1083+
if (is_array($this->data_mesgs[$date_time['message_name']][$date_time['field_name']])) {
1084+
foreach ($this->data_mesgs[$date_time['message_name']][$date_time['field_name']] as &$element) {
1085+
$element += FIT_UNIX_TS_DIFF;
1086+
}
1087+
} else {
1088+
$this->data_mesgs[$date_time['message_name']][$date_time['field_name']] += FIT_UNIX_TS_DIFF;
1089+
}
1090+
}
1091+
}
1092+
}
1093+
1094+
10381095
// Find messages that have been unpacked as unsigned integers that should be signed integers.
10391096
// http://php.net/manual/en/function.pack.php - signed integers endianness is always machine dependent.
10401097
// 131 s signed short (always 16 bit, machine byte order)

0 commit comments

Comments
 (0)