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.