Repository: obspy/obspy

Issue:
Trim function
I have a SAC file with a length major than 1 day. When I cut the file into a 1-day length, the "stats.sac" information is the same that the original file. How I can update this information?

Thank you

Miguel



Discussion:
When you write the trimmed trace to a new SAC file, is that header correct?  

`Trace.trim` modifies the Obspy headers, but doesn't know about the `.stats.sac` header, so it can't modify it.  When you write to a file and specify SAC, though, the SAC plugin takes responsibility to rectify the current Obspy header and the output SAC file header.  It should be correct in the written file.
I solved the problem. When I wrote the SAC file, the stats.sac information was not modified. I added 'st2[0].stats.sac = AttribDict({})' before writing the SAC file and it worked.
Best 
Miguel
If you have to delete the `.stats.sac` header in order to write a valid header, that sounds like a bug.  I'm happy to look into this further with you, if you'd like.  Pasting the full `.stats` and `.stats.sac` headers before and after trimming and writing the file would be helpful to diagnose.  If you'd prefer to just close the issue, I won't push further:-) Thanks for raising the issue, regardless.
This is the "stats" information of the trimmed object:

```python
Stats(
    {
        "sampling_rate": 200.0,
        "delta": 0.005,
        "starttime": UTCDateTime(2008, 1, 1, 0, 0),
        "endtime": UTCDateTime(2008, 1, 1, 23, 59, 59, 995000),
        "npts": 17280000,
        "calib": 1.0,
        "network": "Y9",
        "station": "ALG",
        "location": "",
        "channel": "HHE",
        "sac": AttribDict(
            {
                "delta": 0.0049999999,
                "depmin": -55782.0,
                "depmax": 54891.0,
                "scale": 1.0,
                "b": 0.0,
                "e": 87614.445,
                "stla": -22.85915,
                "stlo": -69.80658,
                "stel": 1407.0,
                "stdp": 0.0,
                "depmen": 140.41512,
                "cmpaz": 90.0,
                "cmpinc": 0.0,
                "nzyear": 2007,
                "nzjday": 365,
                "nzhour": 23,
                "nzmin": 49,
                "nzsec": 55,
                "nzmsec": 910,
                "nvhdr": 6,
                "npts": 17522890,
                "iftype": 1,
                "iztype": 9,
                "leven": 1,
                "lpspol": 1,
                "lovrok": 1,
                "lcalda": 0,
                "kstnm": "ALG",
                "kcmpnm": "HHE",
                "knetwk": "Y9",
            }
        ),
        "_format": "SAC",
        "processing": [
            "ObsPy 1.2.2: trim(endtime=UTCDateTime(2008, 1, 1, 23, 59, 59, 995000)::fill_value=None::nearest_sample=True::pad=False::starttime=UTCDateTime(2008,1, 1, 0, 0))"
        ],
    }
)

```

if you observe the stats.starttime is not the same that the stats.sac information. The stats.sac information is the same that the original file.

If you write this SAC file and open with sac iris, the information of the file is the stats.sac.
Best Miguel

*edit: formatting*
Flagging this as a bug for now until we can be sure it isn't.
@msaezuchile When I save the trimmed `Trace` as a SAC file, I also see that the `nz`-times haven't changed, but I do see that the `b` value does reflect the trimming.  No relative timing information has been lost, it's just that the reference time doesn't update to the first sample time.  Do you see that `b` is `604.09`, equal to the time that has been trimmed off the front of the time series, in your SAC file header?

To solve this you deleted the `stats.sac` header before writing, but there is a boolean `keep_sac_header` used in the [internal SAC write function](https://github.com/obspy/obspy/blob/master/obspy/io/sac/core.py#L472) that does the same thing.  I could exposed it to the main `write` plugin, which would allow you to write `tr.write('filename.sac', format='SAC', keep_sac_header=False)` to accomplish this.  Would you like me to make a PR for this?

Resolving patch:
diff --git a/obspy/io/sac/core.py b/obspy/io/sac/core.py
--- a/obspy/io/sac/core.py
+++ b/obspy/io/sac/core.py
@@ -445,7 +445,8 @@ def _write_sac(stream, filename, byteorder="<", **kwargs):  # @UnusedVariable
         raise ValueError("Cannot open '%s'." % filename)
 
 
-def _internal_write_sac(trace, buf, byteorder="<", **kwargs):
+def _internal_write_sac(trace, buf, byteorder="<", keep_sac_header=True,
+                        **kwargs):
     """
     Writes a single trace to an open file or file-like object
 
@@ -462,6 +463,10 @@ def _internal_write_sac(trace, buf, byteorder="<", **kwargs):
         little-endian, ``1`` or ``'>'`` for MSBF or big-endian.
         Defaults to little endian.
     :type byteorder: int or str
+    :param keep_sac_header: Whether to merge the ``Stats`` header with an
+        existing ``Stats.sac`` SAC header, if one exists. Defaults to ``True``.
+        See :func:`~obspy.io.sac.util.obspy_to_sac_header` for more.
+    :type keep_sac_header: bool
     """
     if byteorder in ("<", 0, "0"):
         byteorder = 'little'
@@ -470,5 +475,6 @@ def _internal_write_sac(trace, buf, byteorder="<", **kwargs):
     else:
         msg = "Invalid byte order. It must be either '<', '>', 0 or 1"
         raise ValueError(msg)
-    sac = SACTrace.from_obspy_trace(trace)
+
+    sac = SACTrace.from_obspy_trace(trace, keep_sac_header)
     sac.write(buf, ascii=False, byteorder=byteorder)


Test patch:
diff --git a/obspy/io/sac/tests/test_core.py b/obspy/io/sac/tests/test_core.py
--- a/obspy/io/sac/tests/test_core.py
+++ b/obspy/io/sac/tests/test_core.py
@@ -925,3 +925,89 @@ def test_encoding_flag(self):
         """
         tr0 = read(self.file_encode, encoding='cp1252')[0]
         self.assertEqual(tr0.stats.get('channel'), 'ÇÏÿÿÇÏÿÿ')
+
+    def test_write_keep_sac_header_false(self):
+        """
+        We expect a Trace that came from a SAC file and trimmed to write a SAC
+        file with iztype 'ib' and 'b = 0'.
+
+        https://github.com/obspy/obspy/issues/2760#issuecomment-735555420
+
+        "If you observe the stats.starttime is not the same that the
+        stats.sac information. The stats.sac information is the same that the
+        original file. If you write this SAC file and open with sac iris, the
+        information of the file is the stats.sac ."
+
+        1. build the SAC file, and write it to file
+        2. read it and trim it in ObsPy
+        3. compare the trimmed header
+
+        """
+        hdr = {
+            "delta": 0.1,
+            "depmin": -55782.0,
+            "depmax": 54891.0,
+            "scale": 1.0,
+            "b": 0.0,
+            "e": 87004.1,
+            "stla": -22.85915,
+            "stlo": -69.80658,
+            "stel": 1407.0,
+            "stdp": 0.0,
+            "depmen": 140.41512,
+            "cmpaz": 90.0,
+            "cmpinc": 0.0,
+            "nzyear": 2007,
+            "nzjday": 365,
+            "nzhour": 23,
+            "nzmin": 49,
+            "nzsec": 55,
+            "nzmsec": 900,
+            "nvhdr": 6,
+            "npts": 870041,
+            "iftype": 'itime',
+            "iztype": 'ib',
+            "leven": 1,
+            "lpspol": 1,
+            "lovrok": 1,
+            "lcalda": 0,
+            "kstnm": "ALG",
+            "kcmpnm": "HHE",
+            "knetwk": "Y9",
+        }
+        data = np.zeros(hdr['npts'], dtype=np.float32)
+        data[-1] = hdr['depmin']
+        data[-2] = hdr['depmax']
+        sac = SACTrace(data=data, **hdr)
+        with NamedTemporaryFile() as tf:
+            tempfile = tf.name
+            sac.write(tempfile)
+            tr0 = read(tempfile, format='SAC')[0]
+
+        tr_trimmed = tr0.trim(
+            starttime=UTCDateTime(2008, 1, 1, 0, 0),
+            endtime=UTCDateTime(2008, 1, 1, 23, 59, 59, 800000),
+            fill_value=None,
+            nearest_sample=True,
+            pad=False,
+            )
+
+        with NamedTemporaryFile() as tf:
+            tempfile = tf.name
+            tr_trimmed.write(tempfile, format='SAC', keep_sac_header=False)
+            tr_trimmed_read = read(tempfile, format='SAC')[0]
+
+        # the SAC iztype was 'ib', so the reference time of the written/read
+        # SAC file should be the first sample time of the trimmed trace.
+        self.assertEqual(tr_trimmed_read.stats.sac['nzyear'],
+                         tr_trimmed.stats.starttime.year)
+        self.assertEqual(tr_trimmed_read.stats.sac['nzjday'],
+                         tr_trimmed.stats.starttime.julday)
+        self.assertEqual(tr_trimmed_read.stats.sac['nzhour'],
+                         tr_trimmed.stats.starttime.hour)
+        self.assertEqual(tr_trimmed_read.stats.sac['nzmin'],
+                         tr_trimmed.stats.starttime.minute)
+        self.assertEqual(tr_trimmed_read.stats.sac['nzsec'],
+                         tr_trimmed.stats.starttime.second)
+        self.assertEqual(tr_trimmed_read.stats.sac['nzmsec'],
+                         tr_trimmed.stats.starttime.microsecond * 1000)