Skip to main content

drizzle_core/expr/
datetime.rs

1//! Type-safe date/time functions.
2//!
3//! These functions work with `Temporal` types (Date, Time, Timestamp, `TimestampTz`)
4//! and provide compile-time enforcement of temporal operations.
5//!
6//! # Database Compatibility
7//!
8//! Some functions are database-specific:
9//! - `SQLite`: `date()`, `time()`, `datetime()`, `strftime()`, `julianday()`
10//! - `PostgreSQL`: `now()`, `date_trunc()`, `extract()`, `age()`
11//!
12//! Cross-database functions try to use compatible SQL where possible.
13
14use crate::dialect::DialectTypes;
15use crate::sql::{SQL, Token};
16use crate::traits::SQLParam;
17use crate::types::{DataType, Numeric, Temporal, Textual};
18use crate::{PostgresDialect, SQLiteDialect};
19use drizzle_types::postgres::types::{Timestamp as PgTimestamp, Timestamptz as PgTimestamptz};
20
21use super::{AggOr, Expr, NullOr, Nullability, SQLExpr, Scalar};
22
23#[diagnostic::on_unimplemented(
24    message = "this date/time function is not available for this dialect",
25    label = "use a dialect-specific alternative"
26)]
27pub trait SQLiteDateTimeSupport {}
28
29#[diagnostic::on_unimplemented(
30    message = "this date/time function is not available for this dialect",
31    label = "use a dialect-specific alternative"
32)]
33pub trait PostgresDateTimeSupport {}
34
35#[diagnostic::on_unimplemented(
36    message = "DATE_TRUNC output type is not defined for `{Self}` on this dialect",
37    label = "DATE_TRUNC accepts timestamp/timestamptz and preserves the timestamp flavor"
38)]
39pub trait DateTruncPolicy<D>: Temporal {
40    type Output: DataType;
41}
42
43impl SQLiteDateTimeSupport for SQLiteDialect {}
44impl PostgresDateTimeSupport for PostgresDialect {}
45
46impl DateTruncPolicy<PostgresDialect> for PgTimestamptz {
47    type Output = Self;
48}
49impl DateTruncPolicy<PostgresDialect> for PgTimestamp {
50    type Output = Self;
51}
52
53// =============================================================================
54// CURRENT DATE/TIME (Cross-database)
55// =============================================================================
56
57/// `CURRENT_DATE` - returns the current date.
58///
59/// Works on both `SQLite` and `PostgreSQL`.
60///
61/// # Example
62///
63/// ```rust
64/// # let _ = r####"
65/// use drizzle_core::expr::current_date;
66///
67/// // SELECT CURRENT_DATE
68/// let today = current_date::<SQLiteValue>();
69/// # "####;
70/// ```
71#[must_use]
72pub fn current_date<'a, V>()
73-> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Date, super::NonNull, Scalar>
74where
75    V: SQLParam + 'a,
76{
77    SQLExpr::new(SQL::raw("CURRENT_DATE"))
78}
79
80/// `CURRENT_TIME` - returns the current time.
81///
82/// Works on both `SQLite` and `PostgreSQL`.
83///
84/// # Example
85///
86/// ```rust
87/// # let _ = r####"
88/// use drizzle_core::expr::current_time;
89///
90/// // SELECT CURRENT_TIME
91/// let now_time = current_time::<SQLiteValue>();
92/// # "####;
93/// ```
94#[must_use]
95pub fn current_time<'a, V>()
96-> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Time, super::NonNull, Scalar>
97where
98    V: SQLParam + 'a,
99{
100    SQLExpr::new(SQL::raw("CURRENT_TIME"))
101}
102
103/// `CURRENT_TIMESTAMP` - returns the current timestamp with time zone.
104///
105/// Works on both `SQLite` and `PostgreSQL`. Returns `TimestampTz` because
106/// the SQL standard defines `CURRENT_TIMESTAMP` as `timestamp with time zone`.
107/// On `SQLite` (without chrono) this maps to `String`; on `PostgreSQL` it maps
108/// to `DateTime<Utc>` (requires the `chrono` feature).
109///
110/// # Example
111///
112/// ```rust
113/// # let _ = r####"
114/// use drizzle_core::expr::current_timestamp;
115///
116/// // SELECT CURRENT_TIMESTAMP
117/// let now = current_timestamp::<SQLiteValue>();
118/// # "####;
119/// ```
120#[must_use]
121pub fn current_timestamp<'a, V>()
122-> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::TimestampTz, super::NonNull, Scalar>
123where
124    V: SQLParam + 'a,
125{
126    SQLExpr::new(SQL::raw("CURRENT_TIMESTAMP"))
127}
128
129// =============================================================================
130// SQLite-specific DATE/TIME FUNCTIONS
131// =============================================================================
132
133/// DATE - extracts the date part from a temporal expression (`SQLite`).
134///
135/// Preserves the nullability of the input expression.
136///
137/// # Example
138///
139/// ```rust
140/// # let _ = r####"
141/// use drizzle_core::expr::date;
142///
143/// // SELECT DATE(users.created_at)
144/// let created_date = date(users.created_at);
145/// # "####;
146/// ```
147pub fn date<'a, V, E>(
148    expr: E,
149) -> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Date, E::Nullable, E::Aggregate>
150where
151    V: SQLParam + 'a,
152    V::DialectMarker: SQLiteDateTimeSupport,
153    E: Expr<'a, V>,
154    E::SQLType: Temporal,
155{
156    SQLExpr::new(SQL::func("DATE", expr.into_sql()))
157}
158
159/// TIME - extracts the time part from a temporal expression (`SQLite`).
160///
161/// Preserves the nullability of the input expression.
162///
163/// # Example
164///
165/// ```rust
166/// # let _ = r####"
167/// use drizzle_core::expr::time;
168///
169/// // SELECT TIME(users.created_at)
170/// let created_time = time(users.created_at);
171/// # "####;
172/// ```
173pub fn time<'a, V, E>(
174    expr: E,
175) -> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Time, E::Nullable, E::Aggregate>
176where
177    V: SQLParam + 'a,
178    V::DialectMarker: SQLiteDateTimeSupport,
179    E: Expr<'a, V>,
180    E::SQLType: Temporal,
181{
182    SQLExpr::new(SQL::func("TIME", expr.into_sql()))
183}
184
185/// DATETIME - creates a datetime from a temporal expression (`SQLite`).
186///
187/// Preserves the nullability of the input expression.
188///
189/// # Example
190///
191/// ```rust
192/// # let _ = r####"
193/// use drizzle_core::expr::datetime;
194///
195/// // SELECT DATETIME(users.created_at)
196/// let dt = datetime(users.created_at);
197/// # "####;
198/// ```
199pub fn datetime<'a, V, E>(
200    expr: E,
201) -> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Timestamp, E::Nullable, E::Aggregate>
202where
203    V: SQLParam + 'a,
204    V::DialectMarker: SQLiteDateTimeSupport,
205    E: Expr<'a, V>,
206    E::SQLType: Temporal,
207{
208    SQLExpr::new(SQL::func("DATETIME", expr.into_sql()))
209}
210
211/// STRFTIME - formats a temporal expression as text (`SQLite`).
212///
213/// Returns Text type, preserves nullability of the time value.
214///
215/// # Format Specifiers (common)
216///
217/// - `%Y` - 4-digit year
218/// - `%m` - month (01-12)
219/// - `%d` - day of month (01-31)
220/// - `%H` - hour (00-23)
221/// - `%M` - minute (00-59)
222/// - `%S` - second (00-59)
223/// - `%s` - Unix timestamp
224/// - `%w` - day of week (0-6, Sunday=0)
225/// - `%j` - day of year (001-366)
226///
227/// # Example
228///
229/// ```rust
230/// # let _ = r####"
231/// use drizzle_core::expr::strftime;
232///
233/// // SELECT STRFTIME('%Y-%m-%d', users.created_at)
234/// let formatted = strftime("%Y-%m-%d", users.created_at);
235/// # "####;
236/// ```
237#[allow(clippy::type_complexity)]
238pub fn strftime<'a, V, F, E>(
239    format: F,
240    expr: E,
241) -> SQLExpr<
242    'a,
243    V,
244    <V::DialectMarker as DialectTypes>::Text,
245    E::Nullable,
246    <F::Aggregate as AggOr<E::Aggregate>>::Output,
247>
248where
249    V: SQLParam + 'a,
250    V::DialectMarker: SQLiteDateTimeSupport,
251    F: Expr<'a, V>,
252    F::SQLType: Textual,
253    E: Expr<'a, V>,
254    E::SQLType: Temporal,
255    F::Aggregate: AggOr<E::Aggregate>,
256{
257    SQLExpr::new(SQL::func(
258        "STRFTIME",
259        format.into_sql().push(Token::COMMA).append(expr.into_sql()),
260    ))
261}
262
263/// JULIANDAY - converts a temporal expression to Julian day number (`SQLite`).
264///
265/// Returns a dialect-aware double type, preserves nullability.
266///
267/// # Example
268///
269/// ```rust
270/// # let _ = r####"
271/// use drizzle_core::expr::julianday;
272///
273/// // SELECT JULIANDAY(users.created_at)
274/// let julian = julianday(users.created_at);
275/// # "####;
276/// ```
277pub fn julianday<'a, V, E>(
278    expr: E,
279) -> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Double, E::Nullable, E::Aggregate>
280where
281    V: SQLParam + 'a,
282    V::DialectMarker: SQLiteDateTimeSupport,
283    E: Expr<'a, V>,
284    E::SQLType: Temporal,
285{
286    SQLExpr::new(SQL::func("JULIANDAY", expr.into_sql()))
287}
288
289/// UNIXEPOCH - converts a temporal expression to Unix timestamp (`SQLite` 3.38+).
290///
291/// Returns a dialect-aware `BigInt` type (seconds since 1970-01-01), preserves nullability.
292///
293/// # Example
294///
295/// ```rust
296/// # let _ = r####"
297/// use drizzle_core::expr::unixepoch;
298///
299/// // SELECT UNIXEPOCH(users.created_at)
300/// let unix_ts = unixepoch(users.created_at);
301/// # "####;
302/// ```
303pub fn unixepoch<'a, V, E>(
304    expr: E,
305) -> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::BigInt, E::Nullable, E::Aggregate>
306where
307    V: SQLParam + 'a,
308    V::DialectMarker: SQLiteDateTimeSupport,
309    E: Expr<'a, V>,
310    E::SQLType: Temporal,
311{
312    SQLExpr::new(SQL::func("UNIXEPOCH", expr.into_sql()))
313}
314
315// =============================================================================
316// PostgreSQL-specific DATE/TIME FUNCTIONS
317// =============================================================================
318
319/// NOW - returns the current timestamp with time zone (`PostgreSQL`).
320///
321/// # Example
322///
323/// ```rust
324/// # let _ = r####"
325/// use drizzle_core::expr::now;
326///
327/// // SELECT NOW()
328/// let current = now::<PostgresValue>();
329/// # "####;
330/// ```
331#[must_use]
332pub fn now<'a, V>()
333-> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::TimestampTz, super::NonNull, Scalar>
334where
335    V: SQLParam + 'a,
336    V::DialectMarker: PostgresDateTimeSupport,
337{
338    SQLExpr::new(SQL::raw("NOW()"))
339}
340
341/// `DATE_TRUNC` - truncates a timestamp to specified precision (`PostgreSQL`).
342///
343/// Truncates the timestamp to the specified precision. Common values:
344/// 'microseconds', 'milliseconds', 'second', 'minute', 'hour',
345/// 'day', 'week', 'month', 'quarter', 'year', 'decade', 'century', 'millennium'
346///
347/// Preserves the nullability of the input expression.
348///
349/// # Example
350///
351/// ```rust
352/// # let _ = r####"
353/// use drizzle_core::expr::date_trunc;
354///
355/// // SELECT DATE_TRUNC('month', users.created_at)
356/// let month_start = date_trunc("month", users.created_at);
357/// # "####;
358/// ```
359#[allow(clippy::type_complexity)]
360pub fn date_trunc<'a, V, P, E>(
361    precision: P,
362    expr: E,
363) -> SQLExpr<
364    'a,
365    V,
366    <E::SQLType as DateTruncPolicy<V::DialectMarker>>::Output,
367    E::Nullable,
368    <P::Aggregate as AggOr<E::Aggregate>>::Output,
369>
370where
371    V: SQLParam + 'a,
372    V::DialectMarker: PostgresDateTimeSupport,
373    P: Expr<'a, V>,
374    P::SQLType: Textual,
375    E: Expr<'a, V>,
376    E::SQLType: DateTruncPolicy<V::DialectMarker>,
377    P::Aggregate: AggOr<E::Aggregate>,
378{
379    SQLExpr::new(SQL::func(
380        "DATE_TRUNC",
381        precision
382            .into_sql()
383            .push(Token::COMMA)
384            .append(expr.into_sql()),
385    ))
386}
387
388/// EXTRACT - extracts a component from a temporal expression (PostgreSQL/Standard SQL).
389///
390/// Returns a dialect-aware double type. Common fields:
391/// 'year', 'month', 'day', 'hour', 'minute', 'second',
392/// 'dow' (day of week), 'doy' (day of year), 'epoch' (Unix timestamp)
393///
394/// Preserves the nullability of the input expression.
395///
396/// # Example
397///
398/// ```rust
399/// # let _ = r####"
400/// use drizzle_core::expr::extract;
401///
402/// // SELECT EXTRACT(YEAR FROM users.created_at)
403/// let year = extract("YEAR", users.created_at);
404/// # "####;
405/// ```
406pub fn extract<'a, 'f, V, E>(
407    field: &'f str,
408    expr: E,
409) -> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Double, E::Nullable, E::Aggregate>
410where
411    'f: 'a,
412    V: SQLParam + 'a,
413    V::DialectMarker: PostgresDateTimeSupport,
414    E: Expr<'a, V>,
415    E::SQLType: Temporal,
416{
417    // EXTRACT uses special syntax: EXTRACT(field FROM timestamp)
418    SQLExpr::new(
419        SQL::raw("EXTRACT(")
420            .append(SQL::raw(field))
421            .append(SQL::raw(" FROM "))
422            .append(expr.into_sql())
423            .push(Token::RPAREN),
424    )
425}
426
427/// AGE - calculates the interval between two timestamps (`PostgreSQL`).
428///
429/// Returns `PostgreSQL` INTERVAL. The result is nullable if either input is nullable.
430///
431/// # Example
432///
433/// ```rust
434/// # let _ = r####"
435/// use drizzle_core::expr::age;
436///
437/// // SELECT AGE(NOW(), users.created_at)
438/// let user_age = age(now(), users.created_at);
439/// # "####;
440/// ```
441#[allow(clippy::type_complexity)]
442pub fn age<'a, V, E1, E2>(
443    timestamp1: E1,
444    timestamp2: E2,
445) -> SQLExpr<
446    'a,
447    V,
448    drizzle_types::postgres::types::Interval,
449    <E1::Nullable as NullOr<E2::Nullable>>::Output,
450    <E1::Aggregate as AggOr<E2::Aggregate>>::Output,
451>
452where
453    V: SQLParam + 'a,
454    V::DialectMarker: PostgresDateTimeSupport,
455    E1: Expr<'a, V>,
456    E1::SQLType: Temporal,
457    E2: Expr<'a, V>,
458    E2::SQLType: Temporal,
459    E1::Nullable: NullOr<E2::Nullable>,
460    E2::Nullable: Nullability,
461    E1::Aggregate: AggOr<E2::Aggregate>,
462{
463    SQLExpr::new(SQL::func(
464        "AGE",
465        timestamp1
466            .into_sql()
467            .push(Token::COMMA)
468            .append(timestamp2.into_sql()),
469    ))
470}
471
472/// `TO_CHAR` - formats a temporal expression as text (`PostgreSQL`).
473///
474/// Returns Text type, preserves nullability of the input expression.
475///
476/// # Common Format Patterns
477///
478/// - `YYYY` - 4-digit year
479/// - `MM` - month (01-12)
480/// - `DD` - day of month (01-31)
481/// - `HH24` - hour (00-23)
482/// - `MI` - minute (00-59)
483/// - `SS` - second (00-59)
484/// - `Day` - full day name
485/// - `Month` - full month name
486///
487/// # Example
488///
489/// ```rust
490/// # let _ = r####"
491/// use drizzle_core::expr::to_char;
492///
493/// // SELECT TO_CHAR(users.created_at, 'YYYY-MM-DD')
494/// let formatted = to_char(users.created_at, "YYYY-MM-DD");
495/// # "####;
496/// ```
497#[allow(clippy::type_complexity)]
498pub fn to_char<'a, V, E, F>(
499    expr: E,
500    format: F,
501) -> SQLExpr<
502    'a,
503    V,
504    <V::DialectMarker as DialectTypes>::Text,
505    E::Nullable,
506    <E::Aggregate as AggOr<F::Aggregate>>::Output,
507>
508where
509    V: SQLParam + 'a,
510    V::DialectMarker: PostgresDateTimeSupport,
511    E: Expr<'a, V>,
512    E::SQLType: Temporal,
513    F: Expr<'a, V>,
514    F::SQLType: Textual,
515    E::Aggregate: AggOr<F::Aggregate>,
516{
517    SQLExpr::new(SQL::func(
518        "TO_CHAR",
519        expr.into_sql().push(Token::COMMA).append(format.into_sql()),
520    ))
521}
522
523/// `TO_TIMESTAMP` - converts a Unix timestamp to a timestamp (`PostgreSQL`).
524///
525/// Returns `TimestampTz` type. The input should be a numeric Unix timestamp.
526///
527/// # Example
528///
529/// ```rust
530/// # let _ = r####"
531/// use drizzle_core::expr::to_timestamp;
532///
533/// // SELECT TO_TIMESTAMP(users.created_unix)
534/// let ts = to_timestamp(users.created_unix);
535/// # "####;
536/// ```
537pub fn to_timestamp<'a, V, E>(expr: E) -> SQLExpr<'a, V, PgTimestamptz, E::Nullable, E::Aggregate>
538where
539    V: SQLParam + 'a,
540    V::DialectMarker: PostgresDateTimeSupport,
541    E: Expr<'a, V>,
542    E::SQLType: Numeric,
543{
544    SQLExpr::new(SQL::func("TO_TIMESTAMP", expr.into_sql()))
545}
546
547// =============================================================================
548// Additional PostgreSQL Formatting Functions
549// =============================================================================
550
551/// `TO_DATE` - parses a date from text using a format pattern (`PostgreSQL`).
552///
553/// Returns Date type, preserves nullability of the input expression.
554///
555/// # Example
556///
557/// ```rust
558/// # let _ = r####"
559/// use drizzle_core::expr::to_date;
560///
561/// // SELECT TO_DATE('2024-01-15', 'YYYY-MM-DD')
562/// let d = to_date("2024-01-15", "YYYY-MM-DD");
563/// # "####;
564/// ```
565#[allow(clippy::type_complexity)]
566pub fn to_date<'a, V, E, F>(
567    expr: E,
568    format: F,
569) -> SQLExpr<
570    'a,
571    V,
572    <V::DialectMarker as DialectTypes>::Date,
573    E::Nullable,
574    <E::Aggregate as AggOr<F::Aggregate>>::Output,
575>
576where
577    V: SQLParam + 'a,
578    V::DialectMarker: PostgresDateTimeSupport,
579    E: Expr<'a, V>,
580    E::SQLType: Textual,
581    F: Expr<'a, V>,
582    F::SQLType: Textual,
583    E::Aggregate: AggOr<F::Aggregate>,
584{
585    SQLExpr::new(SQL::func(
586        "TO_DATE",
587        expr.into_sql().push(Token::COMMA).append(format.into_sql()),
588    ))
589}
590
591/// `TO_NUMBER` - parses a number from text using a format pattern (`PostgreSQL`).
592///
593/// Returns Numeric type, preserves nullability of the input expression.
594///
595/// # Example
596///
597/// ```rust
598/// # let _ = r####"
599/// use drizzle_core::expr::to_number;
600///
601/// // SELECT TO_NUMBER('1,234.56', '9G999D99')
602/// let n = to_number("1,234.56", "9G999D99");
603/// # "####;
604/// ```
605#[allow(clippy::type_complexity)]
606pub fn to_number<'a, V, E, F>(
607    expr: E,
608    format: F,
609) -> SQLExpr<
610    'a,
611    V,
612    drizzle_types::postgres::types::Numeric,
613    E::Nullable,
614    <E::Aggregate as AggOr<F::Aggregate>>::Output,
615>
616where
617    V: SQLParam + 'a,
618    V::DialectMarker: PostgresDateTimeSupport,
619    E: Expr<'a, V>,
620    E::SQLType: Textual,
621    F: Expr<'a, V>,
622    F::SQLType: Textual,
623    E::Aggregate: AggOr<F::Aggregate>,
624{
625    SQLExpr::new(SQL::func(
626        "TO_NUMBER",
627        expr.into_sql().push(Token::COMMA).append(format.into_sql()),
628    ))
629}
630
631// =============================================================================
632// DATE_BIN (PostgreSQL 14+)
633// =============================================================================
634
635/// `DATE_BIN` - bins timestamps into intervals (`PostgreSQL` 14+).
636///
637/// Rounds a timestamp down to the nearest multiple of `stride` from `origin`.
638/// Useful for time-series bucketing.
639///
640/// # Example
641///
642/// ```rust
643/// # let _ = r####"
644/// use drizzle_core::expr::date_bin;
645///
646/// // SELECT DATE_BIN('15 minutes', events.created_at, TIMESTAMP '2001-01-01')
647/// let bucketed = date_bin("15 minutes", events.created_at, "2001-01-01");
648/// # "####;
649/// ```
650#[allow(clippy::type_complexity)]
651pub fn date_bin<'a, V, S, E, O>(
652    stride: S,
653    source: E,
654    origin: O,
655) -> SQLExpr<
656    'a,
657    V,
658    E::SQLType,
659    <<S::Nullable as NullOr<E::Nullable>>::Output as NullOr<O::Nullable>>::Output,
660    <<S::Aggregate as AggOr<E::Aggregate>>::Output as AggOr<O::Aggregate>>::Output,
661>
662where
663    V: SQLParam + 'a,
664    V::DialectMarker: PostgresDateTimeSupport,
665    S: Expr<'a, V>,
666    E: Expr<'a, V>,
667    E::SQLType: Temporal,
668    O: Expr<'a, V>,
669    O::SQLType: Temporal,
670    S::Nullable: NullOr<E::Nullable>,
671    E::Nullable: Nullability,
672    <S::Nullable as NullOr<E::Nullable>>::Output: NullOr<O::Nullable>,
673    O::Nullable: Nullability,
674    S::Aggregate: AggOr<E::Aggregate>,
675    <S::Aggregate as AggOr<E::Aggregate>>::Output: AggOr<O::Aggregate>,
676    O::Aggregate: super::AggregateKind,
677{
678    SQLExpr::new(SQL::func(
679        "DATE_BIN",
680        stride
681            .into_sql()
682            .push(Token::COMMA)
683            .append(source.into_sql())
684            .push(Token::COMMA)
685            .append(origin.into_sql()),
686    ))
687}
688
689// =============================================================================
690// MAKE_DATE / MAKE_TIMESTAMP (PostgreSQL)
691// =============================================================================
692
693/// `MAKE_DATE` - constructs a date from year, month, day (`PostgreSQL`).
694///
695/// # Example
696///
697/// ```rust
698/// # let _ = r####"
699/// use drizzle_core::expr::make_date;
700///
701/// // SELECT MAKE_DATE(2024, 1, 15)
702/// let d = make_date(2024, 1, 15);
703/// # "####;
704/// ```
705#[allow(clippy::type_complexity)]
706pub fn make_date<'a, V, Y, M, D>(
707    year: Y,
708    month: M,
709    day: D,
710) -> SQLExpr<
711    'a,
712    V,
713    <V::DialectMarker as DialectTypes>::Date,
714    <<Y::Nullable as NullOr<M::Nullable>>::Output as NullOr<D::Nullable>>::Output,
715    <<Y::Aggregate as AggOr<M::Aggregate>>::Output as AggOr<D::Aggregate>>::Output,
716>
717where
718    V: SQLParam + 'a,
719    V::DialectMarker: PostgresDateTimeSupport,
720    Y: Expr<'a, V>,
721    Y::SQLType: Numeric,
722    M: Expr<'a, V>,
723    M::SQLType: Numeric,
724    D: Expr<'a, V>,
725    D::SQLType: Numeric,
726    Y::Nullable: NullOr<M::Nullable>,
727    M::Nullable: Nullability,
728    <Y::Nullable as NullOr<M::Nullable>>::Output: NullOr<D::Nullable>,
729    D::Nullable: Nullability,
730    Y::Aggregate: AggOr<M::Aggregate>,
731    <Y::Aggregate as AggOr<M::Aggregate>>::Output: AggOr<D::Aggregate>,
732    D::Aggregate: super::AggregateKind,
733{
734    SQLExpr::new(SQL::func(
735        "MAKE_DATE",
736        year.into_sql()
737            .push(Token::COMMA)
738            .append(month.into_sql())
739            .push(Token::COMMA)
740            .append(day.into_sql()),
741    ))
742}
743
744/// `MAKE_TIMESTAMP` - constructs a timestamp from components (`PostgreSQL`).
745///
746/// # Example
747///
748/// ```rust
749/// # let _ = r####"
750/// use drizzle_core::expr::make_timestamp;
751///
752/// // SELECT MAKE_TIMESTAMP(2024, 1, 15, 10, 30, 0.0)
753/// let ts = make_timestamp(2024, 1, 15, 10, 30, 0.0);
754/// # "####;
755/// ```
756#[allow(clippy::type_complexity)]
757pub fn make_timestamp<'a, V, Y, Mo, D, H, Mi, S>(
758    year: Y,
759    month: Mo,
760    day: D,
761    hour: H,
762    minute: Mi,
763    second: S,
764) -> SQLExpr<
765    'a,
766    V,
767    <V::DialectMarker as DialectTypes>::Timestamp,
768    <<<<Y::Nullable as NullOr<Mo::Nullable>>::Output as NullOr<D::Nullable>>::Output as NullOr<
769        H::Nullable,
770    >>::Output as NullOr<<Mi::Nullable as NullOr<S::Nullable>>::Output>>::Output,
771    <<<<Y::Aggregate as AggOr<Mo::Aggregate>>::Output as AggOr<D::Aggregate>>::Output as AggOr<
772        H::Aggregate,
773    >>::Output as AggOr<<Mi::Aggregate as AggOr<S::Aggregate>>::Output>>::Output,
774>
775where
776    V: SQLParam + 'a,
777    V::DialectMarker: PostgresDateTimeSupport,
778    Y: Expr<'a, V>,
779    Y::SQLType: Numeric,
780    Mo: Expr<'a, V>,
781    Mo::SQLType: Numeric,
782    D: Expr<'a, V>,
783    D::SQLType: Numeric,
784    H: Expr<'a, V>,
785    H::SQLType: Numeric,
786    Mi: Expr<'a, V>,
787    Mi::SQLType: Numeric,
788    S: Expr<'a, V>,
789    S::SQLType: Numeric,
790    Y::Nullable: NullOr<Mo::Nullable>,
791    <Y::Nullable as NullOr<Mo::Nullable>>::Output: NullOr<D::Nullable>,
792    <<Y::Nullable as NullOr<Mo::Nullable>>::Output as NullOr<D::Nullable>>::Output:
793        NullOr<H::Nullable>,
794    Mi::Nullable: NullOr<S::Nullable>,
795    <<<Y::Nullable as NullOr<Mo::Nullable>>::Output as NullOr<D::Nullable>>::Output as NullOr<
796        H::Nullable,
797    >>::Output: NullOr<<Mi::Nullable as NullOr<S::Nullable>>::Output>,
798    H::Nullable: Nullability,
799    D::Nullable: Nullability,
800    Mo::Nullable: Nullability,
801    S::Nullable: Nullability,
802    Y::Aggregate: AggOr<Mo::Aggregate>,
803    <Y::Aggregate as AggOr<Mo::Aggregate>>::Output: AggOr<D::Aggregate>,
804    <<Y::Aggregate as AggOr<Mo::Aggregate>>::Output as AggOr<D::Aggregate>>::Output:
805        AggOr<H::Aggregate>,
806    Mi::Aggregate: AggOr<S::Aggregate>,
807    <<<Y::Aggregate as AggOr<Mo::Aggregate>>::Output as AggOr<D::Aggregate>>::Output as AggOr<
808        H::Aggregate,
809    >>::Output: AggOr<<Mi::Aggregate as AggOr<S::Aggregate>>::Output>,
810    H::Aggregate: super::AggregateKind,
811    D::Aggregate: super::AggregateKind,
812    S::Aggregate: super::AggregateKind,
813{
814    SQLExpr::new(SQL::func(
815        "MAKE_TIMESTAMP",
816        year.into_sql()
817            .push(Token::COMMA)
818            .append(month.into_sql())
819            .push(Token::COMMA)
820            .append(day.into_sql())
821            .push(Token::COMMA)
822            .append(hour.into_sql())
823            .push(Token::COMMA)
824            .append(minute.into_sql())
825            .push(Token::COMMA)
826            .append(second.into_sql()),
827    ))
828}
829
830// =============================================================================
831// Current Time (PostgreSQL-specific)
832// =============================================================================
833
834/// LOCALTIME - returns the current time without time zone (`PostgreSQL`).
835///
836/// # Example
837///
838/// ```rust
839/// # let _ = r####"
840/// use drizzle_core::expr::localtime;
841///
842/// // SELECT LOCALTIME
843/// let now_time = localtime::<PostgresValue>();
844/// # "####;
845/// ```
846#[must_use]
847pub fn localtime<'a, V>()
848-> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Time, super::NonNull, Scalar>
849where
850    V: SQLParam + 'a,
851    V::DialectMarker: PostgresDateTimeSupport,
852{
853    SQLExpr::new(SQL::raw("LOCALTIME"))
854}
855
856/// LOCALTIMESTAMP - returns the current timestamp without time zone (`PostgreSQL`).
857///
858/// # Example
859///
860/// ```rust
861/// # let _ = r####"
862/// use drizzle_core::expr::localtimestamp;
863///
864/// // SELECT LOCALTIMESTAMP
865/// let now_ts = localtimestamp::<PostgresValue>();
866/// # "####;
867/// ```
868#[must_use]
869pub fn localtimestamp<'a, V>() -> SQLExpr<'a, V, PgTimestamp, super::NonNull, Scalar>
870where
871    V: SQLParam + 'a,
872    V::DialectMarker: PostgresDateTimeSupport,
873{
874    SQLExpr::new(SQL::raw("LOCALTIMESTAMP"))
875}
876
877/// `CLOCK_TIMESTAMP` - returns the actual wall-clock time (`PostgreSQL`).
878///
879/// Unlike `NOW()` or `CURRENT_TIMESTAMP`, this changes during a transaction.
880///
881/// # Example
882///
883/// ```rust
884/// # let _ = r####"
885/// use drizzle_core::expr::clock_timestamp;
886///
887/// // SELECT CLOCK_TIMESTAMP()
888/// let wall_clock = clock_timestamp::<PostgresValue>();
889/// # "####;
890/// ```
891#[must_use]
892pub fn clock_timestamp<'a, V>() -> SQLExpr<'a, V, PgTimestamptz, super::NonNull, Scalar>
893where
894    V: SQLParam + 'a,
895    V::DialectMarker: PostgresDateTimeSupport,
896{
897    SQLExpr::new(SQL::raw("CLOCK_TIMESTAMP()"))
898}
899
900// =============================================================================
901// TIMEDIFF (SQLite 3.43+)
902// =============================================================================
903
904/// TIMEDIFF - computes the difference between two temporal values (`SQLite` 3.43+).
905///
906/// Returns a text representation of the time difference.
907///
908/// # Example
909///
910/// ```rust
911/// # let _ = r####"
912/// use drizzle_core::expr::timediff;
913///
914/// // SELECT TIMEDIFF(events.end_time, events.start_time)
915/// let duration = timediff(events.end_time, events.start_time);
916/// # "####;
917/// ```
918#[allow(clippy::type_complexity)]
919pub fn timediff<'a, V, E1, E2>(
920    time1: E1,
921    time2: E2,
922) -> SQLExpr<
923    'a,
924    V,
925    <V::DialectMarker as DialectTypes>::Text,
926    <E1::Nullable as NullOr<E2::Nullable>>::Output,
927    <E1::Aggregate as AggOr<E2::Aggregate>>::Output,
928>
929where
930    V: SQLParam + 'a,
931    V::DialectMarker: SQLiteDateTimeSupport,
932    E1: Expr<'a, V>,
933    E1::SQLType: Temporal,
934    E2: Expr<'a, V>,
935    E2::SQLType: Temporal,
936    E1::Nullable: NullOr<E2::Nullable>,
937    E2::Nullable: Nullability,
938    E1::Aggregate: AggOr<E2::Aggregate>,
939{
940    SQLExpr::new(SQL::func(
941        "TIMEDIFF",
942        time1.into_sql().push(Token::COMMA).append(time2.into_sql()),
943    ))
944}