I have been trying, for some time, to find a published API from a reputable source to retrieve currency pairs for a given date. i have found some that are chargeable, but none that are freeware.
yahoo’s finance site is the closest that I can find. i don’t know whether they object to the kind of use that I propose with this piece of code, but i have run a fresh install of this a number of times and they have not blocked me!
this is also a perfect use for sqlite in lieu of a heavier weight rdbs like oracle or mysql. I am using this script in a WordPress plugin and even though the install uses mysql rather than sqlite, i am still using sqlite for the currency exchange. the rates returned, I believe, are the average bank rates for the relevant day.
and now for the code. comments and improvement suggestions are welcomed. In particular the error handling is far from graceful…
<?php /** * class to retrieve exchange rate pairs and calculate a conversion * * use as per this example * $xR = new exchangeRates(); $xR ->setFormat(2,'.', ','); //optional $xR->convert('GBP', 'EUR', '100', null, true); * see the methods for more information on the API */ class exchangeRates{ private $fields = array('from', 'to', 'date', 'amount'); private $table = 'currency'; private $dbFile = './currencyExchangeRates2.sqlite'; private $firstSupportedDay = 1167606000 ; //1st Jan 2007 /** * constructor function * @return void */ public function __construct(){ if (!file_exists($this->dbFile)){ $this->pdo = new PDO ("sqlite:{$this->dbFile}"); $this->init(); } else { $this->pdo = new PDO ("sqlite:{$this->dbFile}"); } $this->today = strtotime('today'); if (empty($this->date)){ $this->date = $this->today; } $this->addRates(); if (isset($this->statement)) $this->statement= null; } /** * main API for the conversion * @return a [formatted] string for the currency conversion * @param string $from the ISO code of the currency you want to convert from * @param string $to the ISO code of the currency you want to convert tp * @param float $amount[optional] the amount of the from currency you wish to convert. defaults to 1 * @param int $date[optional] the day for the conversion expressed as a unix time stamp. defaults to today's date. M * @param bool $format[optional] whether to format the returned information. defaults to false (unformatted */ public function convert($from, $to, $amount =1 , $date=null, $format=false){ global $wpdb; if(empty($date)){ $date = $this->today; } foreach (array($from, $to) as $symbol){ $sql = "Select xDate as xDate from $this->table where symbol=? and xDate <= ? order by xDate desc limit 5"; $statement = $this->pdo->prepare($sql); if ($statement === false){ die (print_r($this->pdo->errorinfo(), true)); } $result = $statement->execute(array($symbol, $date)); if ($result === false){ die (print_r($result->errorinfo(), true)); } while ($row = $statement->fetchObject()){ $results[$symbol][] = $row->xDate; } } $dates = array_intersect($results[$from], $results[$to]); if (count($dates) == 0){ die ('We do not have a currency exchange rate pair listed for the requested day nor for any day close thereto'); } //will be the first that has the latest date $sql = "select rate, symbol from $this->table where symbol in (?, ?) and xDate=?"; $statement = $this->pdo->prepare($sql); $statement->execute(array($to, $from, $dates[0])); $results = $statement->fetchAll(PDO::FETCH_OBJ); //convert to USD foreach($results as $pair){ if ($pair->symbol == $from){ $this->fromRate = $pair->rate; } else { $this->toRate = $pair->rate; } } $usd = $amount / $this->fromRate; $conversion = $usd * $this->toRate; if ($format) { return $this->getSymbol($to) . $this->formatted($conversion); } else { return $conversion; } } /** * dummy function to return the usual currency symbol for an ISO code * @return * @param object $iso */ private function getSymbol($iso){ //will perform lookup of currency symbols. for the time being return the ISO symbol return $iso; } /** * helper method to format the converted rate if required. * @return * @param object $number */ private function formatted ($number){ return number_format($number, $this->decPlaces, $this->decSeparator, $this->kSeparator); } /** * method to set the required information for the formatted method. * @return void * @param integer $decPlaces[optional] defaults to 2 * @param string $decSeparator[optional] defaults to . * @param string $kSeparator[optional] defaults to , */ public function setFormat($decPlaces=2, $decSeparator='.', $kSeparator=','){ list($this->decPlaces, $this->decSeparator, $this->kSeparator) = func_get_args(); } /** * method to ratched the day property by one day * @return void */ private function incrementDay(){ $this->day = strtotime('+1 day', $this->day); } /** * method to determine whether we need to get more data. if we do, go and get it * @return void */ private function addRates(){ global $wpdb; $query = "select max(xdate) as mD from {$this->table}"; $s = $this->pdo->query($query); $obj = $s->fetchObject(); $day = $obj->mD; unset($s); if ($day < $this->firstSupportedDay){ $day = $this->firstSupportedDay; } $this->day = $day; if ($this->day < $this->today){ while ($this->day <= $this->today){ if ($this->day !== $this->firstSupportedDay){ //increment the day to avoid duplication $this->incrementDay(); } set_time_limit(30); $this->getDailyRates($this->day); $this->incrementDay(); } if (!empty($this->parser)){ xml_parser_free($this->parser); unset($this->parser); } } } /** * method to obtain a day's currency pairs from the yahoo api */ private function getDailyRates($day){ $date = date('Ymd', $day); $url = "http://finance.yahoo.com/webservice/v1/symbols/allcurrencies/quote;date=$date;currency=true?view=basic&format=xml&callback=currencyConverter.addConversionRates"; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_HEADER, false); curl_setopt($ch, CURLOPT_FRESH_CONNECT, true); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30); $response = curl_exec($ch); $tdb = $this->getRatePairs($response); $this->writeDB($tdb, $day); } /** * helper method to set up the insert statement for optimised db handling * @return */ private function prepareInsertStatement(){ $sql = "insert into $this->table (xdate, rate, symbol) values (?, ?, ?)"; $statement = $this->pdo->prepare($sql); if ($statement === false){ die (print_r($this->pdo->errorinfo(), true)); } $this->insertStatement = $statement; } /** * method to write the currency data to the database * * @return void * @param object $obj an object holding the currency rate data * @param int $date - unix date of the currency rate data */ private function writeDB($obj, $date){ if (empty($obj->symbol)) return; if (empty($this->insertStatement )){ $this->prepareInsertStatement(); } $result = $this->insertStatement->execute(array($date, $obj->price, $obj->symbol)); if ($result === false){ die (print_r($this->insertStatement->errorInfo(), true)); } } /** * method to parse the incoming data from yahoo api * * @return void * @param string $xml */ private function getRatePairs($xml){ if (empty($this->parser)){ $this->parser = xml_parser_create(); xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, 0); xml_parser_set_option($this->parser, XML_OPTION_SKIP_WHITE, 1); } xml_parse_into_struct($this->parser, $xml, $values, $tags); // loop through the structures foreach ($tags as $key=>$val) { if ($key == "resource") { $dataRanges = $val; for ($i=0; $i < count($dataRanges); $i+=2) { $offset = $dataRanges[$i] + 1; $len = $dataRanges[$i + 1] - $offset; $obj = $this->parseFields(array_slice($values, $offset, $len)); $this->writeDB($obj, $this->day); } } else { continue; } } } /** * method to parse individual data sets from the xml field * @return object * @param array $fields */ private function parseFields($fields) { $d = array('symbol', 'price'); $obj = new stdClass(); foreach ($fields as $r){ //$item = array(); if ($r['type'] == 'complete'){ if (in_array($r['attributes']['name'], $d)){ $obj->$r['attributes']['name'] = $r['value']; } } } if (isset($obj->symbol)){ $obj->symbol = str_replace('=X', '', $obj->symbol); } return $obj; } /** * method to create the necessary tables * @return */ private function init(){ $query[] = <<<sql CREATE TABLE if not exists $this->table ( symbol text , rate float , xdate int, PRIMARY KEY (symbol, xdate) on conflict REPLACE) SQL; foreach ($query as $q) { $result = $this->pdo->exec($q); if ($result === false){ die (print_r($this->pdo->errorinfo(), true)); } } } } ?> |