Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1441 ariadna 1
<?php
2
// This file is part of Moodle - http://moodle.org/
3
//
4
// Moodle is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8
//
9
// Moodle is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13
//
14
// You should have received a copy of the GNU General Public License
15
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
 
17
namespace core_calendar\output;
18
 
19
use DateInterval;
20
use DateTimeInterface;
21
use DateTimeImmutable;
22
use core\output\pix_icon;
23
use core\output\templatable;
24
use core\output\renderable;
25
use core\output\renderer_base;
26
use core\clock;
27
use core\url;
28
 
29
/**
30
 * Class humandate.
31
 *
32
 * This class is used to render a timestamp as a human readable date.
33
 * The main difference between userdate and this class is that this class
34
 * will render the date as "Today", "Yesterday", "Tomorrow" if the date is
35
 * close to the current date. Also, it will add alert styling if the date
36
 * is near.
37
 *
38
 * @package    core_calendar
39
 * @copyright  2024 Ferran Recio <ferran@moodle.com>
40
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
41
 */
42
class humandate implements renderable, templatable {
43
 
44
    /** @var int|null The number of seconds within which a date is considered near. 1 day by default. */
45
    protected ?int $near = DAYSECS;
46
 
47
    /** @var bool Whether we should show time only or date and time. */
48
    protected bool $timeonly = false;
49
 
50
    /** @var url|null Link for the date. */
51
    protected ?url $link = null;
52
 
53
    /** @var string|null An optional date format to apply.  */
54
    protected ?string $langtimeformat = null;
55
 
56
    /** @var bool Whether to use human relative terminology. */
57
    protected bool $userelatives = true;
58
 
59
    /** @var clock The clock interface to handle time. */
60
    protected clock $clock;
61
 
62
    /**
63
     * Class constructor.
64
     *
65
     * Use the factory methods, such as create_from_timestamp or create_from_datetime, instead.
66
     *
67
     * @param DateTimeImmutable $datetime The datetime.
68
     */
69
    protected function __construct(
70
        /** @var DateTimeImmutable $datetime The datetime. **/
71
        protected DateTimeImmutable $datetime,
72
    ) {
73
        $this->clock = \core\di::get(clock::class);
74
    }
75
 
76
    /**
77
     * Creates a new humandate instance from a timestamp.
78
     *
79
     * @param int $timestamp The timestamp.
80
     * @param int|null $near The number of seconds within which a date is considered near. 1 day by default.
81
     * @param bool $timeonly Whether we should show time only or date and time.
82
     * @param url|null $link Link for the date.
83
     * @param string|null $langtimeformat An optional date format to apply.
84
     * @param bool $userelatives Whether to use human relative terminology.
85
     * @return humandate The new instance.
86
     */
87
    public static function create_from_timestamp(
88
        int $timestamp,
89
        ?int $near = DAYSECS,
90
        bool $timeonly = false,
91
        ?url $link = null,
92
        ?string $langtimeformat = null,
93
        bool $userelatives = true,
94
    ): self {
95
 
96
        return self::create_from_datetime(
97
            (new DateTimeImmutable("@{$timestamp}")),
98
            $near,
99
            $timeonly,
100
            $link,
101
            $langtimeformat,
102
            $userelatives
103
        );
104
    }
105
 
106
    /**
107
     * Creates a new humandate instance from a datetime.
108
     *
109
     * @param DateTimeInterface $datetime The datetime.
110
     * @param int|null $near The number of seconds within which a date is considered near. 1 day by default.
111
     * @param bool $timeonly Whether we should show time only or date and time.
112
     * @param url|null $link Link for the date.
113
     * @param string|null $langtimeformat An optional date format to apply.
114
     * @param bool $userelatives Whether to use human relative terminology.
115
     * @return humandate The new instance.
116
     */
117
    public static function create_from_datetime(
118
        DateTimeInterface $datetime,
119
        ?int $near = DAYSECS,
120
        bool $timeonly = false,
121
        ?url $link = null,
122
        ?string $langtimeformat = null,
123
        bool $userelatives = true,
124
    ): self {
125
 
126
        if (!($datetime instanceof DateTimeImmutable)) {
127
            // Always use an Immutable object to ensure that the value does not change externally before it is rendered.
128
            $datetime = DateTimeImmutable::createFromInterface($datetime);
129
        }
130
 
131
        return (new self($datetime))
132
            ->set_near_limit($near)
133
            ->set_display_time_only($timeonly)
134
            ->set_link($link)
135
            ->set_lang_time_format($langtimeformat)
136
            ->set_use_relatives($userelatives);
137
    }
138
 
139
    /**
140
     * Sets the number of seconds within which a date is considered near.
141
     *
142
     * @param int|null $near The number of seconds within which a date is considered near.
143
     * @return humandate The instance.
144
     */
145
    public function set_near_limit(?int $near): self {
146
        $this->near = $near;
147
        return $this;
148
    }
149
 
150
    /**
151
     * Sets whether we should show time only or date and time.
152
     *
153
     * @param bool $timeonly Whether we should show time only or date and time.
154
     * @return humandate The instance.
155
     */
156
    public function set_display_time_only(bool $timeonly): self {
157
        $this->timeonly = $timeonly;
158
        return $this;
159
    }
160
 
161
    /**
162
     * Sets the link for the date. If null, no link will be added.
163
     *
164
     * @param url|null $link The link for the date.
165
     * @return humandate The instance.
166
     */
167
    public function set_link(?url $link): self {
168
        $this->link = $link;
169
        return $this;
170
    }
171
 
172
    /**
173
     * Sets an optional date format to apply.
174
     *
175
     * @param string|null $langtimeformat Lang date and time format to use to format the date.
176
     * @return humandate The instance.
177
     */
178
    public function set_lang_time_format(?string $langtimeformat): self {
179
        $this->langtimeformat = $langtimeformat;
180
        return $this;
181
    }
182
 
183
    /**
184
     * Sets whether to use human relative terminology.
185
     *
186
     * @param bool $userelatives Whether to use human relative terminology.
187
     * @return humandate The instance.
188
     */
189
    public function set_use_relatives(bool $userelatives): self {
190
        $this->userelatives = $userelatives;
191
        return $this;
192
    }
193
 
194
    #[\Override]
195
    public function export_for_template(renderer_base $output): array {
196
        $userdate = $this->default_userdate();
197
        $relative = null;
198
        if ($this->userelatives) {
199
            $relative = $this->format_relative_date();
200
        }
201
 
202
        if ($this->timeonly) {
203
            $date = null;
204
        } else {
205
            $date = $relative ?? $userdate;
206
        }
207
        $data = [
208
            'timestamp' => $this->datetime->getTimestamp(),
209
            'userdate' => $userdate,
210
            'date' => $date,
211
            'time' => $this->format_time(),
212
            'ispast' => $this->datetime < $this->clock->now(),
213
            'needtitle' => ($relative !== null || $this->timeonly),
214
            'link' => $this->link ? $this->link->out(false) : '',
215
        ];
216
        if ($this->is_near()) {
217
            $icon = new pix_icon(
218
                pix: 'i/warning',
219
                alt: get_string('warning'),
220
                component: 'moodle',
221
                attributes: ['class' => 'me-0 pb-1']
222
            );
223
            $data['isnear'] = true;
224
            $data['nearicon'] = $icon->export_for_template($output);
225
        }
226
        return $data;
227
    }
228
 
229
    /**
230
     * Returns the default user date format.
231
     *
232
     * @return string The formatted date.
233
     */
234
    private function default_userdate(): string {
235
        $timestamp = $this->datetime->getTimestamp();
236
        if ($this->is_current_year()) {
237
            $format = get_string('strftimedayshort', 'langconfig');
238
        } else {
239
            $format = get_string('strftimedaydate', 'langconfig');
240
        }
241
        return userdate($timestamp, $format);
242
    }
243
 
244
    /**
245
     * Checks if the date is near.
246
     *
247
     * @return bool Whether the date is near.
248
     */
249
    private function is_near(): bool {
250
        if ($this->near === null) {
251
            return false;
252
        }
253
        $due = $this->datetime->diff($this->clock->now());
254
        $intervalseconds = $this->interval_to_seconds($due);
255
        return $intervalseconds < $this->near && $intervalseconds > 0;
256
    }
257
 
258
    /**
259
     * Checks if the datetime is from the current year.
260
     *
261
     * @return bool True if the datetime is from the current year, false otherwise.
262
     */
263
    private function is_current_year(): bool {
264
        $currentyear = $this->clock->now()->format('Y');
265
        $datetimeyear = $this->datetime->format('Y');
266
        return $currentyear === $datetimeyear;
267
    }
268
 
269
    /**
270
     * Converts a DateInterval object to total seconds.
271
     *
272
     * @param \DateInterval $interval The interval to convert.
273
     * @return int The total number of seconds.
274
     */
275
    private function interval_to_seconds(DateInterval $interval): int {
276
        $reference = new DateTimeImmutable();
277
        $entime = $reference->add($interval);
278
        return $reference->getTimestamp() - $entime->getTimestamp();
279
    }
280
 
281
    /**
282
     * Formats the timestamp as a relative date string (e.g., "Today", "Yesterday", "Tomorrow").
283
     *
284
     * This method compares the given timestamp with the current date and returns a formatted
285
     * string representing the relative date. If the timestamp corresponds to today, yesterday,
286
     * or tomorrow, it returns the appropriate string. Otherwise, it returns null.
287
     *
288
     * @return string|null
289
     */
290
    private function format_relative_date(): ?string {
291
        $usertimestamp = $this->get_user_date($this->datetime->getTimestamp());
292
        if ($usertimestamp == $this->get_user_date($this->clock->now()->getTimestamp())) {
293
            $format = get_string(
294
                'timerelativetoday',
295
                'calendar',
296
                get_string('strftimedateshort', 'langconfig')
297
            );
298
        } else if ($usertimestamp == $this->get_user_date(strtotime('yesterday', $this->clock->now()->getTimestamp()))) {
299
            $format = get_string(
300
                'timerelativeyesterday',
301
                'calendar',
302
                get_string('strftimedateshort', 'langconfig')
303
            );
304
        } else if ($usertimestamp == $this->get_user_date(strtotime('tomorrow', $this->clock->now()->getTimestamp()))) {
305
            $format = get_string(
306
                'timerelativetomorrow',
307
                'calendar',
308
                get_string('strftimedateshort', 'langconfig')
309
            );
310
        } else {
311
            return null;
312
        }
313
 
314
        return userdate($this->datetime->getTimestamp(), $format);
315
    }
316
 
317
    /**
318
     * Formats the timestamp as a human readable time.
319
     *
320
     * @param int $timestamp The timestamp to format.
321
     * @param string $format The format to use.
322
     * @return string The formatted date.
323
     */
324
    private function get_user_date(int $timestamp, string $format = '%Y-%m-%d'): string {
325
        $calendartype = \core_calendar\type_factory::get_calendar_instance();
326
        $timezone = \core_date::get_user_timezone_object();
327
        return $calendartype->timestamp_to_date_string(
328
            time: $timestamp,
329
            format: $format,
330
            timezone: $timezone->getName(),
331
            fixday: true,
332
            fixhour: true,
333
        );
334
    }
335
 
336
    /**
337
     * Formats the timestamp as a human readable time.
338
     *
339
     * This method compares the given timestamp with the current date and returns a formatted
340
     * string representing the time.
341
     *
342
     * @return string
343
     */
344
    private function format_time(): string {
345
        global $CFG;
346
        // Ensure calendar constants are loaded.
347
        require_once($CFG->dirroot . '/calendar/lib.php');
348
 
349
        $timeformat = get_user_preferences('calendar_timeformat');
350
        if (empty($timeformat)) {
351
            $timeformat = get_config(null, 'calendar_site_timeformat');
352
        }
353
 
354
        // Allow language customization of selected time format.
355
        if ($timeformat === CALENDAR_TF_12) {
356
            $timeformat = get_string('strftimetime12', 'langconfig');
357
        } else if ($timeformat === CALENDAR_TF_24) {
358
            $timeformat = get_string('strftimetime24', 'langconfig');
359
        }
360
 
361
        if ($timeformat) {
362
            return userdate($this->datetime->getTimestamp(), $timeformat);
363
        }
364
 
365
        // Let's use default format.
366
        if ($this->langtimeformat === null) {
367
            $langtimeformat = get_string('strftimetime');
368
        }
369
 
370
        return userdate($this->datetime->getTimestamp(), $langtimeformat);
371
    }
372
}