Compare commits

...
17 Commits
3 changed files with 77 additions and 73 deletions
+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

+40 -41
View File
@@ -4,52 +4,18 @@ 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
# Our inflation-table as a global-var
_years = None
def _loadYearsTable(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()}
_years = 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)
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
with open('cache.json', 'w') as f:
f.write(json.dumps([datetime.now().isoformat(),years]))
return years
# 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:
_loadYearsTable()
reload()
if date==None:
date = datetime.now()
month, year = date.month, date.year
@@ -100,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(',','')
@@ -107,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')