634

I have a file. In Python, I would like to take its creation time, and convert it to an ISO time (ISO 8601) string while preserving the fact that it was created in the Eastern Time Zone (ET).

How do I take the file's ctime and convert it to an ISO time string that indicates the Eastern Time Zone (and takes into account daylight savings time, if necessary)?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Joseph Turian
  • 15,430
  • 14
  • 47
  • 62
  • 12
    @Nick- Useful link, but not a duplicate- he's asking about converting the other way. – Yarin Dec 19 '11 at 03:54
  • 1
    @Joseph- I just asked this in regards to RFC 3999- should work for this - [Generate RFC 3339 timestamp in Python](http://stackoverflow.com/q/8556398/165673) – Yarin Dec 19 '11 at 03:59

15 Answers15

1089

Local to ISO 8601:

import datetime
datetime.datetime.now().isoformat()
>>> 2020-03-20T14:28:23.382748

UTC to ISO 8601:

import datetime
datetime.datetime.utcnow().isoformat()
>>> 2020-03-20T01:30:08.180856

Local to ISO 8601 without microsecond:

import datetime
datetime.datetime.now().replace(microsecond=0).isoformat()
>>> 2020-03-20T14:30:43

UTC to ISO 8601 with TimeZone information (Python 3):

import datetime
datetime.datetime.now(tz=datetime.timezone.utc).isoformat()
>>> 2020-03-20T01:31:12.467113+00:00

UTC to ISO 8601 with Local TimeZone information without microsecond (Python 3):

import datetime
datetime.datetime.now().astimezone().replace(microsecond=0).isoformat()
>>> 2020-03-20T14:31:43+13:00

Local to ISO 8601 with TimeZone information (Python 3):

import datetime
datetime.datetime.now().astimezone().isoformat()
>>> 2020-03-20T14:32:16.458361+13:00

Notice there is a bug when using astimezone() on utc time. This gives an incorrect result:

datetime.datetime.utcnow().astimezone().isoformat() #Incorrect result

For Python 2, see and use pytz.

xtreak
  • 1,376
  • 18
  • 42
estani
  • 24,254
  • 2
  • 93
  • 76
  • 49
    `.isoformat()` can take a separator parameter,(In case you want something other than 'T'): `.isoformat(' ')` – ThorSummoner Aug 24 '16 at 23:21
  • 10
    @ThorSummoner good to know! But take care that changing the separator won't comply with ISO-8601 anymore... which makes little sense (besides, there are better ways to print dates but that't wasn't the question here). [RFC](https://tools.ietf.org/html/rfc3339#section-5.6) – estani Aug 25 '16 at 07:48
  • 20
    Space is allowed as part of ISO-8601 standard. `It is permitted to omit the 'T' character by mutual agreement.` – raychi Sep 07 '16 at 19:52
  • 9
    @raychi It's always permitted to change a standard by mutual agreement (which in many cases will break the standard, but who cares if it's mutual agreement, right?). My 2c: just don't, leave it as is. – estani Sep 08 '16 at 12:45
  • 23
    This answer is wrong because datetime.datetime.now().isoformat() returns a local ISO 8601 string without labeling it as local. You need to have a datetime.timezone representing the local timezone to get isoformat to do the right thing. – Sam Hartman Apr 30 '17 at 21:15
  • Looks like `offset=utc_offset_sec` should be replaced with `offset=datetime.timedelta(seconds=-utc_offset_sec)` – imbolc Apr 06 '18 at 03:44
  • 2
    The "Local to ISO-8601 with TimeZone info (python 3)" section could be improved by using: `datetime.datetime.now(datetime.timezone(datetime.timedelta(seconds=time.localtime().tm_gmtoff))).isoformat()`. There's no need to replace the `tzinfo` when you can pass it to `now`, and the `altzone` problem is resolved by using `tm_gmtoff`. – ToBeReplaced Jul 14 '18 at 03:08
  • How to get the format of ISO-8601 in Python? Because we need to `strptime` the string. `%Y-%m-%dT%H:%M:%S%z` return `-HHMM` or `+HHMM` but not `-HH:MM`. – Cloud Aug 08 '18 at 04:02
  • UTC to ISO-8601 with timezone info (uses [pytz](http://pytz.sourceforge.net/)) `datetime.datetime.now(tz=pytz.utc).isoformat()` – Christian Long Sep 05 '18 at 21:25
  • 4
    but my other javascript app time is in the format `2019-02-23T04:01:55.360Z`, `Z` for zero hour offset, isn't there an option for this format? – Mzq Feb 25 '19 at 09:33
  • 2
    UTC to ISO-8601 with timezone info (Python 3, without pytz): `datetime.datetime.now(tz=datetime.timezone.utc).isoformat()` – Vinod Kurup Sep 05 '19 at 13:26
  • 2
    Omitting the 'T' does not change the standard since it is a part of the ISO 8601 standard. – tomsv Nov 23 '19 at 17:24
  • `datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc).astimezone().replace(microsecond=0).isoformat()` – David Valenzuela Urrutia Dec 26 '19 at 14:08
  • I went through bad times using `datetime.replace(tzinfo)`! I would say: avoid it! It adds minutes between the conversions. Use `pytz.localize()` instead. See more [here](https://stackoverflow.com/questions/60996205/python-datetime-timezone-conversion-off-by-4-minutes/67943912#67943912). – Renato Aloi Jun 17 '21 at 01:49
  • SO: how come one has to scroll down so much to find the most upvoted (and ideal) answer? Shame on you! :-D – Clint Eastwood Mar 25 '22 at 19:54
  • Shorter version: datetime.now(timezone.utc).isoformat(timespec='seconds') – Kiruahxh Jan 17 '23 at 14:23
109

ISO 8601 allows a compact representation with no separators except for the T, so I like to use this one-liner to get a quick timestamp string:

>>> datetime.datetime.utcnow().strftime("%Y%m%dT%H%M%S.%fZ")
'20180905T140903.591680Z'

If you don't need the microseconds, just leave out the .%f part:

>>> datetime.datetime.utcnow().strftime("%Y%m%dT%H%M%SZ")
'20180905T140903Z'

For local time:

>>> datetime.datetime.now().strftime("%Y%m%dT%H%M%S")
'20180905T140903'

In general, I recommend you leave the punctuation in. RFC 3339 recommends that style because if everyone uses punctuation, there isn't a risk of things like multiple ISO 8601 strings being sorted in groups on their punctuation. So the one liner for a compliant string would be:

>>> datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%SZ")
'2018-09-05T14:09:03Z'
Neuron
  • 5,141
  • 5
  • 38
  • 59
Jim Hunziker
  • 14,111
  • 8
  • 58
  • 64
  • 5
    is there a way to limit the digits of milliseconds to 3? i.e. my other javascript app will produce `2019-02-23T04:02:04.051Z` with `new Date().toISOString()` – Mzq Feb 25 '19 at 09:48
  • 1
    Warning: ‘Z’ at the end is incorrect. The correct way to output the time zone including the ‘Z’ symbol is ‘%z’ or ‘%Z’, which makes it ‘datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%S%z")’. I’ve spent 2h trying to find a bug in my code… – Dr_Zaszuś Jul 31 '20 at 13:18
  • 3
    @Dr_Zaszuś That's not right. %z generates a timezone offset for your local time, which is not what I recommend for timestamps, especially if you're looking at multiple systems. A literal Z is ISO 8601 shorthand for UTC. – Jim Hunziker Aug 03 '20 at 15:54
  • 1
    @Miranda javascript like output datetime.datetime.now(datetime.timezone.utc).isoformat()[:23]+"Z" – seizu Apr 03 '21 at 13:37
  • @Dr_Zaszuś You can also use `utcnow()` instead of `now()` – DollarAkshay Dec 20 '22 at 08:20
79

Here is what I use to convert to the XSD datetime format:

from datetime import datetime
datetime.now().replace(microsecond=0).isoformat()
# You get your ISO string

I came across this question when looking for the XSD date time format (xs:dateTime). I needed to remove the microseconds from isoformat.

radtek
  • 34,210
  • 11
  • 144
  • 111
66

ISO 8601 Time Representation

The international standard ISO 8601 describes a string representation for dates and times. Two simple examples of this format are

2010-12-16 17:22:15
20101216T172215

(which both stand for the 16th of December 2010), but the format also allows for sub-second resolution times and to specify time zones. This format is of course not Python-specific, but it is good for storing dates and times in a portable format. Details about this format can be found in the Markus Kuhn entry.

I recommend use of this format to store times in files.

One way to get the current time in this representation is to use strftime from the time module in the Python standard library:

>>> from time import strftime
>>> strftime("%Y-%m-%d %H:%M:%S")
'2010-03-03 21:16:45'

You can use the strptime constructor of the datetime class:

>>> from datetime import datetime
>>> datetime.strptime("2010-06-04 21:08:12", "%Y-%m-%d %H:%M:%S")
datetime.datetime(2010, 6, 4, 21, 8, 12)

The most robust is the Egenix mxDateTime module:

>>> from mx.DateTime.ISO import ParseDateTimeUTC
>>> from datetime import datetime
>>> x = ParseDateTimeUTC("2010-06-04 21:08:12")
>>> datetime.fromtimestamp(x)
datetime.datetime(2010, 3, 6, 21, 8, 12)

References

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Voislav Sauca
  • 3,007
  • 2
  • 18
  • 12
53

I found the datetime.isoformat in the documentation. It seems to do what you want:

datetime.isoformat([sep])

Return a string representing the date and time in ISO 8601 format, YYYY-MM-DDTHH:MM:SS.mmmmmm or, if microsecond is 0, YYYY-MM-DDTHH:MM:SS

If utcoffset() does not return None, a 6-character string is appended, giving the UTC offset in (signed) hours and minutes: YYYY-MM-DDTHH:MM:SS.mmmmmm+HH:MM or, if microsecond is 0 YYYY-MM-DDTHH:MM:SS+HH:MM

The optional argument sep (default 'T') is a one-character separator, placed between the date and time portions of the result. For example,
>>>

>>> from datetime import tzinfo, timedelta, datetime
>>> class TZ(tzinfo):
...     def utcoffset(self, dt): return timedelta(minutes=-399)
...
>>> datetime(2002, 12, 25, tzinfo=TZ()).isoformat(' ')
'2002-12-25 00:00:00-06:39'
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
user2120810
  • 647
  • 5
  • 2
31

For those who are looking for a date-only solution, it is:

import datetime

datetime.date.today().isoformat()
26

The ISO 8601 time format does not store a time zone name, only the corresponding UTC offset is preserved.

To convert a file ctime to an ISO 8601 time string while preserving the UTC offset in Python 3:

>>> import os
>>> from datetime import datetime, timezone
>>> ts = os.path.getctime(some_file)
>>> dt = datetime.fromtimestamp(ts, timezone.utc)
>>> dt.astimezone().isoformat()
'2015-11-27T00:29:06.839600-05:00'

The code assumes that your local timezone is Eastern Time Zone (ET) and that your system provides a correct UTC offset for the given POSIX timestamp (ts), i.e., Python has access to a historical timezone database on your system or the time zone had the same rules at a given date.

If you need a portable solution; use the pytz module that provides access to the tz database:

>>> import os
>>> from datetime import datetime
>>> import pytz  # pip install pytz
>>> ts = os.path.getctime(some_file)
>>> dt = datetime.fromtimestamp(ts, pytz.timezone('America/New_York'))
>>> dt.isoformat()
'2015-11-27T00:29:06.839600-05:00'

The result is the same in this case.

If you need the time zone name/abbreviation/zone id, store it separately.

>>> dt.astimezone().strftime('%Y-%m-%d %H:%M:%S%z (%Z)')
'2015-11-27 00:29:06-0500 (EST)'

Note: no, : in the UTC offset and EST timezone abbreviation is not part of the ISO 8601 time format. It is not unique.

Different libraries/different versions of the same library may use different time zone rules for the same date/timezone. If it is a future date then the rules might be unknown yet. In other words, the same UTC time may correspond to a different local time depending on what rules you use -- saving a time in ISO 8601 format preserves UTC time and the local time that corresponds to the current time zone rules in use on your platform. You might need to recalculate the local time on a different platform if it has different rules.

Community
  • 1
  • 1
jfs
  • 399,953
  • 195
  • 994
  • 1,670
20

You'll need to use os.stat to get the file creation time and a combination of time.strftime and time.timezone for formatting:

>>> import time
>>> import os
>>> t = os.stat('C:/Path/To/File.txt').st_ctime
>>> t = time.localtime(t)
>>> formatted = time.strftime('%Y-%m-%d %H:%M:%S', t)
>>> tz = str.format('{0:+06.2f}', float(time.timezone) / 3600)
>>> final = formatted + tz
>>> 
>>> final
'2008-11-24 14:46:08-02.00'
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Max Shawabkeh
  • 37,799
  • 10
  • 82
  • 91
  • Does the timezone account for daylight savings time? It seems like tz will be the same regardless of daylight savings time or not. – Joseph Turian Jan 29 '10 at 06:30
  • 2
    It will be whatever offset is currently in effect. If DST is active, it will have a different offset from when DST is not. – Max Shawabkeh Jan 29 '10 at 06:35
14

Just make the life simple please:

  1. Use UTC time
  2. Microsecond
  3. one line code

f"{datetime.datetime.utcnow().isoformat()[:-3]}Z"

output:

2022-02-25T02:08:40.684Z

John Jang
  • 2,567
  • 24
  • 28
6

Standard RFC-3339 in milliseconds

I needed the time in this format for a LoRa application so I came up with this, I hope it helps:

from datetime import datetime
from time import strftime


# Get the current time in the format: 2021-03-20T16:51:23.644+01:00
def rfc3339_time_ms():
        datetime_now = datetime.utcnow()
        # Remove the microseconds
        datetime_now_ms = datetime_now.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3]
        # Add the timezone as "+/-HHMM", and the colon in "+/-HH:MM"
        datetime_now_ms_tz = datetime_now_ms + strftime("%z")
        rfc3339_ms_now = datetime_now_ms_tz[:-2] + ":" + datetime_now_ms_tz[-2:]
        # print(f"Current time in ms in RFC-3339 format: {rfc3339_ms_now}")
        return rfc3339_ms_now
Stéphane Bruckert
  • 21,706
  • 14
  • 92
  • 130
chronoclast
  • 81
  • 1
  • 5
4

Correct me if I'm wrong (I'm not), but the offset from UTC changes with daylight saving time. So you should use

tz = str.format('{0:+06.2f}', float(time.altzone) / 3600)

I also believe that the sign should be different:

tz = str.format('{0:+06.2f}', -float(time.altzone) / 3600)

I could be wrong, but I don't think so.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Jarek Przygódzki
  • 4,284
  • 2
  • 31
  • 41
3

Adding a small variation to estani's excellent answer

Local to ISO 8601 with TimeZone and no microsecond info (Python 3):

import datetime, time

utc_offset_sec = time.altzone if time.localtime().tm_isdst else time.timezone
utc_offset = datetime.timedelta(seconds=-utc_offset_sec)
datetime.datetime.now().replace(microsecond=0, tzinfo=datetime.timezone(offset=utc_offset)).isoformat()

Sample Output:

'2019-11-06T12:12:06-08:00'

Tested that this output can be parsed by both Javascript Date and C# DateTime/DateTimeOffset

Sharpiro
  • 2,305
  • 2
  • 18
  • 27
2

I agree with Jarek, and I furthermore note that the ISO offset separator character is a colon, so I think the final answer should be:

isodate.datetime_isoformat(datetime.datetime.now()) + str.format('{0:+06.2f}', -float(time.timezone) / 3600).replace('.', ':')
mattbornski
  • 11,895
  • 4
  • 31
  • 25
1

I've developed this function:

def iso_8601_format(dt):
    """YYYY-MM-DDThh:mm:ssTZD (1997-07-16T19:20:30-03:00)"""

    if dt is None:
        return ""

    fmt_datetime = dt.strftime('%Y-%m-%dT%H:%M:%S')
    tz = dt.utcoffset()
    if tz is None:
        fmt_timezone = "+00:00"
    else:
        fmt_timezone = str.format('{0:+06.2f}', float(tz.total_seconds() / 3600))

    return fmt_datetime + fmt_timezone
-6
import datetime, time    
def convert_enddate_to_seconds(self, ts):
    """Takes ISO 8601 format(string) and converts into epoch time."""
     dt = datetime.datetime.strptime(ts[:-7],'%Y-%m-%dT%H:%M:%S.%f')+\
                datetime.timedelta(hours=int(ts[-5:-3]),
                minutes=int(ts[-2:]))*int(ts[-6:-5]+'1')
    seconds = time.mktime(dt.timetuple()) + dt.microsecond/1000000.0
    return seconds 

>>> import datetime, time
>>> ts = '2012-09-30T15:31:50.262-08:00'
>>> dt = datetime.datetime.strptime(ts[:-7],'%Y-%m-%dT%H:%M:%S.%f')+ datetime.timedelta(hours=int(ts[-5:-3]), minutes=int(ts[-2:]))*int(ts[-6:-5]+'1')
>>> seconds = time.mktime(dt.timetuple()) + dt.microsecond/1000000.0
>>> seconds
1348990310.26
ronak
  • 1,770
  • 3
  • 20
  • 34