Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
93.94% covered (success)
93.94%
31 / 33
50.00% covered (danger)
50.00%
1 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
PdfDate
93.94% covered (success)
93.94%
31 / 33
50.00% covered (danger)
50.00%
1 / 2
9.02
0.00% covered (danger)
0.00%
0 / 1
 fromDateTime
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
3
 parse
91.30% covered (success)
91.30%
21 / 23
0.00% covered (danger)
0.00%
0 / 1
6.02
1<?php
2
3declare(strict_types=1);
4
5namespace Phpdftk\Pdf\Core;
6
7/**
8 * PDF date string helper — ISO 32000-2 §7.9.4.
9 *
10 * PDF dates are encoded as `D:YYYYMMDDHHmmSSOHH'mm`, where `O` is `+`,
11 * `-`, or `Z`. Producing and consuming these strings by hand is
12 * error-prone; this helper wraps PHP's DateTimeInterface.
13 */
14final class PdfDate
15{
16    /**
17     * Format a PHP DateTime as a PDF date string and return it wrapped
18     * in a PdfString.
19     */
20    public static function fromDateTime(\DateTimeInterface $dt): PdfString
21    {
22        $base = $dt->format('YmdHis');
23        $offsetSeconds = $dt->getOffset();
24        if ($offsetSeconds === 0) {
25            $tz = 'Z';
26        } else {
27            $sign = $offsetSeconds >= 0 ? '+' : '-';
28            $abs = abs($offsetSeconds);
29            $hours = intdiv($abs, 3600);
30            $minutes = intdiv($abs % 3600, 60);
31            $tz = sprintf("%s%02d'%02d", $sign, $hours, $minutes);
32        }
33        return new PdfString('D:' . $base . $tz);
34    }
35
36    /**
37     * Parse a PDF date string back into a DateTimeImmutable. Returns
38     * null on unparseable input.
39     */
40    public static function parse(string $pdfDate): ?\DateTimeImmutable
41    {
42        if (!preg_match(
43            "/^D?:?(\d{4})(\d{2})?(\d{2})?(\d{2})?(\d{2})?(\d{2})?([Z+-])?(\d{2})?'?(\d{2})?'?$/",
44            $pdfDate,
45            $m,
46        )) {
47            return null;
48        }
49        $year  = (int) $m[1];
50        $month = (int) ($m[2] ?? 1);
51        $day   = (int) ($m[3] ?? 1);
52        $hour  = (int) ($m[4] ?? 0);
53        $min   = (int) ($m[5] ?? 0);
54        $sec   = (int) ($m[6] ?? 0);
55
56        $tz = new \DateTimeZone('UTC');
57        if (isset($m[7]) && $m[7] !== '' && $m[7] !== 'Z') {
58            $sign = $m[7];
59            $offH = (int) ($m[8] ?? 0);
60            $offM = (int) ($m[9] ?? 0);
61            $tz = new \DateTimeZone(sprintf('%s%02d:%02d', $sign, $offH, $offM));
62        }
63
64        try {
65            return (new \DateTimeImmutable('now', $tz))
66                ->setDate($year, $month, $day)
67                ->setTime($hour, $min, $sec);
68        } catch (\Throwable) {
69            return null;
70        }
71    }
72}