/ CPP_Modules / CPP_Module_09 / ex00 / BitcoinExchange.cpp
BitcoinExchange.cpp
  1  /* ************************************************************************** */
  2  /*                                                                            */
  3  /*                                                        :::      ::::::::   */
  4  /*   BitcoinExchange.cpp                                :+:      :+:    :+:   */
  5  /*                                                    +:+ +:+         +:+     */
  6  /*   By: gychoi <gychoi@student.42seoul.kr>         +#+  +:+       +#+        */
  7  /*                                                +#+#+#+#+#+   +#+           */
  8  /*   Created: 2024/01/12 18:13:09 by gychoi            #+#    #+#             */
  9  /*   Updated: 2024/01/14 23:06:33 by gychoi           ###   ########.fr       */
 10  /*                                                                            */
 11  /* ************************************************************************** */
 12  
 13  #include "BitcoinExchange.hpp"
 14  
 15  std::map<std::time_t, double>	BitcoinExchange::sDataMap;
 16  
 17  static bool			_isValidDateFormat(std::string const& date);
 18  static bool			_isValidDouble(std::string const& s);
 19  static bool			_isNumber(std::string const& s);
 20  static bool			_isLeapYear(int year);
 21  static int			_stoi(std::string const& s);
 22  static double		_stod(std::string const& s);
 23  static std::string	_trim(std::string const& s);
 24  
 25  /* ************************************************************************** */
 26  /*                          Constructor & Destructor                          */
 27  /* ************************************************************************** */
 28  
 29  BitcoinExchange::BitcoinExchange()
 30  {
 31  	// nothing to do
 32  }
 33  
 34  BitcoinExchange::BitcoinExchange
 35  (__attribute__((unused)) BitcoinExchange const& other)
 36  {
 37  	// nothing to do
 38  }
 39  
 40  BitcoinExchange&	BitcoinExchange::operator=
 41  (__attribute__((unused)) BitcoinExchange const& other)
 42  {
 43  	return *this;
 44  }
 45  
 46  BitcoinExchange::~BitcoinExchange()
 47  {
 48  	// nothig to do
 49  }
 50  
 51  /* ************************************************************************** */
 52  /*                           Public Member Function                           */
 53  /* ************************************************************************** */
 54  
 55  void	BitcoinExchange::setExchangeMap(char const* path)
 56  {
 57  	std::ifstream	dbFile(path);
 58  	
 59  	if (!dbFile.is_open())
 60  	{
 61  		throw std::runtime_error("could not open DB.");
 62  	}
 63  	else
 64  	{
 65  		std::string	header;
 66  
 67  		if (!std::getline(dbFile, header))
 68  		{
 69  			throw std::runtime_error("Unable to read DB header.");
 70  		}
 71  		else if (header != "date,exchange_rate")
 72  		{
 73  			throw std::runtime_error("Malformed csv header.");
 74  		}
 75  		else
 76  		{
 77  			// header OK
 78  		}
 79  	}
 80  
 81  	std::string	line;
 82  
 83  	while (std::getline(dbFile, line))
 84  	{
 85  		std::istringstream	iss(line);
 86  		std::string			date;
 87  		std::string			exchangeRate;
 88  		std::string			remain;
 89  
 90  		if (dbFile.fail())
 91  		{
 92  			throw std::runtime_error("Failed to read DB file.");
 93  		}
 94  		else if (line.empty())
 95  		{
 96  			continue;
 97  		}
 98  		else if (std::getline(iss, date, ','))
 99  		{
100  			if (!_isValidDateFormat(date))
101  			{
102  				throw std::runtime_error(std::string("bad DB input => ")
103  										 + date);
104  			}
105  			else if (!(iss >> exchangeRate))
106  			{
107  				throw std::runtime_error("Unable to read exchange rate in DB.");
108  			}
109  			else if (!_isValidDouble(exchangeRate))
110  			{
111  				throw std::runtime_error(std::string("bad DB input => ")
112  										 + exchangeRate);
113  			}
114  			else if (std::getline(iss, remain))
115  			{
116  				throw std::runtime_error(std::string("bad DB input => ")
117  										 + line);
118  			}
119  			else
120  			{
121  				// data format OK
122  			}
123  
124  			double	rate = _stod(exchangeRate);
125  
126  			if (rate < 0)
127  			{
128  				throw std::runtime_error("not a positive number in DB.");
129  			}
130  			else if (rate > std::numeric_limits<int>::max())
131  			{
132  				throw std::runtime_error("too large a number in DB.");
133  			}
134  			else
135  			{
136  				std::tm		tm = {};
137  				std::time_t	epochTime;
138  
139  				tm.tm_year = _stoi(date.substr(0, 4)) - 1900;
140  				tm.tm_mon = _stoi(date.substr(5, 2)) - 1;
141  				tm.tm_mday = _stoi(date.substr(8));
142  
143  				if ((epochTime = std::mktime(&tm)) == -1)
144  				{
145  					throw std::runtime_error("Failed to convert epoch time in DB.");
146  				}
147  				else
148  				{
149  					sDataMap.insert(std::make_pair(epochTime, rate));
150  				}
151  			}
152  		}
153  		else
154  		{
155  			throw std::runtime_error("Unable to read DB data.");
156  		}
157  	}
158  }
159  
160  void	BitcoinExchange::execute(char const* path)
161  {
162  	std::ifstream	inputFile(path);
163  
164  	if (!inputFile.is_open())
165  	{
166  		throw std::runtime_error("could not open file.");
167  	}
168  	else
169  	{
170  		std::string	header;
171  
172  		if (!std::getline(inputFile, header))
173  		{
174  			throw std::runtime_error("Unable to read input file header.");
175  		}
176  		else if (header != "date | value")
177  		{
178  			throw std::runtime_error("Malformed input file header.");
179  		}
180  		else
181  		{
182  			// header OK
183  		}
184  	}
185  
186  	std::string	line;
187  
188  	while (std::getline(inputFile, line))
189  	{
190  		std::istringstream	iss(line);
191  		std::string			date;
192  		std::string			exchangeRate;
193  		std::string			remain;
194  
195  		if (inputFile.fail())
196  		{
197  			throw std::runtime_error("Failed to read input file.");
198  		}
199  		else if (line.empty())
200  		{
201  			continue;
202  		}
203  		else if (std::getline(iss, date, '|'))
204  		{
205  			date = _trim(date);
206  
207  			if (!_isValidDateFormat(date))
208  			{
209  				std::cout << "Error: bad input => " << date << std::endl;
210  				continue;
211  			}
212  			else if (!(iss >> exchangeRate))
213  			{
214  				std::cout << "Error: Unable to read exchange rate."
215  						  << std::endl;
216  				continue;
217  			}
218  			else if (!_isValidDouble(exchangeRate))
219  			{
220  				std::cout << "Error: bad input => " << exchangeRate
221  						  << std::endl;
222  				continue;
223  			}
224  			else if (std::getline(iss, remain))
225  			{
226  				std::cout << "Error: bad input => " << line << std::endl;
227  				continue;
228  			}
229  			else
230  			{
231  				// data format OK
232  			}
233  
234  			double	rate = _stod(exchangeRate);
235  
236  			if (rate < 0)
237  			{
238  				std::cout << "Error: not a positive number." << std::endl;
239  				continue;
240  			}
241  			else if (rate > 1000)
242  			{
243  				std::cout << "Error: too large a number." << std::endl;
244  				continue;
245  			}
246  			else
247  			{
248  				std::tm		tm = {};
249  				std::time_t	epochTime;
250  
251  				tm.tm_year = _stoi(date.substr(0, 4)) - 1900;
252  				tm.tm_mon = _stoi(date.substr(5, 2)) - 1;
253  				tm.tm_mday = _stoi(date.substr(8));
254  
255  				if ((epochTime = std::mktime(&tm)) == -1)
256  				{
257  					throw std::runtime_error("Failed to convert epoch time.");
258  				}
259  				else
260  				{
261  					std::map<std::time_t, double>::const_iterator
262  									  mit = sDataMap.lower_bound(epochTime);
263  
264  					if (mit == sDataMap.begin() && epochTime < mit->first)
265  					{
266  						std::cout << "Error: data not found." << std::endl;
267  						continue;
268  					}
269  					else if (mit == sDataMap.end() || epochTime != mit->first)
270  					{
271  						--mit;
272  					}
273  					else
274  					{
275  						// found exact exchange date
276  					}
277  
278  					std::cout << date << " => " << rate << " = "
279  							  << rate * mit->second << std::endl;
280  				}
281  			}
282  		}
283  		else
284  		{
285  			throw std::runtime_error("Unable to read input file data.");
286  		}
287  	}
288  }
289  
290  /* ************************************************************************** */
291  /*                              Utility Function                              */
292  /* ************************************************************************** */
293  
294  static bool	_isValidDateFormat(std::string const& date)
295  {
296  	int	year;
297  	int	month;
298  	int	day;
299  
300  	/* Check date format(YYYY-MM-DD) and parse into int */
301  	if (date.size() != 10 || date[4] != '-' || date[7] != '-')
302  	{
303  		return false;
304  	}
305  	else
306  	{
307  		std::string	sYear = date.substr(0, 4);
308  		std::string	sMonth = date.substr(5, 2);
309  		std::string	sDay = date.substr(8);
310  
311  		if (!_isNumber(sYear) || !_isNumber(sMonth) || !_isNumber(sDay))
312  		{
313  			return false;
314  		}
315  		else
316  		{
317  			year = _stoi(sYear);
318  			month = _stoi(sMonth);
319  			day = _stoi(sDay);
320  		}
321  	}
322  
323  	/* check date has valid range */
324  	if (year < 1900 || year > 9999)
325  	{
326  		return false;
327  	}
328  	else if (month < 1 || month > 12)
329  	{
330  		return false;
331  	}
332  	else if (day < 1 || day > 31)
333  	{
334  		return false;
335  	}
336  
337  	/* check date has valid day relate to month */
338  	switch (month)
339  	{
340  		case 2:
341  		{
342  			if (day == 29 && !_isLeapYear(year))
343  			{
344  				return false;
345  			}
346  			else if (day > 29)
347  			{
348  				return false;
349  			}
350  			else
351  			{
352  				break;
353  			}
354  		}
355  		case 4:
356  		case 6:
357  		case 9:
358  		case 11:
359  		{
360  			if (day > 30)
361  			{
362  				return false;
363  			}
364  			else
365  			{
366  				break;
367  			}
368  		}
369  		default:
370  		{
371  			if (day > 31)
372  			{
373  				return false;
374  			}
375  			else
376  			{
377  				break;
378  			}
379  		}
380  	}
381  
382  	return true;
383  }
384  
385  static bool	_isValidDouble(std::string const& s)
386  {
387  	char*	endptr;
388  
389  	std::strtod(s.c_str(), &endptr);
390  
391  	return *endptr == '\0';
392  }
393  
394  static bool	_isNumber(std::string const& s)
395  {
396  	for (std::string::const_iterator cit = s.begin(); cit != s.end(); ++cit)
397  	{
398  		if (!std::isdigit(*cit))
399  		{
400  			return false;
401  		}
402  	}
403  
404  	return true;
405  }
406  
407  static bool	_isLeapYear(int year)
408  {
409  	return (year % 4 == 0 && year % 100 != 0)
410  			|| (year % 100 == 0 && year % 400 == 0);
411  }
412  
413  static int	_stoi(std::string const& s)
414  {
415  	std::istringstream	iss(s);
416  	int					ret;
417  
418  	if (!(iss >> ret))
419  	{
420  		throw std::runtime_error("input stream error.");
421  	}
422  
423  	return ret;
424  }
425  
426  static double	_stod(std::string const& s)
427  {
428  	std::istringstream	iss(s);
429  	double				ret;
430  
431  	if (!(iss >> ret))
432  	{
433  		throw std::runtime_error("input stream error.");
434  	}
435  
436  	return ret;
437  }
438  
439  static std::string	_trim(std::string const& s)
440  {
441  	std::string	ret;
442  
443  	for (std::string::const_iterator cit = s.begin(); cit != s.end(); ++cit)
444  	{
445  		if (!std::isspace(*cit))
446  		{
447  			ret += *cit;
448  		}
449  	}
450  
451  	return ret;
452  }
453