Im Zeichen des Euro (Linux-Magazin, Januar 2002)

Ein Skript rechnet Währungen entsprechend den aktuellen Umtauschkursen einfach auf der Kommandozeile um.

Um hundert DM in Euro zu konvertieren, installiert man das heute vorgestellte Skript ccon, tippt

    $ ccon 100 DEM EUR
    51.1292

ein und erhält mit 51.1292 sofort das richtige Ergebnis. Und wieviel Dollars bekommt man für 100 Euros?

    $ ccon 100 EUR USD
    92.06

92 Dollars und 6 Cents, richtig, denn zum Zeitpunkt der Entwicklung dieses Skripts war der Dollar gerade auf 1.086248 Euro. Hexerei? Keineswegs.

Yahoo und einige andere Anbieter stellen die aktuellen Umtauschkurse kostenlos auf dem Internet zur Verfügung. Das Modul Finance::Currency::Convert von Jan Willamowius abstrahiert diesen Zugriff schön, die objektorientierte Schnittstelle offeriert einfach eine Methode convert(), die den Geldbetrag, die Ausgangs- und die Zielwährung erwartet und das Ergebnis zurückliefert.

Eine Auswahl der verfügbaren Währungen und deren Kürzel:

    EUR Euro
    DEM Deutsche Mark
    USD US-Dollars
    GBP Britische Pfund
    ATS Österreichische Schilling
    ESP Spanische Peseten
    FRF Französiche Franc
    GRD Griechische Drachmen
    ITL Italienische Lira
    NLG Niederländische Gulden
    AUD Australische Dollars
    CHF Schweizer Franken
    JPY Japanische Yen

Schnelle Konservennahrung

Handelt es sich um eine feststehende europäische Währungsbeziehung, nutzt das Modul lediglich seine internen Tabellen und gibt sofort das Ergebnis zurück -- schließlich haben die europäischen Teilnehmer an der Währungsunion feste Kurse ausgekartelt. Liegt aber eine mit den Märkten fluktuierende Beziehung vor, muss vor dem Aufruf von convert() die Methode updateRate() ran, die mittels des Moduls Finance::Quote den aktuellen Umrechnungskurs von der Finanzseite des Anbieters Yahoo abholt. Da dieser Netzzugriff eine Weile dauert und Währungen für den Normalverbraucher nicht so stark wie Aktienkurse schwanken, lassen sich die eingeholten Werte mit der Methode writeRatesFile() in einer Cache-Datei abspeichern und von dort später wieder mit readRatesFile() laden.

Existiert noch keine Cache-Datei, weiss das Skript zunächst nicht, wie sich zum Beispiel österreichische Schillinge in Schweizer Franken umrechnen lassen:

    $ ccon 100 ATS CHF
    Updating ... Please wait.
    10.8509

Das nächste mal hingegen geht's sofort, da die Cache-Datei alle notwendigen Werte bereits enthält:

    $ ccon 100 ATS CHF
    10.8509

Verstreicht aber eine gewisse Zeitspanne, ist der Kurs veraltet und das Skript muss trotzdem wieder auf dem Internet nachfragen. Da Otto Normalverbraucher einmal täglich aufgefrischte Kurse genügen, geht das Skript nach folgendem Algorithmus vor: Morgens um zehn sollte der aktuelle Tageskurs vorliegen. Deshalb stellt es sicher, dass mindestens der 10-Uhr Kurs des letzten Werktages eingeholt wurde.

Listing ccon zeigt die Implementierung. Die Zeilen 9 bis 11 setzen mindestens perl 5.6.0 voraus und lassen es mit use warnings und use strict gehörig mit der Peitsche knallen. Zeile 13 zieht das schon erwähnte Modul Finance::Currency::Convert hinzu, das seinerseits ein installiertes Finance::Quotes voraussetzt. Das in Zeile 14 hereingezogene Time::Local liegt Perl-Distributionen ab perl 5.6.0 bereits bei und wird später zu Datumsberechnungen herangezogen.

Listing 1: ccon

    01 #!/usr/bin/perl
    02 ##################################################
    03 # ccon -- Mike Schilli, 2001 (m@perlmeister.com)
    04 # Convert currencies on the command line.
    05 # Syntax: ccon amount source target
    06 #                   [EUR,USD,DEM,GBP,ATS,ESP,FRF,
    07 #                     FRF,GRD,ITL,NLG,AUD,CHF,JPY]
    08 ##################################################
    09 use 5.6.0;
    10 use warnings;
    11 use strict;
    12 
    13 use Finance::Currency::Convert;
    14 use Time::Local;
    15 
    16 my $RATES_CACHE = "$ENV{HOME}/.currency";
    17 
    18 my($amount, @WAY) = @ARGV;
    19 
    20 die "usage: $0 amount from to\n" 
    21     unless defined $ARGV[2];
    22 
    23 my $conv = Finance::Currency::Convert->new();
    24 
    25     # Cache-Datei setzen und lesen falls
    26     # vorhanden
    27 $conv->setRatesFile($RATES_CACHE);
    28 
    29 if(!$conv->convert($amount, @WAY) or
    30     update_recommended()) {
    31         # Vom Internet nachladen
    32     print "Updating ... Please wait.\n";
    33     $conv->updateRate(@WAY);
    34     $conv->writeRatesFile();
    35 }
    36 
    37 my $rate = $conv->convert($amount, @WAY);
    38 
    39 die "Can't convert ", 
    40     join(' => ', @WAY) unless defined $rate;
    41 
    42 print "$rate\n";
    43 
    44 ##################################################
    45 sub update_recommended {
    46 ##################################################
    47 
    48         # nicht vorhanden => nachladen
    49     return 1 unless -f $RATES_CACHE;
    50 
    51     my $date = time();
    52     my @date = localtime($date);
    53 
    54     $date -= 3600*24 if $date[2] < 10;
    55 
    56         # Letzten Werktag suchen
    57     { @date = localtime($date);
    58       $date -= 3600*24, redo if
    59           $date[6] == 0 or $date[6] == 6;
    60     }
    61 
    62         # Auf 10:00 setzen
    63     @date[0..2] = (0, 0, 10);
    64 
    65         # Zurück ins Sekundenformat
    66     $date = timelocal(@date);
    67 
    68     return (stat($RATES_CACHE))[9] < $date;
    69 }

Den Namen der Cache-Datei des Skripts setzt der Skalar $RATES_CACHE in Zeile 16 auf das unsichtbare .currency im Heimatverzeichnis des ausführenden Benutzers. Zeile 18 sammelt die Kommandozeilenparameter ein. Die Signatur des Skripts ist

    ccon betrag ausgangswährung zielwährung

und deswegen liegen ab Zeile 18 der Betrag in $amount und die beiden Währungskürzel in den ersten zwei Elementen von @WAY.

Falls weniger als drei Kommandozeilenparameter vorliegen, ist $ARGV[2] undefiniert und Zeile 20/21 bricht das Programm mit einer kurzen Bedienungsanleitung ab. Andernfalls erzeugt Zeile 23 ein neues Objekt vom Typ Finance::Currency::Convert und legt eine Referenz darauf in $conv ab. Zeile 27 setzt den Namen der Cache-Datei und liest die alten Kurse von dort, falls vorhanden, hinter den Kulissen auch gleich ein.

Falls die convert-Methode in Zeile 29 fehlschlägt oder die weiter unten definierte Funktion update_recommended einen wahren Wert liefert, muss ccon Daten aus dem Internet nachladen. Da das einige Zeit dauern kann, gibt Zeile 32 eine kurze Meldung aus, bevor Zeile 33 mit der updateRates-Methode des Finance::Currency::Convert-Objekts Daten vom Internet holt. Zeile 34 legt den neu gewonnenen Datensatz in der Cache-Datei ab.

Zeile 37 konvertiert schließlich die Währungen, basierend auf den verfügbaren Daten, und gibt sie aus. Zeile 39 bricht im Falle eines Konvertierungsproblems das Programm mit einer Fehlermeldung ab, Zeile 42 gibt im Erfolgsfall das Ergebnis aus.

Die ab Zeile 45 implementierte Funktion update_recommended() rechnet aus, ob die aktuelle Cache-Datei gut genug ist oder ob das Skript neue Daten vom Internet braucht. Es ermittelt den Zeitpunkt 10:00 des letzten (oder aktuellen) Werktags und findet heraus, ob die aktuelle Cache-Datei vor oder nach diesem Zeitpunkt erzeugt wurde. Im ersten Fall gibt es 1 zurück, zum Zeichen dafür, dass die aktuelle Cache-Datei eine Verjüngungskur braucht. Im zweiten gibt's nichts neues und der von update_recommended() zurückgegebene falsche Wert signalisiert dem Hauptprogramm, dass die aktuelle Cache-Datei gut genug ist.

Hierzu ermittelt time() in Zeile 51 die aktuelle Uhrzeit in Sekunden seit 1970 und legt sie im Skalar $date ab. Im Array @date hingegen (völlig unabhängig von $date übrigens) liegen später die neun Datumsparameter, die localtime() zurückliefert.

Falls es noch nicht 10 Uhr ist, fängt Zeile 54 erst gestern an zu zählen. Wenn der ermittelte Wochentag ein Samstag oder Sonntag ist, also $date[6] entweder 6 oder 0 enthält, setzt Zeile 58 um 24 Stunden zurück, springt zurück an den Anfang des Blocks in Zeile 55 und versucht's nochmal. Sobald ein Werktag vorliegt, wird der Block verlassen und Zeile 63 setzt die Uhrzeit auf 10:00 morgens. Zeile 66 macht aus dem 9-Parameter-Array mit der Funktion timelocal() aus dem Modul Time::Local aus der Standarddistribution wieder eine Zeit in Sekunden seit 1970. Zeile 68 holt mittels des 10. Feldes der stat-Funktion auf die Cache-Datei deren letztes Modifikationsdatum hervor -- ebenfalls in Sekunden seit 1970. Falls die Datei nach dem 10:00-Zeitpunkt verändert wurde, ist sie okay und update_recommended gibt einen falschen Wert zurück zum Zeichen dafür, dass keine Update erforderlich ist. Andernfalls kommt ein wahrer Wert zurück, der im Hauptprogramm einen Abgleich mit den Yahoo-Daten vom Internet auslöst. Feiertage erkennt der Algorithmus nicht, es kann also sein, dass das Skript einmal am Tag einen sinnlosen Versuch unternimmt, neue Daten zu holen.

Installation

Die benötigten Module Finance::Quote und Finance::Currency::Convert installieren sich wie immer über eine CPAN-Shell:

    perl -MCPAN -eshell
    cpan> install Finance::Quote
    cpan> install Finance::Currency::Convert

Wer bestimmte Umrechnungen häufig durchführt und Tipparbeit ersparen will, darf sich freilich Shell-Skripts definieren:

    #!/bin/sh
    # Skript: d2e
    ccon $1 USD EUR

Oder auch eine bash-Funktion:

    function d2e () { ccon $1 USD EUR }

In beiden Fällen ergibt ein anschließend von der Kommandozeile aufgerufenes

    d2e 1

flugs 1.086248, wenn der Dollar gerade auf 1.086248 Euro steht. Rechnet fleißig um und gebt haufenweise Euros aus!

Infos

[1]
Listings zu diesem Artikel: ftp://www.linux-magazin.de/pub/listings/magazin/2002/01/Perl

Michael Schilli

arbeitet als Software-Engineer bei Yahoo! in Sunnyvale, Kalifornien. Er hat "Goto Perl 5" (deutsch) und "Perl Power" (englisch) für Addison-Wesley geschrieben und ist unter mschilli@perlmeister.com zu erreichen. Seine Homepage: http://perlmeister.com.