You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

164 lines
4.9 KiB

  1. <?php
  2. namespace App\Http\Controllers;
  3. use Laravel\Lumen\Routing\Controller as BaseController;
  4. use Illuminate\Http\Request;
  5. use DB, Log;
  6. use Quartz;
  7. use DateTime, DateTimeZone, DateInterval;
  8. use App\Jobs\TripComplete;
  9. class LocalTime extends BaseController
  10. {
  11. public function find(Request $request) {
  12. $token = $request->input('token');
  13. if(!$token)
  14. return response(json_encode(['error' => 'no token provided']))->header('Content-Type', 'application/json');
  15. $db = DB::table('databases')->where('read_token','=',$token)->first();
  16. if(!$db)
  17. return response(json_encode(['error' => 'invalid token']))->header('Content-Type', 'application/json');
  18. $qz = new Quartz\DB(env('STORAGE_DIR').$db->name, 'r');
  19. $timezones = [
  20. '-23:00','-22:00','-21:00','-20:00',
  21. '-19:00','-18:00','-17:00','-16:00','-15:00','-14:00','-13:00','-12:00','-11:00','-10:00',
  22. '-09:00','-08:00','-07:00','-06:00','-05:00','-04:00','-03:00','-02:00','-01:00','+00:00',
  23. '+01:00','+02:00','+03:00','+04:00','+05:00','+06:00','+07:00','+08:00','+09:00','+10:00',
  24. '+11:00','+12:00','+13:00','+14:00','+15:00','+16:00','+17:00','+18:00','+19:00','+20:00',
  25. '+21:00','+22:00','+23:00',
  26. ];
  27. $date = false;
  28. if($input=$request->input('input')) {
  29. // Strict input format
  30. if(preg_match('/^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}$/', $input)) {
  31. $date = $input;
  32. } else {
  33. // Invalid format
  34. }
  35. }
  36. if(!$date) {
  37. return response(json_encode(['error' => 'invalid date provided']))->header('Content-Type', 'application/json');
  38. }
  39. $candidates = [];
  40. foreach($timezones as $tz) {
  41. // Interpret the input date in each timezone
  42. $date = DateTime::createFromFormat('Y-m-d H:i:s', $input, new DateTimeZone($tz));
  43. // Find the closest record to that absolute timestamp
  44. if($record = $this->_find_closest_record($qz, $date)) {
  45. if($record->data) {
  46. $local = $this->_timezone_for_location($record->data->geometry->coordinates[1], $record->data->geometry->coordinates[0], $record->date->format('c'));
  47. $diff = strtotime($local->localtime) - strtotime($date->format('c'));
  48. // Find the record where the localized timezone offset matches the candidate offset
  49. if($tz == $local->date->format('P')) {
  50. $candidates[] = [
  51. 'record' => $record->data,
  52. 'local' => $local,
  53. 'tz' => $tz,
  54. 'diff' => $diff
  55. ];
  56. }
  57. }
  58. }
  59. }
  60. // Choose the candidate with the smallest absolute time difference
  61. usort($candidates, function($a, $b){
  62. return abs($a['diff']) < abs($b['diff']) ? -1 : 1;
  63. });
  64. if(count($candidates)) {
  65. $record = $candidates[0];
  66. $response = [
  67. 'data' => $record['record'],
  68. 'timezone' => [
  69. 'offset' => $record['local']->offset,
  70. 'seconds' => $record['local']->seconds,
  71. 'localtime' => $record['local']->localtime,
  72. 'name' => $record['local']->name,
  73. ]
  74. ];
  75. } else {
  76. $response = ['data'=>null];
  77. }
  78. return response(json_encode($response))->header('Content-Type', 'application/json');
  79. }
  80. private function _timezone_for_location($lat, $lng, $date) {
  81. $tz = \p3k\Timezone::timezone_for_location($lat, $lng, $date);
  82. return new TimezoneResult($tz, $date);
  83. }
  84. private function _find_closest_record($qz, $date) {
  85. // TODO: move this logic into QuartzDB
  86. // Load the shard for the given date
  87. $shard = $qz->shardForDate($date);
  88. // If the shard doesn't exist, check one day before
  89. if(!$shard->exists()) {
  90. $date = $date->sub(new DateInterval('PT86400S'));
  91. $shard = $qz->shardForDate($date);
  92. }
  93. // Now if the shard doesn't exist, return an empty result
  94. if(!$shard->exists()) {
  95. return false;
  96. }
  97. // Start iterating through the shard and look for the last line that is before the given date
  98. $shard->init();
  99. $record = false;
  100. foreach($shard as $r) {
  101. if($r && $r->date > $date)
  102. break;
  103. $record = $r;
  104. }
  105. return $record;
  106. }
  107. }
  108. class TimezoneResult {
  109. public $timezone = null;
  110. private $_now;
  111. private $_name;
  112. public function __construct($timezone, $date=false) {
  113. if($date)
  114. $this->_now = new DateTime($date);
  115. else
  116. $this->_now = new DateTime();
  117. $this->_now->setTimeZone(new DateTimeZone($timezone));
  118. $this->_name = $timezone;
  119. }
  120. public function __get($key) {
  121. switch($key) {
  122. case 'date':
  123. return $this->_now;
  124. case 'offset':
  125. return $this->_now->format('P');
  126. case 'seconds':
  127. return (int)$this->_now->format('Z');
  128. case 'localtime':
  129. return $this->_now->format('c');
  130. case 'name':
  131. return $this->_name;
  132. }
  133. }
  134. public function __toString() {
  135. return $this->_name;
  136. }
  137. }