diff --git a/obspy/io/sac/arrayio.py b/obspy/io/sac/arrayio.py
index c63b9d2..fd641b2 100644
--- a/obspy/io/sac/arrayio.py
+++ b/obspy/io/sac/arrayio.py
@@ -621,10 +621,10 @@ def validate_sac_content(hf, hi, hs, data, *tests):
                 hdr = 'b'
             elif iztype_val == 11:
                 hdr = 'o'
-            elif val == 12:
+            elif iztype_val == 12:
                 hdr = 'a'
-            elif val in range(13, 23):
-                hdr = 'it'+str(val-13)
+            elif iztype_val in range(13, 23):
+                hdr = 'it' + str(iztype_val - 13)
 
             if hi[HD.FLOATHDRS.index(hdr)] == HD.INULL:
                 msg = "Reference header '{}' for iztype '{}' not set."
diff --git a/obspy/io/sac/sactrace.py b/obspy/io/sac/sactrace.py
index af8b891..26c517c 100644
--- a/obspy/io/sac/sactrace.py
+++ b/obspy/io/sac/sactrace.py
@@ -557,7 +557,7 @@ def _set_lcalda(self, value):
     # make and use a bool setter for lcalda
     lcalda_setter = _boolsetter('lcalda')
     lcalda_setter(self, value)
-    # try to set set distances if value evaluates to True
+    # try to set set distances if "value" evaluates to True
     if value:
         try:
             self._set_distances()
@@ -804,6 +804,8 @@ class SACTrace(object):
         self._hs = hs
         self.data = data
 
+        self._set_distances()
+
     # ---------------------------- SET UP HEADERS -----------------------------
     # SAC header values are set up as attributes, with getters and setters
     # format: header = property(getter_function, setter_function)
@@ -1539,6 +1541,7 @@ class SACTrace(object):
     def _set_distances(self, force=False):
         """
         Calculate dist, az, baz, gcarc.  If force=True, ignore lcalda.
+        Raises SacHeaderError if force=True and geographic headers are unset.
 
         """
         if self.lcalda or force:
@@ -1547,14 +1550,13 @@ class SACTrace(object):
                                               self.stlo)
                 dist = m / 1000.0
                 gcarc = kilometer2degrees(dist)
-                self.az = az
-                self.baz = baz
-                self.dist = dist
-                self.gcarc = gcarc
-            except TypeError:
-                # one of the geographic values is None
-                msg = "Not enough information to calculate distance, azimuth."
-                raise SacHeaderError(msg)
-        else:
-            msg = "lcalda is False or unset. To set distances, set it to True."
-            raise SacError(msg)
+                self._hf[HD.FLOATHDRS.index('az')] = az
+                self._hf[HD.FLOATHDRS.index('baz')] = baz
+                self._hf[HD.FLOATHDRS.index('dist')] = dist
+                self._hf[HD.FLOATHDRS.index('gcarc')] = gcarc
+            except (ValueError, TypeError):
+                # one or more of the geographic values is None
+                if force:
+                    msg = ("Not enough information to calculate distance, "
+                           "azimuth.")
+                    raise SacHeaderError(msg)
diff --git a/obspy/io/sac/tests/test_core.py b/obspy/io/sac/tests/test_core.py
index 66199d7..bffae8c 100644
--- a/obspy/io/sac/tests/test_core.py
+++ b/obspy/io/sac/tests/test_core.py
@@ -804,6 +804,27 @@ class CoreTestCase(unittest.TestCase):
         self.assertEqual(tr1.stats.sac.stla, 1.0)
         self.assertEqual(tr1.stats.sac.stlo, 2.0)
 
+    def test_merge_sac_obspy_headers(self):
+        """
+        Test that manually setting a set of SAC headers not related
+        to validity or reference time on Trace.stats.sac is properly merged
+        with the Trace.stats header. Issue 1285.
+        """
+        tr = Trace(data=np.arange(30))
+        o = 10.0
+        tr.stats.sac = {'o': o}
+
+        with NamedTemporaryFile() as tf:
+            tempfile = tf.name
+            with warnings.catch_warnings(record=True) as w:
+                warnings.simplefilter('always')
+                tr.write(tempfile, format='SAC')
+                self.assertEqual(len(w), 1)
+            tr1 = read(tempfile)[0]
+
+        self.assertEqual(tr1.stats.starttime, tr.stats.starttime)
+        self.assertEqual(tr1.stats.sac.o, o)
+
 
 def suite():
     return unittest.makeSuite(CoreTestCase, 'test')
diff --git a/obspy/io/sac/tests/test_sactrace.py b/obspy/io/sac/tests/test_sactrace.py
index 923ffee..fcc0baa 100644
--- a/obspy/io/sac/tests/test_sactrace.py
+++ b/obspy/io/sac/tests/test_sactrace.py
@@ -11,8 +11,10 @@ import numpy as np
 
 from obspy import UTCDateTime
 from obspy.core.util import NamedTemporaryFile
+from obspy.geodetics import gps2dist_azimuth, kilometer2degrees
 
 from ..sactrace import SACTrace
+from ..util import SacHeaderError
 
 
 class SACTraceTestCase(unittest.TestCase):
@@ -175,6 +177,41 @@ class SACTraceTestCase(unittest.TestCase):
         self.assertAlmostEqual(sac.b, b - 1.0e-3, 6)
         self.assertAlmostEqual(sac.t1, t1 - 1.0e-3, 5)
 
+    def test_lcalda(self):
+        """
+        Test that distances are set when geographic information is present and
+        lcalda is True, and that they're not set when geographic information
+        is missing or lcalca is false.
+        """
+        stla, stlo, evla, evlo = -35.0, 100, 42.5, -37.5
+        meters, az, baz = gps2dist_azimuth(evla, evlo, stla, stlo)
+        km = meters / 1000.0
+        gcarc = kilometer2degrees(km)
+
+        # distances are set when lcalda True and all distance values set
+        sac = SACTrace(lcalda=True, stla=stla, stlo=stlo, evla=evla, evlo=evlo)
+        self.assertAlmostEqual(sac.az, az, places=4)
+        self.assertAlmostEqual(sac.baz, baz, places=4)
+        self.assertAlmostEqual(sac.dist, km, places=4)
+        self.assertAlmostEqual(sac.gcarc, gcarc, places=4)
+        # distances are not set when lcalda False and all distance values set
+        sac = SACTrace(lcalda=False, stla=stla, stlo=stlo, evla=evla,
+                       evlo=evlo)
+        self.assertIs(sac.az, None)
+        self.assertIs(sac.baz, None)
+        self.assertIs(sac.dist, None)
+        self.assertIs(sac.gcarc, None)
+        # distances are not set when lcalda True, not all distance values set
+        sac = SACTrace(lcalda=True, stla=stla)
+        self.assertIs(sac.az, None)
+        self.assertIs(sac.baz, None)
+        self.assertIs(sac.dist, None)
+        self.assertIs(sac.gcarc, None)
+        # exception raised when set_distances is forced but not all distances
+        # values are set. NOTE: still have a problem when others are "None".
+        sac = SACTrace(lcalda=True, stla=stla)
+        self.assertRaises(SacHeaderError, sac._set_distances, force=True)
+
 
 def suite():
     return unittest.makeSuite(SACTraceTestCase, 'test')
diff --git a/obspy/io/sac/util.py b/obspy/io/sac/util.py
index 02b9be2..dad76e9 100644
--- a/obspy/io/sac/util.py
+++ b/obspy/io/sac/util.py
@@ -288,11 +288,18 @@ def obspy_to_sac_header(stats, keep_sac_header=True):
         header values are kept, and a minimal set of values are updated from
         the stats dictionary according to these guidelines:
         * npts, delta always come from stats
-        * If an old reftime is found and valid, the new b and e will be made
-          and properly referenced to it. If the SAC reftime is invalid, the
-          reftime will be set from stats.starttime (with micro/milliseconds
-          precision adjustments) only if an existing SAC iztype is 9 and no
-          other relative time headers are set.
+        * If a valid old reftime is found, the new b and e will be made
+          and properly referenced to it. All other old SAC headers are simply
+          carried along.
+        * If the old SAC reftime is invalid and relative time headers are set,
+          a SacHeaderError exception will be raised.
+        * If the old SAC reftime is invalid, no relative time headers are set,
+          and "b" is set, "e" is updated from stats and other old SAC headers
+          are carried along.
+        * If the old SAC reftime is invalid, no relative time headers are set,
+          and "b" is not set, the reftime will be set from stats.starttime
+          (with micro/milliseconds precision adjustments) and "b" and "e" are
+          set accordingly.
         * If 'kstnm', 'knetwk', 'kcmpnm', or 'khole' are not set, they are
           taken from 'station', 'network', 'channel', and 'location' in stats.
         If keep_sac_header is False, a new SAC header is constructed from only
@@ -310,45 +317,58 @@ def obspy_to_sac_header(stats, keep_sac_header=True):
     header['delta'] = stats['delta']
 
     if keep_sac_header and oldsac:
-        # start with the old header, and only update a minimal set of headers
+        # start with the old header
         header.update(oldsac)
 
-        # try to set "b" and "e"
-        # NOTE: if you don't know the old absolute first sample time, you don't
-        # know the difference btwn the old SAC 1st sample time and the current
-        # stats.starttime (in the case of Trace merging or trimming). If the
-        # old iztype was 9, knowing this shift is required in order to keep SAC
-        # relative time headers valid.
-        # XXX: what about synthetic data, sac funcgen files?
+        # The Plan:
+        # 1. Try to get the old SAC reference time.  If you have this, all the
+        #    old SAC header can be kept, with no loss of information.
+        # 2. If the old reftime isn't valid, we can still keep most of the old
+        #    SAC header, just not the relative time headers.  Without a valid
+        #    SAC reftime, their absolute times have been lost, and they can't
+        #    be referenced correctly to anything in the Trace.stats header.
+
         try:
+            # Try to set "b" and "e" using the old SAC 'b' and reftime.
+            # NOTE: if "b" or "e" were null, they will become set here.
             reftime = get_sac_reftime(header)
             # reftime + b is the old first sample time
             b = stats['starttime'] - reftime
-            # NOTE: if b or e is null, it will become set here.
             header['b'] = b
             header['e'] = b + (stats['endtime'] - stats['starttime'])
         except SacHeaderTimeError:
-            # can't determine reftime or absolute time shift.
-            msg = "Old header has invalid reftime."
-            warnings.warn(msg)
-
-            # If no relative headers are set and the iztype was 9, we're OK to
-            # use stats.starttime as the reftime, and set b, e
+            # Can't determine reftime. Any relative time headers are lost.
+            # If none are set, and the "iztype" is 9 or null, we will assume
+            # that user intends use the Trace.stats starttime as the reftime.
             # ObsPy issue 1204
-            # TODO: consolidate relative-time header list in header.py
+
+            # If there are relative times, even if there's a "b", throw an
+            #   error/warning
+            # If there's a "b" and no relative times, use it and proceed
+            # If there's no "b" and no relative times, write it like it's an
+            #   "ib" type file and back away slowly
+
             relhdrs = ['t' + str(i) for i in range(10)] + ['a', 'f']
-            nr = all([header.get(hdr) in (None, HD.SNULL) for hdr in relhdrs])
-            if header.get('iztype') == 9 and nr:
+            has_relhdrs = any([header.get(hdr) not in (None, HD.SNULL)
+                               for hdr in relhdrs])
+            has_b = header.get('b') not in (None, HD.FNULL)
+            if has_relhdrs:
+                msg = ("Invalid reference time, unrecoverable relative time"
+                       " headers.")
+                raise SacHeaderError(msg)
+            elif has_b:
+                header['e'] = header['b'] + (stats['endtime'] -
+                                             stats['starttime'])
+            else:
                 reftime = stats['starttime']
                 nztimes, microsecond = utcdatetime_to_sac_nztimes(reftime)
                 header.update(nztimes)
                 header['b'] = microsecond * 1e-6
                 header['e'] = header['b'] +\
                     (header['npts'] - 1) * header['delta']
-        except (KeyError, TypeError):
-            # b isn't present or is -12345.0
-            # TODO: is this needed anymore?
-            pass
+
+            msg = "Old header has invalid reftime."
+            warnings.warn(msg)
 
         # merge some values from stats if they're missing in the SAC header
         # ObsPy issue 1204
