• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

FluidTYPO3 / vhs / 28229017084

26 Jun 2026 09:17AM UTC coverage: 69.907% (-0.07%) from 69.98%
28229017084

push

github

NamelessCoder
[REMOVAL] Drop support for PHP versions below 8.3

4811 of 6882 relevant lines covered (69.91%)

4.06 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

79.82
/Classes/ViewHelpers/Format/DateRangeViewHelper.php
1
<?php
2
namespace FluidTYPO3\Vhs\ViewHelpers\Format;
3

4
/*
5
 * This file is part of the FluidTYPO3/Vhs project under GPLv2 or later.
6
 *
7
 * For the full copyright and license information, please read the
8
 * LICENSE.md file that was distributed with this source code.
9
 */
10

11
use FluidTYPO3\Vhs\Core\ViewHelper\AbstractViewHelper;
12
use FluidTYPO3\Vhs\Utility\ErrorUtility;
13
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
14

15
/**
16
 * ### Date range calculation/formatting ViewHelper
17
 *
18
 * Uses DateTime and DateInterval operations to calculate a range
19
 * between two DateTimes.
20
 *
21
 * #### Usages
22
 *
23
 * - As formatter, the ViewHelper can output a string value such as
24
 *   "2013-04-30 - 2013-05-30" where you can configure both the start
25
 *   and end date (or their common) formats as well as the "glue"
26
 *   which binds the two dates together.
27
 * - As interval calculator, the ViewHelper can be used with a special
28
 *   "intervalFormat" which is a string used in the constructor method
29
 *   for the DateInterval class - for example, "P3M" to add three months.
30
 *   Used this way, you can specify the start date (or rely on the
31
 *   default "now" DateTime) and specify the "intervalFormat" to add
32
 *   your desired duration to your starting date and use that as end
33
 *   date. Without the "return" attribute, this mode simply outputs
34
 *   the formatted dates with interval deciding the end date.
35
 * - When used with the "return" attribute you can specify which type
36
 *   of data to return:
37
 *   - if "return" is "DateTime", a single DateTime instance is returned
38
 *     (which is the end date). Use this with a start date to return the
39
 *     DateTime corresponding to "intervalFormat" into the future/past.
40
 *   - if "return" is a string such as "w", "d", "h" etc. the corresponding
41
 *     counter value (weeks, days, hours etc.) is returned.
42
 *   - if "return" is an array of counter IDs, for example ["w", "d"],
43
 *     the corresponding counters from the range are returned as an array.
44
 *
45
 * #### Note about LLL support and array consumers
46
 *
47
 * When used with the "return" attribute and when this attribute is an
48
 * array, the output becomes suitable for consumption by f:translate, v:l
49
 * or f:format.sprintf for example - as the "arguments" attribute:
50
 *
51
 * ```
52
 * <f:translate key="myDateDisplay"
53
 *     arguments="{v:format.dateRange(intervalFormat: 'P3W', return: {0: 'w', 1: 'd'})}"
54
 * />
55
 * ```
56
 *
57
 * Which if "myDateDisplay" is a string such as "Deadline: %d week(s) and
58
 * %d day(s)" would output a result such as "Deadline: 4 week(s) and 2 day(s)".
59
 *
60
 * > Tip: the values returned by this ViewHelper in both array and single
61
 * > value return modes, are also nicely consumable by the "math" suite
62
 * > of ViewHelpers, for example `v:math.division` would be able to divide
63
 * > number of days by two, three etc. to further divide the date range.
64
 */
65
class DateRangeViewHelper extends AbstractViewHelper
66
{
67
    /**
68
     * @var boolean
69
     */
70
    protected $escapingInterceptorEnabled = false;
71

72
    public function initializeArguments(): void
73
    {
74
        $this->registerArgument(
2✔
75
            'start',
2✔
76
            'mixed',
2✔
77
            'Start date which can be a DateTime object or a string consumable by DateTime constructor',
2✔
78
            false,
2✔
79
            'now'
2✔
80
        );
2✔
81
        $this->registerArgument(
2✔
82
            'end',
2✔
83
            'mixed',
2✔
84
            'End date which can be a DateTime object or a string consumable by DateTime constructor'
2✔
85
        );
2✔
86
        $this->registerArgument(
2✔
87
            'intervalFormat',
2✔
88
            'string',
2✔
89
            'Interval format consumable by DateInterval'
2✔
90
        );
2✔
91
        $this->registerArgument(
2✔
92
            'format',
2✔
93
            'string',
2✔
94
            'Date format to apply to both start and end date',
2✔
95
            false,
2✔
96
            'Y-m-d'
2✔
97
        );
2✔
98
        $this->registerArgument('startFormat', 'string', 'Date format to apply to start date');
2✔
99
        $this->registerArgument('endFormat', 'string', 'Date format to apply to end date');
2✔
100
        $this->registerArgument('glue', 'string', 'Glue string to concatenate dates with', false, '-');
2✔
101
        $this->registerArgument(
2✔
102
            'spaceGlue',
2✔
103
            'boolean',
2✔
104
            'If TRUE glue string is surrounded with whitespace',
2✔
105
            false,
2✔
106
            true
2✔
107
        );
2✔
108
        $this->registerArgument(
2✔
109
            'return',
2✔
110
            'mixed',
2✔
111
            'Return type; can be exactly "DateTime" to return a DateTime instance, a string like "w" or "d" to ' .
2✔
112
            'return weeks, days between the two dates - or an array of w, d, etc. strings to return the ' .
2✔
113
            'corresponding range count values as an array.'
2✔
114
        );
2✔
115
    }
116

117
    /**
118
     * @return mixed
119
     */
120
    public static function renderStatic(
121
        array $arguments,
122
        \Closure $renderChildrenClosure,
123
        RenderingContextInterface $renderingContext
124
    ) {
125
        /** @var string|null $start */
126
        $start = $renderChildrenClosure() ?? $arguments['start'] ?? 'now';
6✔
127
        if (empty($arguments['start'])) {
6✔
128
            $start = 'now';
×
129
        }
130
        $startDateTime = static::enforceDateTime($start);
6✔
131

132
        /** @var string|null $end */
133
        $end = $arguments['end'];
6✔
134
        $endDateTime = null;
6✔
135
        if (!empty($end)) {
6✔
136
            $endDateTime = static::enforceDateTime($end);
4✔
137
        }
138

139
        $intervalFormat = null;
6✔
140
        if (!empty($arguments['intervalFormat'])) {
6✔
141
            /** @var string $intervalFormat */
142
            $intervalFormat = $arguments['intervalFormat'];
2✔
143
        }
144

145
        if (null === $intervalFormat && null === $endDateTime) {
6✔
146
            ErrorUtility::throwViewHelperException('Either end or intervalFormat has to be provided.', 1369573110);
×
147
        }
148

149
        $interval = null;
6✔
150
        if ($intervalFormat !== null) {
6✔
151
            try {
152
                $interval = new \DateInterval($intervalFormat);
2✔
153
            } catch (\Exception | \Error $exception) {
×
154
                ErrorUtility::throwViewHelperException(
×
155
                    '"' . $intervalFormat . '" could not be parsed by \DateInterval constructor.',
×
156
                    1369573111
×
157
                );
×
158
            }
159
        } elseif ($endDateTime instanceof \DateTime) {
4✔
160
            $interval = $endDateTime->diff($startDateTime);
4✔
161
        }
162

163
        if (null !== $interval && null === $endDateTime) {
6✔
164
            $endDateTime = new \DateTime();
2✔
165
            $endDateTime->add($endDateTime->diff($startDateTime));
2✔
166
            $endDateTime->add($interval);
2✔
167
        }
168

169
        $output = null;
6✔
170
        $return = $arguments['return'];
6✔
171
        if (null === $return) {
6✔
172
            $spaceGlue = (bool) $arguments['spaceGlue'];
4✔
173
            /** @var string $glue */
174
            $glue = $arguments['glue'];
4✔
175
            /** @var string $startFormat */
176
            $startFormat = $arguments['format'] ?? '';
4✔
177
            /** @var string $endFormat */
178
            $endFormat = $arguments['format'] ?? '';
4✔
179
            if (!empty($arguments['startFormat'])) {
4✔
180
                /** @var string $startFormat */
181
                $startFormat = $arguments['startFormat'];
4✔
182
            }
183
            if (!empty($arguments['endFormat'])) {
4✔
184
                /** @var string $endFormat */
185
                $endFormat = $arguments['endFormat'];
4✔
186
            }
187
            $output = static::formatDate($startDateTime, $startFormat);
4✔
188
            $output .= true === $spaceGlue ? ' ' : '';
4✔
189
            $output .= $glue;
4✔
190
            $output .= true === $spaceGlue ? ' ' : '';
4✔
191
            $output .= static::formatDate($endDateTime ?? new \DateTime('now'), $endFormat);
4✔
192
        } elseif ('DateTime' === $return) {
2✔
193
            $output = $endDateTime;
×
194
        } elseif (is_string($return) && $interval instanceof \DateInterval) {
2✔
195
            if (false === strpos($return, '%')) {
2✔
196
                $return = '%' . $return;
2✔
197
            }
198
            $output = $interval->format($return);
2✔
199
        } elseif (is_array($return) && $interval instanceof \DateInterval) {
×
200
            $output = [];
×
201
            foreach ($return as $format) {
×
202
                if (false === strpos($format, '%')) {
×
203
                    $format = '%' . $format;
×
204
                }
205
                $output[] = $interval->format($format);
×
206
            }
207
        }
208
        return $output;
6✔
209
    }
210

211
    /**
212
     * @param \DateTime|scalar|null $date
213
     */
214
    protected static function enforceDateTime($date): \DateTime
215
    {
216
        if (!$date instanceof \DateTime) {
6✔
217
            try {
218
                $input = $date;
6✔
219
                if (is_integer($date)) {
6✔
220
                    $date = new \DateTime('@' . $date);
6✔
221
                } elseif (is_scalar($date)) {
×
222
                    $date = new \DateTime((string) $date);
×
223
                } else {
224
                    $date = new \DateTime('now');
×
225
                }
226
                $date->setTimezone(new \DateTimeZone(date_default_timezone_get()));
6✔
227
            } catch (\Exception | \Error $exception) {
×
228
                ErrorUtility::throwViewHelperException(
×
229
                    '"' . (string) $input . '" could not be parsed by \DateTime constructor.',
×
230
                    1369573112
×
231
                );
×
232
            }
233
        }
234
        /** @var \DateTime $date */
235
        return $date;
6✔
236
    }
237

238
    protected static function formatDate(\DateTime $date, string $format = 'Y-m-d'): string
239
    {
240
        if (false !== strpos($format, '%')) {
4✔
241
            return (string) strftime($format, (int) $date->format('U'));
×
242
        }
243
        return $date->format($format);
4✔
244
    }
245
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc