Convert DatetimeWithNanoseconds to date format in python firestore

I am trying to convert a firestore timestamp into milliseconds or a date format using python to perform a calculation using this date. Trying to parse it to date returns.

TypeError: strptime() argument 1 must be str, not DatetimeWithNanoseconds.

How can I convert a Firestore timestamp into milliseconds/date format in python? I need this to perform a calculation.

docs = db.collection(u'status').stream()

for doc in docs:
    doc_data = doc.to_dict()

    # TypeError: strptime() argument 1 must be str, not DatetimeWithNanoseconds
    utc_time = datetime.strptime(doc_data['start_date'], "%Y-%m-%dT%H:%M:%S.%fZ")

It turns out that DatetimeWithNanoseconds inherits from datetime.datetime already


As implied by Luke's answer, you can use all of the usual methods of datetime with values of DatetimeWithNanoseconds, because it's a subclass. So for example you can do doc_data['start_date'].timestamp() to get the time in seconds since 1970-01-01 (including fractional seconds).

However, from experimenting and looking at the implementation of DatetimeWithNanoseconds, I've noticed some issues that developers should be aware of (and possibly that Google should fix).

DatetimeWithNanoseconds adds a read-only nanosecond property to datetime, but keeps its microsecond property. The intent appears to be that you can set one or the other, but not both, when creating the object, and then read either one. When using the DatetimeWithNanoseconds constructor, you can either set microsecond or nanosecond, but not both (if setting nanosecond, it must be specified as a keyword argument). If you try to set both, it throws an exception. If you only set nanosecond, it correctly sets microsecond to nanosecond / 1000, rounded down. If you only set microsecond, it sets microsecond, but nanosecond is set to zero.

>>> from google.api_core.datetime_helpers import DatetimeWithNanoseconds
>>> d1 = DatetimeWithNanoseconds(2020, 6, 22, 17, 1, 30, 12345)
>>> d1.microsecond
12345
>>> d1.nanosecond
0
>>> d2 = DatetimeWithNanoseconds(2020, 6, 22, 17, 1, 30, nanosecond=1234567)
>>> d2.microsecond
1234
>>> d2.nanosecond
1234567

If you construct a DatetimeWithNanoseconds in other ways, like for example DatetimeWithNanoseconds.now(), you always get a value with nanosecond set to zero.

>>> now = DatetimeWithNanoseconds.now()
>>> now.microsecond
51570
>>> now.nanosecond
0

The values that I get when fetching documents from Firestore always seem to have nanosecond set to zero! So if I were to fetch a document with a timestamp, then write it back to Firestore, it would lose precision. (Though admittedly I am using a pretty old version of firebase-admin, so it may have been fixed in a more recent version.)

I'm currently doing something like this to extract the seconds and nanoseconds from a timestamp (I use this in a context where I'm not sure if the timestamp is a datetime or a DatetimeWithNanoseconds):

def get_seconds_and_nanoseconds(val):
    if isinstance(val, datetime_helpers.DatetimeWithNanoseconds) and val.nanosecond:
        return {
            'nanoseconds': val.nanosecond,
            'seconds': int(val.timestamp()),
        }
    if isinstance(val, datetime):
        return {
            'nanoseconds': 1000 * int(val.microsecond),
            'seconds': int(val.timestamp()),
        }
    raise TypeError(f'Not a datetime or DatetimeWithNanoseconds')

And to construct a correct DatetimeWithNanoseconds from seconds and nanoseconds, I do this:

def datetime_with_nanoseconds(seconds, nanoseconds):
    dt = datetime.fromtimestamp(seconds, tz=timezone.utc)
    dtnanos = datetime_helpers.DatetimeWithNanoseconds(
        dt.year,
        dt.month,
        dt.day,
        dt.hour,
        dt.minute,
        dt.second,
        nanosecond=nanoseconds,
        tzinfo=dt.tzinfo,
    )
    return dtnanos

It's ugly, but I haven't found a more concise way yet.