Skip to content

Seamlessly Read/Write datetime.date Objects #18

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
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
21 changes: 16 additions & 5 deletions shapefile.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import time
import array
import tempfile
from datetime import date

#
# Constants for shape types
Expand Down Expand Up @@ -473,8 +474,7 @@ def __record(self):
# deleted record
return None
record = []
for (name, typ, size, deci), value in zip(self.fields,
recordContents):
for (name, typ, size, deci), value in zip(self.fields, recordContents):
if name == 'DeletionFlag':
continue
elif not value.strip():
Expand All @@ -491,7 +491,7 @@ def __record(self):
elif typ == b('D'):
try:
y, m, d = int(value[:4]), int(value[4:6]), int(value[6:8])
value = [y, m, d]
value = date(y, m, d)
except:
value = value.strip()
elif typ == b('L'):
Expand Down Expand Up @@ -882,10 +882,12 @@ def __dbfRecords(self):
for (fieldName, fieldType, size, dec), value in zip(self.fields, record):
fieldType = fieldType.upper()
size = int(size)
if fieldType.upper() == "N":
if fieldType == 'N':
value = str(value).rjust(size)
elif fieldType == 'L':
value = str(value)[0].upper()
elif fieldType == 'D' and isinstance(value, date):
value = value.strftime('%Y%m%d').ljust(size)
else:
value = str(value)[:size].ljust(size)
assert len(value) == size
Expand Down Expand Up @@ -939,10 +941,19 @@ def poly(self, parts=[], shapeType=POLYGON, partTypes=[]):
polyShape.partTypes = partTypes
self._shapes.append(polyShape)

def field(self, name, fieldType="C", size="50", decimal=0):
def field(self, name, fieldType='C', size=50, decimal=0):
"""Adds a dbf field descriptor to the shapefile."""
fieldType = fieldType.upper()
size = int(size)
decimal = int(decimal)
if fieldType == 'D' and size < 8:
raise ShapefileException("Date fields require min size of 8 (%d is too small)" % size)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens when you have a date with size > 8? In my tests it seems to result in corrupt shapefiles. If I save a date with a size bigger than 8, then open the shapefile in arcmap and try to edit it, the data in the dbf file become corrupted.

Might it be better to mandate a size of exactly 8?

self.fields.append((name, fieldType, size, decimal))

def fieldDate(self, name):
"""Adds a Date type dbf descriptor to the shapefile"""
self.field(name, 'D', 8, 0)

def record(self, *recordList, **recordDict):
"""Creates a dbf attribute record. You can submit either a sequence of
field values or keyword arguments of field names and values. Before
Expand Down
41 changes: 41 additions & 0 deletions tests/date_tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"""
Unit tests for Pythonic `datetime.date` object handling.
"""

import unittest
import datetime
try:
from StringIO import StringIO
except ImportError:
from io import BytesIO as StringIO

import shapefile


class TestDateHandling(unittest.TestCase):

def testDateReadWrite(self):
"""Round-trip read and write Python `date` as value for 'D' field"""
today = datetime.date.today()

# Write a one-field, one-record shp to memory; use `date` obj as value
writer = shapefile.Writer()
writer.field('DATEFIELD', 'D', 8)
writer.null()
writer.record(today)
shp, shx, dbf = StringIO(), StringIO(), StringIO()
writer.save(shp=shp, shx=shx, dbf=dbf)

# Read our in-memory shp to verify that Reader gives us a `date` obj
reader = shapefile.Reader(shp=shp, shx=shx, dbf=dbf)
self.assertEqual(reader.fields[-1][1], 'D')
self.assertEqual(len(reader.records()), 1)
record = reader.record(0)
d = record[0]
self.assertTrue(isinstance(d, datetime.date),
"Expected a `date` object back from Reader (we got a %s)" % type(d))
self.assertEqual(d, today)


if __name__ == '__main__':
unittest.main()