Repository: pandas-dev/pandas
Pull request: ENH: Support timespec argument in Timestamp.isoformat()
- [x] closes #26131 
- [x] tests added / passed
- [x] Ensure all linting tests pass, see [here](https://pandas.pydata.org/pandas-docs/dev/development/contributing_codebase.html#pre-commit) for how to run them
- [x] whatsnew entry

This is an update of PR #38550.  I added support for "nanoseconds" as an argument, expanded the test cases, and addressed most of the comments in the original PR.

Problem statement:
pd.Timestamp.isoformat does not implement keyword timespec
## Code Sample

**[IN]**
```python
import pandas as pd
now = pd.Timestamp.utcnow()
help(now.isoformat)
```
**[OUT]**
    Help on method isoformat in module pandas._libs.tslibs.timestamps:
    
    isoformat(sep='T') method of pandas._libs.tslibs.timestamps.Timestamp instance
        [sep] -> string in ISO 8601 format, YYYY-MM-DDT[HH[:MM[:SS[.mmm[uuu]]]]][+HH:MM].
        sep is used to separate the year from the time, and defaults to 'T'.
        timespec specifies what components of the time to include (allowed values are 'auto', 'hours', 'minutes', 'seconds', 'milliseconds', and 'microseconds').
    
    

**[IN]**
```python
now.isoformat(timespec="seconds")
```

**[OUT]**
    ---------------------------------------------------------------------------

    TypeError                                 Traceback (most recent call last)

    <ipython-input-8-3626478eeb62> in <module>
    ----> 1 now.isoformat(timespec="seconds")
    

    pandas\_libs\tslibs\timestamps.pyx in pandas._libs.tslibs.timestamps.Timestamp.isoformat()
    

    TypeError: isoformat() got an unexpected keyword argument 'timespec'


**[IN]**
```python
now.to_pydatetime().isoformat(timespec="seconds")
```



**[OUT]**
    '2019-04-18T07:38:50+00:00'



## Problem description

The help method has given me the feeling that I can use timespec just like I can with standard datetime objects (since python 3.6).

## Expected Output

We could mimic standard datetime.isoformat behavior, or at least remove this false information from the help message.


## Output of ``pd.show_versions()``

<details>


INSTALLED VERSIONS
------------------
commit: None
python: 3.7.3.final.0
python-bits: 64
OS: Windows
OS-release: 10
machine: AMD64
processor: Intel64 Family 6 Model 142 Stepping 9, GenuineIntel
byteorder: little
LC_ALL: None
LANG: fr_FR.UTF-8
LOCALE: None.None

pandas: 0.24.2
pytest: 4.4.1
pip: 19.0.3
setuptools: 41.0.0
Cython: None
numpy: 1.16.2
scipy: 1.2.1
pyarrow: None
xarray: None
IPython: 7.4.0
sphinx: 2.0.1
patsy: 0.5.1
dateutil: 2.8.0
pytz: 2019.1
blosc: None
bottleneck: None
tables: None
numexpr: None
feather: None
matplotlib: 3.0.3
openpyxl: None
xlrd: None
xlwt: None
xlsxwriter: None
lxml.etree: 4.3.3
bs4: 4.7.1
html5lib: None
sqlalchemy: None
pymysql: None
psycopg2: None
jinja2: 2.10.1
s3fs: None
fastparquet: None
pandas_gbq: None
pandas_datareader: None
gcsfs: None

</details>

Hints from issue discussion:
@mroeschke @jbrockmendel 
`isoformat` is one function that `Timestamp` doesn't just directly inherit. I don't see why the `timespec` keyword argument can't be incorporated in as well.

One thing to note is that if we mirror  `timespec` from `datetime.datetime`, our implementation also should handle `'nanoseconds'` as an argument
Might be helpful to have a test checking compatibility of all the signatures between datetime vs timestamp.
I have another remark : the help message is very different when looking at the Timestamp class or at the instance : 


```python
import pandas as pd
```


```python
help(pd.Timestamp.isoformat)
```

    Help on cython_function_or_method in module pandas._libs.tslibs.timestamps:
    
    isoformat(self, sep='T')
    
    


```python
help(pd.Timestamp(2019).isoformat)
```

    Help on method isoformat in module pandas._libs.tslibs.timestamps:
    
    isoformat(sep='T') method of pandas._libs.tslibs.timestamps.Timestamp instance
        [sep] -> string in ISO 8601 format, YYYY-MM-DDT[HH[:MM[:SS[.mmm[uuu]]]]][+HH:MM].
        sep is used to separate the year from the time, and defaults to 'T'.
        timespec specifies what components of the time to include (allowed values are 'auto', 'hours', 'minutes', 'seconds', 'milliseconds', and 'microseconds').
    
   

update this apli, please
> update this apli, please

@77QingLiu 
you are welcome to submit a PR to patch this
Hello, I am still having this same issue.
@boringow this is an open issue
you are welcome to submit a PR
there is an open PR but it is stale
How can I do a PR in this issue? can someone assign it to me please? 
@phofl Hi again and sorry to bother, I need to be a triage member to do PR. How can I become one?

Commit message: Copy isoformat docstring to NaTType

Patch to review:
diff --git a/doc/source/whatsnew/v1.4.0.rst b/doc/source/whatsnew/v1.4.0.rst
index 8732e1c397ce5..a6751c486f25b 100644
--- a/doc/source/whatsnew/v1.4.0.rst
+++ b/doc/source/whatsnew/v1.4.0.rst
@@ -184,6 +184,7 @@ Other enhancements
 - :meth:`DataFrame.dropna` now accepts a single label as ``subset`` along with array-like (:issue:`41021`)
 - :meth:`read_excel` now accepts a ``decimal`` argument that allow the user to specify the decimal point when parsing string columns to numeric (:issue:`14403`)
 - :meth:`.GroupBy.mean` now supports `Numba <http://numba.pydata.org/>`_ execution with the ``engine`` keyword (:issue:`43731`)
+- :meth:`Timestamp.isoformat`, now handles the ``timespec`` argument from the base :class:``datetime`` class (:issue:`26131`)
 
 .. ---------------------------------------------------------------------------
 
diff --git a/pandas/_libs/tslibs/nattype.pyx b/pandas/_libs/tslibs/nattype.pyx
index 2aebf75ba35d4..16e3826975ef0 100644
--- a/pandas/_libs/tslibs/nattype.pyx
+++ b/pandas/_libs/tslibs/nattype.pyx
@@ -295,7 +295,39 @@ cdef class _NaT(datetime):
     def __str__(self) -> str:
         return "NaT"
 
-    def isoformat(self, sep="T") -> str:
+    def isoformat(self, sep: str = "T", timespec: str = "auto") -> str:
+        """
+        Return the time formatted according to ISO.
+
+        The full format looks like 'YYYY-MM-DD HH:MM:SS.mmmmmmnnn'.
+        By default, the fractional part is omitted if self.microsecond == 0
+        and self.nanosecond == 0.
+
+        If self.tzinfo is not None, the UTC offset is also attached, giving
+        giving a full format of 'YYYY-MM-DD HH:MM:SS.mmmmmmnnn+HH:MM'.
+
+        Parameters
+        ----------
+        sep : str, default 'T'
+            String used as the separator between the date and time.
+
+        timespec : str, default 'auto'
+            Specifies the number of additional terms of the time to include.
+            The valid values are 'auto', 'hours', 'minutes', 'seconds',
+            'milliseconds', 'microseconds', and 'nanoseconds'.
+
+        Returns
+        -------
+        str
+
+        Examples
+        --------
+        >>> ts = pd.Timestamp('2020-03-14T15:32:52.192548651')
+        >>> ts.isoformat()
+        '2020-03-14T15:32:52.192548651'
+        >>> ts.isoformat(timespec='microseconds')
+        '2020-03-14T15:32:52.192548'
+        """
         # This allows Timestamp(ts.isoformat()) to always correctly roundtrip.
         return "NaT"
 
diff --git a/pandas/_libs/tslibs/timestamps.pyx b/pandas/_libs/tslibs/timestamps.pyx
index 613da5a691736..28b8158548ca8 100644
--- a/pandas/_libs/tslibs/timestamps.pyx
+++ b/pandas/_libs/tslibs/timestamps.pyx
@@ -737,9 +737,42 @@ cdef class _Timestamp(ABCTimestamp):
     # -----------------------------------------------------------------
     # Rendering Methods
 
-    def isoformat(self, sep: str = "T") -> str:
-        base = super(_Timestamp, self).isoformat(sep=sep)
-        if self.nanosecond == 0:
+    def isoformat(self, sep: str = "T", timespec: str = "auto") -> str:
+        """
+        Return the time formatted according to ISO.
+
+        The full format looks like 'YYYY-MM-DD HH:MM:SS.mmmmmmnnn'.
+        By default, the fractional part is omitted if self.microsecond == 0
+        and self.nanosecond == 0.
+
+        If self.tzinfo is not None, the UTC offset is also attached, giving
+        giving a full format of 'YYYY-MM-DD HH:MM:SS.mmmmmmnnn+HH:MM'.
+
+        Parameters
+        ----------
+        sep : str, default 'T'
+            String used as the separator between the date and time.
+
+        timespec : str, default 'auto'
+            Specifies the number of additional terms of the time to include.
+            The valid values are 'auto', 'hours', 'minutes', 'seconds',
+            'milliseconds', 'microseconds', and 'nanoseconds'.
+
+        Returns
+        -------
+        str
+
+        Examples
+        --------
+        >>> ts = pd.Timestamp('2020-03-14T15:32:52.192548651')
+        >>> ts.isoformat()
+        '2020-03-14T15:32:52.192548651'
+        >>> ts.isoformat(timespec='microseconds')
+        '2020-03-14T15:32:52.192548'
+        """
+        base_ts = "microseconds" if timespec == "nanoseconds" else timespec
+        base = super(_Timestamp, self).isoformat(sep=sep, timespec=base_ts)
+        if self.nanosecond == 0 and timespec != "nanoseconds":
             return base
 
         if self.tzinfo is not None:
@@ -747,10 +780,11 @@ cdef class _Timestamp(ABCTimestamp):
         else:
             base1, base2 = base, ""
 
-        if self.microsecond != 0:
-            base1 += f"{self.nanosecond:03d}"
-        else:
-            base1 += f".{self.nanosecond:09d}"
+        if timespec == "nanoseconds" or (timespec == "auto" and self.nanosecond):
+            if self.microsecond:
+                base1 += f"{self.nanosecond:03d}"
+            else:
+                base1 += f".{self.nanosecond:09d}"
 
         return base1 + base2
 
diff --git a/pandas/tests/scalar/timestamp/test_formats.py b/pandas/tests/scalar/timestamp/test_formats.py
new file mode 100644
index 0000000000000..71dbf3539bdb2
--- /dev/null
+++ b/pandas/tests/scalar/timestamp/test_formats.py
@@ -0,0 +1,71 @@
+import pytest
+
+from pandas import Timestamp
+
+ts_no_ns = Timestamp(
+    year=2019,
+    month=5,
+    day=18,
+    hour=15,
+    minute=17,
+    second=8,
+    microsecond=132263,
+)
+ts_ns = Timestamp(
+    year=2019,
+    month=5,
+    day=18,
+    hour=15,
+    minute=17,
+    second=8,
+    microsecond=132263,
+    nanosecond=123,
+)
+ts_ns_tz = Timestamp(
+    year=2019,
+    month=5,
+    day=18,
+    hour=15,
+    minute=17,
+    second=8,
+    microsecond=132263,
+    nanosecond=123,
+    tz="UTC",
+)
+ts_no_us = Timestamp(
+    year=2019,
+    month=5,
+    day=18,
+    hour=15,
+    minute=17,
+    second=8,
+    microsecond=0,
+    nanosecond=123,
+)
+
+
+@pytest.mark.parametrize(
+    "ts, timespec, expected_iso",
+    [
+        (ts_no_ns, "auto", "2019-05-18T15:17:08.132263"),
+        (ts_no_ns, "seconds", "2019-05-18T15:17:08"),
+        (ts_no_ns, "nanoseconds", "2019-05-18T15:17:08.132263000"),
+        (ts_ns, "auto", "2019-05-18T15:17:08.132263123"),
+        (ts_ns, "hours", "2019-05-18T15"),
+        (ts_ns, "minutes", "2019-05-18T15:17"),
+        (ts_ns, "seconds", "2019-05-18T15:17:08"),
+        (ts_ns, "milliseconds", "2019-05-18T15:17:08.132"),
+        (ts_ns, "microseconds", "2019-05-18T15:17:08.132263"),
+        (ts_ns, "nanoseconds", "2019-05-18T15:17:08.132263123"),
+        (ts_ns_tz, "auto", "2019-05-18T15:17:08.132263123+00:00"),
+        (ts_ns_tz, "hours", "2019-05-18T15+00:00"),
+        (ts_ns_tz, "minutes", "2019-05-18T15:17+00:00"),
+        (ts_ns_tz, "seconds", "2019-05-18T15:17:08+00:00"),
+        (ts_ns_tz, "milliseconds", "2019-05-18T15:17:08.132+00:00"),
+        (ts_ns_tz, "microseconds", "2019-05-18T15:17:08.132263+00:00"),
+        (ts_ns_tz, "nanoseconds", "2019-05-18T15:17:08.132263123+00:00"),
+        (ts_no_us, "auto", "2019-05-18T15:17:08.000000123"),
+    ],
+)
+def test_isoformat(ts, timespec, expected_iso):
+    assert ts.isoformat(timespec=timespec) == expected_iso


Code review comments:
diff_hunk: @@ -295,7 +295,39 @@ cdef class _NaT(datetime):
     def __str__(self) -> str:
         return "NaT"
 
-    def isoformat(self, sep="T") -> str:
+    def isoformat(self, sep: str = "T", timespec: str = "auto") -> str:
+        """
+        Return the time formatted according to ISO.
+
+        The full format looks like 'YYYY-MM-DD HH:MM:SS.mmmmmmnnn'.
+        By default, the fractional part is omitted if self.microsecond == 0
+        and self.nanosecond == 0.
+
+        If self.tzinfo is not None, the UTC offset is also attached, giving
+        giving a full format of 'YYYY-MM-DD HH:MM:SS.mmmmmmnnn+HH:MM'.
+
+        Parameters
+        ----------
+        sep : str, default 'T'
+            String used as the separator between the date and time.
+
+        timespec : str, default 'auto'
+            Specifies the number of additional terms of the time to include.
+            The valid values are 'auto', 'hours', 'minutes', 'seconds',
+            'milliseconds', 'microseconds', and 'nanoseconds'.
+
+        Returns
+        -------
+        str
+
+        Examples
path: pandas/_libs/tslibs/nattype.pyx
text: @user1:
this example should be for NaT (to be honest dont' need it here though)

@author:
The problem I ran into - there was a test failure because the docstrings differed between NaT and Timestamp, so that's why I copied the docstring into NaT.  Do you have any advice about that?

@user1:
right we have tests that confirm this, but since we are not explicitly making these differnt you can change the test

@author:
OK, so I ended up reverting the docstring change to NaT and updated the test to ignore the docstring changes with Timestamp.