Skip to content

Commit b2fe98e

Browse files
authored
Added read/write support for more datatypes, and writing nullvalues
Added read and write support for dbf float fieldtype. Date type now also returns and writes datetime.date objects, and can also write a list of yr,month,day for backward compat. Previously "N" numeric was the only supported number type, sometimes resulting in strange string nrs and subsequent saving errors. Also enabled writing QGIS nullvalues, previously only when reading.
1 parent c6bf63a commit b2fe98e

File tree

1 file changed

+38
-9
lines changed

1 file changed

+38
-9
lines changed

shapefile.py

Lines changed: 38 additions & 9 deletions
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,15 @@ 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
499501
elif not value.strip():
502+
# why this? better to delete this and leave it up to the field type?
500503
record.append(value)
501504
continue
502-
elif typ == "N":
505+
elif typ in ("N","F"):
506+
# numeric or float: number stored as a string, right justified, and padded with blanks to the width of the field.
503507
value = value.replace(b('\0'), b('')).strip()
504508
value = value.replace(b('*'), b('')) # QGIS NULL is all '*' chars
505509
if value == b(''):
@@ -517,18 +521,24 @@ def __record(self):
517521
#not parseable as int, set to None
518522
value = None
519523
elif typ == b('D'):
524+
# date: 8 bytes - date stored as a string in the format YYYYMMDD.
520525
if value.count(b('0')) == len(value): # QGIS NULL is all '0' chars
521526
value = None
522527
else:
523528
try:
524529
y, m, d = int(value[:4]), int(value[4:6]), int(value[6:8])
525-
value = [y, m, d]
530+
value = date(y, m, d)
526531
except:
527532
value = value.strip()
528533
elif typ == b('L'):
529-
value = (value in b('YyTt') and b('T')) or \
530-
(value in b('NnFf') and b('F')) or b('?')
534+
# logical: 1 byte - initialized to 0x20 (space) otherwise T or F.
535+
if value == " ":
536+
value = None # space means missing or not yet set
537+
else:
538+
value = (value in b('YyTt') and b('T')) or \
539+
(value in b('NnFf') and b('F')) or b('?')
531540
else:
541+
# anything else is forced to string/unicode
532542
value = u(value)
533543
value = value.strip()
534544
record.append(value)
@@ -918,11 +928,30 @@ def __dbfRecords(self):
918928
for (fieldName, fieldType, size, dec), value in zip(self.fields, record):
919929
fieldType = fieldType.upper()
920930
size = int(size)
921-
if fieldType.upper() == "N":
922-
value = str(value).rjust(size)
931+
if fieldType in ("N","F"):
932+
# numeric or float: number stored as a string, right justified, and padded with blanks to the width of the field.
933+
if value in MISSING:
934+
value = str("*"*size) # QGIS NULL
935+
else:
936+
value = str(value).rjust(size)
937+
elif fieldType == "D":
938+
# date: 8 bytes - date stored as a string in the format YYYYMMDD.
939+
if isinstance(value, date):
940+
value = b"%s%s%s"%(value.year,value.month,value.day)
941+
elif isinstance(value, list) and len(value) == 3:
942+
value = b"%s%s%s"%value
943+
elif value in MISSING:
944+
value = b('0') * 8 # QGIS NULL for date type
945+
else:
946+
raise ShapefileException("Date values must be either a datetime.date object, a list, or a missing value of None or ''.")
923947
elif fieldType == 'L':
924-
value = str(value)[0].upper()
948+
# logical: 1 byte - initialized to 0x20 (space) otherwise T or F.
949+
if value in MISSING:
950+
value = str(' ') # missing is set to space
951+
else:
952+
value = str(value)[0].upper()
925953
else:
954+
# anything else is forced to string
926955
value = str(value)[:size].ljust(size)
927956
if len(value) != size:
928957
raise ShapefileException(

0 commit comments

Comments
 (0)