Skip to content

Instantly share code, notes, and snippets.

@ridercz
Last active Feb 15, 2021
Embed
What would you like to do?
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