Repository: mne-tools/mne-python

Issue:
brainvision _str_to_meas_date breaks with incorrect date
if the original brainvision file lacks date information and we do segmentation on it, the offset of "New Segment" is added to "000000000000000000" so we end with something like '00000000000304125000' which passes sanity check in _str_to_meas_date, but it is stil invalid:

`def _str_to_meas_date(date_str):
    date_str = date_str.strip()

    if date_str in ['0', '00000000000000000000']:
        return None`

the solution is to add additional check:

```
    try:
        meas_date = datetime.strptime(date_str, '%Y%m%d%H%M%S%f')
    except ValueError:
        return None
```


Discussion:
@petergrey75 can you share a full code snipped with data so we can replicate?
it will be used then as a non-regression test in the PR. thanks
@agramfort the only change is in brainvision.py in _str_to_meas_date function.

```
def _str_to_meas_date(date_str):
    date_str = date_str.strip()

    if date_str in ['0', '00000000000000000000']:
        return None
	
    try:
        meas_date = datetime.strptime(date_str, '%Y%m%d%H%M%S%f')
    except ValueError:
        return None
```

I just added try/except clause around strptime function because if the date_str is invalid it breaks the loading of the file.

The problem is that segmentation of the file without (empty) meas_date just adds the offset value of the 1st segment to the initial date_str (which is '00000000000000000000' in that case) then the new date_str is something like '00000000000304125000' which pass the first check but will cause ValueError on strptime.
we don't do changes in code without adding a unit test.

can you share a code snippet that would break in current master and that
works with your suggested  change? thanks

>

i can share the code, but unfortunately i'm not allowed to share the data, so code without the data is meaningless, i guess?
Within a test, you can copy `shutil.copyfile` an existing `.eeg` test file and then `with open(fname, 'r') as fid:` "copy" the `.vhdr` file with an appropriate modification that would show the error on `master` but is fixed in this PR.

I know it might seem a bit overkill for a simple fix that is easily explained, but the 10-20 lines necessary to make this test will help maintainability for us.

If what I'm suggesting isn't clear or doesn't make sense I can try to push a commit to your branch to do it.

Resolving patch:
diff --git a/mne/io/brainvision/brainvision.py b/mne/io/brainvision/brainvision.py
--- a/mne/io/brainvision/brainvision.py
+++ b/mne/io/brainvision/brainvision.py
@@ -326,7 +326,13 @@ def _str_to_meas_date(date_str):
     if date_str in ['0', '00000000000000000000']:
         return None
 
-    meas_date = datetime.strptime(date_str, '%Y%m%d%H%M%S%f')
+    try:
+        meas_date = datetime.strptime(date_str, '%Y%m%d%H%M%S%f')
+    except ValueError as e:
+        if 'does not match format' in str(e):
+            return None
+        else:
+            raise
 
     # We need list of unix time in milliseconds and as second entry
     # the additional amount of microseconds
@@ -477,7 +483,7 @@ def _get_vhdr_info(vhdr_fname, eog, misc, scale, montage):
         match = re.findall(regexp, line.strip())
 
         # Always take first measurement date we find
-        if match and match[0] != '00000000000000000000':
+        if match:
             date_str = match[0]
             info['meas_date'] = _str_to_meas_date(date_str)
             break


Test patch:
diff --git a/mne/io/brainvision/tests/data/test_bad_date.vhdr b/mne/io/brainvision/tests/data/test_bad_date.vhdr
new file mode 100644
--- /dev/null
+++ b/mne/io/brainvision/tests/data/test_bad_date.vhdr
@@ -0,0 +1,142 @@
+Brain Vision Data Exchange Header File Version 1.0
+; Data created by the Vision Recorder
+
+[Common Infos]
+Codepage=UTF-8
+DataFile=test.eeg
+MarkerFile=test_bad_date.vmrk
+DataFormat=BINARY
+; Data orientation: MULTIPLEXED=ch1,pt1, ch2,pt1 ...
+DataOrientation=MULTIPLEXED
+NumberOfChannels=32
+; Sampling interval in microseconds
+SamplingInterval=1000
+
+[Binary Infos]
+BinaryFormat=INT_16
+
+[Channel Infos]
+; Each entry: Ch<Channel number>=<Name>,<Reference channel name>,
+; <Resolution in "Unit">,<Unit>, Future extensions..
+; Fields are delimited by commas, some fields might be omitted (empty).
+; Commas in channel names are coded as "\1".
+Ch1=FP1,,0.5,µV
+Ch2=FP2,,0.5,µV
+Ch3=F3,,0.5,µV
+Ch4=F4,,0.5,µV
+Ch5=C3,,0.5,µV
+Ch6=C4,,0.5,µV
+Ch7=P3,,0.5,µV
+Ch8=P4,,0.5,µV
+Ch9=O1,,0.5,µV
+Ch10=O2,,0.5,µV
+Ch11=F7,,0.5,µV
+Ch12=F8,,0.5,µV
+Ch13=P7,,0.5,µV
+Ch14=P8,,0.5,µV
+Ch15=Fz,,0.5,µV
+Ch16=FCz,,0.5,µV
+Ch17=Cz,,0.5,µV
+Ch18=CPz,,0.5,µV
+Ch19=Pz,,0.5,µV
+Ch20=POz,,0.5,µV
+Ch21=FC1,,0.5,µV
+Ch22=FC2,,0.5,µV
+Ch23=CP1,,0.5,µV
+Ch24=CP2,,0.5,µV
+Ch25=FC5,,0.5,µV
+Ch26=FC6,,0.5,µV
+Ch27=CP5,,0.5,BS
+Ch28=CP6,,0.5,µS
+Ch29=HL,,0.5,ARU
+Ch30=HR,,0.5,uS
+Ch31=Vb,,0.5,S
+Ch32=ReRef,,0.5,C
+
+[Comment]
+
+A m p l i f i e r  S e t u p
+============================
+Number of channels: 32
+Sampling Rate [Hz]: 1000
+Sampling Interval [µS]: 1000
+
+Channels
+--------
+#     Name      Phys. Chn.    Resolution / Unit   Low Cutoff [s]   High Cutoff [Hz]   Notch [Hz]    Series Res. [kOhm] Gradient         Offset
+1     FP1         1                0.5 µV             DC              250              Off                0
+2     FP2         2                0.5 µV             DC              250              Off                0
+3     F3          3                0.5 µV             DC              250              Off                0
+4     F4          4                0.5 µV             DC              250              Off                0
+5     C3          5                0.5 µV             DC              250              Off                0
+6     C4          6                0.5 µV             DC              250              Off                0
+7     P3          7                0.5 µV             DC              250              Off                0
+8     P4          8                0.5 µV             DC              250              Off                0
+9     O1          9                0.5 µV             DC              250              Off                0
+10    O2          10               0.5 µV             DC              250              Off                0
+11    F7          11               0.5 µV             DC              250              Off                0
+12    F8          12               0.5 µV             DC              250              Off                0
+13    P7          13               0.5 µV             DC              250              Off                0
+14    P8          14               0.5 µV             DC              250              Off                0
+15    Fz          15               0.5 µV             DC              250              Off                0
+16    FCz         16               0.5 µV             DC              250              Off                0
+17    Cz          17               0.5 µV             DC              250              Off                0
+18    CPz         18               0.5 µV             DC              250              Off                0
+19    Pz          19               0.5 µV             DC              250              Off                0
+20    POz         20               0.5 µV             DC              250              Off                0
+21    FC1         21               0.5 µV             DC              250              Off                0
+22    FC2         22               0.5 µV             DC              250              Off                0
+23    CP1         23               0.5 µV             DC              250              Off                0
+24    CP2         24               0.5 µV             DC              250              Off                0
+25    FC5         25               0.5 µV             DC              250              Off                0
+26    FC6         26               0.5 µV             DC              250              Off                0
+27    CP5         27               0.5 BS             DC              250              Off                0
+28    CP6         28               0.5 µS             DC              250              Off                0
+29    HL          29               0.5 ARU            DC              250              Off                0
+30    HR          30               0.5 uS             DC              250              Off                0
+31    Vb          31               0.5 S              DC              250              Off                0
+32    ReRef       32               0.5 C              DC              250              Off                0
+
+S o f t w a r e  F i l t e r s
+==============================
+Disabled
+
+
+Data Electrodes Selected Impedance Measurement Range: 0 - 100 kOhm
+Ground Electrode Selected Impedance Measurement Range: 0 - 10 kOhm
+Reference Electrode Selected Impedance Measurement Range: 0 - 10 kOhm
+Impedance [kOhm] at 16:12:27 :
+FP1:        ???
+FP2:        ???
+F3:         ???
+F4:         ???
+C3:         ???
+C4:         ???
+P3:         ???
+P4:         ???
+O1:         ???
+O2:         ???
+F7:         ???
+F8:         ???
+P7:         ???
+P8:         ???
+Fz:         ???
+FCz:        ???
+Cz:         ???
+CPz:        ???
+Pz:         ???
+POz:        ???
+FC1:        ???
+FC2:        ???
+CP1:        ???
+CP2:        ???
+FC5:        ???
+FC6:        ???
+CP5:        ???
+CP6:        ???
+HL:         ???
+HR:         ???
+Vb:         ???
+ReRef:      ???
+Ref:          0
+Gnd:          4
diff --git a/mne/io/brainvision/tests/data/test_bad_date.vmrk b/mne/io/brainvision/tests/data/test_bad_date.vmrk
new file mode 100644
--- /dev/null
+++ b/mne/io/brainvision/tests/data/test_bad_date.vmrk
@@ -0,0 +1,25 @@
+Brain Vision Data Exchange Marker File, Version 1.0
+
+[Common Infos]
+Codepage=UTF-8
+DataFile=test.eeg
+
+[Marker Infos]
+; Each entry: Mk<Marker number>=<Type>,<Description>,<Position in data points>,
+; <Size in data points>, <Channel number (0 = marker is related to all channels)>
+; Fields are delimited by commas, some fields might be omitted (empty).
+; Commas in type or description text are coded as "\1".
+Mk1=New Segment,,1,1,0,00000000000304125000
+Mk2=Stimulus,S253,487,0,0
+Mk3=Stimulus,S255,497,1,0
+Mk4=Stimulus,S254,1770,1,0
+Mk5=Stimulus,S255,1780,1,0
+Mk6=Stimulus,S254,3253,1,0
+Mk7=Stimulus,S255,3263,1,0
+Mk8=Stimulus,S253,4936,1,0
+Mk9=Stimulus,S255,4946,1,0
+Mk10=Response,R255,6000,1,0
+Mk11=Stimulus,S254,6620,1,0
+Mk12=Stimulus,S255,6630,1,0
+Mk13=SyncStatus,Sync On,7630,1,0
+Mk14=Optic,O  1,7700,1,0
diff --git a/mne/io/brainvision/tests/test_brainvision.py b/mne/io/brainvision/tests/test_brainvision.py
--- a/mne/io/brainvision/tests/test_brainvision.py
+++ b/mne/io/brainvision/tests/test_brainvision.py
@@ -53,6 +53,9 @@
 # Test for nanovolts as unit
 vhdr_nV_path = op.join(data_dir, 'test_nV.vhdr')
 
+# Test bad date
+vhdr_bad_date = op.join(data_dir, 'test_bad_date.vhdr')
+
 montage = op.join(data_dir, 'test.hpts')
 eeg_bin = op.join(data_dir, 'test_bin_raw.fif')
 eog = ['HL', 'HR', 'Vb']
@@ -91,6 +94,12 @@ def test_vmrk_meas_date():
     assert raw.info['meas_date'] is None
     assert 'unspecified' in repr(raw.info)
 
+    # Test files with faulty dates introduced by segmenting a file without
+    # date information. Should not raise a strptime ValueError
+    raw = read_raw_brainvision(vhdr_bad_date)
+    assert raw.info['meas_date'] is None
+    assert 'unspecified' in repr(raw.info)
+
 
 def test_vhdr_codepage_ansi():
     """Test BV reading with ANSI codepage."""