Skip to content

Commit

Permalink
feat(Range): Add datetime python conversion
Browse files Browse the repository at this point in the history
  • Loading branch information
philippe2803 committed Nov 8, 2021
1 parent 210d10e commit fe664b6
Show file tree
Hide file tree
Showing 7 changed files with 182 additions and 4 deletions.
9 changes: 8 additions & 1 deletion sheetfu/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

import string
import math
from datetime import datetime
from datetime import datetime, timedelta
from collections import namedtuple


Expand Down Expand Up @@ -118,3 +118,10 @@ def datetime_to_serial_number(date):
time_delta = (date - google_sheets_epoch)
date_serial_number = float(time_delta.days) + (float(time_delta.seconds) / 86400)
return date_serial_number


def serial_number_to_datetime(date_serial_number):
google_sheets_epoch = datetime(1899, 12, 30)
day_decimal = float(date_serial_number) - float(int(date_serial_number))
seconds_in_last_day = day_decimal * 24 * 3600
return google_sheets_epoch + timedelta(days=int(date_serial_number)) + timedelta(seconds=seconds_in_last_day)
2 changes: 1 addition & 1 deletion sheetfu/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -447,7 +447,7 @@ def make_set_request(self, field, data, set_parser, batch_to=None):

def get_values(self):
data = self.make_get_request(
field_mask="sheets/data/rowData/values/effectiveValue",
field_mask="sheets/data/rowData/values/effectiveValue,sheets/data/rowData/values/effectiveFormat/numberFormat/type",
cell_parser=CellParsers.get_value
)
return data
Expand Down
21 changes: 19 additions & 2 deletions sheetfu/parsers.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from sheetfu.helpers import rgb_to_hex, hex_to_rgb, datetime_to_serial_number
from sheetfu.helpers import rgb_to_hex, hex_to_rgb, datetime_to_serial_number, serial_number_to_datetime
from datetime import datetime


Expand All @@ -7,8 +7,16 @@ class CellParsers:
@staticmethod
def get_value(cell):
value_body = cell["effectiveValue"]
format_type = (
cell
.get('effectiveFormat', {})
.get("numberFormat", {})
.get("type")
)
for value_type in ['stringValue', 'numberValue', 'boolValue', 'formulaValue']:
if value_body.get(value_type) is not None:
if format_type in ["DATE", "DATE_TIME"]:
return serial_number_to_datetime(value_body[value_type])
return value_body[value_type]

@staticmethod
Expand All @@ -21,7 +29,16 @@ def set_value(cell):
elif isinstance(cell, int) or isinstance(cell, float):
return {"userEnteredValue": {"numberValue": cell}}
elif isinstance(cell, datetime):
return {"userEnteredValue": {"numberValue": datetime_to_serial_number(cell)}}
return {
"userEnteredValue": {
"numberValue": datetime_to_serial_number(cell)
},
"userEnteredFormat": {
"numberFormat": {
"type": "DATE_TIME"
}
}
}
return {"userEnteredValue": {"stringValue": ''}}

@staticmethod
Expand Down
22 changes: 22 additions & 0 deletions tests/fixtures/get_values_datetime.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"sheets": [{
"data": [{
"rowData": [{
"values": [{
"effectiveValue": {
"stringValue": "foo"
}
}, {
"effectiveValue": {
"numberValue": 44317
},
"effectiveFormat": {
"numberFormat": {
"type": "DATE"
}
}
}]
}]
}]
}]
}
47 changes: 47 additions & 0 deletions tests/fixtures/table_values_datetime.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"sheets": [{
"data": [{
"rowData": [{
"values": [{
"effectiveValue": {
"stringValue": "name"
}
}, {
"effectiveValue": {
"stringValue": "birthday"
}
}]
}, {
"values": [{
"effectiveValue": {
"stringValue": "foo"
}
}, {
"effectiveValue": {
"numberValue": 44317
},
"effectiveFormat": {
"numberFormat": {
"type": "DATE"
}
}
}]
}, {
"values": [{
"effectiveValue": {
"stringValue": "bar"
}
}, {
"effectiveValue": {
"numberValue": 44469
},
"effectiveFormat": {
"numberFormat": {
"type": "DATE"
}
}
}]
}]
}]
}]
}
29 changes: 29 additions & 0 deletions tests/test_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from tests.utils import open_fixture
import pytest
import json
import datetime


class TestModelClients:
Expand Down Expand Up @@ -265,3 +266,31 @@ def test_invalid_row_and_column(self):
with pytest.raises(RowOrColumnEqualsZeroError):
self.sheet.get_range(row=0, column=0)


class TestDatetimeValues:

http_mocks = mock_spreadsheet_instance(fixtures=[
"get_values_datetime.json"
])

client = SpreadsheetApp(http=http_mocks)
spreadsheet = client.open_by_id('some_id')
sheet = spreadsheet.get_sheet_by_name("people")
data_range = sheet.get_range_from_a1("A1:B1")
values = data_range.get_values()

def test_length_values(self):
assert len(self.values) == 1
assert len(self.values[0]) == 2

def test_first_value(self):
assert self.values[0][0] == "foo"

def test_is_datetime(self):
assert isinstance(self.values[0][1], datetime.datetime)

def test_datetime_value(self):
test_date = self.values[0][1]
assert test_date.year == 2021
assert test_date.month == 5
assert test_date.day == 1
56 changes: 56 additions & 0 deletions tests/test_table.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import datetime

import pytest

from sheetfu import SpreadsheetApp, Table
Expand Down Expand Up @@ -291,3 +293,57 @@ def test_empty_select(self, table):
def test_value_error_exception(self, table):
with pytest.raises(ValueError):
table.select([[25, 35]])


class TestTableDatetimeField:
http_mocks = mock_google_sheets_responses([
'table_get_sheets.json',
'table_values_datetime.json',
'table_values_datetime.json'
])
sa = SpreadsheetApp(http=http_mocks)
sheet = sa.open_by_id('whatever').get_sheet_by_name('Sheet1')
data_range = sheet.get_range_from_a1("A1:B3")
table = Table(
full_range=data_range
)

def test_table_size(self):
assert len(self.table) == 2
assert len(self.table.header) == 2

def test_first_row(self):
row = self.table[0]
birthday = row.get_field_value("birthday")
assert row.get_field_value("name") == "foo"
assert isinstance(birthday, datetime.datetime)
assert birthday.year == 2021
assert birthday.month == 5
assert birthday.day == 1

def test_second_row(self):
row = self.table[1]
birthday = row.get_field_value("birthday")
assert row.get_field_value("name") == "bar"
assert isinstance(birthday, datetime.datetime)
assert birthday.year == 2021
assert birthday.month == 9
assert birthday.day == 30

def test_set_datetime_values(self):
for row in self.table:
birthday = row.get_field_value("birthday")
new_birthday = birthday + datetime.timedelta(days=1)
row.set_field_value("birthday", new_birthday)

# we then check the request put in the batch
first_request = self.table.batches[0]
first_value = first_request["updateCells"]["rows"][0]["values"][0]["userEnteredValue"]["numberValue"]
assert first_value == 44318.0

second_request = self.table.batches[1]
second_value = second_request["updateCells"]["rows"][0]["values"][0]["userEnteredValue"]["numberValue"]
assert second_value == 44470.0

for batch in self.table.batches:
assert batch["updateCells"]["rows"][0]["values"][0]["userEnteredFormat"]["numberFormat"]["type"] == "DATE_TIME"

0 comments on commit fe664b6

Please sign in to comment.