Last active
February 15, 2021 13:09
-
-
Save ridercz/66f22ce86d082f059d26cde05ac69f87 to your computer and use it in GitHub Desktop.
Class to process exchange rates of Czech National Bank (CNB), including historic data and sample console application.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Collections.ObjectModel; | |
using System.Globalization; | |
using System.Net; | |
using System.Threading.Tasks; | |
namespace Altairis.KurzyCnb { | |
class Program { | |
static void Main(string[] args) { | |
// Get exchange rate | |
CnbExchangeRateList erl; | |
try { | |
Console.Write("Downloading exchange rates from CNB..."); | |
erl = CnbClient.GetRates(); | |
Console.WriteLine("OK"); | |
} | |
catch (Exception ex) { | |
Console.WriteLine("Failed!"); | |
Console.WriteLine(ex); | |
return; | |
} | |
// Print rates | |
Console.WriteLine(); | |
Console.WriteLine(" | Country | Currency | Amount | Rate"); | |
Console.WriteLine(new string('-', 79)); | |
foreach (var er in erl) { | |
Console.WriteLine($"{er.Code} | {er.Country,-20} | {er.Currency,-10} | {er.Amount,8} | {er.Rate,10:N2}"); | |
} | |
Console.WriteLine(new string('-', 79)); | |
Console.WriteLine(); | |
} | |
} | |
public static class CnbClient { | |
private const string API_URL_FORMAT = @"https://www.cnb.cz/cs/financni_trhy/devizovy_trh/kurzy_devizoveho_trhu/denni_kurz.txt?date={0:dd\.MM\.yyyy}"; | |
public async static Task<CnbExchangeRateList> GetRatesAsync(DateTime date) { | |
// Download content from CNB | |
using (var wc = new WebClient()) { | |
wc.Encoding = System.Text.Encoding.UTF8; | |
var url = string.Format(API_URL_FORMAT, date); | |
var dataString = await wc.DownloadStringTaskAsync(url); | |
return CnbExchangeRateList.Parse(dataString); | |
} | |
} | |
public async static Task<CnbExchangeRateList> GetRatesAsync() { | |
return await GetRatesAsync(DateTime.Today); | |
} | |
public static CnbExchangeRateList GetRates(DateTime date) { | |
return GetRatesAsync(date).Result; | |
} | |
public static CnbExchangeRateList GetRates() { | |
return GetRatesAsync().Result; | |
} | |
} | |
public class CnbExchangeRateList : ReadOnlyCollection<CnbExchangeRate> { | |
public DateTime Date { get; private set; } | |
private CnbExchangeRateList(IList<CnbExchangeRate> list, DateTime date) : base(list) { | |
this.Date = date; | |
} | |
internal static CnbExchangeRateList Parse(string s) { | |
if (s == null) throw new ArgumentNullException(nameof(s)); | |
if (string.IsNullOrWhiteSpace(s)) throw new ArgumentException("Value cannot be empty or whitespace only string.", nameof(s)); | |
// Split data files to lines | |
var lines = s.Split('\n'); | |
if (lines.Length < 3) throw new FormatException($"Invalid data file format - too few lines. Expected at least 3, got {lines.Length}."); | |
// Process lines | |
var erl = new List<CnbExchangeRate>(); | |
var date = DateTime.MinValue; | |
for (int i = 0; i < lines.Length; i++) { | |
if (i == 0) { | |
// First line - date | |
var dateString = lines[0].Substring(0, 10); | |
var result = DateTime.TryParseExact(dateString, @"dd\.MM\.yyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, out date); | |
if (!result) throw new FormatException($"Invalid file format - Error on line 1: {dateString} cannot be parsed as date."); | |
} | |
else if (i == 1) { | |
// Second line - ignore (headers) | |
continue; | |
} | |
else { | |
// Other lines - parse | |
CnbExchangeRate er; | |
try { | |
er = CnbExchangeRate.Parse(lines[i]); | |
} | |
catch (Exception e) { | |
throw new FormatException($"Invalid file format - Error on line {i + 1}: \"{lines[i]}\" cannot be parsed as exchange rate.", e); | |
} | |
if (er == null) break; | |
erl.Add(er); | |
} | |
} | |
return new CnbExchangeRateList(erl, date); | |
} | |
} | |
public class CnbExchangeRate { | |
internal static CnbExchangeRate Parse(string s) { | |
if (string.IsNullOrWhiteSpace(s)) return null; | |
var data = s.Split('|'); | |
if (data.Length != 5) throw new FormatException($"Expected 5 segments, got {data.Length}."); | |
return new CnbExchangeRate { | |
Country = data[0], | |
Currency = data[1], | |
Amount = int.Parse(data[2]), | |
Code = data[3], | |
Rate = decimal.Parse(data[4], CultureInfo.GetCultureInfo("cs-CZ")) | |
}; | |
} | |
public string Country { get; set; } | |
public string Currency { get; set; } | |
public int Amount { get; set; } | |
public string Code { get; set; } | |
public decimal Rate { get; set; } | |
public decimal FromCzk(decimal amount = 1) { | |
return amount / this.Rate * this.Amount; | |
} | |
public decimal ToCzk(decimal amount = 1) { | |
return amount * this.Rate / this.Amount; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment