Repository: sunpy/sunpy

Issue:
VSO Results from RHESSI seem to not have consistent ordering
If you run the following query multiple times:
```python
Fido.search(a.Time('2012/3/4', '2012/3/4 02:00'), a.Instrument('rhessi'))
```

```
In [1]: Results from 2 Providers:

3 Results from the VSOClient:
   Start Time [1]       End Time [1]    Source Instrument     Type    Wavelength [2]
                                                                           keV      
------------------- ------------------- ------ ---------- ----------- --------------
2012-03-03 22:57:40 2012-03-04 00:33:20 RHESSI     RHESSI PARTIAL_SUN 3.0 .. 17000.0
2012-03-04 01:45:40 2012-03-04 02:09:00 RHESSI     RHESSI PARTIAL_SUN 3.0 .. 17000.0
2012-03-04 00:33:20 2012-03-04 01:45:40 RHESSI     RHESSI PARTIAL_SUN 3.0 .. 17000.0

1 Results from the RHESSIClient:
     Start Time           End Time      Source Instrument Wavelength
------------------- ------------------- ------ ---------- ----------
2012-03-04 00:00:00 2012-03-04 23:59:59 rhessi     rhessi        nan
```

```
In [1]: Results from 2 Providers:

3 Results from the VSOClient:
   Start Time [1]       End Time [1]    Source Instrument     Type    Wavelength [2]
                                                                           keV      
------------------- ------------------- ------ ---------- ----------- --------------
2012-03-04 01:45:40 2012-03-04 02:09:00 RHESSI     RHESSI PARTIAL_SUN 3.0 .. 17000.0
2012-03-04 00:33:20 2012-03-04 01:45:40 RHESSI     RHESSI PARTIAL_SUN 3.0 .. 17000.0
2012-03-03 22:57:40 2012-03-04 00:33:20 RHESSI     RHESSI PARTIAL_SUN 3.0 .. 17000.0

1 Results from the RHESSIClient:
     Start Time           End Time      Source Instrument Wavelength
------------------- ------------------- ------ ---------- ----------
2012-03-04 00:00:00 2012-03-04 23:59:59 rhessi     rhessi        nan

```

```
In [1]: Results from 2 Providers:

3 Results from the VSOClient:
   Start Time [1]       End Time [1]    Source Instrument     Type    Wavelength [2]
                                                                           keV      
------------------- ------------------- ------ ---------- ----------- --------------
2012-03-03 22:57:40 2012-03-04 00:33:20 RHESSI     RHESSI PARTIAL_SUN 3.0 .. 17000.0
2012-03-04 01:45:40 2012-03-04 02:09:00 RHESSI     RHESSI PARTIAL_SUN 3.0 .. 17000.0
2012-03-04 00:33:20 2012-03-04 01:45:40 RHESSI     RHESSI PARTIAL_SUN 3.0 .. 17000.0

1 Results from the RHESSIClient:
     Start Time           End Time      Source Instrument Wavelength
------------------- ------------------- ------ ---------- ----------
2012-03-04 00:00:00 2012-03-04 23:59:59 rhessi     rhessi        nan

```

The results come back in a different order.

This seems to be a recent change in the VSO response as before this doctest was stable.


Discussion:
The VSO code itself for RHESSI hasn't changed recently. Testing on IDL/SSW always yields the results in the same order. I can reproduce the above flipping of the order of the results in my test scripts for SunPy 1.0 and 1.1. Perhaps adding a sort on the start time would resolve the issue.
Maybe we could add a sorting option to `search` that by default sorts by date.

Resolving patch:
diff --git a/sunpy/net/vso/vso.py b/sunpy/net/vso/vso.py
--- a/sunpy/net/vso/vso.py
+++ b/sunpy/net/vso/vso.py
@@ -71,11 +71,36 @@ def _parse_date(string):
     return {'time_start': start.strip(), 'time_end': end.strip()}
 
 
-def iter_records(response):
+def iter_sort_response(response):
+    """
+    Sorts the VSO queryresults by their start time.
+
+    Parameters
+    ----------
+    response : `zeep.objects.QueryResponse`
+        A SOAP Object of a VSO queryresult
+
+    Returns
+    -------
+    `list`
+        Sorted record items w.r.t. their start time.
+    """
+    has_time_recs = list()
+    has_notime_recs = list()
     for prov_item in response.provideritem:
         if not hasattr(prov_item, 'record') or not prov_item.record:
             continue
-        yield from prov_item.record.recorditem
+        if not hasattr(prov_item.record, 'recorditem') or not prov_item.record.recorditem:
+            continue
+        rec_item = prov_item.record.recorditem
+        for rec in rec_item:
+            if hasattr(rec, 'time') and hasattr(rec.time, 'start') and rec.time.start is not None:
+                has_time_recs.append(rec)
+            else:
+                has_notime_recs.append(rec)
+    has_time_recs = sorted(has_time_recs, key=lambda x: x.time.start)
+    all_recs = has_time_recs + has_notime_recs
+    return all_recs
 
 
 def iter_errors(response):
@@ -189,7 +214,8 @@ def search(self, *query):
 
     @classmethod
     def create(cls, queryresult):
-        return cls(list(iter_records(queryresult)), queryresult)
+        res = list(iter_sort_response(queryresult))
+        return cls(res, queryresult)
 
     def total_size(self):
         """ Total size of data in KB. May be less than the actual


Test patch:
diff --git a/sunpy/net/tests/test_vso.py b/sunpy/net/tests/test_vso.py
--- a/sunpy/net/tests/test_vso.py
+++ b/sunpy/net/tests/test_vso.py
@@ -45,7 +45,7 @@ def __init__(self, records=None, errors=None):
         self.provideritem = list()
 
         if records is not None:
-            self.provideritem = [MockObject(record=MockObject(recorditem=[ri])) for ri in records]
+            self.provideritem = records
 
         if errors is not None:
             self.provideritem.extend([MockObject(error=err) for err in errors])
@@ -53,7 +53,20 @@ def __init__(self, records=None, errors=None):
 
 @pytest.fixture
 def mock_response():
-    return MockQRResponse(records=[1, 2], errors=['FAILED'])
+    # defining unsorted queryresult to mock test `iter_sort_response()`.
+    # Incorporated cases with no None start time and without time attribute too.
+    recs = [MockObject(record=MockObject(recorditem=[
+                                          MockObject(time=MockObject(start=4), fileid='t4'),
+                                          MockObject(time=MockObject(start=1), fileid='t1'),
+                                          MockObject(time=MockObject(start=2), fileid='t2')
+                                          ]))]
+    rec = MockObject(record=MockObject(recorditem=[
+                                        MockObject(time=MockObject(start=None), fileid='f1'),
+                                        MockObject(fileid='f2'),
+                                        MockObject(time=MockObject(start=3), fileid='t3')
+                                        ]))
+    recs.append(rec)
+    return MockQRResponse(records=recs, errors=['FAILED'])
 
 
 @pytest.fixture
@@ -325,8 +338,11 @@ def test__parse_date(input, expected):
     assert vso.vso._parse_date(input) == expected
 
 
-def test_iter_records(mock_response):
-    assert list(vso.vso.iter_records(mock_response)) == [1, 2]
+def test_iter_sort_response(mock_response):
+    fileids = [i.fileid for i in vso.vso.iter_sort_response(mock_response)]
+    # the function would have sorted records w.r.t. start time,
+    # those without start time appended at last of final response.
+    assert fileids == ['t1', 't2', 't3', 't4', 'f1', 'f2']
 
 
 def test_iter_errors(mock_response):
@@ -493,7 +509,7 @@ def test_incorrect_content_disposition(client):
     files = client.fetch(results[0:1])
 
     assert len(files) == 1
-    assert  files[0].endswith("mdi_vw_v_9466622_9466622.tar")
+    assert files[0].endswith("mdi_vw_v_9466622_9466622.tar")
     assert "Content" not in files[0]