datetime.datetime.fromtimestamp() problem

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
dave307
User
Beiträge: 6
Registriert: Sonntag 18. März 2012, 18:05

Hallo zusammen,
erst einmal vielen Dank für die vielen Posts in diesem Forum die mir schon oft geholfen haben wenn ich nicht mehr weiter wusste. :wink:
Dieses mal habe ich leider ein Problem was ich nicht wirklich in den Griff bekomme, ich sitze mittlerweile schon seit 2 Tagen daran und komme trotz googlen und rumprobieren einfach nicht weiter. Da ich aus einem GPX-Tracklog die Trackpunkte in ein Objekt laden und weiterverarbeiten will, muss ich auch die Zeit aus der time-Zeile importieren. Um timedelta zu verwenden muss ich die Zeit in ein Python-datetime Objekt importieren. Leider funktioniert das nicht, da ich immer nur einen Fehler erhalte.


Der Code:

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Make Linestring table from gpx (Arg1)"""

import sys #Sys lib
import re #Regex lib
import datetime #Datetime lib


class gpxPoint: #Point Class
	lon = "" #Longitude
	lat = "" #Latitude
	ele = 0.0 #Elevation
	tim = [] #Time
	
	
P1 = gpxPoint() #Point 1
P2 = gpxPoint() #Point 2
	
GPX = "" #GPX File content

POS = 0 #Position in XML-trkpt Object

try: #Try to open gpx File else print error message and exit
	with open(sys.argv[1], 'r') as f: #Open GPX-File
		GPX = f.readlines() #Read Content from File in Variable
except:
	print "gpx-file could not be found!"
	sys.exit() 
	

for i in GPX: #Process Lines from GPX-File
	
	if re.search('<trkpt', i) and (POS == 0): #Detect Begin of XML-trkpt
		SPLICED = re.split('"', i) #Split String
		P1.lon = SPLICED[2] #Get Longitude-Value from String
		P1.lat = SPLICED[4] #Get Latitude-Value from String
		POS += 1 #Increase Position Variable
		
	if re.search('<ele', i) and (POS == 1): #Detect elevation string
		SPLICED = re.split('[<>]', i) #Split String
		P1.ele = SPLICED[2] #Get Elevation from String
		POS += 1 #Increase Position Variable
		
	if re.search('<time', i) and (POS == 2): #Detect time string
		SPLICED = re.split('[<>]', i) #Split String
		print SPLICED[2]
		P1.tim = datetime.datetime.fromtimestamp(SPLICED[2]) #Get time from String
		
		#Alternative, welche ebenfalls nicht funktioniert
		#SPLICED[2] = SPLICED[2].replace("Z",'') #Z am Ende des Strings entfernen
		#P1.tim = datetime.datetime(re.sub('[-:.T]', ',', SPLICED[2])) #Trenner durch Kommata ersetzen und datetime Objekt erstellen 
		
		#Funktioniert!? Oo
		#print re.sub('[-:.T]', ',', SPLICED[2])
		#print(datetime.datetime(2012,03,16,06,42,24,000)) #Datetimeobjekt erstellen

		print P1.tim
		POS = 0 #Reset Position Variable 
Beispiel GPX-Daten:

Code: Alles auswählen

<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<gpx xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.1" xmlns:gpxtpx="http://www.garmin.com/xmlschemas/TrackPointExtension/v1" xmlns="http://www.topografix.com/GPX/1/1" xmlns:rmc="urn:net:trekbuddy:1.0:nmea:rmc" creator="QLandkarteGT 0.18.3 http://www.qlandkarte.org/" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd http://www.qlandkarte.org/xmlschemas/v1.1 http://www.qlandkarte.org/xmlschemas/v1.1/ql-extensions.xsd" xmlns:gpxx="http://www.garmin.com/xmlschemas/GpxExtensions/v3" xmlns:ql="http://www.qlandkarte.org/xmlschemas/v1.1">
 <metadata>
  <time>2012-03-16T22:37:46Z</time>
 </metadata>
 <trk>
  <name>ACTIVE LOG_10</name>
  <extensions>
   <gpxx:TrackExtension>
    <gpxx:DisplayColor>Green</gpxx:DisplayColor>
   </gpxx:TrackExtension>
  </extensions>
  <trkseg>
   <trkpt lon="9.59856415" lat="52.39118958">
    <ele>65.5071</ele>
    <time>2012-03-16T07:25:18.000Z</time>
   </trkpt>
   <trkpt lon="13.38976383" lat="52.51198196">
    <ele>45.7999</ele>
    <time>2012-03-16T10:15:57.000Z</time>
   <trkpt lon="8.51249218" lat="52.00837708">
    <ele>139.048</ele>
    <time>2012-03-16T06:42:13.000Z</time>
   </trkpt>
  </trkseg>
 </trk>
 <extensions/>
</gpx>
Die GPX Datei soll als erstes Argument beim Aufruf des Skriptes übergeben werden.

Leider kommt immer die Fehlermeldung
Traceback (most recent call last):
File "./gpxtolinestring.py", line 48, in <module>
P1.tim = datetime.datetime.fromtimestamp(SPLICED[2]) #Get time from String
TypeError: a float is required
bzw.
File "./gpxtolinestring.py", line 53, in <module>
P1.tim = datetime.datetime(re.split(',', re.sub('[-:.T]', ',', SPLICED[2]))) #Trenner durch Kommata ersetzen und datetime Objekt erstellen
TypeError: an integer is required
wenn ich die alternative (auskommentierte) Variante versuche. Interessanterweise funktioniert es jedoch wenn man die Werte aus der Variable direkt in zum Erstellen des datetime-objekts verwendet (Letzter auskommentierter Block im Python Code) :K

Habe mittlerweile schon viel rumprobiert und bekomme es einfach nicht hin. Ich wäre echt dankbar für einen Tipp.

Gruß,
David
webspider
User
Beiträge: 485
Registriert: Sonntag 19. Juni 2011, 13:41

Ich frage mich ob dein Code laufen würde wenn du statt regulärer Ausdrücke mit lxml arbeiten würdest.
dave307
User
Beiträge: 6
Registriert: Sonntag 18. März 2012, 18:05

Hallo und danke für deine Antwort webspider, ich werd mal gucken ob ich es damit hinkriege. Finde es nur etwas ärgerlich das dieser letzte notwendige Schritt beim Parsen der Daten fehlschlägt... :-/
BlackJack

@dave307: Die jeweiligen Fehlermeldungen sind doch recht deutlich. `fromtimestamp()` hätte gerne eine Gleitkommazahl (`float`) als Argument. Da solltest Du vielleicht mal die Dokumentation zu der Methode konsultieren wie dieser Wert aussehen muss.

Und `datetime()` hätte gerne wenn Du es schon nur mit *einem* Argument aufrufst eine Ganzzahl. Das Ergebnis von `re.split()` ist aber eine Liste. Eine Liste mit Zeichenketten übrigens und keine Liste mit Zahlen. Worüber Du Dich hier gerade ernsthaft wunderst ist, dass ``datetime.datetime(['2012', '03', '16', '06', '42', '24', '000'])`` nicht das gleiche ist wie ``datetime.datetime(2012, 3, 16, 6, 42, 24, 0)``. Du solltest vielleicht erst einmal das Tutorial der Python-Dokumentation durcharbeiten und die verschiedenen Grunddatentypen von Python kennen lernen.

Die passende(re) Methode wäre dann übrigens `datetime.strptime()` mit einem entsprechenden Muster.

Und ich würde auch `lxml` statt regulärer Ausdrücke zum Parsen vorschlagen.

An den Kommentaren solltest Du auch arbeiten. Ein Kommentar sollte dem Leser Mehrwert liefern, also Informationen die nicht schon im Quelltext stehen. Die bei den ``import``-Zeilen haben keinen Mehrwert. Und die bei der Klasse haben den nur weil die Attributnamen so schlecht sind. Statt zu Kommentieren das `ele` für „Elevation” oder `tim` für „Time” steht, sollten die Attribute selber `elevation` und `time` heissen. Schon braucht man dort den Kommentar nicht mehr und muss auch im Rest des Quelltextes nicht rätseln was die wohl bedeuten mögen und wo das vielleicht kommentiert sein mag.

Dass das Klassenattribute sind, legt den Verdacht nahe, dass Du Dich mit objektorientierter Programmierung (OOP) in Python noch nicht so gut auskennst. Das sind Attribute die Du *einmal* für *alle* Exemplare dort anlegst. Insbesondere bei veränderbaren Objekten wie zum Beispiel Listen, ist das nicht das was man üblicherweise möchte. Wobei die Typen die Du da bindest bis auf eine Ausnahme nicht zu der Bedeutung passen. Länge und Breite sind keine Zeichenketten und die Zeit ist keine Liste.

”Vordeklarieren” von Namen ist unnötig. Statt einen Wert an einen Namen zu binden, den man dann nirgends verwendet, sollte man den Namen erst dann binden, wenn man tatsächlich einen Wert dafür hat. Das gilt bis zu einem gewissen Grad auch für zusammengesetzte Datentypen, also hier die `gpxPoint`-Exemplare. Wenn man ein Objekt erstellt, sollte es „komplett” und benutzbar sein. Also erst alle Werte für einen Punkt extrahieren und dann damit das Objekt erstellen, statt ein „leeres” Punkt-Objekt erstellen und dem dann die Attribute einzeln von aussen zuweisen.

Man sollte kein „nacktes” ``except`` ohne konkrete Ausnahmen verwenden. Damit behandelt man *alles* was passieren kann. Und im konkreten Fall bekommt der Benutzer immer die Meldung, dass die Datei nicht gefunden werden konnte. Auch wenn der tatsächliche Grund ein völlig anderer ist.

Bezüglich der Namensgebung und der Einrücktiefe: PEP 8 -- Style Guide for Python Code.
dave307
User
Beiträge: 6
Registriert: Sonntag 18. März 2012, 18:05

Hallo BlackJack,
erstmal vielen Dank für die konstruktive Kritik. Du hast recht, ich habe mich bislang nicht besonders viel mit OOP und Python beschäftigt. Bislang habe ich immer bash für mein Scripting verwendet, da ich ständig kleinere Scripts schreibe wollte ich die Gelegenheit nutzen und mich etwas in die beiden Thematiken hereinarbeiten. In bash hätte ich die von mir hier eingesetzte Herangehensweise ohne Objekt und mit SED gelöst, daher die Idee mit den Regular Expressions. Den Großteil der Kommentare hatte ich eigentlich nur Hinzugefügt damit eventuelle Missverständnisse hier im Forum ausgeschlossen werden.
Ich verstehe allerdings immer noch nicht genau warum fromtimestamp() den Timestamp nicht parsen kann, ich hatte es so verstanden, dass es genau die Form ist, welche gefordert ist. Aus dem von dir geposteten Link werde ich leider nicht wirklich schlau.

Ich werde mir lxml noch ein mal genauer ansehen und den fertigen (und hoffentlich funktionierenden) Code hier noch einmal zur Diskussion stellen.

Gruß,
David
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

dave307 hat geschrieben:Ich verstehe allerdings immer noch nicht genau warum fromtimestamp() den Timestamp nicht parsen kann, ich hatte es so verstanden, dass es genau die Form ist, welche gefordert ist. Aus dem von dir geposteten Link werde ich leider nicht wirklich schlau.
Weil fromtimestamp gar nicht zum Parsen von Timestamps gedacht ist. Die Fehlermeldung sagt doch bereits, dass ein float erwartet wird. Oder um es mit den Worten der Doku zu sagen:
fromtimestamp hat geschrieben:Return the local date and time corresponding to the POSIX timestamp, such as is returned by time.time().
time.time hat geschrieben:Return the time as a floating point number expressed in seconds since the epoch, in UTC.
Das Leben ist wie ein Tennisball.
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

@dave307: `strptime()` steht höchstwahrscheinlich für `stringparse_time` oder so ähnlich - einen direkten Beleg kann ich dafür nicht vorweisen. Quasi sein Gegenstück ist `strftime()`, welches einen `struct_time` in einen speziell formatierten String umwandelt. Das sind aber auch die beiden einzigen Funktionen, die direkt etwas mit Strings zu tun haben.

Wenn hingegen von Zeitstempeln (Timestamps) die Rede ist, dann handelt es sich um aller Regel um die berühmten Sekunden "since the epoch", also dem Punkt wo die Zeitmessung angefangen hat - und diese Sekundenzahl ist halt einfach ein Zahlwert, welcher für gewöhnlich auch in seiner Grundform solange verbleibt bis es dem Benutzer präsentiert werden soll.

Kurz gesagt: Du hast da leider etwas falsch / nicht ausreichend verstanden. ;)
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

EyDu hat geschrieben:Weil fromtimestamp gar nicht zum Parsen von Timestamps gedacht ist. Die Fehlermeldung sagt doch bereits, dass ein float erwartet wird.
Also ich finde schon, dass dieser Float einen Zeitstempel repräsentiert. Es ist halt nur kein formatierter String. Parsen meint ja grundsätzlich, etwas "in Form" zu bringen.¹ Dies wird gemacht, indem eine große Zahl in eine Zeitstruktur gebracht wird.

______

¹ Gut, eigentlich würde "Zergliedern" wohl besser als Übersetzung passen. Dann hat man auch die Abgrenzung zum "Formatieren".
Zuletzt geändert von snafu am Montag 19. März 2012, 14:24, insgesamt 1-mal geändert.
BlackJack

Ups — gestern abend glatt vergessen abzuschicken. :-)

@dave307: Der erste Satz zu `fromtimestamp()`:
Return the local date and time corresponding to the POSIX timestamp, such as is returned by time.time().
Man könnte jetzt auf den Link zur `time.time()`-Dokumentation klicken und da weiterlesen, oder die Funktion einfach mal ausprobieren:

Code: Alles auswählen

In [51]: import time

In [52]: time.time()
Out[52]: 1332111813.1292789
*So* sieht das aus was `fromtimestamp()` erwartet. Die Anzahl der Sekunden seit Anbeginn der „Unix-Zeitrechnung” als Gleitkommazahl.

In der Bash gibt es als Datentyp eigentlich nur Zeichenketten. Die können mit einigen Sprachkonstrukten „vorübergehend” als ganze Zahlen behandelt werden, aber es bleiben letztendlich Zeichenketten die nur kurz intern umgewandelt werden. In „richtigen” Programmiersprachen gibt es eine Menge verschiedener Datentypen. Was einen Datentypen ausmacht, ist unter anderem welche Operationen man mit oder auf ihm ausführen kann. Um mit Datumsangaben rechnen zu können, muss man in den `datetime`-Typ Zahlen stecken und keine Zeichenketten. Oder man benutzt die `strptime()`-Methode und ein Muster was angibt wie die Zeichenkette als Datum zu interpretieren ist.

In einem Shellskript würde ich XML übrigens auch nicht mit ``sed`` verarbeiten, sondern zum Beispiel mit ``xmlstarlet``. XML als Textdatei zu behandeln ist nicht wirklich robust.

Deine Beispieldaten sind kaputt — nach Zeile 20 muss man ein schliessendes ``</trkpt>`` einfügen.

Code: Alles auswählen

from datetime import datetime as DateTime
from itertools import imap
from lxml import etree


GPX_NS = '{http://www.topografix.com/GPX/1/1}'


class TrackPoint(object):
    def __init__(self, latitude, longitude, elevation, time):
        self.latitude = latitude
        self.longitude = longitude
        self.elevation = elevation
        self.time = time
    
    def __repr__(self):
        return '%s(%r, %r, %r, %r)' % (
            self.__class__.__name__,
            self.latitude,
            self.longitude,
            self.elevation,
            self.time
        )
    
    @classmethod
    def from_gpx_node(cls, node):
        return cls(
            float(node.get('lat')),
            float(node.get('lon')),
            float(node.find(GPX_NS + 'ele').text),
            DateTime.strptime(
                node.find(GPX_NS + 'time').text, '%Y-%m-%dT%H:%M:%S.%fZ'
            )
        )


def main():
    root = etree.parse('test.xml').getroot()
    track_points = imap(TrackPoint.from_gpx_node, root.iter(GPX_NS + 'trkpt'))
    for i, track_point in enumerate(track_points):
        print '%d: %s' % (i, track_point)


if __name__ == '__main__':
    main()
dave307
User
Beiträge: 6
Registriert: Sonntag 18. März 2012, 18:05

Dachte bislang das mit Timestamp das Format gemeint war, welches auch in dem GPS-Tracklog verwendet wird. Ich hatte die Float-Fehlermeldung auch auf die Millisekunden in dem GPX-Timestamp bezogen. Aber jetzt ist klar warum es nicht funktioniert hat, danke für die Richtigstellung snafu und BlackJack.
Die Methode das XML mit Hilfe von lxml zu parsen gefällt mir sehr gut und wenn man es erstmal halbwegs verstanden hat erscheint es auch gar nicht mehr so kompliziert. In dieser Hinsicht ein großes Dankeschön an BlackJack für den Code, das hat mir den Einstieg deutlich vereinfacht. Was XML in Bash angeht, wusste ich bislang gar nicht, dass es dafür ein Tool gibt. Ich arbeite auch nur sehr selten mit XML weil ich meine Daten wann immer möglich in einer Datenbank speichere und sie auch dementsprechend verarbeite.

@BlackJack
Die fehlende Zeile in dem GPX-Beispieldaten ist entstanden als ich den Tracklog gekürzt habe um im Forum Beispieldaten mit einer angemessenen Größe zur Verfügung zu stellen. Habe es leider beim Kürzen übersehen... :-/
Dein Code hilft mir wirklich weiter, nicht nur im Bezug auf die zu Beginn formulierte Fragestellung. Es wurden auch einige Details aus dem Style Guide und das Erstellen von Klassen klarer. Allgemein ein guter Ausgangspunkt um sich intensiver mit der Materie zu beschäftigen. Eine Sache bezüglich des Einzugs habe ich allerdings noch nicht ganz verstanden: Im Guide ist davon die Rede, dass Leerzeichen oder Tabs verwendet werden können, Tabs aber eher selten sind. Wenn ich das richtig sehe hast du in deinem Code Tabs verwendet, was ist nun richtig(er)?
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Zur Einrückung: Üblich sind 4 Leerzeichen. Das kann man in jedem besseren Editor (Notepad für Windows und sowas mal ausgenommen) auch einstellen. Wichtig (bzw üblich) ist, dass du die Einrückung auch tatsächlich auf Leerzeichen umstellst.

Ich kann mir übrigens nicht vorstellen, dass BlackJack Tabs verwendet hat. Wie kommst du darauf?
dave307
User
Beiträge: 6
Registriert: Sonntag 18. März 2012, 18:05

Ah sorry, war mein Fehler, es sind tatsächlich Leerzeichen.
Ich finde das mit den Leerzeichen etwas merkwürdig, weil die Breite von Tabs in verschiedenen Editoren einstellbar ist, wenn man jedoch Leerzeichen setzt ist die Breite fest. Der zusätzliche Platzbedarf macht sich schon bemerkbar wenn man an einem kleineren Bildschirm arbeitet.


PS
Notepad würde ich noch nicht mal als Editor bezeichnen... :-P
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Einigen wir uns auf Textansichtsversuchsdingsbums, ok? ;)
dave307
User
Beiträge: 6
Registriert: Sonntag 18. März 2012, 18:05

ok... :-D

Der Thread kann denke ich auch geschlossen werden, danke allen für die Antworten.
Antworten