Repository: obspy/obspy

Issue:
UTCDateTime before 1900
**Description:**
printing UTCDateTime raises Exceptions before 1900 whereas datetime and UTCDateTime cover in principle the year 1 all the way to the year 9999.

**Remarks:**
- `obspy.__version__: 1.0.0.dev0+20.g4c9f93ae96.obspy.maintenance.1.0.x`
- `Python 2.7.11 |Continuum Analytics, Inc.`
- this 'bug' already appeared in #168

**Example:**

```
from obspy import UTCDateTime
t1 = UTCDateTime(1, 1, 1)
print t1.datetime
print t1
```

UTCDateTime before 1900
**Description:**
printing UTCDateTime raises Exceptions before 1900 whereas datetime and UTCDateTime cover in principle the year 1 all the way to the year 9999.

**Remarks:**
- `obspy.__version__: 1.0.0.dev0+20.g4c9f93ae96.obspy.maintenance.1.0.x`
- `Python 2.7.11 |Continuum Analytics, Inc.`
- this 'bug' already appeared in #168

**Example:**

```
from obspy import UTCDateTime
t1 = UTCDateTime(1, 1, 1)
print t1.datetime
print t1
```



Discussion:
as you didn't post a concrete example with full traceback, I assume it the same issue as before:

```
Python 2.7.6 (default, Nov 10 2013, 19:24:24) [MSC v.1500 64 bit (AMD64)]

In [1]: import datetime
In [2]: datetime.datetime(1,1,1)
Out[2]: datetime.datetime(1, 1, 1, 0, 0)
In [3]: print datetime.datetime(1,1,1)
0001-01-01 00:00:00
In [4]: print datetime.datetime(1,1,1).strftime("%j")
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-4-14eeeba5d56d> in <module>()
----> 1 print datetime.datetime(1,1,1).strftime("%j")

ValueError: year=1 is before 1900; the datetime strftime() methods require year >= 1900
```

oh and it works for Python 3.4

```
Python 3.4.0 (v3.4.0:04f714765c13, Mar 16 2014, 19:25:23) [MSC v.1600 64 bit (AMD64)]

In [1]: import datetime
In [2]: print(datetime.datetime(1,1,1).strftime("%j"))
001
```

yes. I suggest catching the Exception because it is not necessary ...

its an exception raised by Python's `datetime.strftime` for Python 2.7.x (don't know about Python 3.0-3.3) - so what do you suggest to catch as its an issue with Python and not ObsPy?

Catching the exception and silently ignoring it is IMHO no option - especially if this is fixed in newer Python versions. You can ofc monkey patch the `__str__/__unicode__` methods of the `UTCDateTime` class.

well I would just catch and say: can't print date before 1900 or something. This improves usability without hurting anyone and makes the code more stable. Only disadvantage is that you need two extra lines of code. Just my opinion though. I agree that it is not the most important issue ...

ok I had a look into the code - now I remember that we fixed that: https://github.com/obspy/obspy/blob/master/obspy/core/utcdatetime.py#L800 - and this actually works for python 2.7.x:

```
Python 2.7.6 (default, Nov 10 2013, 19:24:24) [MSC v.1500 64 bit (AMD64)]

In [1]: import datetime
In [2]: datetime.datetime(1,1,1).utctimetuple().tm_yday
Out[2]: 1
```

So I wonder why this issue started reappearing. Can you please post a concrete example where your issue pops up - thanks!

Well the issue pops up in my and your example above. But I can post it again:

```
from obspy import UTCDateTime
In [1]: from obspy import UTCDateTime
In [2]: t1 = UTCDateTime(1, 1, 1)
In [3]: print t1
Out[3]: ---------------------------------------------------------------------------
[...]
ValueError: year=1 is before 1900; the datetime strftime() methods require year >= 1900
```

to me this is an unneccesary and undocumented limitation of UTCDateTime even if the underlying cause comes from python itself. Print is a function that pops up very often. I think I don't need to give a specific use case. Also this behavior is very easy to fix. Yes, the user could catch the Exception as well, but it is a hassle to do this just because of a print function. 

To me one (not very convincing) argument not to do sth. is that maybe in a few years nobody cares about python 2.7 and it works later anyway. Keep in mind that UTCDateTime is not a copy of datetime but something on top. It doesn't have to inherit all of datetimes problems which belongs to the standard library and can't be updated easily.

So my opinion is that it is better to catch the Exception with a meaningful message or an adjusted print function. Anyway, I think I made my point clear enough. No need to repeat everything and as I said it is not the most important issue, so I'll leave it as it is :)

Matthias, I wasn't aware that the example above gave that exception - you did not give a full traceback but stated that "this bug already appeared in #168" - and I was talking about the issue in #168, showing examples and the fix for it at that time. A meaningful example with a full traceback would have spared me the time to look into that.

Anyway, you are right and I can reproduce this issue and a workaround should be possible.

OK, sorry about that. I'll provide input AND output for the minimum working example next time !

And I will update the issue template to remind me doing so! :)

no problem, sorry for all the fuzz above and while trying to fix this issue I discovered another problem with the current UTCDateTime implementation:

```
Python 3.4.3 (v3.4.3:9b73f1c3e601, Feb 24 2015, 22:44:40) [MSC v.1600 64 bit (AMD64)]

In [1]: from obspy import UTCDateTime
In [2]: UTCDateTime(1,1,1,1,1,1,1)
Out[2]: 0001-01-01T01:01:01.000000Z   # microseconds incorrect!
In [3]: UTCDateTime(1,1,1,1,1,1,1).datetime
Out[3]: datetime.datetime(1, 1, 1, 1, 1, 1)   # microseconds missing!
In [4]: from datetime import datetime
In [5]: datetime(1,1,1,1,1,1,1)
Out[5]: datetime.datetime(1, 1, 1, 1, 1, 1, 1)   # microseconds correct
```

I'm currently playing with it - I guess we are going to switch back to datetime.datetime instead of float within UTCDateTime especially since we don't have to support Python2.6 anymore ...

as you didn't post a concrete example with full traceback, I assume it the same issue as before:

```
Python 2.7.6 (default, Nov 10 2013, 19:24:24) [MSC v.1500 64 bit (AMD64)]

In [1]: import datetime
In [2]: datetime.datetime(1,1,1)
Out[2]: datetime.datetime(1, 1, 1, 0, 0)
In [3]: print datetime.datetime(1,1,1)
0001-01-01 00:00:00
In [4]: print datetime.datetime(1,1,1).strftime("%j")
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-4-14eeeba5d56d> in <module>()
----> 1 print datetime.datetime(1,1,1).strftime("%j")

ValueError: year=1 is before 1900; the datetime strftime() methods require year >= 1900
```

oh and it works for Python 3.4

```
Python 3.4.0 (v3.4.0:04f714765c13, Mar 16 2014, 19:25:23) [MSC v.1600 64 bit (AMD64)]

In [1]: import datetime
In [2]: print(datetime.datetime(1,1,1).strftime("%j"))
001
```

yes. I suggest catching the Exception because it is not necessary ...

its an exception raised by Python's `datetime.strftime` for Python 2.7.x (don't know about Python 3.0-3.3) - so what do you suggest to catch as its an issue with Python and not ObsPy?

Catching the exception and silently ignoring it is IMHO no option - especially if this is fixed in newer Python versions. You can ofc monkey patch the `__str__/__unicode__` methods of the `UTCDateTime` class.

well I would just catch and say: can't print date before 1900 or something. This improves usability without hurting anyone and makes the code more stable. Only disadvantage is that you need two extra lines of code. Just my opinion though. I agree that it is not the most important issue ...

ok I had a look into the code - now I remember that we fixed that: https://github.com/obspy/obspy/blob/master/obspy/core/utcdatetime.py#L800 - and this actually works for python 2.7.x:

```
Python 2.7.6 (default, Nov 10 2013, 19:24:24) [MSC v.1500 64 bit (AMD64)]

In [1]: import datetime
In [2]: datetime.datetime(1,1,1).utctimetuple().tm_yday
Out[2]: 1
```

So I wonder why this issue started reappearing. Can you please post a concrete example where your issue pops up - thanks!

Well the issue pops up in my and your example above. But I can post it again:

```
from obspy import UTCDateTime
In [1]: from obspy import UTCDateTime
In [2]: t1 = UTCDateTime(1, 1, 1)
In [3]: print t1
Out[3]: ---------------------------------------------------------------------------
[...]
ValueError: year=1 is before 1900; the datetime strftime() methods require year >= 1900
```

to me this is an unneccesary and undocumented limitation of UTCDateTime even if the underlying cause comes from python itself. Print is a function that pops up very often. I think I don't need to give a specific use case. Also this behavior is very easy to fix. Yes, the user could catch the Exception as well, but it is a hassle to do this just because of a print function. 

To me one (not very convincing) argument not to do sth. is that maybe in a few years nobody cares about python 2.7 and it works later anyway. Keep in mind that UTCDateTime is not a copy of datetime but something on top. It doesn't have to inherit all of datetimes problems which belongs to the standard library and can't be updated easily.

So my opinion is that it is better to catch the Exception with a meaningful message or an adjusted print function. Anyway, I think I made my point clear enough. No need to repeat everything and as I said it is not the most important issue, so I'll leave it as it is :)

Matthias, I wasn't aware that the example above gave that exception - you did not give a full traceback but stated that "this bug already appeared in #168" - and I was talking about the issue in #168, showing examples and the fix for it at that time. A meaningful example with a full traceback would have spared me the time to look into that.

Anyway, you are right and I can reproduce this issue and a workaround should be possible.

OK, sorry about that. I'll provide input AND output for the minimum working example next time !

And I will update the issue template to remind me doing so! :)

no problem, sorry for all the fuzz above and while trying to fix this issue I discovered another problem with the current UTCDateTime implementation:

```
Python 3.4.3 (v3.4.3:9b73f1c3e601, Feb 24 2015, 22:44:40) [MSC v.1600 64 bit (AMD64)]

In [1]: from obspy import UTCDateTime
In [2]: UTCDateTime(1,1,1,1,1,1,1)
Out[2]: 0001-01-01T01:01:01.000000Z   # microseconds incorrect!
In [3]: UTCDateTime(1,1,1,1,1,1,1).datetime
Out[3]: datetime.datetime(1, 1, 1, 1, 1, 1)   # microseconds missing!
In [4]: from datetime import datetime
In [5]: datetime(1,1,1,1,1,1,1)
Out[5]: datetime.datetime(1, 1, 1, 1, 1, 1, 1)   # microseconds correct
```

I'm currently playing with it - I guess we are going to switch back to datetime.datetime instead of float within UTCDateTime especially since we don't have to support Python2.6 anymore ...


Resolving patch:
diff --git a/obspy/core/utcdatetime.py b/obspy/core/utcdatetime.py
--- a/obspy/core/utcdatetime.py
+++ b/obspy/core/utcdatetime.py
@@ -205,7 +205,6 @@ class UTCDateTime(object):
 
     .. _ISO8601:2004: https://en.wikipedia.org/wiki/ISO_8601
     """
-    timestamp = 0.0
     DEFAULT_PRECISION = 6
 
     def __init__(self, *args, **kwargs):
@@ -214,19 +213,27 @@ def __init__(self, *args, **kwargs):
         """
         # set default precision
         self.precision = kwargs.pop('precision', self.DEFAULT_PRECISION)
+        # set directly to nanoseconds if given
+        ns = kwargs.pop('ns', None)
+        if ns is not None:
+            self._ns = ns
+            return
         # iso8601 flag
         iso8601 = kwargs.pop('iso8601', False) is True
         # check parameter
         if len(args) == 0 and len(kwargs) == 0:
-            # use current time if no time is given
-            self.timestamp = time.time()
+            # use current date/time if no argument is given
+            self._from_timestamp(time.time())
             return
         elif len(args) == 1 and len(kwargs) == 0:
             value = args[0]
+            if isinstance(value, UTCDateTime):
+                self._ns = value._ns
+                return
             # check types
             try:
                 # got a timestamp
-                self.timestamp = value.__float__()
+                self._from_timestamp(value.__float__())
                 return
             except:
                 pass
@@ -247,7 +254,7 @@ def __init__(self, *args, **kwargs):
                 # check for ISO8601 date string
                 if value.count("T") == 1 or iso8601:
                     try:
-                        self.timestamp = self._parse_iso_8601(value).timestamp
+                        self._from_iso8601_string(value)
                         return
                     except:
                         if iso8601:
@@ -298,7 +305,8 @@ def __init__(self, *args, **kwargs):
                     self._from_datetime(dt)
                     return
                 dt = datetime.datetime.strptime(value, pattern)
-                self._from_datetime(dt, ms)
+                dt += datetime.timedelta(seconds=ms)
+                self._from_datetime(dt)
                 return
         # check for ordinal/julian date kwargs
         if 'julday' in kwargs:
@@ -322,8 +330,8 @@ def __init__(self, *args, **kwargs):
         # check if seconds are given as float value
         if len(args) == 6 and isinstance(args[5], float):
             _frac, _sec = math.modf(round(args[5], 6))
-            kwargs['microsecond'] = int(round(_frac * 1e6))
             kwargs['second'] = int(_sec)
+            kwargs['microsecond'] = int(round(_frac * 1e6))
             args = args[0:5]
         dt = datetime.datetime(*args, **kwargs)
         self._from_datetime(dt)
@@ -341,32 +349,48 @@ def _set(self, **kwargs):
         microsecond = kwargs.get('microsecond', self.microsecond)
         julday = kwargs.get('julday', None)
         if julday:
-            self.timestamp = UTCDateTime(year=year, julday=julday, hour=hour,
-                                         minute=minute, second=second,
-                                         microsecond=microsecond).timestamp
+            self._ns = UTCDateTime(year=year, julday=julday, hour=hour,
+                                   minute=minute, second=second,
+                                   microsecond=microsecond)._ns
         else:
-            self.timestamp = UTCDateTime(year, month, day, hour, minute,
-                                         second, microsecond).timestamp
+            self._ns = UTCDateTime(year, month, day, hour, minute,
+                                   second, microsecond)._ns
 
-    def _from_datetime(self, dt, ms=0):
+    def _get_ns(self):
+        return self.__ns
+
+    def _set_ns(self, value):
+        if not isinstance(value, int):
+            raise TypeError('nanoseconds must be set as int/long type')
+        self.__ns = value
+
+    _ns = property(_get_ns, _set_ns)
+
+    def _from_datetime(self, dt):
         """
         Use Python datetime object to set current time.
 
         :type dt: :class:`datetime.datetime`
         :param dt: Python datetime object.
-        :type ms: float
-        :param ms: extra seconds to add to current UTCDateTime object.
         """
         # see datetime.timedelta.total_seconds
         try:
             td = (dt - TIMESTAMP0)
         except TypeError:
             td = (dt.replace(tzinfo=None) - dt.utcoffset()) - TIMESTAMP0
-        self.timestamp = (td.microseconds + (td.seconds + td.days * 86400) *
-                          1000000) / 1000000.0 + ms
+        self._ns = \
+            (td.days * 86400 + td.seconds) * 10**9 + td.microseconds * 1000
 
-    @staticmethod
-    def _parse_iso_8601(value):
+    def _from_timestamp(self, value):
+        """
+        Use given timestamp to set current time.
+
+        :type value: int, float
+        :param value: Timestamp in seconds.
+        """
+        self._ns = int(round(value * 10**9))
+
+    def _from_iso8601_string(self, value):
         """
         Parses an ISO8601:2004 date time string.
         """
@@ -447,7 +471,8 @@ def _parse_iso_8601(value):
         dt = datetime.datetime.strptime(date + 'T' + time,
                                         date_pattern + 'T' + time_pattern)
         # add microseconds and eventually correct time zone
-        return UTCDateTime(dt) + (float(delta) + ms)
+        dt += datetime.timedelta(seconds=float(delta) + ms)
+        self._from_datetime(dt)
 
     def _get_timestamp(self):
         """
@@ -462,7 +487,9 @@ def _get_timestamp(self):
         >>> dt.timestamp
         1222864235.123456
         """
-        return self.timestamp
+        return self._ns / 1e9
+
+    timestamp = property(_get_timestamp)
 
     def __float__(self):
         """
@@ -494,7 +521,9 @@ def _get_datetime(self):
         """
         # datetime.utcfromtimestamp will cut off but not round
         # avoid through adding timedelta - also avoids the year 2038 problem
-        return TIMESTAMP0 + datetime.timedelta(seconds=self.timestamp)
+        dt = datetime.timedelta(seconds=self._ns // 10**9,
+                                microseconds=self._ns % 10**9 // 1000)
+        return TIMESTAMP0 + dt
 
     datetime = property(_get_datetime)
 
@@ -511,7 +540,7 @@ def _get_date(self):
         >>> dt.date
         datetime.date(2008, 10, 1)
         """
-        return self._get_datetime().date()
+        return self.datetime.date()
 
     date = property(_get_date)
 
@@ -528,7 +557,7 @@ def _get_year(self):
         >>> dt.year
         2012
         """
-        return self._get_datetime().year
+        return self.datetime.year
 
     def _set_year(self, value):
         """
@@ -562,7 +591,7 @@ def _get_month(self):
         >>> dt.month
         2
         """
-        return self._get_datetime().month
+        return self.datetime.month
 
     def _set_month(self, value):
         """
@@ -595,7 +624,7 @@ def _get_day(self):
         >>> dt.day
         11
         """
-        return self._get_datetime().day
+        return self.datetime.day
 
     def _set_day(self, value):
         """
@@ -629,7 +658,7 @@ def _get_weekday(self):
         >>> dt.weekday
         2
         """
-        return self._get_datetime().weekday()
+        return self.datetime.weekday()
 
     weekday = property(_get_weekday)
 
@@ -646,7 +675,7 @@ def _get_time(self):
         >>> dt.time
         datetime.time(12, 30, 35, 45020)
         """
-        return self._get_datetime().time()
+        return self.datetime.time()
 
     time = property(_get_time)
 
@@ -663,7 +692,7 @@ def _get_hour(self):
         >>> dt.hour
         10
         """
-        return self._get_datetime().hour
+        return self.datetime.hour
 
     def _set_hour(self, value):
         """
@@ -696,7 +725,7 @@ def _get_minute(self):
         >>> dt.minute
         11
         """
-        return self._get_datetime().minute
+        return self.datetime.minute
 
     def _set_minute(self, value):
         """
@@ -729,7 +758,7 @@ def _get_second(self):
         >>> dt.second
         12
         """
-        return self._get_datetime().second
+        return self.datetime.second
 
     def _set_second(self, value):
         """
@@ -745,7 +774,7 @@ def _set_second(self, value):
         >>> dt
         UTCDateTime(2012, 2, 11, 10, 11, 20)
         """
-        self.timestamp += value - self.second
+        self._set(second=value)
 
     second = property(_get_second, _set_second)
 
@@ -762,7 +791,7 @@ def _get_microsecond(self):
         >>> dt.microsecond
         345234
         """
-        return self._get_datetime().microsecond
+        return int(self._ns % 10**9 // 1000)
 
     def _set_microsecond(self, value):
         """
@@ -821,7 +850,7 @@ def timetuple(self):
 
         :rtype: time.struct_time
         """
-        return self._get_datetime().timetuple()
+        return self.datetime.timetuple()
 
     def utctimetuple(self):
         """
@@ -829,7 +858,7 @@ def utctimetuple(self):
 
         :rtype: time.struct_time
         """
-        return self._get_datetime().utctimetuple()
+        return self.datetime.utctimetuple()
 
     def __add__(self, value):
         """
@@ -852,12 +881,12 @@ def __add__(self, value):
         if isinstance(value, datetime.timedelta):
             # see datetime.timedelta.total_seconds
             value = (value.microseconds + (value.seconds + value.days *
-                     86400) * 1000000) / 1000000.0
+                     86400) * 10**6) / 1e6
         elif isinstance(value, UTCDateTime):
             msg = ("unsupported operand type(s) for +: 'UTCDateTime' and "
                    "'UTCDateTime'")
             raise TypeError(msg)
-        return UTCDateTime(self.timestamp + value)
+        return UTCDateTime(ns=self._ns + int(round(value * 1e9)))
 
     def __sub__(self, value):
         """
@@ -882,12 +911,12 @@ def __sub__(self, value):
         86400.0
         """
         if isinstance(value, UTCDateTime):
-            return round(self.timestamp - value.timestamp, self.__precision)
+            return round((self._ns - value._ns) / 1e9, self.__precision)
         elif isinstance(value, datetime.timedelta):
             # see datetime.timedelta.total_seconds
             value = (value.microseconds + (value.seconds + value.days *
-                     86400) * 1000000) / 1000000.0
-        return UTCDateTime(self.timestamp - value)
+                     86400) * 10**6) / 1e6
+        return UTCDateTime(ns=self._ns - int(round(value * 1e9)))
 
     def __str__(self):
         """
@@ -901,8 +930,12 @@ def __str__(self):
         >>> str(dt)
         '2008-10-01T12:30:35.045020Z'
         """
-        return "%s%sZ" % (self.strftime('%Y-%m-%dT%H:%M:%S'),
-                          (self.__ms_pattern % (abs(self.timestamp % 1)))[1:])
+        dt = self.datetime
+        pattern = "%%.%dlf" % (self.precision)
+        ns = pattern % ((self._ns % 10**9) / 1e9)
+        return "%04d-%02d-%02dT%02d:%02d:%02d.%sZ" % (
+            dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second,
+            ns[2:self.precision + 2])
 
     def _repr_pretty_(self, p, cycle):
         p.text(str(self))
@@ -1102,7 +1135,7 @@ def __repr__(self):
         """
         Returns a representation of UTCDatetime object.
         """
-        return 'UTCDateTime' + self._get_datetime().__repr__()[17:]
+        return 'UTCDateTime' + self.datetime.__repr__()[17:]
 
     def __abs__(self):
         """
@@ -1135,7 +1168,7 @@ def strftime(self, format):
         See methods :meth:`~datetime.datetime.strftime()` and
         :meth:`~datetime.datetime.strptime()` for more information.
         """
-        return self._get_datetime().strftime(format)
+        return self.datetime.strftime(format)
 
     @staticmethod
     def strptime(date_string, format):
@@ -1165,7 +1198,7 @@ def timetz(self):
         >>> dt.timetz()
         datetime.time(12, 30, 35, 45020)
         """
-        return self._get_datetime().timetz()
+        return self.datetime.timetz()
 
     def utcoffset(self):
         """
@@ -1176,7 +1209,7 @@ def utcoffset(self):
         >>> dt = UTCDateTime(2008, 10, 1, 12, 30, 35, 45020)
         >>> dt.utcoffset()
         """
-        return self._get_datetime().utcoffset()
+        return self.datetime.utcoffset()
 
     def dst(self):
         """
@@ -1187,7 +1220,7 @@ def dst(self):
         >>> dt = UTCDateTime(2008, 10, 1, 12, 30, 35, 45020)
         >>> dt.dst()
         """
-        return self._get_datetime().dst()
+        return self.datetime.dst()
 
     def tzname(self):
         """
@@ -1198,7 +1231,7 @@ def tzname(self):
         >>> dt = UTCDateTime(2008, 10, 1, 12, 30, 35, 45020)
         >>> dt.tzname()
         """
-        return self._get_datetime().tzname()
+        return self.datetime.tzname()
 
     def ctime(self):
         """
@@ -1209,7 +1242,7 @@ def ctime(self):
         >>> UTCDateTime(2002, 12, 4, 20, 30, 40).ctime()
         'Wed Dec  4 20:30:40 2002'
         """
-        return self._get_datetime().ctime()
+        return self.datetime.ctime()
 
     def isoweekday(self):
         """
@@ -1225,7 +1258,7 @@ def isoweekday(self):
         >>> dt.isoweekday()
         3
         """
-        return self._get_datetime().isoweekday()
+        return self.datetime.isoweekday()
 
     def isocalendar(self):
         """
@@ -1241,7 +1274,7 @@ def isocalendar(self):
         >>> dt.isocalendar()
         (2008, 40, 3)
         """
-        return self._get_datetime().isocalendar()
+        return self.datetime.isocalendar()
 
     def isoformat(self, sep="T"):
         """
@@ -1262,7 +1295,7 @@ def isoformat(self, sep="T"):
         >>> dt.isoformat()
         '2008-10-01T00:00:00'
         """
-        return self._get_datetime().isoformat(sep=native_str(sep))
+        return self.datetime.isoformat(sep=native_str(sep))
 
     def format_fissures(self):
         """
@@ -1343,7 +1376,7 @@ def format_seed(self, compact=False):
         temp = "%04d,%03d" % (self.year, self.julday)
         if self.time == datetime.time(0):
             return temp
-        temp += ",%02d" % self.hour
+        temp += ",%02d" % (self.hour)
         if self.microsecond:
             return temp + ":%02d:%02d.%04d" % (self.minute, self.second,
                                                self.microsecond // 100)
@@ -1413,7 +1446,6 @@ def _set_precision(self, value=6):
             12
         """
         self.__precision = int(value)
-        self.__ms_pattern = "%%0.%df" % (self.__precision)
 
     precision = property(_get_precision, _set_precision)
 
@@ -1431,7 +1463,7 @@ def toordinal(self):
         >>> dt.toordinal()
         734503
         """
-        return self._get_datetime().toordinal()
+        return self.datetime.toordinal()
 
     @staticmethod
     def now():
diff --git a/obspy/io/gcf/libgcf.py b/obspy/io/gcf/libgcf.py
--- a/obspy/io/gcf/libgcf.py
+++ b/obspy/io/gcf/libgcf.py
@@ -68,8 +68,9 @@ def decode_date_time(data):
     Bits 0-16 contain the number of seconds since midnight,
     and bits 17-31 the number of days since 17th November 1989.
     """
-    days = data >> 17
-    secs = data & 0x1FFFF
+    # prevent numpy array
+    days = int(data >> 17)
+    secs = int(data & 0x1FFFF)
     starttime = UTCDateTime('1989-11-17') + days * 86400 + secs
     return starttime
 
diff --git a/obspy/io/zmap/__init__.py b/obspy/io/zmap/__init__.py
--- a/obspy/io/zmap/__init__.py
+++ b/obspy/io/zmap/__init__.py
@@ -22,8 +22,8 @@
 >>> cat = read_events('/path/to/zmap_events.txt')
 >>> print(cat)
 2 Event(s) in Catalog:
-2012-04-04T14:21:42.299999Z | +41.818,  +79.689 | 4.4 None
-2012-04-04T14:21:42.299999Z | +41.822,  +79.684 | 5.1 None
+2012-04-04T14:21:42.300000Z | +41.818,  +79.689 | 4.4 None
+2012-04-04T14:21:42.300000Z | +41.822,  +79.684 | 5.1 None
 >>> cat.write('example.txt', format='ZMAP')  # doctest: +SKIP
 
 


Test patch:
diff --git a/obspy/core/tests/test_utcdatetime.py b/obspy/core/tests/test_utcdatetime.py
--- a/obspy/core/tests/test_utcdatetime.py
+++ b/obspy/core/tests/test_utcdatetime.py
@@ -12,15 +12,6 @@
 from obspy import UTCDateTime
 
 
-# some Python version don't support negative timestamps
-NO_NEGATIVE_TIMESTAMPS = False
-try:  # pragma: no cover
-    # this will fail at Win OS
-    UTCDateTime(-44000).datetime
-except:  # pragma: no cover
-    NO_NEGATIVE_TIMESTAMPS = True
-
-
 class UTCDateTimeTestCase(unittest.TestCase):
     """
     Test suite for obspy.core.utcdatetime.UTCDateTime.
@@ -319,15 +310,11 @@ def test_sub(self):
         end = UTCDateTime(2000, 1, 1, 0, 0, 1, 1)
         self.assertAlmostEqual(end - start, 0.000002, 6)
 
-    @unittest.skipIf(NO_NEGATIVE_TIMESTAMPS, 'times before 1970 are not '
-                                             'supported')
     def test_negative_timestamp(self):
         dt = UTCDateTime(-1000.1)
         self.assertEqual(str(dt), "1969-12-31T23:43:19.900000Z")
         self.assertEqual(dt.timestamp, -1000.1)
 
-    @unittest.skipIf(NO_NEGATIVE_TIMESTAMPS, 'times before 1970 are not '
-                                             'supported')
     def test_sub_with_negative_time_stamp(self):
         start = UTCDateTime(0)
         end = UTCDateTime(-1000.5)
@@ -365,10 +352,10 @@ def test_small_negative_utc_date_time(self):
         dt = UTCDateTime(-0.000001)
         self.assertAlmostEqual(dt.timestamp, -0.000001, 6)
         self.assertEqual(str(dt), "1969-12-31T23:59:59.999999Z")
-        # -0.00000000001
-        dt = UTCDateTime(-0.00000000001)
-        self.assertEqual(dt.timestamp, -0.00000000001)
-        self.assertEqual(str(dt), "1970-01-01T00:00:00.000000Z")
+        # -0.000000001 - max precision is nanosecond!
+        dt = UTCDateTime(-0.000000001)
+        self.assertEqual(dt.timestamp, -0.000000001)
+        self.assertEqual(str(dt), "1969-12-31T23:59:59.000000Z")
         # -1000.1
         dt = UTCDateTime("1969-12-31T23:43:19.900000Z")
         self.assertEqual(dt.timestamp, -1000.1)
@@ -378,8 +365,6 @@ def test_small_negative_utc_date_time(self):
         self.assertAlmostEqual(dt.timestamp, -43199.123456, 6)
         self.assertEqual(str(dt), "1969-12-31T12:00:00.876544Z")
 
-    @unittest.skipIf(NO_NEGATIVE_TIMESTAMPS, 'times before 1970 are not '
-                                             'supported')
     def test_big_negative_utc_date_time(self):
         # 1
         dt = UTCDateTime("1969-12-31T23:43:19.900000Z")
@@ -494,8 +479,6 @@ def test_invalid_times(self):
         self.assertRaises(ValueError, UTCDateTime, "2010-02-13T99999")
         self.assertRaises(TypeError, UTCDateTime, "2010-02-13T02:09:09.XXXXX")
 
-    @unittest.skipIf(NO_NEGATIVE_TIMESTAMPS, 'times before 1970 are not '
-                                             'supported')
     def test_issue_168(self):
         """
         Couldn't calculate julday before 1900.
@@ -1026,6 +1009,92 @@ def test_add_error_message(self):
             "unsupported operand type(s) for +: 'UTCDateTime' and "
             "'UTCDateTime'")
 
+    def test_nanoseconds(self):
+        """
+        Various nanosecond tests.
+
+        Also tests #1318.
+        """
+        # 1
+        dt = UTCDateTime(1, 1, 1, 0, 0, 0, 0)
+        self.assertEqual(dt._ns, -62135596800000000000)
+        self.assertEqual(dt.timestamp, -62135596800.0)
+        self.assertEqual(dt.microsecond, 0)
+        self.assertEqual(dt.datetime, datetime.datetime(1, 1, 1, 0, 0, 0, 0))
+        self.assertEqual(str(dt), '0001-01-01T00:00:00.000000Z')
+        # 2
+        dt = UTCDateTime(1, 1, 1, 0, 0, 0, 1)
+        self.assertEqual(dt._ns, -62135596799999999000)
+        self.assertEqual(dt.microsecond, 1)
+        self.assertEqual(dt.datetime, datetime.datetime(1, 1, 1, 0, 0, 0, 1))
+        self.assertEqual(str(dt), '0001-01-01T00:00:00.000001Z')
+        self.assertEqual(dt.timestamp, -62135596800.000001)
+        # 3
+        dt = UTCDateTime(1, 1, 1, 0, 0, 0, 999999)
+        self.assertEqual(dt._ns, -62135596799000001000)
+        # dt.timestamp should be -62135596799.000001 - not possible to display
+        # correctly using python floats
+        self.assertEqual(dt.timestamp, -62135596799.0)
+        self.assertEqual(dt.microsecond, 999999)
+        self.assertEqual(dt.datetime,
+                         datetime.datetime(1, 1, 1, 0, 0, 0, 999999))
+        self.assertEqual(str(dt), '0001-01-01T00:00:00.999999Z')
+        # 4
+        dt = UTCDateTime(1, 1, 1, 0, 0, 1, 0)
+        self.assertEqual(dt._ns, -62135596799000000000)
+        self.assertEqual(dt.timestamp, -62135596799.0)
+        self.assertEqual(dt.microsecond, 0)
+        self.assertEqual(dt.datetime,
+                         datetime.datetime(1, 1, 1, 0, 0, 1, 0))
+        self.assertEqual(str(dt), '0001-01-01T00:00:01.000000Z')
+        # 5
+        dt = UTCDateTime(1, 1, 1, 0, 0, 1, 1)
+        self.assertEqual(dt._ns, -62135596798999999000)
+        # dt.timestamp should be -62135596799.000001 - not possible to display
+        # correctly using python floats
+        self.assertEqual(dt.timestamp, -62135596799.0)
+        self.assertEqual(dt.microsecond, 1)
+        self.assertEqual(dt.datetime,
+                         datetime.datetime(1, 1, 1, 0, 0, 1, 1))
+        self.assertEqual(str(dt), '0001-01-01T00:00:01.000001Z')
+        # 6
+        dt = UTCDateTime(1970, 1, 1, 0, 0, 0, 1)
+        self.assertEqual(dt._ns, 1000)
+        self.assertEqual(dt.timestamp, 0.000001)
+        self.assertEqual(dt.microsecond, 1)
+        self.assertEqual(dt.datetime,
+                         datetime.datetime(1970, 1, 1, 0, 0, 0, 1))
+        self.assertEqual(str(dt), '1970-01-01T00:00:00.000001Z')
+        # 7
+        dt = UTCDateTime(1970, 1, 1, 0, 0, 0, 999999)
+        self.assertEqual(dt._ns, 999999000)
+        self.assertEqual(dt.timestamp, 0.999999)
+        self.assertEqual(dt.microsecond, 999999)
+        self.assertEqual(dt.datetime,
+                         datetime.datetime(1970, 1, 1, 0, 0, 0, 999999))
+        self.assertEqual(str(dt), '1970-01-01T00:00:00.999999Z')
+        # 8
+        dt = UTCDateTime(3000, 1, 1, 0, 0, 0, 500000)
+        self.assertEqual(dt._ns, 32503680000500000000)
+        self.assertEqual(dt.timestamp, 32503680000.5)
+        self.assertEqual(dt.microsecond, 500000)
+        self.assertEqual(dt.datetime,
+                         datetime.datetime(3000, 1, 1, 0, 0, 0, 500000))
+        self.assertEqual(str(dt), '3000-01-01T00:00:00.500000Z')
+        # 9
+        dt = UTCDateTime(9999, 1, 1, 0, 0, 0, 500000)
+        self.assertEqual(dt._ns, 253370764800500000000)
+        self.assertEqual(dt.timestamp, 253370764800.5)
+        self.assertEqual(dt.microsecond, 500000)
+        self.assertEqual(dt.datetime,
+                         datetime.datetime(9999, 1, 1, 0, 0, 0, 500000))
+        self.assertEqual(str(dt), '9999-01-01T00:00:00.500000Z')
+
+    def test_utcdatetime_from_utcdatetime(self):
+        a = UTCDateTime(1, 1, 1, 1, 1, 1, 999999)
+        self.assertEqual(UTCDateTime(a)._ns, a._ns)
+        self.assertEqual(str(UTCDateTime(a)), str(a))
+
 
 def suite():
     return unittest.makeSuite(UTCDateTimeTestCase, 'test')
diff --git a/obspy/signal/tests/test_trigger.py b/obspy/signal/tests/test_trigger.py
--- a/obspy/signal/tests/test_trigger.py
+++ b/obspy/signal/tests/test_trigger.py
@@ -383,18 +383,26 @@ def test_coincidence_trigger_with_similarity_checking(self):
             # two warnings get raised
             self.assertEqual(len(w), 2)
         # check floats in resulting dictionary separately
-        self.assertAlmostEqual(trig[0].pop('duration'), 3.9600000381469727)
-        self.assertAlmostEqual(trig[1].pop('duration'), 1.9900000095367432)
-        self.assertAlmostEqual(trig[2].pop('duration'), 1.9200000762939453)
-        self.assertAlmostEqual(trig[3].pop('duration'), 3.9200000762939453)
-        self.assertAlmostEqual(trig[0]['similarity'].pop('UH1'), 0.94149447384)
-        self.assertAlmostEqual(trig[0]['similarity'].pop('UH3'), 1)
-        self.assertAlmostEqual(trig[1]['similarity'].pop('UH1'), 0.65228204570)
-        self.assertAlmostEqual(trig[1]['similarity'].pop('UH3'), 0.72679293429)
-        self.assertAlmostEqual(trig[2]['similarity'].pop('UH1'), 0.89404458774)
-        self.assertAlmostEqual(trig[2]['similarity'].pop('UH3'), 0.74581409371)
-        self.assertAlmostEqual(trig[3]['similarity'].pop('UH1'), 1)
-        self.assertAlmostEqual(trig[3]['similarity'].pop('UH3'), 1)
+        self.assertAlmostEqual(trig[0].pop('duration'), 3.96, places=6)
+        self.assertAlmostEqual(trig[1].pop('duration'), 1.99, places=6)
+        self.assertAlmostEqual(trig[2].pop('duration'), 1.92, places=6)
+        self.assertAlmostEqual(trig[3].pop('duration'), 3.92, places=6)
+        self.assertAlmostEqual(trig[0]['similarity'].pop('UH1'),
+                               0.94149447384, places=6)
+        self.assertAlmostEqual(trig[0]['similarity'].pop('UH3'), 1,
+                               places=6)
+        self.assertAlmostEqual(trig[1]['similarity'].pop('UH1'),
+                               0.65228204570, places=6)
+        self.assertAlmostEqual(trig[1]['similarity'].pop('UH3'),
+                               0.72679293429, places=6)
+        self.assertAlmostEqual(trig[2]['similarity'].pop('UH1'),
+                               0.89404458774, places=6)
+        self.assertAlmostEqual(trig[2]['similarity'].pop('UH3'),
+                               0.74581409371, places=6)
+        self.assertAlmostEqual(trig[3]['similarity'].pop('UH1'), 1,
+                               places=6)
+        self.assertAlmostEqual(trig[3]['similarity'].pop('UH3'), 1,
+                               places=6)
         remaining_results = \
             [{'coincidence_sum': 4.0,
               'similarity': {},