db-pkpass

Convert Deutsche Bahn PDF tickets to PKPass
git clone https://git.ce9e.org/db-pkpass.git

commit
efe0b9bab4cdce866b9bb1b6e09800887c36a20e
parent
0e13df517856dadd45812f9db0c10a1fd241f8f3
Author
Tobias Bengfort <tobias.bengfort@posteo.de>
Date
2025-05-30 17:20
extract validity and use it to anchor leg dates

Diffstat

M db_pkpass.py 58 ++++++++++++++++++++++++++++++++++++++++++++--------------

1 files changed, 44 insertions, 14 deletions


diff --git a/db_pkpass.py b/db_pkpass.py

@@ -25,6 +25,8 @@ BARCODE_FORMATS = zxingcpp.BarcodeFormats(
   25    25     or zxingcpp.BarcodeFormat.QRCode
   26    26 )
   27    27 
   -1    28 TZ = ZoneInfo('Europe/Berlin')
   -1    29 
   28    30 ICON = base64.b64decode("""
   29    31 iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAAAXNSR0IArs4c6QAAAARnQU1BAACx
   30    32 jwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAADNQTFRF
@@ -60,6 +62,10 @@ def dump_pkpass(files: dict[str, bytes]) -> bytes:
   60    62     return buf.getvalue()
   61    63 
   62    64 
   -1    65 def strptime(s, _format):
   -1    66     return datetime.datetime.strptime(s, _format).astimezone(TZ)
   -1    67 
   -1    68 
   63    69 def extract_barcodes(pdf):
   64    70     barcodes = []
   65    71     for page in pdf:
@@ -73,17 +79,11 @@ def extract_barcodes(pdf):
   73    79     return barcodes
   74    80 
   75    81 
   76    -1 def parse_leg_dt(datestr, timestr, prefix):
   77    -1     tz = ZoneInfo('Europe/Berlin')
   78    -1     now = datetime.datetime.now(tz=tz)
   79    -1     year = now.year
   80    -1 
   81    -1     f = f'%Y %d.%m. {prefix} %H:%M'
   82    -1     s = f'{year} {datestr} {timestr}'
   83    -1     dt = datetime.datetime.strptime(s, f).astimezone(tz)
   84    -1     if dt < now:
   85    -1         s = f'{year + 1} {datestr} {timestr}'
   86    -1         dt = datetime.datetime.strptime(s, f).astimezone(tz)
   -1    82 def parse_leg_dt(datestr, timestr, prefix, start):
   -1    83     f = f'%d.%m.%Y {prefix} %H:%M'
   -1    84     dt = strptime(f'{datestr}{start.year} {timestr}', f)
   -1    85     if dt < start:
   -1    86         dt = strptime(f'{datestr}{start.year + 1} {timestr}', f)
   87    87     return dt
   88    88 
   89    89 
@@ -91,6 +91,7 @@ def extract_legs(pdf):
   91    91     legs = []
   92    92     state = 0
   93    93     last_x = 0
   -1    94     validity = extract_validity(pdf)
   94    95     for page in pdf:
   95    96         for x, _, _, _, text, _, _ in page.get_text('blocks'):
   96    97             text = text.rstrip('\n').replace(',\n', ', ')
@@ -122,8 +123,12 @@ def extract_legs(pdf):
  122   123                 v1, v2 = (v.strip() for v in text.rstrip('\n').split('\n'))
  123   124                 date1 = legs[-1]['start'].pop('date')
  124   125                 date2 = legs[-1]['destination'].pop('date')
  125    -1                 legs[-1]['start']['datetime'] = parse_leg_dt(date1, v1, 'ab')
  126    -1                 legs[-1]['destination']['datetime'] = parse_leg_dt(date2, v2, 'an')
   -1   126                 legs[-1]['start']['datetime'] = parse_leg_dt(
   -1   127                     date1, v1, 'ab', validity[0]
   -1   128                 )
   -1   129                 legs[-1]['destination']['datetime'] = parse_leg_dt(
   -1   130                     date2, v2, 'an', validity[0]
   -1   131                 )
  127   132                 state = 4
  128   133             elif state == 4:
  129   134                 v1, v2 = (v.strip() for v in text.rstrip('\n').split('\n'))
@@ -158,6 +163,23 @@ def extract_order_id(pdf):
  158   163                 return text[len(key):]
  159   164 
  160   165 
   -1   166 def extract_validity(pdf):
   -1   167     key1 = 'Gültigkeit: '
   -1   168     key2 = 'Fahrtantritt am '
   -1   169     for page in pdf:
   -1   170         for text in page.get_text().split('\n'):
   -1   171             if text.startswith(key1):
   -1   172                 s_start, s_end = text[len(key1):].split(' bis ')
   -1   173                 start = strptime(s_start, '%d.%m.%Y %H:%M Uhr')
   -1   174                 end = strptime(s_end, '%d.%m.%Y %H:%M Uhr')
   -1   175                 return start, end
   -1   176             elif text.startswith(key2):
   -1   177                 s_start = text[len(key2):]
   -1   178                 start = strptime(s_start, '%d.%m.%Y')
   -1   179                 end = start + datetime.timedelta(days=1)
   -1   180                 return start, end
   -1   181 
   -1   182 
  161   183 def format_stop(stop, train=None):
  162   184     t = stop['datetime'].strftime('%H:%M')
  163   185     s = f'{t} {stop["station"]}'
@@ -178,8 +200,9 @@ def format_legs(legs):
  178   200 
  179   201 def extract_content(pdf):
  180   202     order_id = extract_order_id(pdf)
   -1   203     validity = extract_validity(pdf)
  181   204 
  182    -1     legs = extract_legs(pdf)
   -1   205     legs = extract_legs(pdf, validity[0])
  183   206     start = legs[0]['start']['station']
  184   207     destination = legs[-1]['destination']['station']
  185   208     date = legs[0]['start']['datetime']
@@ -191,6 +214,13 @@ def extract_content(pdf):
  191   214         'teamIdentifier': 'XXXXXXXXXX',
  192   215         'serialNumber': order_id,
  193   216         'description': f'{start} → {destination} ({date.date().isoformat()})',
   -1   217         'expirationDate': validity[1].isoformat(),
   -1   218         'relevantDates': [
   -1   219             {
   -1   220                 'startDate': validity[0].isoformat(),
   -1   221                 'endDate': validity[1].isoformat(),
   -1   222             },
   -1   223         ],
  194   224         'barcodes': [
  195   225             {
  196   226                 'format': _format,