Compare commits
17
Commits
872df67776
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ecb387a467 | ||
|
|
89636040c6 | ||
|
|
201c112184 | ||
|
|
eb4aad68fc | ||
|
|
178f1a169d | ||
|
|
9ad1111055 | ||
|
|
87789e8500 | ||
|
|
69c6ee4cd0 | ||
|
|
59bab52160 | ||
|
|
8147f64b4b | ||
|
|
338e9af870 | ||
|
|
32a15c8353 | ||
|
|
d87d260a1c | ||
|
|
2de965414a | ||
|
|
d95bb4e78a | ||
|
|
afeee78836 | ||
|
|
58cf8809ad |
@@ -1,6 +1,6 @@
|
|||||||
# Meuro
|
# Meuro
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Prices in euros from different times can not be compared without taking inflation into account.
|
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.
|
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.
|
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.)
|
(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
|
## 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
|
## Usage
|
||||||
You probably want to use this tool using the cli-interface which tries to understand requests in human language.
|
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;
|
### Examples:
|
||||||
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:
|
|
||||||
#### ./meuro.py What is 200 eur in meuro
|
#### ./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 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 3.14E 01.02.2003 12:34
|
||||||
#### ./meuro.py How much meuro are 12 euro
|
### For best Results:
|
||||||
### Python-API
|
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
|
You can also import this libary from another python file
|
||||||
##### The provided functions are:
|
##### The provided functions are:
|
||||||
#### exchangeRate(date=None)
|
#### 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)
|
#### 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)
|
Converts the given amount of meuros to euros for the given date. (wholeCents means it rounds to two decimal places)
|
||||||
#### liveValue(eur, interval=10)
|
#### 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
|
##### 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
|
## Caveats
|
||||||
The ecb only gives an estimate for the inflation of the last month and no data for the current month.
|
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).
|
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
@@ -6,11 +6,11 @@
|
|||||||
id="Ebene_1"
|
id="Ebene_1"
|
||||||
x="0px"
|
x="0px"
|
||||||
y="0px"
|
y="0px"
|
||||||
width="91.336815"
|
width="194.44644"
|
||||||
height="91.336815"
|
height="194.44644"
|
||||||
viewBox="0 0 91.336817 91.336815"
|
viewBox="0 0 194.44645 194.44644"
|
||||||
xml:space="preserve"
|
xml:space="preserve"
|
||||||
sodipodi:docname="Meuro (copy).svg"
|
sodipodi:docname="icon_meuro.svg"
|
||||||
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
|
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
|
||||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
@@ -28,11 +28,11 @@
|
|||||||
inkscape:snap-object-midpoints="false"
|
inkscape:snap-object-midpoints="false"
|
||||||
inkscape:snap-others="true"
|
inkscape:snap-others="true"
|
||||||
inkscape:snap-nodes="true"
|
inkscape:snap-nodes="true"
|
||||||
inkscape:zoom="4.778903"
|
inkscape:zoom="1.589075"
|
||||||
inkscape:cx="52.627141"
|
inkscape:cx="-84.325789"
|
||||||
inkscape:cy="61.625022"
|
inkscape:cy="19.193557"
|
||||||
inkscape:window-width="960"
|
inkscape:window-width="1251"
|
||||||
inkscape:window-height="1043"
|
inkscape:window-height="991"
|
||||||
inkscape:window-x="26"
|
inkscape:window-x="26"
|
||||||
inkscape:window-y="23"
|
inkscape:window-y="23"
|
||||||
inkscape:window-maximized="0"
|
inkscape:window-maximized="0"
|
||||||
@@ -41,18 +41,18 @@
|
|||||||
|
|
||||||
|
|
||||||
<circle
|
<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"
|
id="path191"
|
||||||
cx="45.668407"
|
cx="97.223221"
|
||||||
cy="45.668407"
|
cy="97.223221"
|
||||||
r="43.168407" /><text
|
r="91.90097" /><text
|
||||||
xml:space="preserve"
|
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"
|
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="20.912342"
|
x="44.520176"
|
||||||
y="59.234234"
|
y="126.10342"
|
||||||
id="text4082"><tspan
|
id="text4082"><tspan
|
||||||
sodipodi:role="line"
|
sodipodi:role="line"
|
||||||
id="tspan4080"
|
id="tspan4080"
|
||||||
x="20.912342"
|
x="44.520176"
|
||||||
y="59.234234"
|
y="126.10342"
|
||||||
style="stroke-width:1.94412;fill:#743f15;fill-opacity:1">µ</tspan></text></svg>
|
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 |
@@ -4,52 +4,18 @@ from datetime import datetime
|
|||||||
from dateutil import relativedelta
|
from dateutil import relativedelta
|
||||||
from dateutil import parser as dparser
|
from dateutil import parser as dparser
|
||||||
from dateutil.parser._parser import ParserError as DateParserError
|
from dateutil.parser._parser import ParserError as DateParserError
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
# Our inflation-table as a global-var
|
||||||
_years = None
|
_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.
|
# Gives you the exchange rate between euros and meuros for the given date.
|
||||||
# (Should always be a float < 1)
|
# (Should always be a float < 1)
|
||||||
# date should be either a datetime-object or None (= current date)
|
# date should be either a datetime-object or None (= current date)
|
||||||
def exchangeRate(date=None):
|
def exchangeRate(date=None):
|
||||||
global _years
|
global _years
|
||||||
if _years==None:
|
if _years==None:
|
||||||
_loadYearsTable()
|
reload()
|
||||||
if date==None:
|
if date==None:
|
||||||
date = datetime.now()
|
date = datetime.now()
|
||||||
month, year = date.month, date.year
|
month, year = date.month, date.year
|
||||||
@@ -100,6 +66,39 @@ def _extractDate(s):
|
|||||||
except DateParserError:
|
except DateParserError:
|
||||||
return datetime.now()
|
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():
|
def cliInterface():
|
||||||
import sys, re
|
import sys, re
|
||||||
arg = " ".join(sys.argv[1:]).replace(',','')
|
arg = " ".join(sys.argv[1:]).replace(',','')
|
||||||
@@ -107,18 +106,18 @@ def cliInterface():
|
|||||||
reFromMeur = re.compile(r'(\d+(.\d\d)?)\W?(μ|m|M)')
|
reFromMeur = re.compile(r'(\d+(.\d\d)?)\W?(μ|m|M)')
|
||||||
if (m := reFromEur.search(arg))!=None:
|
if (m := reFromEur.search(arg))!=None:
|
||||||
eurS = m.groups()[0]
|
eurS = m.groups()[0]
|
||||||
date = _extractDate(arg.replace(eurS, '-'))
|
date = _extractDate(arg[:m.span()[0]] + '-' + arg[m.span()[1]:])
|
||||||
eur = float(eurS)
|
eur = float(eurS)
|
||||||
meur = euroToMeuro(eur, date)
|
meur = euroToMeuro(eur, date)
|
||||||
print('Exchange rate for '+date.strftime("%d.%m.%Y at %H:%M")+':')
|
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:
|
elif (m:= reFromMeur.search(arg))!=None:
|
||||||
meurS = m.groups()[0]
|
meurS = m.groups()[0]
|
||||||
date = _extractDate(arg.replace(meurS, '-'))
|
date = _extractDate(arg[:m.span()[0]] + '-' + arg[m.span()[1]:])
|
||||||
meur = float(meurS)
|
meur = float(meurS)
|
||||||
eur = meuroToEuro(meur, date)
|
eur = meuroToEuro(meur, date)
|
||||||
print('Exchange rate for '+date.strftime("%d.%m.%Y at %H:%M")+':')
|
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:
|
else:
|
||||||
print('[!] Unable to parse input')
|
print('[!] Unable to parse input')
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user