Harmonize timezones

This commit is contained in:
2019-02-06 18:56:08 +01:00
parent f1d8879624
commit c9503bf3ef
6 changed files with 116 additions and 64 deletions

View File

@@ -0,0 +1,15 @@
ALTER TABLE messages ADD iso_time VARCHAR(24) AFTER longitude;
ALTER TABLE messages CHANGE COLUMN timestamp site_time TIMESTAMP DEFAULT 0;
ALTER TABLE messages CHANGE COLUMN unix_timestamp unix_time INT;
UPDATE messages SET iso_time = CONCAT(REPLACE(CONVERT_TZ(FROM_UNIXTIME(unix_timestamp), @@session.time_zone, 'Pacific/Auckland'), ' ', 'T'), '+1200') WHERE id_feed = 1;
ALTER TABLE feeds MODIFY last_update TIMESTAMP DEFAULT 0;
ALTER TABLE projects MODIFY active_from TIMESTAMP DEFAULT 0;
ALTER TABLE projects MODIFY active_to TIMESTAMP DEFAULT 0;
ALTER TABLE posts CHANGE COLUMN timestamp site_time TIMESTAMP DEFAULT 0;
ALTER TABLE pictures CHANGE COLUMN timestamp posted_on TIMESTAMP DEFAULT 0;
UPDATE pictures INNER JOIN projects USING(id_project) SET taken_on = CONVERT_TZ(taken_on, projects.timezone, @@session.time_zone) where id_project = 1;
ALTER TABLE pictures MODIFY taken_on TIMESTAMP DEFAULT 0;

View File

@@ -32,7 +32,7 @@ class Picture extends PhpObject {
if(empty($this->asPics)) {
if($this->oProject->getProjectId()) {
$asPics = $this->oDb->selectRows(array(
'select' => array(Db::getId(self::PIC_TABLE), 'filename', 'taken_on', 'timestamp AS added_on', 'rotate'),
'select' => array(Db::getId(self::PIC_TABLE), 'filename', 'taken_on', 'posted_on', 'rotate'),
'from' => self::PIC_TABLE,
'constraint'=> array(Db::getId(Project::PROJ_TABLE) => $this->oProject->getProjectId())
));
@@ -54,17 +54,20 @@ class Picture extends PhpObject {
public function addPic($sPicName, $sMethod='upload') {
$sError = '';
if(!$this->isProjectModeValid() && $sMethod!='sync') $sError = 'Le projet (id='.$this->oProject->getProjectId().') n\'est pas en mode "blog"';
elseif($this->oDb->pingValue(self::PIC_TABLE, array('filename'=>$sPicName))) $sError = 'l\'image existe déjà';
elseif($this->oDb->pingValue(self::PIC_TABLE, array('filename'=>$sPicName)) && $sMethod!='sync') $sError = 'l\'image existe déjà';
else {
//Add picture to DB
$asPicInfo = self::getPicInfo($sPicName);
$iPicId = $this->oDb->insertRow(self::PIC_TABLE, array(
$asPicInfo = $this->getPicInfoFromFile($sPicName);
$asDbInfo = array(
Db::getId(Project::PROJ_TABLE) => $this->oProject->getProjectId(),
'filename' => $sPicName,
'taken_on' => date(Db::TIMESTAMP_FORMAT, $asPicInfo['taken_ts']),
'timestamp' => date(Db::TIMESTAMP_FORMAT, $asPicInfo['file_ts']),
'taken_on' => ($asPicInfo['taken_ts'] > 0)?date(Db::TIMESTAMP_FORMAT, $asPicInfo['taken_ts']):0, //Site Time (Settings::TIMEZONE)
'posted_on' => date(Db::TIMESTAMP_FORMAT, $asPicInfo['file_ts']), //Site Time
'rotate' => $asPicInfo['rotate']
));
);
if($sMethod=='sync') $iPicId = $this->oDb->insertUpdateRow(self::PIC_TABLE, $asDbInfo, array(Db::getId(Project::PROJ_TABLE), 'filename'));
else $iPicId = $this->oDb->insertRow(self::PIC_TABLE, $asDbInfo);
if(!$iPicId) $sError = 'l\'image n\'a pas pu être entrée en base';
else {
@@ -95,19 +98,26 @@ class Picture extends PhpObject {
return $this->getCleanMessageStack();
}
private static function getPicInfo($sPicName)
private function getPicInfoFromFile($sPicName)
{
$sPicPath = self::getPicPath($sPicName);
$iPicTimeStamp = $iPicTakenTimeStamp = $iPicFileTimeStamp = 0;
$iTimeStamp = $iTakenOn = $iPostedOn = 0;
$sTakenOn = '';
$asExif = @exif_read_data($sPicPath, 0, true);
if(!$asExif) $asExif['FILE']['FileDateTime'] = filemtime($sPicPath);
//Timestamps
if(array_key_exists('EXIF', $asExif) && array_key_exists('DateTimeOriginal', $asExif['EXIF'])) $iPicTakenTimeStamp = strtotime($asExif['EXIF']['DateTimeOriginal']);
if(array_key_exists('FILE', $asExif) && array_key_exists('FileDateTime', $asExif['FILE'])) $iPicFileTimeStamp = $asExif['FILE']['FileDateTime'];
if(array_key_exists('EXIF', $asExif) && array_key_exists('DateTimeOriginal', $asExif['EXIF'])) $sTakenOn = $asExif['EXIF']['DateTimeOriginal'];
if(array_key_exists('FILE', $asExif) && array_key_exists('FileDateTime', $asExif['FILE'])) $iPostedOn = $asExif['FILE']['FileDateTime'];
//Picture info do not have any TZ: Interpreting date time using project timezone (assuming all pictures have been taken in this time zone)
if($sTakenOn != '') {
$oTakenOn = new DateTime($sTakenOn, new DateTimeZone($this->oProject->getTimeZone()));
$iTakenOn = $oTakenOn->format('U');
}
//Merge timestamps
$iPicTimeStamp = ($iPicTakenTimeStamp > 0)?$iPicTakenTimeStamp:$iPicFileTimeStamp;
$iTimeStamp = ($iTakenOn > 0)?$iTakenOn:$iPostedOn;
//Orientation
if(array_key_exists('IFD0', $asExif) && array_key_exists('Orientation', $asExif['IFD0'])) {
@@ -123,9 +133,9 @@ class Picture extends PhpObject {
else $sRotate = '0';
return array(
'timestamp' => $iPicTimeStamp,
'taken_ts' => $iPicTakenTimeStamp,
'file_ts' => $iPicFileTimeStamp,
'timestamp' => $iTimeStamp,
'taken_ts' => $iTakenOn,
'file_ts' => $iPostedOn,
'rotate' => $sRotate
);
}

View File

@@ -25,7 +25,7 @@ class Project extends PhpObject {
private $sName;
private $sCodeName;
private $asActive;
private $sGeo;
private $asGeo;
public function __construct(Db &$oDb) {
parent::__construct(__CLASS__, Settings::DEBUG);
@@ -68,7 +68,12 @@ class Project extends PhpObject {
return ($sFromTo=='')?$this->asActive:$this->asActive[$sFromTo];
}
public function getTimeZone() {
return $this->asGeo['timezone'];
}
public function getProjects($iProjectId=0) {
$bSpecificProj = ($iProjectId > 0);
$asInfo = array(
'select'=> array(
Db::getId(self::PROJ_TABLE)." AS id",
@@ -82,7 +87,7 @@ class Project extends PhpObject {
),
'from' => self::PROJ_TABLE
);
if($iProjectId > 0) $asInfo['constraint'] = array(Db::getId(self::PROJ_TABLE)=>$iProjectId);
if($bSpecificProj) $asInfo['constraint'] = array(Db::getId(self::PROJ_TABLE)=>$iProjectId);
$asProjects = $this->oDb->selectRows($asInfo, 'codename');
foreach($asProjects as $sCodeName=>&$asProject) {
@@ -94,17 +99,19 @@ class Project extends PhpObject {
$asProject['geofile'] = Spot::addTimestampToFilePath(self::GEO_FOLDER.$asProject['geofile']);
$asProject['codename'] = $sCodeName;
}
return $asProjects;
return $bSpecificProj?$asProject:$asProjects;
}
private function setProjectInfo() {
$asResult = $this->getProjects($this->iProjectId);
$asProject = reset($asResult);
$this->sMode = $asProject['mode'];
$this->asActive = array('from'=>$asProject['active_from'], 'to'=>$asProject['active_to']);
$this->sCodeName = key($asResult);
$this->sName = $asProject['name'];
$this->sGeo = array('file'=>$asProject['geofile'], 'timezone'=>$asProject['timezone']);
if($this->iProjectId > 0) {
$asProject = $this->getProjects($this->iProjectId);
$this->sMode = $asProject['mode'];
$this->asActive = array('from'=>$asProject['active_from'], 'to'=>$asProject['active_to']);
$this->sCodeName = $asProject['codename'];
$this->sName = $asProject['name'];
$this->asGeo = array('file'=>$asProject['geofile'], 'timezone'=>$asProject['timezone']);
}
else $this->addError('Error while setting project: no project ID');
}
}

View File

@@ -1,5 +1,19 @@
<?php
/* Timezones
* - Feeds (table `feeds`):
* - last_update: timestamp in site time (see Settings::TIMEZONE)
* - Spot Messages (table `messages`):
* - unix_time: UNIX (int) in UTC
* - site_time: timestamp in site time (see Settings::TIMEZONE)
* - iso_time: raw ISO 8601 in local timezone
* - Pictures (table `pictures`):
* - posted_on: timestamp in site time (see Settings::TIMEZONE)
* - taken_on: timestamp in site time (see Settings::TIMEZONE)
* - Posts (table `posts`):
* - site_time: timestamp in site time (see Settings::TIMEZONE)
*/
class Spot extends Main
{
//Spot feed
@@ -54,12 +68,12 @@ class Spot extends Main
(
'tables' => array
(
self::MSG_TABLE => array('ref_msg_id', Db::getId(self::FEED_TABLE), 'type', 'latitude', 'longitude', 'timestamp', 'unix_timestamp', 'content', 'battery_state'),
self::MSG_TABLE => array('ref_msg_id', Db::getId(self::FEED_TABLE), 'type', 'latitude', 'longitude', 'iso_time', 'site_time', 'unix_time', 'content', 'battery_state'),
self::FEED_TABLE => array('ref_feed_id', Db::getId(self::SPOT_TABLE), Db::getId(Project::PROJ_TABLE), 'name', 'description', 'status', 'last_update'),
self::SPOT_TABLE => array('ref_spot_id', 'name', 'model'),
Project::PROJ_TABLE => array('name', 'codename', 'active_from', 'active_to', 'geofile', 'timezone'),
self::POST_TABLE => array(Db::getId(Project::PROJ_TABLE), 'name', 'content', 'timestamp'),
Picture::PIC_TABLE => array(Db::getId(Project::PROJ_TABLE), 'filename', 'taken_on', 'timestamp', 'rotate')
self::POST_TABLE => array(Db::getId(Project::PROJ_TABLE), 'name', 'content', 'site_time'),
Picture::PIC_TABLE => array(Db::getId(Project::PROJ_TABLE), 'filename', 'taken_on', 'posted_on', 'rotate')
),
'types' => array
(
@@ -67,8 +81,9 @@ class Spot extends Main
'type' => "VARCHAR(20)",
'latitude' => "DECIMAL(7,5)",
'longitude' => "DECIMAL(8,5)",
'timestamp' => "DATETIME",
'unix_timestamp'=> "INT",
'iso_time' => "VARCHAR(24)",
'site_time' => "TIMESTAMP DEFAULT 0", //DEFAULT 0 removes auto-set to current time
'unix_time' => "INT",
'content' => "LONGTEXT",
'battery_state' => "VARCHAR(10)",
'ref_spot_id' => "VARCHAR(10)",
@@ -78,13 +93,14 @@ class Spot extends Main
'ref_feed_id' => "VARCHAR(40)",
'description' => "VARCHAR(100)",
'status' => "VARCHAR(10)",
'active_from' => "DATETIME",
'active_to' => "DATETIME",
'active_from' => "TIMESTAMP DEFAULT 0",
'active_to' => "TIMESTAMP DEFAULT 0",
'geofile' => "VARCHAR(50)",
'timezone' => "VARCHAR(100)",
'last_update' => "DATETIME",
'last_update' => "TIMESTAMP DEFAULT 0",
'filename' => "VARCHAR(100) NOT NULL",
'taken_on' => "DATETIME",
'taken_on' => "TIMESTAMP DEFAULT 0",
'posted_on' => "TIMESTAMP DEFAULT 0",
'rotate' => "SMALLINT"
),
'constraints' => array
@@ -155,7 +171,11 @@ class Spot extends Main
{
//Update Spot Info
$asFirstMsg = array_values($asMsgs)[0];
$asSpotInfo = array('ref_spot_id'=>$asFirstMsg['messengerId'], 'name'=>$asFirstMsg['messengerName'], 'model'=>$asFirstMsg['modelId']);
$asSpotInfo = array(
'ref_spot_id' => $asFirstMsg['messengerId'],
'name' => $asFirstMsg['messengerName'],
'model' => $asFirstMsg['modelId']
);
$iSpotId = $this->oDb->insertUpdateRow(self::SPOT_TABLE, $asSpotInfo, array('ref_spot_id'));
//Update Feed Info and last update date
@@ -179,8 +199,9 @@ class Spot extends Main
'type' => $asMsg['messageType'],
'latitude' => $asMsg['latitude'],
'longitude' => $asMsg['longitude'],
'timestamp' => date(Db::TIMESTAMP_FORMAT, strtotime($asMsg['dateTime'])), //Stored in Local Time
'unix_timestamp' => $asMsg['unixTime'], //Stored in UNIX time
'iso_time' => $asMsg['dateTime'], //ISO 8601 time (backup)
'site_time' => date(Db::TIMESTAMP_FORMAT, $asMsg['unixTime']), //Conversion to Site Time (see Settings::TIMEZONE)
'unix_time' => $asMsg['unixTime'], //UNIX Time (backup)
'content' => $asMsg['messageContent'],
'battery_state' => $asMsg['batteryState']
);
@@ -203,14 +224,14 @@ class Spot extends Main
$iIndex = 0;
$iMaxIndex = count($asMessages) - 1;
foreach($asPics as $asPic) {
while($iIndex <= $iMaxIndex && $asPic['unix_timestamp'] > $asMessages[$iIndex]['unix_timestamp']) {
while($iIndex <= $iMaxIndex && $asPic['unix_time'] > $asMessages[$iIndex]['unix_time']) {
$iIndex++;
}
if($iIndex == 0) $iMsgIndex = $iIndex;
elseif($iIndex > $iMaxIndex) $iMsgIndex = $iMaxIndex;
else {
$iHalfWayPoint = ($asMessages[$iIndex]['unix_timestamp'] - $asMessages[$iIndex - 1]['unix_timestamp'])/2;
$iMsgIndex = ($asPic['unix_timestamp'] >= $iHalfWayPoint)?$iIndex:($iIndex - 1);
$iHalfWayPoint = ($asMessages[$iIndex]['unix_time'] - $asMessages[$iIndex - 1]['unix_time'])/2;
$iMsgIndex = ($asPic['unix_time'] >= $iHalfWayPoint)?$iIndex:($iIndex - 1);
}
$asMessages[$iMsgIndex]['pics'][] = $asPic;
@@ -239,15 +260,15 @@ class Spot extends Main
}
$asInfo = array(
'select' => array('id_message', 'ref_msg_id', 'type', 'latitude', 'longitude', 'timestamp', 'unix_timestamp'),
'select' => array('id_message', 'ref_msg_id', 'type', 'latitude', 'longitude', 'site_time', 'unix_time'),
'from' => self::MSG_TABLE,
'constraint'=> array(
Db::getId(Spot::FEED_TABLE) => $asFeed[Db::getId(self::FEED_TABLE)],
'timestamp' => $this->oProject->getActivePeriod()),
'site_time' => $this->oProject->getActivePeriod()),
'constOpe' => array(
Db::getId(Spot::FEED_TABLE) => "=",
'timestamp' => "BETWEEN"),
'orderBy' => array('timestamp'=>'ASC'));
'site_time' => "BETWEEN"),
'orderBy' => array('site_time'=>'ASC'));
$asMessages = $this->oDb->selectRows($asInfo);
foreach($asMessages as $asMessage)
@@ -257,20 +278,20 @@ class Spot extends Main
$asMessage['lat_dms'] = self::DecToDMS($asMessage['latitude'], 'lat');
$asMessage['lon_dms'] = self::DecToDMS($asMessage['longitude'], 'lon');
$this->addTimeStamp($asMessage, $asMessage['unix_timestamp']);
$this->addTimeStamp($asMessage, $asMessage['unix_time']);
$asAllFeedMessages[] = $asMessage;
}
}
usort($asAllFeedMessages, function($a, $b){return $a['unix_timestamp'] > $b['unix_timestamp'];});
usort($asAllFeedMessages, function($a, $b){return $a['unix_time'] > $b['unix_time'];});
return $asAllFeedMessages;
}
/**
* Get valid pictures based on $sTimeRefField:
* - taken_on: Date/time on which the picture was taken (local time)
* - added_on: Date/time on which the picture was uploaded (site's time: Settings::TIMEZONE)
* Get valid pictures based on $sTimeRefField (both are on site time):
* - taken_on: Date/time on which the picture was taken
* - posted_on: Date/time on which the picture was uploaded
* @param String $sTimeRefField Field to calculate relative times
* @return Array Pictures info
*/
@@ -286,7 +307,7 @@ class Spot extends Main
$asValidPics[] = $asPic;
}
}
usort($asValidPics, function($a, $b){return $a['unix_timestamp'] > $b['unix_timestamp'];});
usort($asValidPics, function($a, $b){return $a['unix_time'] > $b['unix_time'];});
return $asValidPics;
}
@@ -299,24 +320,24 @@ class Spot extends Main
'constOpe' => array(Db::getId(Project::PROJ_TABLE) => "=")
);
if($this->oProject->getMode()==Project::MODE_HISTO) {
$asInfo['constraint']['timestamp'] = $this->oProject->getActivePeriod();
$asInfo['constOpe']['timestamp'] = "BETWEEN";
$asInfo['constraint']['site_time'] = $this->oProject->getActivePeriod();
$asInfo['constOpe']['site_time'] = "BETWEEN";
}
$asPosts = $this->oDb->selectRows($asInfo);
foreach($asPosts as &$asPost) {
$iUnixTimeStamp = strtotime($asPost['timestamp']);
$iUnixTimeStamp = strtotime($asPost['site_time']); //assumes site timezone (Settings::TIMEZONE)
$asPost['formatted_name'] = Toolbox::mb_ucwords($asPost['name']);
$this->addTimeStamp($asPost, $iUnixTimeStamp);
}
usort($asPosts, function($a, $b){return $a['unix_timestamp'] > $b['unix_timestamp'];});
usort($asPosts, function($a, $b){return $a['unix_time'] > $b['unix_time'];});
return $asPosts;
}
private function addTimeStamp(&$asData, $iTime) {
$asData['unix_timestamp'] = (int) $iTime;
$asData['unix_time'] = (int) $iTime;
$asData['relative_time'] = Toolbox::getDateTimeDesc($iTime, 'fr');
$asData['formatted_time'] = date(self::FORMAT_TIME, $iTime);
}
@@ -333,7 +354,7 @@ class Spot extends Main
),
'picture' => array(
'table' => Picture::PIC_TABLE,
'feed' => $this->getPictures('added_on'),
'feed' => $this->getPictures('posted_on'),
'priority' => 1
),
'post' => array(
@@ -347,7 +368,7 @@ class Spot extends Main
$sPriority = $asFeedTypeInfo['priority'];
if($bHistoMode) $sPriority = count($asFeedTypes) - 1 - $asFeedTypeInfo['priority'];
$iId = ($asFeed['unix_timestamp'] * -1).'.'.$sPriority.$asFeed[Db::getId($asFeedTypeInfo['table'])];
$iId = ($asFeed['unix_time'] * -1).'.'.$sPriority.$asFeed[Db::getId($asFeedTypeInfo['table'])];
$asFeeds[$iId] = $asFeed;
$asFeeds[$iId]['type'] = $sFeedType;
@@ -375,7 +396,7 @@ class Spot extends Main
Db::getId(Project::PROJ_TABLE) => $this->oProject->getProjectId(),
'name' => mb_strtolower(trim($sName)),
'content' => trim($sPost),
'timestamp' => date(Db::TIMESTAMP_FORMAT)
'site_time' => date(Db::TIMESTAMP_FORMAT) //site time (Settings::TIMEZONE)
);
$iPostId = $this->oDb->insertRow(self::POST_TABLE, $asData);
return self::getJsonResult(($iPostId > 0), '');

View File

@@ -235,7 +235,7 @@ function initSpotMessages(aoMessages, aoTracks) {
if(oMsg.pics) {
var $Pics = $('<div>', {'class':'pics'});
$.each(oMsg.pics, function(iKey, asPic){
$Pics.append($('<a>', {href: asPic.pic_path, 'data-lightbox': self.consts.title, 'data-title': asPic.formatted_time})
$Pics.append($('<a>', {href: asPic.pic_path, 'data-lightbox': self.consts.title, 'data-title': asPic.formatted_time+' (heure française)'})
.append($('<img>', {'src': asPic.thumb_path})));
});
$Tooltip
@@ -387,7 +387,7 @@ function getPost(asPost) {
break;
case 'picture':
var $Image = $('<img>', {'src': asPost.thumb_path/*, 'style':'transform:rotate('+asPost.rotate+'deg);'*/});
$Body = $('<a>', {href: asPost.pic_path, 'data-lightbox': self.consts.title, 'data-title': 'Photo ajoutée le '+sAbsTime+' (prise le '+asPost.taken_on_formatted+' heure locale)'}).append($Image);
$Body = $('<a>', {href: asPost.pic_path, 'data-lightbox': self.consts.title, 'data-title': 'Photo ajoutée le '+sAbsTime+' et prise le '+asPost.taken_on_formatted+' (heure française)'}).append($Image);
break;
case 'post':
$Body = $('<div>')

5
todo
View File

@@ -2,10 +2,9 @@ To Do List
----------
- Elevation chart
- Device/Spot Class
- Manage projects.timezone
- Remove files2/ on server
- Replace all images with FA icons
- add timezone to feed
- on hover sur message : open popup on map (check zoom)
- on hover on relative time: display absolute time
- bug on picture assignment to markers (routeburn) - related to TZ issue
- bug on picture assignment to markers (routeburn) - related to TZ issue
- Replace 'heure française' with actual project timezone