function getFlightFromIGC($filename) { if (!is_file($filename)) { DEBUG("IGC", 1, "getFlightFromIGC: File was not found:{$filename}<br>"); return 0; } set_time_limit(100); if ($this->forceBounds) { $startTime = $this->START_TIME; $endTime = $this->END_TIME; } $this->resetData(); if ($this->forceBounds) { $this->START_TIME = $startTime; $this->END_TIME = $endTime; } $this->setAllowedParams(); $this->filename = basename($filename); $this->checkForOLCfile($filename); $done = 0; $try_no_takeoff_detection = 0; $tm1 = time() + 3600 * 24 * 30; $tm2 = 0; while (!$done) { $lines = file($filename); $linesNum = count($lines); DEBUG("IGC", 1, "File has total {$linesNum} lines<br>"); $points = 0; $outputBuffer = ""; // process points // filter bad ones $p = 0; $Brecords = 0; $getPointsNum = 5; // + 4 points + this one for ($i = 0; $i < count($lines) - 10; $i++) { $pointOK = 1; $line = trim($lines[$i]); if (strlen($line) == 0) { continue; } if ($line[0] != 'B') { continue; } /* if ($i>2){ $prevline=trim($lines[$i-1]); if (!substr_compare($line,$prevline,1,6)) continue; //throw out Brecords with same time signatures P. Wild 4.11.2012 } */ $Brecords++; // also check for bad points // 012345678901234567890123456789 // B1522144902558N00848090EV0058400000 if (strlen($line) < 23 || $line[24] == 'V') { $lines[$i][1] = 'X'; continue; } $neighboors = array(); $nextPointPos = $i; for ($t1 = 0; $t1 < $getPointsNum; $t1++) { $thisPoint = new gpsPoint(trim($lines[$nextPointPos]), $this->timezone); $neighboors[$t1] = $thisPoint; $nextPointPos = $this->getNextPointPos($lines, $nextPointPos); } // got all next points // find mean values $mean_speed = 0; $mean_vario = 0; for ($t1 = 1; $t1 < $getPointsNum; $t1++) { // for 4 (5-1) points in a row // create arrays $T_distance[$t1] = $neighboors[$t1]->calcDistance($neighboors[$t1 - 1]); $T_alt[$t1] = $neighboors[$t1]->getAlt(); $T_deltaseconds[$t1] = $neighboors[$t1]->getTime() - $neighboors[$t1 - 1]->getTime(); $T_speed[$t1] = $T_deltaseconds[$t1] ? $T_distance[$t1] * 3.6 / $T_deltaseconds[$t1] : 0.0; /* in km/h */ if ($T_deltaseconds[$t1]) { $T_vario[$t1] = ($T_alt[$t1] - $neighboors[$t1 - 1]->getAlt()) / $T_deltaseconds[$t1]; } $mean_speed += $T_speed[$t1]; $mean_vario += $T_vario[$t1]; } $mean_speed = $mean_speed / ($getPointsNum - 1); $mean_vario = ($neighboors[$getPointsNum - 1]->getAlt() - $neighboors[0]->getAlt()) / ($neighboors[$getPointsNum - 1]->getTime() - $neighboors[0]->getTime()); //if ($mean_vario<0) { // DEBUG("IGC",8,"[$Brecords-$p] mean_vario :$mean_vario <0 <br>"); //} $data_speed[$i] = $mean_speed; $data_vario[$i] = $mean_vario; // $mean_vario = $mean_vario/($getPointsNum-1); // mean vario is wrong if ($neighboors[0]->getTime() < $tm1) { $tm1 = $neighboors[0]->getTime(); } if ($neighboors[0]->getTime() > $tm2) { $tm2 = $neighboors[0]->getTime(); } if ($neighboors[0]->getTime() - $neighboors[1]->getTime() > 0) { // the next point is more than one hour in the past // echo "#"; $pointOK=0; } if ($T_distance[1] < 0.5 && $T_deltaseconds[1] > 2) { // less than 0.5 m distance $pointOK = 0; DEBUG("IGC", 8, "[{$Brecords}-{$p}] Distance <0.5 m <br>"); $REJ_T_distance++; // we dont go through the other tests //echo "{$lines[$i]} =>"; $lines[$i][1] = 'X'; //echo "{$lines[$i]}<br>"; continue; //echo $T_distance[1]."*<br>"; } if (abs($mean_speed - $T_speed[1]) > 40) { // diff more than 40 km/h $pointOK = 0; $REJ_T_mean_speed++; DEBUG("IGC", 8, "[{$Brecords}-{$p}] Mean speed > 40 km/h <br>"); //echo "@"; } //if ( abs ($mean_vario - $T_vario[1] ) > 6 ) { // diff more than 6 m/sec // $pointOK=0; //echo "#"; //} if ($T_deltaseconds[1] == 0) { $pointOK = 0; $REJ_T_zero_time_diff++; DEBUG("IGC", 8, "[{$Brecords}-{$p}] No time Diff<br>"); } if ($T_alt[1] > $this->maxAllowedHeight) { $pointOK = 0; $REJ_max_alt++; } if (abs($T_speed[1]) > $this->maxAllowedSpeed) { $pointOK = 0; $REJ_max_speed++; DEBUG("IGC", 8, "[{$Brecords}-{$p}] > " . abs($T_speed[1]) . "km/h max allowed speed<br>"); // echo "S"; } if (abs($T_vario[1]) > $this->maxAllowedVario) { $pointOK = 0; $REJ_max_vario++; // echo "V"; DEBUG("IGC", 8, "[{$Brecords}-{$p}] > " . abs($T_vario[1]) . "m/sec > max allowed vario<br>"); } if ($p < 5 && !$try_no_takeoff_detection && !$this->forceBounds) { // first 5 points need special care $takeoffMaxSpeed = $this->maxAllowedSpeed * 0.5; DEBUG("IGC", 8, "[{$Brecords}-{$p}] TAKEOFF sequence SPEED: " . abs($T_speed[1]) . " max:{$takeoffMaxSpeed}<br>"); if (abs($T_speed[1]) > $takeoffMaxSpeed) { $pointOK = 0; $REJ_max_speed_start++; // echo "s"; } if (abs($T_vario[1]) > $this->maxAllowedVario * 0.4) { $pointOK = 0; $REJ_max_vario_start++; //echo "v"; } } if (!$pointOK) { $lines[$i][1] = 'X'; } else { $p++; if ($p == 5) { DEBUG("IGC", 1, "Passed the strict testing (p=5)<br>"); } } } DEBUG("IGC", 1, "REJ: [{$REJ_T_distance}] <0.5 distance<br>"); DEBUG("IGC", 1, "REJ: [{$REJ_T_zero_time_diff}] zero_time_diff<br>"); DEBUG("IGC", 1, "REJ: [{$REJ_T_mean_speed}] mean_speed diff >40km/h<br>"); DEBUG("IGC", 1, "REJ: [{$REJ_max_alt}] >max_alt<br>"); DEBUG("IGC", 1, "REJ: [{$REJ_max_speed}] >max_speed<br>"); DEBUG("IGC", 1, "REJ: [{$REJ_max_vario}] >max_vario<br>"); DEBUG("IGC", 1, "REJ: [{$REJ_max_speed_start}] >max_speed_start<br>"); DEBUG("IGC", 1, "REJ: [{$REJ_max_vario_start}] >max_vario_start<br>"); DEBUG("IGC", 1, "Found {$p} valid B records out of {$Brecords} total<br>"); if ($p > 0) { $done = 1; } else { if ($Brecords > 0 && $REJ_T_zero_time_diff / $Brecords > 0.9) { // more than 90% stopped points $lines = file($filename); $done = 1; $garminSpecialCase = 1; $p = $Brecords; DEBUG("IGC", 1, "Many Stopped points, it is a Garmin Special Case<br>"); } else { if (!$try_no_takeoff_detection) { $try_no_takeoff_detection = 1; DEBUG("IGC", 1, "Will try no_takeoff_detection<br>"); } else { $done = 1; } } } } // while not done // if ($p == 0) { DEBUG("IGC", 1, "NO VALID POINTS FOUND"); return 0; // no valid points found } $mod = 0; if ($p > $this->maxPointNum) { $reduceArray = getReduceArray($p, $this->maxPointNum); // print_r($recudeArray); $mod = count($reduceArray); // $mod= ceil( $p / $this->maxPointNum ); } DEBUG("IGC", 1, "will use a reduce array of length {$mod}<br>"); $duration = $tm2 - $tm1; $intervalSecs = round($duration / $p); // echo "<hr>good points: $p duration:$duration, intervalSecs:$intervalSecs<hr>"; $pointsNeededForTakeoff = 5; if ($intervalSecs >= 8) { $pointsNeededForTakeoff = 2; } $alreadyInPoints = 0; $stopReadingPoints = 0; $this->timezone = 1000; $day_offset = 0; $foundNewTrack = 0; $slow_points = 0; $slow_points_dt = 0; $stillOnGround = 1; $tmpDate = 0; foreach ($lines as $iii => $line) { if ($foundNewTrack) { break; } $outputLine = $line; $line = trim($line); if (strlen($line) == 0) { continue; } // if (strtoupper(substr($line,0,1)) !="B" ) echo "@$line<BR>"; if (strtoupper(substr($line, 0, 3)) == "OLC") { continue; } // it is an olc file , dont put the OLC... line in to the saned file if (strtoupper(substr($line, 0, 5)) == "HFDTE" || strtoupper(substr($line, 0, 5)) == "HPDTE") { // HFDTE170104 OR HPDTE310805 if ($alreadyInPoints && $points > 0) { if ($prevPoint->gpsTime < 86200) { // if last good point is > 86200 (200 secs before day change at 86400) we dont treat this as a new track $stopReadingPoints = 1; DEBUG("IGC", 1, "[{$points}] {$line}<br>"); DEBUG("IGC", 1, "[{$points}] Found a new track (NEW HFDTE)<br>"); } } else { $this->DATE = substr($line, 5, 6); $yr_last = substr($this->DATE, 4, 2); // case of YY=0 (1 digit) HFDTE08070 if ($yr_last == "0") { $yr_last = "00"; } if ($yr_last > 80) { $yr = "19" . $yr_last; } else { $yr = "20" . $yr_last; } $this->DATE = $yr . "-" . substr($this->DATE, 2, 2) . "-" . substr($this->DATE, 0, 2); $alreadyInPoints = 1; } } else { if (strtoupper(substr($line, 0, 13)) == "HFTZOTIMEZONE") { // HFTZOTimezone:3 OR HFTZOTimezone:-8 $this->timezone = substr($line, 14) + 0; // echo $this->timezone."#^^"; } else { if (strtoupper(substr($line, 2, 13)) == "GTYGLIDERTYPE") { // HOGTYGLIDERTYPE: Gradient Bliss 26 OR HPGTYGliderType:Gradient Nevada if (!$this->glider) { $this->glider = trim(substr($line, 16)); } // HFGTYGLIDERTYPE // HOGTYGLIDERTYPE } else { if ($line[0] == 'B') { if ($stopReadingPoints) { continue; } if ($line[1] == 'X') { DEBUG("IGC", 1, "[{$points}] BAD : {$line}<br>"); continue; // MARKED BAD from BEFORE } if (strlen($line) < 23 || strlen($line) > 100) { continue; } if ($points == 0) { // first point // echo "######## first point <br>"; $firstPoint = new gpsPoint($line, $this->timezone); if ($this->timezone == 1000) { // no timezone in the file // echo "calc timezone<br>"; $this->timezone = getUTMtimeOffset($firstPoint->lat, $firstPoint->lon, $this->DATE); $this->timezone = getTZ($firstPoint->lat, $firstPoint->lon, $this->DATE); // echo $this->timezone; $firstPoint->timezone = $this->timezone; } $tmpTime = $firstPoint->gpsTime + $this->timezone * 3600; // Now also check if we are one day minus (for US flights ) if ($tmpTime < 0 && $tmpDate == 0) { // one day before! $this->DATE = dates::moveDaysFromDate($this->DATE, -1); $tmpDate = 1; } // take care of day change for timezones in australia/nz etc if ($tmpTime > 86400 && $tmpDate == 0) { // UTC date in the igc file needs to be +1 $this->DATE = dates::moveDaysFromDate($this->DATE, 1); $tmpDate = 1; } // sanity checks if ($firstPoint->getAlt() > $this->maxAllowedHeight) { continue; } // echo "times: ".$firstPoint->gpsTime.", ".$firstPoint->getTime()." start_time: ".$this->START_TIME ."<BR> "; if ($this->forceBounds && !$this->checkBound($firstPoint->getTime())) { continue; } // not inside time window //$this->FIRST_POINT=$line; $this->firstPointTM = $firstPoint->gpsTime; $this->firstLat = $firstPoint->lat(); $this->firstLon = $firstPoint->lon(); $this->TAKEOFF_ALT = $firstPoint->getAlt(); $this->MIN_ALT = $firstPoint->getAlt(); if (!$this->forceBounds) { $this->START_TIME = $firstPoint->getTime(); } $prevPoint = new gpsPoint($line, $this->timezone); $prevPoint2 = new gpsPoint($line, $this->timezone); } else { $lastPoint = new gpsPoint($line, $this->timezone); $lastPoint->gpsTime += $day_offset; if ($this->forceBounds && !$this->checkBound($lastPoint->getTime())) { $lastPoint = $prevPoint; continue; // not inside time window } $time_diff = $lastPoint->getTime() - $prevPoint->getTime(); $time_diff2 = $lastPoint->getTime() - $prevPoint2->getTime(); // echo "time diff: $time_diff # $line<br>"; if ($time_diff < 0 && $time_diff > -36000) { // if the time is less than 10 hours in the past we just ignore it // $day_offset = 86400; // if time seems to have gone backwards, add a day DEBUG("IGC", 1, "[{$points}] {$line}<br>"); DEBUG("IGC", 1, "[{$points}] Point in the past<br>"); continue; } else { if ($time_diff < 0 && $time_diff > -86000) { // CHANGING DAY , means the flight is at night $lastPoint = $prevPoint; $foundNewTrack = 1; DEBUG("IGC", 1, "[{$points}] {$line}<br>"); DEBUG("IGC", 1, "[{$points}] Flight at night ????<br>"); continue; } else { if ($time_diff > $this->max_allowed_time_gap) { // found time gap // if we are forceBounds check to see if inside window if ($this->forceBounds && !$this->checkBound($lastPoint->getTime()) || !$this->forceBounds) { // if we are still on the ground we dont care about time gap if (!$stillOnGround) { // not inside time window OR not checking go ahead $lastPoint = $prevPoint; $foundNewTrack = 1; DEBUG("IGC", 1, "[{$points}] {$line}<br>"); DEBUG("IGC", 1, "[{$points}] Found a new track (Time diff of {$time_diff} secs)<br>"); continue; } } else { // inside the window, forced to continue } } } } $this->LAST_POINT = $line; // compute some things $tmp = $lastPoint->calcDistance($prevPoint); $alt = $lastPoint->getAlt(); // GUS begin $deltaseconds = $lastPoint->getTime() - $prevPoint->getTime(); $speedDeltaSecs = $deltaseconds + ($deltaseconds < -85000 ? 86400 : 0); $speed = $speedDeltaSecs ? $tmp * 3.6 / $speedDeltaSecs : 0.0; /* in km/h */ if ($speedDeltaSecs) { $vario = ($alt - $prevPoint->getAlt()) / $speedDeltaSecs; } // GUS end if (!$garminSpecialCase && !$this->forceBounds) { if (($fast_points >= $pointsNeededForTakeoff || $fast_points_dt > 30) && $stillOnGround) { // found 5 flying points or 30 secs $stillOnGround = 0; DEBUG("IGC", 1, "[{$points}] {$line}<br>"); DEBUG("IGC", 1, "[{$points}] Found Takeoff <br>"); } if ($stillOnGround) { //takeoff scan // either speed >= 15 or if we already have 2 fast points settle with speed>=10 if ($speed >= 15 || $speed >= 10 && $fast_points >= 2) { $fast_points++; $fast_points_dt += $speedDeltaSecs; DEBUG("IGC", 1, "[{$points}] {$line}<br>"); DEBUG("IGC", 1, "[{$points}] Found a fast speed point <br>"); } else { // reset takeoff scan DEBUG("IGC", 1, "[{$points}] {$line}<br>"); DEBUG("IGC", 1, "[{$points}] takeoff scan: speed: {$speed} time_diff: {$time_diff}<br>"); $fast_points = 0; $fast_points_dt = 0; } $points = 0; continue; } else { //landing scan if ($speed < 5) { $slow_points++; $slow_points_dt += $speedDeltaSecs; DEBUG("IGC", 1, "[{$points}] {$line}<br>"); DEBUG("IGC", 1, "[{$points}] Found a slow speed point (speed, dt)=({$speed},{$speedDeltaSecs})<br>"); } else { $slow_points = 0; $slow_points_dt = 0; } } // found landing (5 stopped points and >2mins) or 5 mins (300 secs) if ($slow_points > $pointsNeededForTakeoff && $slow_points_dt > 180 || $slow_points_dt > 300) { $foundNewTrack = 1; DEBUG("IGC", 1, "[{$points}] {$line}<br>"); DEBUG("IGC", 1, "[{$points}] Found a new track /landing <br>"); } } // sanity checks if ($deltaseconds == 0 && !$garminSpecialCase) { continue; } if ($alt > $this->maxAllowedHeight) { continue; } if (abs($speed) > $this->maxAllowedSpeed) { continue; } if (abs($vario) > $this->maxAllowedVario) { continue; } $takeoffDistance = $lastPoint->calcDistance($firstPoint); if ($takeoffDistance > $this->LINEAR_DISTANCE) { $this->LINEAR_DISTANCE = $takeoffDistance; } if ($time_diff2 > 10) { $tmp = $lastPoint->calcDistance($prevPoint2); $alt = $lastPoint->getAlt(); $deltaseconds = $time_diff2; // GUS begin $speedDeltaSecs = $deltaseconds + ($deltaseconds < -85000 ? 86400 : 0); $speed = $speedDeltaSecs ? $tmp * 3.6 / $speedDeltaSecs : 0.0; /* in km/h */ if ($speedDeltaSecs) { $vario = ($alt - $prevPoint2->getAlt()) / $speedDeltaSecs; } // GUS end $prevPoint2 = new gpsPoint($line, $this->timezone); $prevPoint2->gpsTime += $day_offset; // update maximum speed if ($speed > $this->MAX_SPEED) { $this->MAX_SPEED = $speed; } $this->MEAN_SPEED += $speed; $MEAN_SPEED_POINTS++; // UPDATE MIN-MAX VARIO if ($vario > $this->MAX_VARIO) { $this->MAX_VARIO = $vario; } if ($vario < $this->MIN_VARIO) { $this->MIN_VARIO = $vario; } } //$speed=$data_speed[$iii-1]+0; // $vario=$data_vario[$iii-1]+0; // UPDATE MIN-MAX ALT if ($alt > $this->MAX_ALT) { $this->MAX_ALT = $alt; } if ($alt < $this->MIN_ALT) { $this->MIN_ALT = $alt; } // end computing $prevPoint = new gpsPoint($line, $this->timezone); $prevPoint->gpsTime += $day_offset; if ($mod >= 1) { if ($reduceArray[$points % $mod] == 0) { $outputLine = ""; } } } $points++; } } } } // end else $outputBuffer .= $outputLine; } // end main loop // echo "<HR>MIN VARIO".$this->MIN_VARIO."<HR>"; // if ($stillOnGround && $this->LINEAR_DISTANCE < 50) { DEBUG("IGC", 1, "NO TAKEOFF FOUND: "); return 0; // no valid points found } $path_igc = dirname($this->getIGCFilename(1)); if (!is_dir($path_igc)) { makeDir($path_igc, 0755); } /*write the full saned file */ $fullSanedFile = ''; foreach ($lines as $line) { $line = trim($line); if (strlen($line) == 0) { continue; } if ($line[0] == 'B' && $line[1] == 'X') { continue; } // MARKED BAD from BEFORE // if ( strlen($line) < 23 || strlen($line) > 100 ) continue; $fullSanedFile .= $line . "\n"; } if (!writeFile($this->getIGCFilename(2), $fullSanedFile)) { echo "Problem writing to file (" . $this->getIGCFilename(2) . ")"; } DEBUG("IGC", 1, "<HR>" . $this->getIGCFilename(2) . ' size: ' . strlen($fullSanedFile) . "<HR>"); // echo "<HR><HR>". $this->getIGCFilename(2) .strlen($fullSanedFile)."<HR><HR>"; /* done wrting the full saned file */ // write saned IGC file if (!writeFile($this->getIGCFilename(1), $outputBuffer)) { echo "Problem writing to file (" . $this->getIGCFilename(1) . ")"; } // done write saned IGC file DEBUG("IGC", 1, "<HR>" . $this->getIGCFilename(1) . ' size: ' . strlen($outputBuffer) . "<HR>"); if ($lastPoint) { $this->lastPointTM = $lastPoint->gpsTime; $this->lastLon = $lastPoint->lon(); $this->lastLat = $lastPoint->lat(); $this->LANDING_ALT = $lastPoint->getAlt(); $this->END_TIME = $lastPoint->getTime(); } else { $this->lastPointTM = 0; $this->lastLon = 0; $this->lastLat = 0; $this->LANDING_ALT = 0; $this->END_TIME = 0; } $this->DURATION = $this->END_TIME - $this->START_TIME; if ($this->DURATION < 0) { $this->DURATION += 86400; } $this->MEAN_SPEED = $this->MEAN_SPEED / $MEAN_SPEED_POINTS; return 1; }
function getTZ($lat, $lon, $theDate) { global $db, $waypointsTable; global $CONF_use_date_for_TZ_detection; // fall back to simple lon method in case of old php (4.4.1) if (!$CONF_use_date_for_TZ_detection) { return getUTMtimeOffset($lat, $lon, $theDate); } $query = "SELECT lat,lon,ABS({$lat}-lat) as dlat , ABS({$lon}- lon ) as dlon ,countryCode from {$waypointsTable} \n\t\t\t\tWHERE ABS({$lat}-lat) < 1 AND ABS({$lon}- lon ) < 1\n\t\t\t\tORDER BY dlat,dlon ASC"; DEBUG('getTZ', 128, "getTZ: {$query}<BR>"); $res = $db->sql_query($query); if ($res <= 0) { DEBUG('getTZ', 128, "getTZ: no waypont near by will try rough method<BR>"); return getUTMtimeOffset($lat, $lon, $theDate); } $i = 0; $minTakeoffDistance = 1000000; while ($row = mysql_fetch_assoc($res)) { $i++; $this_distance = gpsPoint::calc_distance($row["lat"], $row["lon"], $lat, $lon); DEBUG('getTZ', 128, "getTZ: " . $row["lat"] . " , " . $row["lon"] . " country-> " . $row["countryCode"] . " distance-> {$this_distance} <BR>"); if ($this_distance < $minTakeoffDistance) { $minTakeoffDistance = $this_distance; $countryCode = $row["countryCode"]; } } if (!$i) { DEBUG('getTZ', 128, "getTZ: No waypont near by #2. Will try rough method<BR>"); return getUTMtimeOffset($lat, $lon, $theDate); } DEBUG('getTZ', 128, "getTZ: Min dist: {$minTakeoffDistance} , country: {$countryCode} <BR>"); if ($minTakeoffDistance > 50000) { DEBUG('getTZ', 128, "getTZ: Nearest waypoint is too far. Will try rough method<BR>"); return getUTMtimeOffset($lat, $lon, $theDate); } // now we will try the getTZoffset() // make $tm from YYYY-MM-DD $tm = gmmktime(1, 0, 0, substr($theDate, 5, 2), substr($theDate, 8, 2), substr($theDate, 0, 4)); // this will return good results only for countries that have ONE timezone // else '' will be returned require_once dirname(__FILE__) . '/FN_timezones.php'; // $TZone=getTZforCountry($countryCode); global $Countries2timeZones; $TZone = $Countries2timeZones[strtoupper($countryCode)]; if (strtoupper($countryCode) == 'AU') { DEBUG('getTZ', 128, "getTZ: Australia timezones<BR>"); /* australia http://www.statoids.com/tau.html central - west is on 129E central - east is on lon > 141E for lat < -26 (S) lon > 138E for lat > -26 (S) east - > Australia/Sydney west central */ if ($lon > -129) { $TZone = 'Australia/Perth'; } else { if ($lon > -138 && $lat > -26) { $TZone = 'Australia/Darwin'; } else { if ($lon > -141 && $lat < -26) { $TZone = 'Australia/Adelaide'; } else { if ($lat > -29) { $TZone = 'Australia/Brisbane'; } else { $TZone = 'Australia/Sydney'; } } } } // South-East } if ($TZone == '') { DEBUG('getTZ', 128, "getTZ: Country {$countryCode} has more than one timezones.. Back to rough method<BR>"); return getUTMtimeOffset($lat, $lon, $theDate); } return getTZoffset($TZone, $tm) / 3600; }