Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG-WIP.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
- Element slideouts now automatically refresh when the same element is updated in another tab/slideout. ([#18625](https://github.com/craftcms/cms/pull/18625))
- Added the “Time Zone” user preference. ([#8518](https://github.com/craftcms/cms/discussions/8518))
- Element indexes now automatically refresh after duplicating elements and the queue is completed, if there’s an active search term. ([#18636](https://github.com/craftcms/cms/issues/18636))
- Timestamps in the control panel now include their time zone abbreviation. ([#18639](https://github.com/craftcms/cms/pull/18639))

### Administration
- Time fields’ “Max Time” settings can now be set to an earlier time than “Min Time”, for overnight time ranges. ([#18575](https://github.com/craftcms/cms/pull/18575))
Expand All @@ -26,6 +27,7 @@
### Development
- Added the `heading()`/`h()` and `h1()``h6()` Twig functions. ([#18524](https://github.com/craftcms/cms/pull/18524))
- The `tag()` function now accepts a string for its second argument. ([#18524](https://github.com/craftcms/cms/pull/18524))
- The `|time` and `|datetime` Twig filters now have `$withTimeZone` arguments. ([#18639](https://github.com/craftcms/cms/pull/18639))
- `delete` GraphQL queries now have a `hardDelete` argument. ([#18511](https://github.com/craftcms/cms/pull/18511))
- Assets’ `url` GraphQL fields’ `immediately` arguments are no longer deprecated. ([#18581](https://github.com/craftcms/cms/issues/18581))
- `craft\fields\data\LinkData::getUrl()` now has an `$anyStatus` argument, which can be set to `false` to prevent a value from being returned if a disabled/pending/expired element is linked. ([#18527](https://github.com/craftcms/cms/issues/18527))
Expand All @@ -38,6 +40,7 @@
- Added `Craft.CpScreenSlideout::reload()`. ([#18625](https://github.com/craftcms/cms/pull/18625))
- `craft\elements\PopulateElementEvent::$row` no longer includes `fieldValues` or `generatedFieldValues` keys.
- `craft\helpers\DateTimeHelper::timeZoneAbbreviation()` is no longer deprecated, and now has a `$date` argument.
- `craft\i18n\Formatter::asTime()` and `asDatetime()` now have `$withTimeZone` arguments. ([#18639](https://github.com/craftcms/cms/pull/18639))
- `Craft.CP` now triggers a `queueCompleted` event when the last queue job is completed.

### System
Expand Down
4 changes: 2 additions & 2 deletions src/base/Element.php
Original file line number Diff line number Diff line change
Expand Up @@ -6456,10 +6456,10 @@ public function getMetadata(): array
},
], $metadata, [
Craft::t('app', 'Created at') => $this->dateCreated && !$this->getIsUnpublishedDraft()
? $formatter->asDatetime($this->dateCreated, Formatter::FORMAT_WIDTH_SHORT)
? $formatter->asDatetime($this->dateCreated, Formatter::FORMAT_WIDTH_SHORT, true)
: false,
Craft::t('app', 'Updated at') => $this->dateUpdated && !$this->getIsUnpublishedDraft()
? $formatter->asDatetime($this->dateUpdated, Formatter::FORMAT_WIDTH_SHORT)
? $formatter->asDatetime($this->dateUpdated, Formatter::FORMAT_WIDTH_SHORT, true)
: false,
Craft::t('app', 'Notes') => function() {
if ($this->getIsRevision()) {
Expand Down
6 changes: 5 additions & 1 deletion src/controllers/AssetsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -483,7 +483,11 @@ public function actionReplaceFile(): Response
'filename' => $resultingAsset->getFilename(),
'formattedSize' => $resultingAsset->getFormattedSize(0),
'formattedSizeInBytes' => $resultingAsset->getFormattedSizeInBytes(false),
'formattedDateUpdated' => Craft::$app->getFormatter()->asDatetime($resultingAsset->dateUpdated, Formatter::FORMAT_WIDTH_SHORT),
'formattedDateUpdated' => Craft::$app->getFormatter()->asDatetime(
$resultingAsset->dateUpdated,
Formatter::FORMAT_WIDTH_SHORT,
true,
),
'dimensions' => $resultingAsset->getDimensions(),
'updatedTimestamp' => $resultingAsset->dateUpdated->getTimestamp(),
'resultingUrl' => $resultingAsset->getUrl(),
Expand Down
4 changes: 2 additions & 2 deletions src/controllers/ElementsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -890,7 +890,7 @@ private function _contextMenuItems(
/** @var ElementInterface&DraftBehavior $draft */
$creator = $draft->getCreator();
$timestamp = $formatter->asTimestamp($draft->dateUpdated, Locale::LENGTH_SHORT, true);
$timestampWithDate = $formatter->asDatetime($draft->dateUpdated, Locale::LENGTH_SHORT);
$timestampWithDate = $formatter->asDatetime($draft->dateUpdated, Locale::LENGTH_SHORT, true);

return [
'label' => $draft->draftName,
Expand Down Expand Up @@ -921,7 +921,7 @@ private function _contextMenuItems(
/** @var ElementInterface&RevisionBehavior $revision */
$creator = $revision->getCreator();
$timestamp = $formatter->asTimestamp($revision->dateCreated, Locale::LENGTH_SHORT, true);
$timestampWithDate = $formatter->asDatetime($revision->dateCreated, Locale::LENGTH_SHORT);
$timestampWithDate = $formatter->asDatetime($revision->dateCreated, Locale::LENGTH_SHORT, true);

return [
'label' => $revision->getRevisionLabel(),
Expand Down
2 changes: 1 addition & 1 deletion src/elements/Entry.php
Original file line number Diff line number Diff line change
Expand Up @@ -2961,7 +2961,7 @@ public function beforeSave(bool $isNew): bool
Craft::$app->getRevisions()->createRevision(
$current,
$current->getAuthorId(),
sprintf('Revision from %s', Craft::$app->getFormatter()->asDatetime($current->dateUpdated)),
sprintf('Revision from %s', Craft::$app->getFormatter()->asDatetime($current->dateUpdated, withTimeZone: true)),
);
}
}
Expand Down
6 changes: 3 additions & 3 deletions src/elements/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -2522,21 +2522,21 @@ protected function metadata(): array
}
return $formatter->asDuration($duration);
},
Craft::t('app', 'Created at') => $formatter->asDatetime($this->dateCreated, Formatter::FORMAT_WIDTH_SHORT),
Craft::t('app', 'Created at') => $formatter->asDatetime($this->dateCreated, Formatter::FORMAT_WIDTH_SHORT, true),
Craft::t('app', 'Last login') => function() use ($formatter) {
if ($this->pending) {
return false;
}
if (!$this->lastLoginDate) {
return Craft::t('app', 'Never');
}
return $formatter->asDatetime($this->lastLoginDate, Formatter::FORMAT_WIDTH_SHORT);
return $formatter->asDatetime($this->lastLoginDate, Formatter::FORMAT_WIDTH_SHORT, true);
},
Craft::t('app', 'Last login fail') => function() use ($formatter) {
if (!$this->locked || !$this->lastInvalidLoginDate) {
return false;
}
return $formatter->asDatetime($this->lastInvalidLoginDate, Formatter::FORMAT_WIDTH_SHORT);
return $formatter->asDatetime($this->lastInvalidLoginDate, Formatter::FORMAT_WIDTH_SHORT, true);
},
Craft::t('app', 'Login fail count') => function() use ($formatter) {
if (!$this->locked) {
Expand Down
8 changes: 2 additions & 6 deletions src/fields/Date.php
Original file line number Diff line number Diff line change
Expand Up @@ -356,16 +356,12 @@ public function getPreviewHtml(mixed $value, ElementInterface $element): string
if ($this->showTimeZone) {
$timeZone = $formatter->timeZone;
$formatter->timeZone = $value->getTimezone()->getName();
$html = sprintf(
'%s %s',
$formatter->asDatetime($value, Locale::LENGTH_SHORT),
DateTimeHelper::timeZoneAbbreviation($value->getTimezone(), $value),
);
$html = $formatter->asDatetime($value, Locale::LENGTH_SHORT, true);
$formatter->timeZone = $timeZone;
return $html;
}

return $formatter->asDatetime($value, Locale::LENGTH_SHORT);
return $formatter->asDatetime($value, Locale::LENGTH_SHORT, true);
}

if ($this->showDate) {
Expand Down
8 changes: 7 additions & 1 deletion src/gql/directives/FormatDateTime.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,12 @@ public static function create(): GqlDirective
'type' => Type::string(),
'description' => 'The locale to use when formatting the date. (E.g., en-US)',
]),
new FieldArgument([
'name' => 'withTimeZone',
'type' => Type::boolean(),
'description' => 'Whether the time zone abbreviation should be appended to the formatted time.',
'defaultValue' => false,
]),
],
'description' => 'Formats a date in the desired format. Can be applied to all fields, only changes output of DateTime fields.',
]));
Expand Down Expand Up @@ -108,7 +114,7 @@ public static function apply(mixed $source, mixed $value, array $arguments, Reso

$formatter->timeZone = $timezone;

$value = $formatter->asDatetime($value, $format);
$value = $formatter->asDatetime($value, $format, $arguments['withTimeZone'] ?? false);
}

return $value;
Expand Down
2 changes: 1 addition & 1 deletion src/helpers/ElementHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -818,7 +818,7 @@ public static function attributeHtml(mixed $value): string
if ($value instanceof DateTime) {
$formatter = Craft::$app->getFormatter();
return Html::tag('span', $formatter->asTimestamp($value, Locale::LENGTH_SHORT), [
'title' => $formatter->asDatetime($value, Locale::LENGTH_SHORT),
'title' => $formatter->asDatetime($value, Locale::LENGTH_SHORT, true),
]);
}

Expand Down
28 changes: 22 additions & 6 deletions src/i18n/Formatter.php
Original file line number Diff line number Diff line change
Expand Up @@ -146,11 +146,12 @@ private function formatDateWithoutIntl(int|string|DateTime $value, ?string $form
* @inheritdoc
* @param int|string|DateTime $value
* @param string|null $format
* @param bool $withTimeZone Whether the time zone abbreviation should be appended to the formatted time
* @return string
* @throws InvalidArgumentException
* @throws InvalidConfigException
*/
public function asTime($value, $format = null): string
public function asTime($value, $format = null, bool $withTimeZone = false): string
{
if ($format === null) {
$format = $this->timeFormat;
Expand All @@ -161,22 +162,31 @@ public function asTime($value, $format = null): string
}

if (str_starts_with($format, 'php:')) {
return $this->_formatDateTimeValueWithPhpFormat($value, substr($format, 4), 'time');
$result = $this->_formatDateTimeValueWithPhpFormat($value, substr($format, 4), 'time');
} else {
$result = parent::asTime($value, $format);
}

if ($withTimeZone && $result && $result !== $this->nullDisplay) {
$result .= ' ' . DateTimeHelper::timeZoneAbbreviation($value->getTimezone());
}

return parent::asTime($value, $format);
return $result;
}

/**
* @inheritdoc
* @param int|string|DateTime $value
* @param string|null $format
* @param bool $withTimeZone Whether the time zone abbreviation should be appended to the formatted time
* @return string
* @throws InvalidArgumentException
* @throws InvalidConfigException
*/
public function asDatetime($value, $format = null): string
public function asDatetime($value, $format = null, bool $withTimeZone = false): string
{
$value = DateTimeHelper::toDateTime($value, false, false);

if ($format === null) {
$format = $this->datetimeFormat;
}
Expand All @@ -186,10 +196,16 @@ public function asDatetime($value, $format = null): string
}

if (str_starts_with($format, 'php:')) {
return $this->_formatDateTimeValueWithPhpFormat($value, substr($format, 4), 'datetime');
$result = $this->_formatDateTimeValueWithPhpFormat($value, substr($format, 4), 'datetime');
} else {
$result = parent::asDatetime($value, $format);
}

if ($withTimeZone && $result && $result !== $this->nullDisplay) {
$result .= ' ' . DateTimeHelper::timeZoneAbbreviation($value->getTimezone());
}

return parent::asDatetime($value, $format);
return $result;
}

/**
Expand Down
6 changes: 3 additions & 3 deletions src/queue/Queue.php
Original file line number Diff line number Diff line change
Expand Up @@ -503,9 +503,9 @@ public function getJobDetails(string $id): array
'job' => $job,
'ttr' => (int)$result['ttr'],
'Priority' => $result['priority'],
'Pushed at' => $result['timePushed'] ? $formatter->asDatetime($result['timePushed']) : '',
'Updated at' => $result['timeUpdated'] ? $formatter->asDatetime($result['timeUpdated']) : '',
'Failed at' => $result['dateFailed'] ? $formatter->asDatetime($result['dateFailed']) : '',
'Pushed at' => $result['timePushed'] ? $formatter->asDatetime($result['timePushed'], withTimeZone: true) : '',
'Updated at' => $result['timeUpdated'] ? $formatter->asDatetime($result['timeUpdated'], withTimeZone: true) : '',
'Failed at' => $result['dateFailed'] ? $formatter->asDatetime($result['dateFailed'], withTimeZone: true) : '',
]);
}

Expand Down
2 changes: 1 addition & 1 deletion src/templates/_components/utilities/Migrations.twig
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
<tr>
<td>{{ migrationName }}</td>
<td>{{ 'Applied'|t('app') }}</td>
<td>{{ migration|datetime() }}</td>
<td>{{ migration|datetime(withTimeZone: true) }}</td>
</tr>
{% endfor %}
</tbody>
Expand Down
2 changes: 1 addition & 1 deletion src/templates/_elements/revisions.twig
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
<th data-title="{{ "Revision"|t('app') }}">
<a href="{{ revision.getCpEditUrl() }}">{{ revisionLabel }}</a>
</th>
<td data-title="{{ "Created at"|t('app') }}">{{ revision.dateCreated|datetime('short') }}</td>
<td data-title="{{ "Created at"|t('app') }}">{{ revision.dateCreated|datetime('short', withTimeZone: true) }}</td>
<td data-title="{{ "Created by"|t('app') }}">
{% if creator %}
{{ elementChip(creator) }}
Expand Down
2 changes: 1 addition & 1 deletion src/views/debug/deprecated/traces.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
],
[
Craft::t('app', 'Last Occurrence'),
Craft::$app->getFormatter()->asDatetime($log->lastOccurrence, 'short'),
Craft::$app->getFormatter()->asDatetime($log->lastOccurrence, \craft\i18n\Locale::LENGTH_SHORT, true),
],
],
]);
Expand Down
28 changes: 21 additions & 7 deletions src/web/twig/Extension.php
Original file line number Diff line number Diff line change
Expand Up @@ -1066,10 +1066,17 @@ public function rssFilter(TwigEnvironment $env, mixed $date, mixed $timezone = n
* @param string|null $format The target format, null to use the default
* @param DateTimeZone|string|false|null $timezone The target timezone, null to use the default, false to leave unchanged
* @param string|null $locale The target locale the date should be formatted for. By default, the current system locale will be used.
* @param bool $withTimeZone Whether the time zone abbreviation should be appended to the formatted time
* @return string
*/
public function timeFilter(TwigEnvironment $env, mixed $date, ?string $format = null, mixed $timezone = null, ?string $locale = null): string
{
public function timeFilter(
TwigEnvironment $env,
mixed $date,
?string $format = null,
mixed $timezone = null,
?string $locale = null,
bool $withTimeZone = false,
): string {
// Is this a custom PHP date format?
if ($format !== null && !in_array($format, [Locale::LENGTH_SHORT, Locale::LENGTH_MEDIUM, Locale::LENGTH_LONG, Locale::LENGTH_FULL], true)) {
if (str_starts_with($format, 'icu:')) {
Expand All @@ -1079,11 +1086,11 @@ public function timeFilter(TwigEnvironment $env, mixed $date, ?string $format =
}
}

$date = $env->getExtension(CoreExtension::class)->convertDate($date, $timezone);
$date = DateTime::createFromInterface($env->getExtension(CoreExtension::class)->convertDate($date, $timezone));
$formatter = $locale ? Craft::$app->getI18n()->getLocaleById($locale)->getFormatter() : Craft::$app->getFormatter();
$fmtTimeZone = $formatter->timeZone;
$formatter->timeZone = $timezone !== null ? $date->getTimezone()->getName() : $formatter->timeZone;
$formatted = $formatter->asTime(DateTime::createFromInterface($date), $format);
$formatted = $formatter->asTime($date, $format, $withTimeZone);
$formatter->timeZone = $fmtTimeZone;
return $formatted;
}
Expand All @@ -1096,10 +1103,17 @@ public function timeFilter(TwigEnvironment $env, mixed $date, ?string $format =
* @param string|null $format The target format, null to use the default
* @param DateTimeZone|string|false|null $timezone The target timezone, null to use the default, false to leave unchanged
* @param string|null $locale The target locale the date should be formatted for. By default, the current system locale will be used.
* @param bool $withTimeZone Whether the time zone abbreviation should be appended to the formatted time
* @return string
*/
public function datetimeFilter(TwigEnvironment $env, mixed $date, ?string $format = null, mixed $timezone = null, ?string $locale = null): string
{
public function datetimeFilter(
TwigEnvironment $env,
mixed $date,
?string $format = null,
mixed $timezone = null,
?string $locale = null,
bool $withTimeZone = false,
): string {
// Is this a custom PHP date format?
if ($format !== null && !in_array($format, [Locale::LENGTH_SHORT, Locale::LENGTH_MEDIUM, Locale::LENGTH_LONG, Locale::LENGTH_FULL], true)) {
if (str_starts_with($format, 'icu:')) {
Expand All @@ -1113,7 +1127,7 @@ public function datetimeFilter(TwigEnvironment $env, mixed $date, ?string $forma
$formatter = $locale ? Craft::$app->getI18n()->getLocaleById($locale)->getFormatter() : Craft::$app->getFormatter();
$fmtTimeZone = $formatter->timeZone;
$formatter->timeZone = $timezone !== null ? $date->getTimezone()->getName() : $formatter->timeZone;
$formatted = $formatter->asDatetime(DateTime::createFromInterface($date), $format);
$formatted = $formatter->asDatetime(DateTime::createFromInterface($date), $format, $withTimeZone);
$formatter->timeZone = $fmtTimeZone;
return $formatted;
}
Expand Down