Skip to content

Instantly share code, notes, and snippets.

@ridercz
Last active February 15, 2021 13:09
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ridercz/66f22ce86d082f059d26cde05ac69f87 to your computer and use it in GitHub Desktop.
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.
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