Compare commits

..
19 Commits
Author SHA1 Message Date
dodox ecb387a467 ECB chnaged their API without telling anyone... (Ok, its an internal API, but I still dont like it!) 2024-01-14 16:58:36 +01:00
dodox 89636040c6 Merge branch 'master' of dominik.roth.ml:dodox/meuro 2021-10-30 18:21:33 +02:00
dodox 201c112184 Fixed README typo 2021-10-30 18:21:09 +02:00
dodox eb4aad68fc Fixed README typo 2021-10-30 18:18:55 +02:00
dodox 178f1a169d README++ 2021-10-30 18:18:22 +02:00
dodox 9ad1111055 Removed uneccesary code and more comments 2021-10-30 18:12:53 +02:00
dodox 87789e8500 README: Better description of the cache 2021-10-30 18:01:50 +02:00
dodox 69c6ee4cd0 Prettier code 2021-10-30 17:56:30 +02:00
dodox 59bab52160 Better README 2021-10-30 17:52:53 +02:00
dodox 8147f64b4b Updated README 2021-10-30 17:52:13 +02:00
dodox 338e9af870 Changed Heading-sizes in README 2021-10-30 17:46:34 +02:00
dodox 32a15c8353 Better printing: Now with , for every 3-group 2021-10-30 17:39:56 +02:00
dodox d87d260a1c Bug Fix: Exchange-Rate for future years was wrong 2021-10-30 17:37:27 +02:00
dodox 2de965414a Better icon 2021-10-30 14:43:16 +02:00
dodox d95bb4e78a Added Month-Name example to README 2021-10-30 14:36:32 +02:00
dodox afeee78836 Fixed Bug: Currencly removal from string could destroy date 2021-10-30 14:35:03 +02:00
dodox 58cf8809ad Describing the cache in the README 2021-10-30 14:24:52 +02:00
dodox 872df67776 Changed cache workings to allow better controll when used as lib 2021-10-30 14:19:50 +02:00
dodox 237940861f Included a cache 2021-10-30 14:18:14 +02:00
4 changed files with 87 additions and 60 deletions
+1
View File
@@ -1 +1,2 @@
__pycache__
cache.json
+17 -12
View File
@@ -1,6 +1,6 @@
# Meuro
![Logo Missing](./Meuro.svg)
![Logo Missing](./icon_meuro.svg)
Prices in euros from different times can not be compared without taking inflation into account.
This also negatively impacts peoples abilities to gauge their own financial prosperity because what they conceive as a raise might not even cover they loss in purchase-power due to inflation.
@@ -10,21 +10,22 @@ To fix this issue, I present:
Meuro's worth is equivalent to that of the euro on January 1st 2001 00:00 GMT.
(Yes, thats 2001 and not 2000 because the first millenium started in the year 1 and so the third one started in 2001. I do realize that this is stupid, but that's not my fault and all the people that celebrated the beginning of the new century at silvester 2000 should technically have waited another year.)
## This repo
What you find in this repo is a small python-libary that connects to the ecb to get historic inflation-data and computes the conversion-factor between € and µ for any given date (after Jan 1st 2001).
What you find in this repo is a small python-libary that connects to the ecb to get historic inflation-data and computes the conversion-factor between € and µ for any given date after Jan 1st 2001 (cli-interface only works before Jan 1st 10000).
## Usage
You probably want to use this tool using the cli-interface which tries to understand requests in human language.
To reference the currencies use €, eur, euro, e, E, or µ, meu, meuro, m, M;
Currencies should be an integer or have 2 digits after the point (.).
Dates should always be dd.mm.YYYY or dd.mm.yy
If no date is supplied (or understood) the current date is used.
Examples:
### Examples:
#### ./meuro.py What is 200 eur in meuro
#### ./meuro.py 120.50 meur at 12.12.2012
#### ./meuro.py 50,000€ 10.10.2010
#### ./meuro.py I want to know how much 2000€ from 11.11.2011 are in meuro
#### ./meuro.py How much meuro are 12 euro from January 2010
#### ./meuro.py 50,000€ 10.10.2010
#### ./meuro.py 1000 meur 2055
#### ./meuro.py 3.14E 01.02.2003 12:34
#### ./meuro.py How much meuro are 12 euro
### Python-API
### For best Results:
To reference the currencies use €, eur, euro, e, E or µ, meu, meur, meuro, m, M.
Currencies should be an integer or have 2 digits after the point (.).
Dates should always be dd.mm.YYYY or dd.mm.yy.
If no date is supplied (or understood) the current date is used.
## Python-API
You can also import this libary from another python file
##### The provided functions are:
#### exchangeRate(date=None)
@@ -34,8 +35,12 @@ Converts the given amount of euros to meuros for the given date. (wholeCents mea
#### meuroToEuro(meur, date=None, wholeCents=True)
Converts the given amount of meuros to euros for the given date. (wholeCents means it rounds to two decimal places)
#### liveValue(eur, interval=10)
Print the current amount of meuros for the given amunt of euros every interval-seconds. Comes in handy when you want to watch your life savings slowly fade away thanks to inflation.
Print the current amount of meuros for the given amount of euros every interval-seconds. Comes in handy when you want to watch your life savings slowly fade away thanks to inflation.
##### When the date is set to None the current date is used. Otherwise a datetime-object is expected
### Cache
Because we dont want to annoy the ecb, we cache their data while we are running and for up to an hour on disc.
#### reload(maxCacheSeconds=3600)
Will reload the cache if it is older than maxCacheSeconds. Gets called once in the background the first time you call any other function.
## Caveats
The ecb only gives an estimate for the inflation of the last month and no data for the current month.
This libary just uses this estimate and assumes a yearly-inflation-rate of the current month (and all others months without data) of 2% (which is the stated goal-inflation-rate of the ecb).
+19 -19
View File
@@ -6,11 +6,11 @@
id="Ebene_1"
x="0px"
y="0px"
width="91.336815"
height="91.336815"
viewBox="0 0 91.336817 91.336815"
width="194.44644"
height="194.44644"
viewBox="0 0 194.44645 194.44644"
xml:space="preserve"
sodipodi:docname="Meuro (copy).svg"
sodipodi:docname="icon_meuro.svg"
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
@@ -28,11 +28,11 @@
inkscape:snap-object-midpoints="false"
inkscape:snap-others="true"
inkscape:snap-nodes="true"
inkscape:zoom="4.778903"
inkscape:cx="52.627141"
inkscape:cy="61.625022"
inkscape:window-width="960"
inkscape:window-height="1043"
inkscape:zoom="1.589075"
inkscape:cx="-84.325789"
inkscape:cy="19.193557"
inkscape:window-width="1251"
inkscape:window-height="991"
inkscape:window-x="26"
inkscape:window-y="23"
inkscape:window-maximized="0"
@@ -41,18 +41,18 @@
<circle
style="fill:#f6d70d;stroke:#743f15;stroke-width:5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1"
style="fill:#f6d70d;fill-opacity:1;stroke:#743f15;stroke-width:10.6445;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path191"
cx="45.668407"
cy="45.668407"
r="43.168407" /><text
cx="97.223221"
cy="97.223221"
r="91.90097" /><text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:77.7649px;line-height:1.25;font-family:sans-serif;fill:#743f15;fill-opacity:1;stroke:none;stroke-width:1.94412"
x="20.912342"
y="59.234234"
style="font-style:normal;font-weight:normal;font-size:165.553px;line-height:1.25;font-family:sans-serif;fill:#743f15;fill-opacity:1;stroke:none;stroke-width:4.13882"
x="44.520176"
y="126.10342"
id="text4082"><tspan
sodipodi:role="line"
id="tspan4080"
x="20.912342"
y="59.234234"
style="stroke-width:1.94412;fill:#743f15;fill-opacity:1">µ</tspan></text></svg>
x="44.520176"
y="126.10342"
style="fill:#743f15;fill-opacity:1;stroke-width:4.13882">µ</tspan></text></svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

+49 -28
View File
@@ -1,34 +1,21 @@
#!/usr/bin/python3
import requests
import requests, json, os.path
from datetime import datetime
from dateutil import relativedelta
from dateutil import parser as dparser
from dateutil.parser._parser import ParserError as DateParserError
from collections import defaultdict
# Ugly code thats outside of any function
url = 'https://sdw.ecb.europa.eu/quickviewexport.do?SERIES_KEY=122.ICP.M.U2.N.000000.4.ANR&type=csv'
resp = requests.get(url)
lines = resp.text.split('\n')[6:]
years = {} # Will later contain the monthly factor of inflation (~1.0016667) for every month
for line in lines:
vals = line.split(',')
year = int(vals[0][:4])
month = datetime.strptime(vals[0][4:],'%b').month
inflation = float(vals[1])
if not year in years:
years[year] = {}
years[year][month] = 1 + (inflation/100)/12
for year in years:
months = years[year]
for month in range(1,13):
if month not in months:
years[year][month] = 1 + 0.02/12 # Lets say the ECB archives their target
# Our inflation-table as a global-var
_years = None
# Gives you the exchange rate between euros and meuros for the given date.
# (Should always be a float < 1)
# date should be either a datetime-object or None (= current date)
def exchangeRate(date=None):
global _years
if _years==None:
reload()
if date==None:
date = datetime.now()
month, year = date.month, date.year
@@ -37,23 +24,24 @@ def exchangeRate(date=None):
akk = 1
for fullYear in range(2001, year):
yearlyInf = 1
for m in years[fullYear]:
monthlyInf = years[fullYear][m]
for m in _years[fullYear]:
monthlyInf = _years[fullYear][m]
yearlyInf *= monthlyInf
akk *= yearlyInf
for fullMonth in range(1, month):
monthlyInf = years[year][fullMonth]
monthlyInf = _years[year][fullMonth]
akk *= monthlyInf
beginningOfMonth = date.replace(hour=0, minute=0, second=0, microsecond=0, day=1)
endOfMonth = beginningOfMonth + relativedelta.relativedelta(months=1)
fracOfMonth = (date - beginningOfMonth).total_seconds() / (endOfMonth - beginningOfMonth).total_seconds()
inflationThisMonth = 1 + fracOfMonth * (years[year][month]-1)
inflationThisMonth = 1 + fracOfMonth * (_years[year][month]-1)
akk *= inflationThisMonth
return 1/akk
# Converts the given amount of euros to meuros for the given date. (wholeCents means it rounds to two decimal places)
# date should be either a datetime-object or None (= current date)
def euroToMeuro(eur,date=None,wholeCents=True):
@@ -78,6 +66,39 @@ def _extractDate(s):
except DateParserError:
return datetime.now()
def reload(maxCacheSeconds=3600):
global _years
if os.path.isfile('cache.json'):
with open('cache.json', 'r') as f:
cacheUpdate, cacheYears = json.loads(f.read())
if (datetime.now() - dparser.isoparse(cacheUpdate)).total_seconds() < maxCacheSeconds:
# JSON does not allow integers as keys; so we convert them back here
cacheYears = {int(y):{int(m):float(n) for m,n in ms.items()} for y,ms in cacheYears.items()}
# For every month we dont have data for, we assume the ECB nailed their 2%-target
_years = defaultdict(lambda: {m:1 + 0.02/12 for m in range(1,13)}, cacheYears)
return
_years = _loadYearsTableWeb()
def _loadYearsTableWeb():
print('[i] Fetching new data from ECB-Servers...')
#url = 'https://sdw.ecb.europa.eu/quickviewexport.do?SERIES_KEY=122.ICP.M.U2.N.000000.4.ANR&type=csv'
#resp = requests.get(url)
url = 'https://data.ecb.europa.eu/download-api/csv/wide'
resp = requests.post(url, {'serieskeys': ['ICP.M.U2.N.000000.4.ANR']})
lines = resp.text.split('\n')[6:]
years = defaultdict(lambda: {m:1 + 0.02/12 for m in range(1,13)})
for line in lines:
vals = line.split(',')
year = int(vals[0][:4])
month = datetime.strptime(vals[0][4:],'%b').month
inflation = float(vals[1])
years[year][month] = 1 + (inflation/100)/12
with open('cache.json', 'w') as f:
f.write(json.dumps([datetime.now().isoformat(),years]))
return years
def cliInterface():
import sys, re
arg = " ".join(sys.argv[1:]).replace(',','')
@@ -85,18 +106,18 @@ def cliInterface():
reFromMeur = re.compile(r'(\d+(.\d\d)?)\W?(μ|m|M)')
if (m := reFromEur.search(arg))!=None:
eurS = m.groups()[0]
date = _extractDate(arg.replace(eurS, '-'))
date = _extractDate(arg[:m.span()[0]] + '-' + arg[m.span()[1]:])
eur = float(eurS)
meur = euroToMeuro(eur, date)
print('Exchange rate for '+date.strftime("%d.%m.%Y at %H:%M")+':')
print(f'{eur:.2f}€ = {meur:.2f}µ')
print(f'{eur:,.2f}€ = {meur:,.2f}µ')
elif (m:= reFromMeur.search(arg))!=None:
meurS = m.groups()[0]
date = _extractDate(arg.replace(meurS, '-'))
date = _extractDate(arg[:m.span()[0]] + '-' + arg[m.span()[1]:])
meur = float(meurS)
eur = meuroToEuro(meur, date)
print('Exchange rate for '+date.strftime("%d.%m.%Y at %H:%M")+':')
print(f'{meur:.2f}µ = {eur:.2f}')
print(f'{meur:,.2f}µ = {eur:,.2f}')
else:
print('[!] Unable to parse input')