Skip to content

Commit

Permalink
Advanced date query examples
Browse files Browse the repository at this point in the history
  • Loading branch information
AugustMiller committed Jan 22, 2025
1 parent 8ec7788 commit e2b3db6
Showing 1 changed file with 110 additions and 65 deletions.
175 changes: 110 additions & 65 deletions docs/5.x/reference/field-types/date-time.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,10 @@ related:

# Date Fields

Date fields give you a date picker, and optionally a time picker as well.
Date fields provide a specialized input that captures and stores a date and time, in UTC. You may [configure](#settings) it to only accept a date (no time), and to expose a timezone selector.

<!-- more -->

You can also pick minimum and maximum dates that should be allowed, and if you’re showing the time, you can choose what the minute increment should be.

## Settings

Date fields have the following settings:
Expand All @@ -25,22 +23,73 @@ Date fields have the following settings:

## Development

Date values are always stored in UTC, and normalized to the system timezone (set in <Journey path="Settings, General" />), or whichever timezone was selected by the author.

As a result, outputting dates in a template will typically not require

### Working with Date Field Data

If you have an element with a date field in your template, you can access its value by its handle:

::: code
```twig
{% set value = entry.myFieldHandle %}
```
```php
$value = $entry->myFieldHandle;
```
:::

That will give you a [DateTime](http://php.net/manual/en/class.datetime.php) object that represents the selected date, or `null` if no date was selected.

::: code
```twig
{% if entry.myDateField %}
Selected date: {{ entry.myDateField|datetime('short') }}
{% endif %}
```
```php
if ($entry->myDateField) {
$selectedDate = \Craft::$app->getFormatter()->asDatetime(
$entry->myDateField,
'short'
);
}
```
:::

Twig has a number of filters for manipulating and outputting dates:

- [date](../twig/filters.md#date)
- [time](../twig/filters.md#time)
- [datetime](../twig/filters.md#datetime)
- [duration](../twig/filters.md#duration)
- [timestamp](../twig/filters.md#timestamp)
- [atom](../twig/filters.md#atom)
- [rss](../twig/filters.md#rss)
- [date_modify](https://twig.symfony.com/doc/3.x/filters/date_modify.html)

As a `DateTime` object, you can also use PHP’s native formatting method:

```twig
{{ entry.myDateField.format('F j, Y') }}
{# -> January 1, 2025 #}
```

### Querying Elements with Date Fields

When [querying for elements](element-queries.md) that have a Date field, you can filter the results based on the Date field data using a query param named after your field’s handle.
When [querying for elements](element-queries.md) that have a date field, you can filter the results using a query param named after your field’s handle.

Possible values include:

| Value | Fetches elements…
| - | -
| `':empty:'` | that don’t have a selected date.
| `':notempty:'` | that have a selected date.
| `'>= 2018-04-01'` | that have a date selected on or after 2018-04-01.
| `'< 2018-05-01'` | that have a date selected before 2018-05-01
| `['and', '>= 2018-04-01', '< 2018-05-01']` | that have a date selected between 2018-04-01 and 2018-05-01.
| `['or', '< 2018-04-01', '> 2018-05-01']` | that have a date selected before 2018-04-01 or after 2018-05-01.

Date values are always assumed to be in the system timezone, which is set in **Settings** &rarr; **General**.
Value | Fetches elements…
--- | ---
`':empty:'` | …that don’t have a selected date.
`':notempty:'` | …that have a selected date.
`'>= 2025-01-01'` | …that have a date selected on or after 1 January, 2025.
`'< 2025-01-01'` | …that have a date selected before 1 January, 2025.
`['and', '>= 2024-01-01', '< 2024-06-01']` | …that have a date selected after 1 January, 2024 and before 1 June, 2025.
`['or', '< 2024-06-01', '> 2025-01-01']` | …that have a date selected before 1 June, 2024 or after 1 January, 2025.

::: code
```twig
Expand All @@ -49,7 +98,7 @@ Date values are always assumed to be in the system timezone, which is set in **S
{% set end = now|date_modify('+1 month')|atom %}
{% set entries = craft.entries()
.myFieldHandle(['and', ">= #{start}", "< #{end}"])
.myDateField(['and', ">= #{start}", "< #{end}"])
.all() %}
```
```php
Expand All @@ -58,95 +107,87 @@ $start = (new \DateTime())->format(\DateTime::ATOM);
$end = (new \DateTime('+1 month'))->format(\DateTime::ATOM);

$entries = \craft\elements\Entry::find()
->myFieldHandle(['and', ">= ${start}", "< ${end}"])
->myDateField(['and', ">= ${start}", "< ${end}"])
->all();
```
:::

::: tip
The [atom](../twig/filters.md#atom) filter converts a date to an ISO-8601 timestamp.
The [atom](../twig/filters.md#atom) filter converts a date to an ISO-8601 timestamp. `#{...}` interpolates the timestamp into a double-quoted string (`"`) with an operator.
:::

Craft 3.7 added support for using `now` in date comparison strings:
In place of a timestamp, you can use a handful of tokens to stand in for commonly-referenced relative times:

::: code
```twig
{# Fetch entries with a selected date in the past #}
{% set pastEntries = craft.entries()
.myFieldHandle('< now')
.myDateField('< now')
.all() %}
{# Fetch entries with a selected date now onward #}
{% set futureEntries = craft.entries()
.myFieldHandle('>= now')
.myDateField('>= now')
.all() %}
```
```php
// Fetch entries with a selected date in the past
$pastEntries = \craft\elements\Entry::find()
->myFieldHandle('< now')
->myDateField('< now')
->all();
// Fetch entries with a selected date now onward
$futureEntries = \craft\elements\Entry::find()
->myFieldHandle('>= now')
->myDateField('>= now')
->all();
```
:::

In these examples, `now` can be substituted for `today` (midnight of the current day), `tomorrow` (midnight of the following day), or `yesterday` (midnight of the previous day).

::: tip
Me mindful of [template caching](../twig/tags.md#cache) when comparing against the current time!
:::

### Working with Date Field Data
#### Timezones

If you have an element with a Date field in your template, you can access its value by its handle:
Craft treats all dates as though they are in the system’s timezone, except when one is set explicitly for a date field.

::: code
```twig
{% set value = entry.myFieldHandle %}
```
```php
$value = $entry->myFieldHandle;
```
The returned `DateTime` object’s timezone will be set, accordingly. If you wish to display the date in a _different_ timezone than it was defined, use the `timezone` argument supported by Craft’s [date](../twig/filters.md#date), [datetime](../twig/filters.md#datetime) and [time](../twig/filters.md#time) Twig filters.

::: tip
This flexibility is only a feature of date fields, and native element properties (like entries’ _post date_) are always stored in the system timezone.
:::

That will give you a [DateTime](http://php.net/manual/en/class.datetime.php) object that represents the selected date, or `null` if no date was selected.
#### Advanced Conditions <Since ver="5.6.0" feature="Field aliases in advanced where() clauses" />

Building some date-based constraints can be cumbersome or inscrutable in Twig. In these cases, the database or query builder may be able to make them more declarative:

::: code
```twig
{% if entry.myFieldHandle %}
Selected date: {{ entry.myFieldHandle|datetime('short') }}
{% endif %}
```twig{5} MySQL
{% set year = 2024 %}
{% set sentInYear = craft.entries()
.section('letters')
.andWhere("YEAR([[dateSent]]) = :y", { y: year })
.all() %}
```
```php
if ($entry->myFieldHandle) {
$selectedDate = \Craft::$app->getFormatter()->asDatetime(
$entry->myFieldHandle,
'short'
);
}
```twig{5} Postgres
{% set year = 2024 %}
{% set sentInYear = craft.entries()
.section('letters')
.andWhere("EXTRACT(YEAR FROM TIMESTAMP [[dateSent]]) = :y", { y: year })
.all() %}
```
:::

Craft and Twig provide several Twig filters for manipulating and outputting dates, which you can use depending on your needs:

- [date](../twig/filters.md#date)
- [time](../twig/filters.md#time)
- [datetime](../twig/filters.md#datetime)
- [duration](../twig/filters.md#duration)
- [timestamp](../twig/filters.md#timestamp)
- [atom](../twig/filters.md#atom)
- [rss](../twig/filters.md#rss)
- [date_modify](https://twig.symfony.com/doc/3.x/filters/date_modify.html)
This uses the database’s built-in functions to extract and compare only the year of each date; similarly, you could use the `MONTH()` function (or `MONTH FROM TIMESTAMP`) to find entries with a `dateSent` only in a specific month.

#### Timezones
::: danger
_Do not_ pass user input (like a field value or query string) directly to `.andWhere()`! Here, we’ve used a bound parameter (`:y`) to safely escape the input.
:::

Craft treats all dates as though they are in the system’s timezone, except when one is set explicitly for a date field.
Prior to the introduction of custom field column aliases in Craft 5.6.0, advanced queries such as this were still possible, but required that you build the field value SQL expression using the [`fieldValueSql()` function](../twig/functions.md#fieldvaluesql).

The returned `DateTime` object’s timezone will be set, accordingly. If you wish to display the date in a _different_ timezone than it was defined, use the `timezone` argument supported by Craft’s [date](../twig/filters.md#date), [datetime](../twig/filters.md#datetime) and [time](../twig/filters.md#time) Twig filters.

::: tip
This flexibility is only a feature of date fields, and native element properties (like entries’ _post date_) are always stored in the system timezone.
:::

### Saving Date Fields

Expand Down Expand Up @@ -174,7 +215,7 @@ If you want the user to be able to select a time as well, use a `datetime-local`

#### Customizing the Timezone

By default, Craft will assume the date is posted in UTC. As of Craft 3.1.6 you can post dates in a different timezone by changing the input name to `fields[myFieldHandle][datetime]` and adding a hidden input named `fields[myFieldHandle][timezone]`, set to a [valid PHP timezone](http://php.net/manual/en/timezones.php):
By default, Craft will assume the date is posted in UTC. You can post dates in a different timezone by changing the primary input’s `name` to `fields[myFieldHandle][datetime]` and adding a hidden input named `fields[myFieldHandle][timezone]`, set to a [valid PHP timezone](http://php.net/manual/en/timezones.php):

```twig
{# Use the timezone selected under Settings → General Settings → Time Zone #}
Expand All @@ -183,6 +224,7 @@ By default, Craft will assume the date is posted in UTC. As of Craft 3.1.6 you c
{# Or set a specific timezone #}
{% set tz = 'America/Los_Angeles' %}
{# Normalize saved dates with the specified timezone: #}
{% set currentValue = entry is defined and entry.myFieldHandle
? entry.myFieldHandle|date('Y-m-d\\TH:i', timezone=tz)
: '' %}
Expand All @@ -207,13 +249,14 @@ Or you can let users decide which timezone the date should be posted in:
</select>
```

#### Posting the Date and Time Separately
In this example, we’ve elected to normalize the stored date (and timezone) to UTC, then allow the user to send it back to Craft in a localized way.

If you’d like to post the date and time as separate HTML inputs, give them the names `fields[myFieldHandle][date]` and `fields[myFieldHandle][time]`.
#### Posting the Date and Time Separately

The date input can either be set to the `YYYY-MM-DD` format, or the current locale’s short date format.
If you’d like to post the date and time as separate HTML inputs, give them the names `fields[myDateField][date]` and `fields[myDateField][time]`.

The time input can either be set to the `HH:MM` format (24-hour), or the current locale’s short time format.
- The `date` input can use either the `YYYY-MM-DD` format, or the current locale’s short date format.
- The `time` input can use either the `HH:MM` format (24-hour), or the current locale’s short time format.

::: tip
To find out what your current locale’s date and time formats are, add this to your template:
Expand All @@ -224,6 +267,8 @@ Time format: <code>{{ craft.app.locale.getTimeFormat('short', 'php') }}</code>
```

Then refer to PHP’s [date()](http://php.net/manual/en/function.date.php) function docs to see what each of the format letters mean.

The locale that Craft uses to parse dates and times depends on the [site](../../system/sites.md)’s language for front-end requests, and user preferences for control panel requests.
:::

This strategy can by combined with [timezone customization](#customizing-the-timezone).

0 comments on commit e2b3db6

Please sign in to comment.