Quantcast
Channel: Rather Curious ... » Sqlite
Viewing all articles
Browse latest Browse all 10

Currency Exchange Class – multiple currencies and historic dates

$
0
0

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));
			}
		}
	}
}
?>

Viewing all articles
Browse latest Browse all 10

Trending Articles