gspread icon indicating copy to clipboard operation
gspread copied to clipboard

Unable to insert datetime object

Open p-doyle opened this issue 7 years ago • 5 comments

Environment info Operating System: Windows 10 Python version: 2.7.12 gspread version: 2.0.0

Steps to reproduce

  1. worksheet.insert_row([datetime.datetime.now()])

Traceback:

Traceback (most recent call last):
  File "D:/gspreadinserttest.py", line 21, in <module>
    sh.insert_row([datetime.datetime.now()])
  File "C:\Python27\lib\site-packages\gspread\v4\models.py", line 685, in insert_row
    'values': [values]
  File "C:\Python27\lib\site-packages\gspread\v4\models.py", line 112, in values_update
    r = self.client.request('put', url, params=params, json=body)
  File "C:\Python27\lib\site-packages\gspread\v4\client.py", line 67, in request
    endpoint, json=json, params=params, data=data, files=files
  File "C:\Python27\lib\site-packages\requests\sessions.py", line 566, in put
    return self.request('PUT', url, data=data, **kwargs)
  File "C:\Python27\lib\site-packages\requests\sessions.py", line 494, in request
    prep = self.prepare_request(req)
  File "C:\Python27\lib\site-packages\requests\sessions.py", line 437, in prepare_request
    hooks=merge_hooks(request.hooks, self.hooks),
  File "C:\Python27\lib\site-packages\requests\models.py", line 308, in prepare
    self.prepare_body(data, files, json)
  File "C:\Python27\lib\site-packages\requests\models.py", line 458, in prepare_body
    body = complexjson.dumps(json)
  File "C:\Python27\lib\json\__init__.py", line 231, in dumps
    return _default_encoder.encode(obj)
  File "C:\Python27\lib\json\encoder.py", line 201, in encode
    chunks = self.iterencode(o, _one_shot=True)
  File "C:\Python27\lib\json\encoder.py", line 264, in iterencode
    return _iterencode(o, 0)
  File "C:\Python27\lib\json\encoder.py", line 178, in default
    raise TypeError(repr(o) + " is not JSON serializable")
TypeError: datetime.datetime(2018, 3, 11, 13, 26, 34, 292000) is not JSON serializable

I know it is possible to convert to a string before inserting however in previous gspread versions I could insert a datetime object and it would format the datetime using the formatting from the google sheet. Is there anyway this kind of functionality could be replicated in 2.0.0?

p-doyle avatar Mar 11 '18 18:03 p-doyle

First of all, thanks for trying a new version :)

It's a good point. Currently, there's no such functionality.

You're right, in the previous version (0.6.2), you could just pass an arbitrary object and it'll be converted to a string representation with the help of unicode function. Here the code responsible for the construction of an update xml feed in the version 0.6.2:

https://github.com/burnash/gspread/blob/a6fefbaea8c4e915f841246fde1ca6984ac08dd3/gspread/models.py#L460-L487

This is where it happens:'inputValue': unicode(cell.value).

In the new version, the implicit conversion is no longer needed, since API v4 uses JSON. In 2.0.0 the values go to the Sheets API as is:

https://github.com/burnash/gspread/blob/a6fefbaea8c4e915f841246fde1ca6984ac08dd3/gspread/v4/models.py#L511-L551

And that's where this age old problem arises. On the one hand, the idea of implicitly casting everything to string is not my favorite. On the other hand, for certain types having an implicit cast can improve end-user experience.

One option is to implement something like default parameter in json.dump:

If specified, default should be a function that gets called for objects that can’t otherwise be serialized. It should return a JSON encodable version of the object or raise a TypeError. If not specified, TypeError is raised.

But I haven't yet figured out how to squeeze it in properly.

burnash avatar Mar 11 '18 20:03 burnash

I'm not sure how helpful this will be, but I was able to write a date to the sheet by converting it to a count of the number of days since the Google Sheets epoch date of "1899-12-30" (https://developers.google.com/sheets/api/guides/concepts).

Something like: cell.value = (datetime.datetime(2018, 3, 11) - datetime.datetime(1899, 12, 30)).days

cgmorton avatar May 04 '18 02:05 cgmorton

@cgmorton thanks so much I was looking for a solution all over the web, this worked on pyton 2.7:

(date.today() - date(1899, 12, 30)).days

aramirez087 avatar May 29 '18 22:05 aramirez087

The main issue here is:

  • HTTP requests sent to Google API only accepts strings or unicode (aka some text in some languages)
  • Should we convert everything to a string or not ?

I would agree that everything should be converted to its string representation as in all cases we can't send a datetime object over to the spreadsheet, and sending the string representation of a datetime object is working fine and SpreadSheet can convert it to a date format seamlessly.

I first fix would be: make sure everything sent over is a string or raise otherwise. this would prevent the raise of an underlying library that stops because it cannot read this json object.

lavigne958 avatar May 18 '21 10:05 lavigne958

You could use this function, it worked for me:

Imports:

import gspread
import datetime

Lets have a variable now as datetime object:

now = datetime.datetime.now()

then pass now to the following function as string.

wb.values_update(
    'sheet1!A2',
    params={
        'valueInputOption': 'USER_ENTERED'
    },
    body={
        'values': [[str(now)]]
    }
)

This way your entered datetime will be recognized by google sheet as if it was entered by a person. If you want to know more about how to use gspread follow this link.

Entering multiple values could be done as well by replacing [[str(now)]]

with:

[[str(now) ,'b','c','d']]

MAlhussaini avatar Nov 24 '21 11:11 MAlhussaini