Skip to content

Commit 86fbadb

Browse files
authored
Merge pull request #71 from karimbahgat/dtypes-nullvals,-take2
More stable datatypes, writing nullvals, and type testing
2 parents afa7b1a + ba61854 commit 86fbadb

File tree

1 file changed

+47
-12
lines changed

1 file changed

+47
-12
lines changed

shapefile.py

+47-12
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import array
1919
import tempfile
2020
import itertools
21+
from datetime import date
2122

2223
#
2324
# Constants for shape types
@@ -36,6 +37,8 @@
3637
MULTIPOINTM = 28
3738
MULTIPATCH = 31
3839

40+
MISSING = [None,'']
41+
3942
PYTHON3 = sys.version_info[0] == 3
4043

4144
if PYTHON3:
@@ -492,14 +495,11 @@ def __record(self):
492495
# deleted record
493496
return None
494497
record = []
495-
for (name, typ, size, deci), value in zip(self.fields,
496-
recordContents):
498+
for (name, typ, size, deci), value in zip(self.fields, recordContents):
497499
if name == 'DeletionFlag':
498500
continue
499-
elif not value.strip():
500-
record.append(value)
501-
continue
502-
elif typ == "N":
501+
elif typ in ("N","F"):
502+
# numeric or float: number stored as a string, right justified, and padded with blanks to the width of the field.
503503
value = value.replace(b('\0'), b('')).strip()
504504
value = value.replace(b('*'), b('')) # QGIS NULL is all '*' chars
505505
if value == b(''):
@@ -517,18 +517,28 @@ def __record(self):
517517
#not parseable as int, set to None
518518
value = None
519519
elif typ == b('D'):
520+
# date: 8 bytes - date stored as a string in the format YYYYMMDD.
520521
if value.count(b('0')) == len(value): # QGIS NULL is all '0' chars
521522
value = None
522523
else:
523524
try:
524525
y, m, d = int(value[:4]), int(value[4:6]), int(value[6:8])
525-
value = [y, m, d]
526+
value = date(y, m, d)
526527
except:
527528
value = value.strip()
528529
elif typ == b('L'):
529-
value = (value in b('YyTt') and b('T')) or \
530-
(value in b('NnFf') and b('F')) or b('?')
530+
# logical: 1 byte - initialized to 0x20 (space) otherwise T or F.
531+
if value == " ":
532+
value = None # space means missing or not yet set
533+
else:
534+
if value in b('YyTt1'):
535+
value = True
536+
elif value in b('NnFf0'):
537+
value = False
538+
else:
539+
value = b('?')
531540
else:
541+
# anything else is forced to string/unicode
532542
value = u(value)
533543
value = value.strip()
534544
record.append(value)
@@ -920,11 +930,30 @@ def __dbfRecords(self):
920930
for (fieldName, fieldType, size, dec), value in zip(self.fields, record):
921931
fieldType = fieldType.upper()
922932
size = int(size)
923-
if fieldType.upper() == "N":
924-
value = str(value).rjust(size)
933+
if fieldType in ("N","F"):
934+
# numeric or float: number stored as a string, right justified, and padded with blanks to the width of the field.
935+
if value in MISSING:
936+
value = str("*"*size) # QGIS NULL
937+
else:
938+
value = str(value).rjust(size)
939+
elif fieldType == "D":
940+
# date: 8 bytes - date stored as a string in the format YYYYMMDD.
941+
if isinstance(value, date):
942+
value = value.strftime("%Y%m%d")
943+
elif isinstance(value, list) and len(value) == 3:
944+
value = date(*value).strftime("%Y%m%d")
945+
elif value in MISSING:
946+
value = b('0') * 8 # QGIS NULL for date type
947+
else:
948+
raise ShapefileException("Date values must be either a datetime.date object, a list, or a missing value of None or ''.")
925949
elif fieldType == 'L':
926-
value = str(value)[0].upper()
950+
# logical: 1 byte - initialized to 0x20 (space) otherwise T or F.
951+
if value in MISSING:
952+
value = str(' ') # missing is set to space
953+
else:
954+
value = str(value)[0].upper()
927955
else:
956+
# anything else is forced to string
928957
value = str(value)[:size].ljust(size)
929958
if len(value) != size:
930959
raise ShapefileException(
@@ -982,6 +1011,12 @@ def poly(self, parts=[], shapeType=POLYGON, partTypes=[]):
9821011

9831012
def field(self, name, fieldType="C", size="50", decimal=0):
9841013
"""Adds a dbf field descriptor to the shapefile."""
1014+
if fieldType == "D":
1015+
size = "8"
1016+
decimal = 0
1017+
elif fieldType == "L":
1018+
size = "1"
1019+
decimal = 0
9851020
self.fields.append((name, fieldType, size, decimal))
9861021

9871022
def record(self, *recordList, **recordDict):

0 commit comments

Comments
 (0)