Skip to content
This repository was archived by the owner on Jan 11, 2023. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion python_wikibase/data_types/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
from python_wikibase.data_types.geo_location import GeoLocation
from python_wikibase.data_types.quantity import Quantity
from python_wikibase.data_types.string_value import StringValue
from python_wikibase.data_types.time import Time

__all__ = ["ExternalId", "GeoLocation", "Quantity", "StringValue"]
__all__ = ["ExternalId", "GeoLocation", "Quantity", "StringValue", "Time"]
2 changes: 1 addition & 1 deletion python_wikibase/data_types/data_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def unmarshal_data_value(py_wb, main_snak):
elif data_type == "tabular-data":
raise NotImplementedError # TODO
elif data_type == "time":
raise NotImplementedError # TODO
return py_wb.Time().unmarshal(data_value)
elif data_type == "url":
raise NotImplementedError # TODO
elif data_type == "wikibase-form":
Expand Down
84 changes: 84 additions & 0 deletions python_wikibase/data_types/time.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import collections
import datetime

from python_wikibase.data_types.data_type import DataType

GREGORIAN_CALENDAR_WIKIDATA_URL = "http://www.wikidata.org/entity/Q1985727"


class DataTypeException(BaseException):
pass

# Largely inspired by https://github.com/dahlia/wikidata/blob/master/wikidata/datavalue.py


class Time(DataType):
def __init__(self, py_wb, api, language):
super().__init__(py_wb, api, language)
self.value = None

def __str__(self):
return self.value.isoformat()

def unmarshal(self, data_value):
value = data_value["value"]

calendar_model = value['calendarmodel']
if calendar_model != GREGORIAN_CALENDAR_WIKIDATA_URL:
raise DataTypeException(f"Unsupported calendar model: [{calendar_model}]")

# See https://www.mediawiki.org/wiki/Wikibase/DataModel/JSON#time
# for property to extract from data_value

time = value['time']
# Strip '+' at beginning of string and 'Z' at the end
time = time[1:-1]

tz = value['timezone']
# Don't take in account 'before' and 'after' property
precision = value['precision']

if precision not in (11, 14):
raise DataTypeException(f"Not supported precision: [{precistion}]")

if precision == 11:
self.value = datetime.date.fromisoformat(time[:-9])
elif precision == 14:
val = datetime.datetime.fromisoformat(time)
self.value = val.replace(tzinfo=datetime.timezone(offset=datetime.timedelta(minutes=tz)))
return self

def marshal(self):

common_properties = {
"timezone": 0,
"before": 0,
"after": 0,
"calendarmodel": GREGORIAN_CALENDAR_WIKIDATA_URL
}

if isinstance(self.value, datetime.date):
return {
**common_properties,
"time": "+{}T00:00:00Z".format(self.value.isoformat()),
"precision": 11
}
elif isinstance(self.value, datetime.datetime):
dt = self.value.astimezone(tz=datetime.timezone.utc)
return {
**common_properties,
"time": "+{}Z".format(iso_str[:19]),
"precision": 14
}

def create(self, value):
if isinstance(value, datetime.datetime):
# consider as utc if timezone info is missing
if value.tzinfo is None:
value = value.replace(tzinfo=datetime.timezone.utc)

elif not isinstance(value, datetime.date):
raise DataTypeException("Unknown value type {!r}".format(value))
self.value = value

return self
5 changes: 4 additions & 1 deletion python_wikibase/python_wikibase.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
Reference,
References,
)
from python_wikibase.data_types import ExternalId, GeoLocation, Quantity, StringValue
from python_wikibase.data_types import ExternalId, GeoLocation, Quantity, StringValue, Time

DEFAULT_CONFIG = {
"api_url": "https://www.wikidata.org/w/api.php",
Expand Down Expand Up @@ -96,3 +96,6 @@ def Quantity(self):

def StringValue(self):
return StringValue(self, self.api, self.language)

def Time(self):
return Time(self, self.api, self.language)
9 changes: 9 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
PROP_ITEM_LABEL,
PROP_LABEL,
PROP_QUANTITY_LABEL,
PROP_TIME_LABEL,
STRING_VALUE,
)

Expand Down Expand Up @@ -110,8 +111,16 @@ def prop_quantity(py_wb):
prop.delete()


@pytest.fixture(scope="function")
def prop_time(py_wb):
prop = py_wb.Property().create(PROP_TIME_LABEL, data_type="Time")
assert prop.label.get(LANGUAGE) == PROP_TIME_LABEL
yield prop
prop.delete()

# Values


@pytest.fixture(scope="function")
def string_value(py_wb):
string_value = py_wb.StringValue().create(STRING_VALUE)
Expand Down
1 change: 1 addition & 0 deletions tests/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@
PROP_ITEM_LABEL = "Wikibase item property"
PROP_GEO_LOCATION_LABEL = "GeoLocation property"
PROP_QUANTITY_LABEL = "Quantity property"
PROP_TIME_LABEL = "Time property"

STRING_VALUE = "String value"
10 changes: 10 additions & 0 deletions tests/test_claim.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,13 @@ def test_quantity_with_unit(self, py_wb, item, prop_quantity, item_unit):
assert claim.value.amount == amount
assert float(claim.value) == amount
assert claim.value.marshal() == quantity.marshal()

# Time

def test_time(self, py_wb, item, prop_time):
date = datetime.date.fromisoformart("2012-05-12")
time_ = py_wb.Time().create(date_str)
claim = item.claims.add(prop_time, time_)
assert claim.property.data_type == "Time"
assert claim.value.date == date
assert claim.value.marshal() == time_.marshal()