Repository: open-telemetry/opentelemetry-python

Issue:
Add ability to set explicit start & end time for span
This is present in other implementations. 
For example, Java has `spanBuilder.withStartTimestamp`.

It is crucial for situations where the start time is only known after an event has completed, for example when marshalling a span from a 'foreign' tracing system (this is my situation).



Discussion:
Spec reference: https://github.com/open-telemetry/oteps/blob/master/text/0002-remove-spandata.md
Great, sounds like it's on the todo list :) Thanks.
Same for setting an explicit end time when calling `finish()` on a span. I guess we can handle both in this issue.

Resolving patch:
diff --git a/opentelemetry-api/src/opentelemetry/trace/__init__.py b/opentelemetry-api/src/opentelemetry/trace/__init__.py
--- a/opentelemetry-api/src/opentelemetry/trace/__init__.py
+++ b/opentelemetry-api/src/opentelemetry/trace/__init__.py
@@ -142,7 +142,7 @@ class SpanKind(enum.Enum):
 class Span:
     """A span represents a single operation within a trace."""
 
-    def start(self) -> None:
+    def start(self, start_time: int = None) -> None:
         """Sets the current time as the span's start time.
 
         Each span represents a single operation. The span's start time is the
@@ -152,7 +152,7 @@ def start(self) -> None:
         implementations are free to ignore or raise on further calls.
         """
 
-    def end(self) -> None:
+    def end(self, end_time: int = None) -> None:
         """Sets the current time as the span's end time.
 
         The span's end time is the wall time at which the operation finished.
diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py
--- a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py
+++ b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py
@@ -340,19 +340,21 @@ def add_lazy_link(self, link: "trace_api.Link") -> None:
             return
         self.links.append(link)
 
-    def start(self):
+    def start(self, start_time: int = None):
         with self._lock:
             if not self.is_recording_events():
                 return
             has_started = self.start_time is not None
             if not has_started:
-                self.start_time = util.time_ns()
+                self.start_time = (
+                    start_time if start_time is not None else util.time_ns()
+                )
         if has_started:
             logger.warning("Calling start() on a started span.")
             return
         self.span_processor.on_start(self)
 
-    def end(self):
+    def end(self, end_time: int = None):
         with self._lock:
             if not self.is_recording_events():
                 return
@@ -360,7 +362,9 @@ def end(self):
                 raise RuntimeError("Calling end() on a not started span.")
             has_ended = self.end_time is not None
             if not has_ended:
-                self.end_time = util.time_ns()
+                self.end_time = (
+                    end_time if end_time is not None else util.time_ns()
+                )
         if has_ended:
             logger.warning("Calling end() on an ended span.")
             return


Test patch:
diff --git a/opentelemetry-sdk/tests/trace/test_trace.py b/opentelemetry-sdk/tests/trace/test_trace.py
--- a/opentelemetry-sdk/tests/trace/test_trace.py
+++ b/opentelemetry-sdk/tests/trace/test_trace.py
@@ -234,6 +234,16 @@ def test_start_span(self):
         span.start()
         self.assertEqual(start_time, span.start_time)
 
+    def test_span_override_start_and_end_time(self):
+        """Span sending custom start_time and end_time values"""
+        span = trace.Span("name", mock.Mock(spec=trace_api.SpanContext))
+        start_time = 123
+        span.start(start_time)
+        self.assertEqual(start_time, span.start_time)
+        end_time = 456
+        span.end(end_time)
+        self.assertEqual(end_time, span.end_time)
+
     def test_ended_span(self):
         """"Events, attributes are not allowed after span is ended"""
         tracer = trace.Tracer("test_ended_span")