From 1e21ccecea0b81f13cea220b69e387542a861551 Mon Sep 17 00:00:00 2001 From: Guilhem Moulin Date: Mon, 10 Jun 2024 19:07:19 +0200 Subject: =?UTF-8?q?Add=20TZFlag=20support=20(for=20GDAl=20=E2=89=A53.8).?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (Commented out in config.yml for now since Bookworm has only v3.6.) --- config.yml | 10 ++++++--- webmap-import | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 63 insertions(+), 12 deletions(-) diff --git a/config.yml b/config.yml index a28ec0b..89a32c7 100644 --- a/config.yml +++ b/config.yml @@ -135,7 +135,11 @@ layers: # # * UUID (UUID string representation, only valid for String). # SubType: UUID # -# # Default timezone (optional), for Time and DateTime types +# # Feature field timezone (optional), for Time, Date and DateTime types. One of: +# # * None (unknown timezone); +# # * Local (local time); +# # * UTC (alias GMT); or +# # * [+-]HH:MM or UTC[+-]HH:MM # #TZFlag: local # # # Formatting precision for this field in characters (optional, this should @@ -237,7 +241,7 @@ layers: #width: 36 - name: skapad type: DateTime - #TZFlag: TODO + #tz: local - name: lanskod type: Integer subtype: Int16 @@ -270,7 +274,7 @@ layers: #width: 36 - name: skapad type: DateTime - #TZFlag: TODO + #tz: local - name: kommunkod type: Integer subtype: Int16 diff --git a/webmap-import b/webmap-import index 5515dd0..2939ca1 100755 --- a/webmap-import +++ b/webmap-import @@ -22,6 +22,7 @@ import os import logging import argparse import tempfile +import re from fnmatch import fnmatchcase from pathlib import Path @@ -282,6 +283,52 @@ def parseSubFieldType(name): else: raise Exception(f'Unknown field subtype "{name}"') +# Parse timezone (XXX for GDAL ≥3.8 only) +TZ_RE = re.compile(r'(?:UTC\b)?([\+\-]?)([0-9][0-9]):?([0-9][0-9])', flags=re.IGNORECASE) +def parseTimeZone(tz): + if tz is None: + raise Exception('parseTimeZone(None)') + tz2 = tz.lower() + if tz2 == 'none': + return ogr.TZFLAG_UNKNOWN + elif tz2 == 'local': + return ogr.TZFLAG_LOCALTIME + elif tz2 == 'utc' or tz2 == 'gmt': + return ogr.TZFLAG_UTC + + m = TZ_RE.fullmatch(tz) + if m is None: + raise Exception(f'Invalid timezone "{tz}"') + tzSign = m.group(1) + tzHour = int(m.group(2)) + tzMinute = int(m.group(3)) + if tzHour > 14 or tzMinute >= 60 or tzMinute % 15 != 0: + raise Exception(f'Invalid timezone "{tz}"') + tzFlag = tzHour*4 + int(tzMinute/15) + if tzSign == '-': + tzFlag = 100 - tzFlag + else: + tzFlag += 100 + return tzFlag + +# Pretty-print timezone flag, cf. +# ogr/ogrutils.cpp:OGRGetISO8601DateTime() +def formatTZFlag(tzFlag): + if tzFlag is None: + raise Exception('printTimeZone(None)') + if tzFlag == ogr.TZFLAG_UNKNOWN: + return 'none' + elif tzFlag == ogr.TZFLAG_LOCALTIME: + return 'local' + elif tzFlag == ogr.TZFLAG_UTC: + return 'UTC' + + tzOffset = abs(tzFlag - 100) * 15; + tzHour = int(tzOffset / 60); + tzMinute = int(tzOffset % 60); + tzSign = '+' if tzFlag > 100 else '-' + return f'{tzSign}{tzHour:02}{tzMinute:02}' + # Validate layer creation options and schema. The schema is modified in # place with the parsed result. # (We need the driver of the output dataset to determine capability on @@ -339,9 +386,8 @@ def validateSchema(layers, drvo=None, lco_defaults=None): fld_def2['Type'] = parseFieldType(v) elif k2 == 'subtype': fld_def2['SubType'] = parseSubFieldType(v) - elif k2 == 'tzflag': - pass # TODO - #fld_def2['TZFlag'] = v + elif k2 == 'tz': + fld_def2['TZFlag'] = parseTimeZone(v) elif k2 == 'width' and v is not None and isinstance(v, int): fld_def2['Width'] = v elif k2 == 'precision' and v is not None and isinstance(v, int): @@ -471,10 +517,10 @@ def validateOutputLayer(lyr, srs=None, options=None): if 'TZFlag' in fld: v1 = defn.GetTZFlag() - v2, n2 = fld['TZFlag'] + v2 = fld['TZFlag'] if v1 != v2: - logging.warning('Field "%s" has TZFlag=%d, expected %d (%s)', - fldName, v1, v2, n2) + logging.warning('Field "%s" has TZFlag=%d (%s), expected %d (%s)', + fldName, v1, formatTZFlag(v1), v2, formatTZFlag(v2)) ok = False if 'Precision' in fld: @@ -589,8 +635,9 @@ def createOutputLayer(ds, layername, srs=None, options=None): defn.SetSubType(v) if 'TZFlag' in fld: - v, n = fld['TZFlag'] - logging.debug('Set TZFlag=%d (%s) on output field "%s"', v, n, fldName) + v = fld['TZFlag'] + logging.debug('Set TZFlag=%d (%s) on output field "%s"', + v, formatTZFlag(v), fldName) defn.SetTZFlag(v) if 'Precision' in fld: @@ -674,7 +721,7 @@ def clearLayer(ds, lyr): return layername_esc = escapeIdentifier(lyr.GetName()) - # GDAL <3.9 doesn't have lyr.GetDataset() so we pass the DS along with the layer + # XXX GDAL <3.9 doesn't have lyr.GetDataset() so we pass the DS along with the layer drv = ds.GetDriver() if drv.ShortName == 'PostgreSQL': # https://www.postgresql.org/docs/15/sql-truncate.html -- cgit v1.2.3