Repository: spyder-ide/spyder

Issue:
System time change causes Spyder error
## Description

When the system time is changed to the past, Spyder opens an error dialog.

It has something to do with line 571 in:
> C:\Users\user\Anaconda3\Lib\site-packages\spyder\widgets\ipythonconsole\client.py

**What steps will reproduce the problem?**

1. Open Spyder.
2. Change system time to 2017 or earlier.

**What is the expected output? What do you see instead?**

An error dialog as shown below

![image](https://user-images.githubusercontent.com/17932244/37169677-7885734a-2300-11e8-9c69-5b286aaa61b3.png)

When the time is changed back, the error dialog goes away.

**Please provide any additional information below**

  OS: Windows 10

Error message details:

> File "C:\Users\angert\Anaconda3\lib\site-packages\spyder\widgets\ipythonconsole\client.py", line 571, in show_time
>     "</b></span>" % (color, strftime(fmt, gmtime(elapsed_time)))
> OSError: [Errno 22] Invalid argument
> Traceback (most recent call last):
> Traceback (most recent call last):
>   File "C:\Users\angert\Anaconda3\lib\site-packages\spyder\widgets\ipythonconsole\client.py", line 571, in show_time
>     "</b></span>" % (color, strftime(fmt, gmtime(elapsed_time)))
> OSError: [Errno 22] Invalid argument
>   File "C:\Users\angert\Anaconda3\lib\site-packages\spyder\widgets\ipythonconsole\client.py", line 571, in show_time
>     "</b></span>" % (color, strftime(fmt, gmtime(elapsed_time)))
> OSError: [Errno 22] Invalid argument
> Traceback (most recent call last):
> Traceback (most recent call last):
>   File "C:\Users\angert\Anaconda3\lib\site-packages\spyder\widgets\ipythonconsole\client.py", line 571, in show_time
>     "</b></span>" % (color, strftime(fmt, gmtime(elapsed_time)))
> OSError: [Errno 22] Invalid argument
>   File "C:\Users\angert\Anaconda3\lib\site-packages\spyder\widgets\ipythonconsole\client.py", line 571, in show_time
>     "</b></span>" % (color, strftime(fmt, gmtime(elapsed_time)))
> OSError: [Errno 22] Invalid argument
> Traceback (most recent call last):
> Traceback (most recent call last):


## Version and main components

* Spyder Version: 3.2.6 
* Python Version: 3.6.4
* Qt Versions: 5.6.2, PyQt5 5.6 on Windows

## Dependencies
```
pyflakes >=0.6.0 :  1.6.0 (OK)
pycodestyle >=2.3:  2.3.1 (OK)
pygments >=2.0   :  2.2.0 (OK)
pandas >=0.13.1  :  0.22.0 (OK)
numpy >=1.7      :  1.14.0 (OK)
sphinx >=0.6.6   :  1.6.6 (OK)
rope >=0.9.4     :  0.10.7 (OK)
jedi >=0.9.0     :  0.11.1 (OK)
nbconvert >=4.0  :  5.3.1 (OK)
sympy >=0.7.3    :  1.1.1 (OK)
cython >=0.21    :  0.27.3 (OK)
qtconsole >=4.2.0:  4.3.1 (OK)
IPython >=4.0    :  6.2.1 (OK)
pylint >=0.25    :  1.8.2 (OK)

```

Edit: Added image of error dialog box.





Discussion:
Thanks for reporting. This seems like an easy fix.
@ccordoba12 No problem, thank you for fixing it. I am not very familiar with Spyder patches so please excuse me if this is common knowledge, but is there an estimated release date of v3.2.9 ?
It isn't common knowledge (I actually don't know it myself); but given we haven't released 3.2.8 yet and we just released 3.2.7 it will probably be a bit, since we're currently unfunded. 
> but is there an estimated release date of v3.2.9 ?

Two months, perhaps a bit less.
@ccordoba12  @CAM-Gerlach  I see, thank you for the info.

Edit: sorry for accidentally closing the issue

Resolving patch:
diff --git a/spyder/widgets/ipythonconsole/client.py b/spyder/widgets/ipythonconsole/client.py
--- a/spyder/widgets/ipythonconsole/client.py
+++ b/spyder/widgets/ipythonconsole/client.py
@@ -18,7 +18,7 @@
 import os.path as osp
 from string import Template
 from threading import Thread
-from time import ctime, time, strftime, gmtime
+import time
 
 # Third party imports (qtpy)
 from qtpy.QtCore import QUrl, QTimer, Signal, Slot
@@ -58,6 +58,11 @@
 LOADING = open(osp.join(TEMPLATES_PATH, 'loading.html')).read()
 KERNEL_ERROR = open(osp.join(TEMPLATES_PATH, 'kernel_error.html')).read()
 
+try:
+    time.monotonic  # time.monotonic new in 3.3
+except AttributeError:
+    time.monotonic = time.time
+
 
 #-----------------------------------------------------------------------------
 # Auxiliary functions
@@ -84,7 +89,7 @@ class ClientWidget(QWidget, SaveHistoryMixin):
     to print different messages there.
     """
 
-    SEPARATOR = '{0}## ---({1})---'.format(os.linesep*2, ctime())
+    SEPARATOR = '{0}## ---({1})---'.format(os.linesep*2, time.ctime())
     INITHISTORY = ['# -*- coding: utf-8 -*-',
                    '# *** Spyder Python Console History Log ***',]
 
@@ -566,8 +571,13 @@ def show_time(self, end=False):
         """Text to show in time_label."""
         if self.time_label is None:
             return
-        elapsed_time = time() - self.t0
-        if elapsed_time > 24 * 3600: # More than a day...!
+
+        elapsed_time = time.monotonic() - self.t0
+        # System time changed to past date, so reset start.
+        if elapsed_time < 0:
+            self.t0 = time.monotonic()
+            elapsed_time = 0
+        if elapsed_time > 24 * 3600:  # More than a day...!
             fmt = "%d %H:%M:%S"
         else:
             fmt = "%H:%M:%S"
@@ -576,7 +586,8 @@ def show_time(self, end=False):
         else:
             color = "#AA6655"
         text = "<span style=\'color: %s\'><b>%s" \
-               "</b></span>" % (color, strftime(fmt, gmtime(elapsed_time)))
+               "</b></span>" % (color,
+                                time.strftime(fmt, time.gmtime(elapsed_time)))
         self.time_label.setText(text)
 
     def update_time_label_visibility(self):
diff --git a/spyder/widgets/ipythonconsole/namespacebrowser.py b/spyder/widgets/ipythonconsole/namespacebrowser.py
--- a/spyder/widgets/ipythonconsole/namespacebrowser.py
+++ b/spyder/widgets/ipythonconsole/namespacebrowser.py
@@ -9,7 +9,7 @@
 the Variable Explorer
 """
 
-from time import time
+import time
 
 from qtpy.QtCore import QEventLoop
 from qtpy.QtWidgets import QMessageBox
@@ -21,6 +21,12 @@
 from spyder.py3compat import PY2, to_text_string
 
 
+try:
+    time.monotonic  # time.monotonic new in 3.3
+except AttributeError:
+    time.monotonic = time.time
+
+
 class NamepaceBrowserWidget(RichJupyterWidget):
     """
     Widget with the necessary attributes and methods to handle communications
@@ -211,7 +217,7 @@ def _handle_execute_reply(self, msg):
                 self.set_namespace_view_settings()
                 self.refresh_namespacebrowser()
             self._kernel_is_starting = False
-            self.ipyclient.t0 = time()
+            self.ipyclient.t0 = time.monotonic()
 
         # Handle silent execution of kernel methods
         if info and info.kind == 'silent_exec_method' and not self._hidden:
@@ -230,7 +236,7 @@ def _handle_status(self, msg):
         if state == 'starting':
             # This is needed to show the time a kernel
             # has been alive in each console.
-            self.ipyclient.t0 = time()
+            self.ipyclient.t0 = time.monotonic()
             self.ipyclient.timer.timeout.connect(self.ipyclient.show_time)
             self.ipyclient.timer.start(1000)
 
@@ -243,6 +249,6 @@ def _handle_status(self, msg):
             if self.namespacebrowser is not None:
                 self.set_namespace_view_settings()
                 self.refresh_namespacebrowser()
-            self.ipyclient.t0 = time()
+            self.ipyclient.t0 = time.monotonic()
         else:
             super(NamepaceBrowserWidget, self)._handle_status(msg)


Test patch:
diff --git a/spyder/plugins/tests/test_ipythonconsole.py b/spyder/plugins/tests/test_ipythonconsole.py
--- a/spyder/plugins/tests/test_ipythonconsole.py
+++ b/spyder/plugins/tests/test_ipythonconsole.py
@@ -821,5 +821,33 @@ def test_sys_argv_clear(ipyconsole, qtbot):
     assert argv == ['']
 
 
+@pytest.mark.slow
+@flaky(max_runs=3)
+def test_set_elapsed_time(ipyconsole, qtbot):
+    """Test timer."""
+    shell = ipyconsole.get_current_shellwidget()
+    client = ipyconsole.get_current_client()
+    qtbot.waitUntil(lambda: shell._prompt_html is not None,
+                    timeout=SHELL_TIMEOUT)
+
+    # Set time to 2 minutes ago.
+    client.t0 -= 120
+    with qtbot.waitSignal(client.timer.timeout, timeout=3000):
+        ipyconsole.set_elapsed_time(client)
+    assert '00:02:00' in client.time_label.text()
+
+    with qtbot.waitSignal(client.timer.timeout, timeout=3000):
+        pass
+    assert '00:02:01' in client.time_label.text()
+
+    # Make previous time later than current time.
+    client.t0 += 2000
+    with qtbot.waitSignal(client.timer.timeout, timeout=3000):
+        pass
+    assert '00:00:00' in client.time_label.text()
+
+    client.timer.timeout.disconnect(client.show_time)
+
+
 if __name__ == "__main__":
     pytest.main()