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). PostgreSQL 14+
418    // returns NUMERIC, so the result is cast to the declared double type.
419    let extracted = SQL::raw("EXTRACT(")
420        .append(SQL::raw(field))
421        .append(SQL::raw(" FROM "))
422        .append(expr.into_sql())
423        .push(Token::RPAREN);
424    SQLExpr::new(SQL::func(
425        "CAST",
426        extracted
427            .push(Token::AS)
428            .append(SQL::raw("DOUBLE PRECISION")),
429    ))
430}
431
432/// AGE - calculates the interval between two timestamps (`PostgreSQL`).
433///
434/// Returns `PostgreSQL` INTERVAL. The result is nullable if either input is nullable.
435///
436/// # Example
437///
438/// ```rust
439/// # let _ = r####"
440/// use drizzle_core::expr::age;
441///
442/// // SELECT AGE(NOW(), users.created_at)
443/// let user_age = age(now(), users.created_at);
444/// # "####;
445/// ```
446#[allow(clippy::type_complexity)]
447pub fn age<'a, V, E1, E2>(
448    timestamp1: E1,
449    timestamp2: E2,
450) -> SQLExpr<
451    'a,
452    V,
453    drizzle_types::postgres::types::Interval,
454    <E1::Nullable as NullOr<E2::Nullable>>::Output,
455    <E1::Aggregate as AggOr<E2::Aggregate>>::Output,
456>
457where
458    V: SQLParam + 'a,
459    V::DialectMarker: PostgresDateTimeSupport,
460    E1: Expr<'a, V>,
461    E1::SQLType: Temporal,
462    E2: Expr<'a, V>,
463    E2::SQLType: Temporal,
464    E1::Nullable: NullOr<E2::Nullable>,
465    E2::Nullable: Nullability,
466    E1::Aggregate: AggOr<E2::Aggregate>,
467{
468    SQLExpr::new(SQL::func(
469        "AGE",
470        timestamp1
471            .into_sql()
472            .push(Token::COMMA)
473            .append(timestamp2.into_sql()),
474    ))
475}
476
477/// `TO_CHAR` - formats a temporal expression as text (`PostgreSQL`).
478///
479/// Returns Text type, preserves nullability of the input expression.
480///
481/// # Common Format Patterns
482///
483/// - `YYYY` - 4-digit year
484/// - `MM` - month (01-12)
485/// - `DD` - day of month (01-31)
486/// - `HH24` - hour (00-23)
487/// - `MI` - minute (00-59)
488/// - `SS` - second (00-59)
489/// - `Day` - full day name
490/// - `Month` - full month name
491///
492/// # Example
493///
494/// ```rust
495/// # let _ = r####"
496/// use drizzle_core::expr::to_char;
497///
498/// // SELECT TO_CHAR(users.created_at, 'YYYY-MM-DD')
499/// let formatted = to_char(users.created_at, "YYYY-MM-DD");
500/// # "####;
501/// ```
502#[allow(clippy::type_complexity)]
503pub fn to_char<'a, V, E, F>(
504    expr: E,
505    format: F,
506) -> SQLExpr<
507    'a,
508    V,
509    <V::DialectMarker as DialectTypes>::Text,
510    E::Nullable,
511    <E::Aggregate as AggOr<F::Aggregate>>::Output,
512>
513where
514    V: SQLParam + 'a,
515    V::DialectMarker: PostgresDateTimeSupport,
516    E: Expr<'a, V>,
517    E::SQLType: Temporal,
518    F: Expr<'a, V>,
519    F::SQLType: Textual,
520    E::Aggregate: AggOr<F::Aggregate>,
521{
522    SQLExpr::new(SQL::func(
523        "TO_CHAR",
524        expr.into_sql().push(Token::COMMA).append(format.into_sql()),
525    ))
526}
527
528/// `TO_TIMESTAMP` - converts a Unix timestamp to a timestamp (`PostgreSQL`).
529///
530/// Returns `TimestampTz` type. The input should be a numeric Unix timestamp.
531///
532/// # Example
533///
534/// ```rust
535/// # let _ = r####"
536/// use drizzle_core::expr::to_timestamp;
537///
538/// // SELECT TO_TIMESTAMP(users.created_unix)
539/// let ts = to_timestamp(users.created_unix);
540/// # "####;
541/// ```
542pub fn to_timestamp<'a, V, E>(expr: E) -> SQLExpr<'a, V, PgTimestamptz, E::Nullable, E::Aggregate>
543where
544    V: SQLParam + 'a,
545    V::DialectMarker: PostgresDateTimeSupport,
546    E: Expr<'a, V>,
547    E::SQLType: Numeric,
548{
549    SQLExpr::new(SQL::func("TO_TIMESTAMP", expr.into_sql()))
550}
551
552// =============================================================================
553// Additional PostgreSQL Formatting Functions
554// =============================================================================
555
556/// `TO_DATE` - parses a date from text using a format pattern (`PostgreSQL`).
557///
558/// Returns Date type, preserves nullability of the input expression.
559///
560/// # Example
561///
562/// ```rust
563/// # let _ = r####"
564/// use drizzle_core::expr::to_date;
565///
566/// // SELECT TO_DATE('2024-01-15', 'YYYY-MM-DD')
567/// let d = to_date("2024-01-15", "YYYY-MM-DD");
568/// # "####;
569/// ```
570#[allow(clippy::type_complexity)]
571pub fn to_date<'a, V, E, F>(
572    expr: E,
573    format: F,
574) -> SQLExpr<
575    'a,
576    V,
577    <V::DialectMarker as DialectTypes>::Date,
578    E::Nullable,
579    <E::Aggregate as AggOr<F::Aggregate>>::Output,
580>
581where
582    V: SQLParam + 'a,
583    V::DialectMarker: PostgresDateTimeSupport,
584    E: Expr<'a, V>,
585    E::SQLType: Textual,
586    F: Expr<'a, V>,
587    F::SQLType: Textual,
588    E::Aggregate: AggOr<F::Aggregate>,
589{
590    SQLExpr::new(SQL::func(
591        "TO_DATE",
592        expr.into_sql().push(Token::COMMA).append(format.into_sql()),
593    ))
594}
595
596/// `TO_NUMBER` - parses a number from text using a format pattern (`PostgreSQL`).
597///
598/// Returns Numeric type, preserves nullability of the input expression.
599///
600/// # Example
601///
602/// ```rust
603/// # let _ = r####"
604/// use drizzle_core::expr::to_number;
605///
606/// // SELECT TO_NUMBER('1,234.56', '9G999D99')
607/// let n = to_number("1,234.56", "9G999D99");
608/// # "####;
609/// ```
610#[allow(clippy::type_complexity)]
611pub fn to_number<'a, V, E, F>(
612    expr: E,
613    format: F,
614) -> SQLExpr<
615    'a,
616    V,
617    drizzle_types::postgres::types::Numeric,
618    E::Nullable,
619    <E::Aggregate as AggOr<F::Aggregate>>::Output,
620>
621where
622    V: SQLParam + 'a,
623    V::DialectMarker: PostgresDateTimeSupport,
624    E: Expr<'a, V>,
625    E::SQLType: Textual,
626    F: Expr<'a, V>,
627    F::SQLType: Textual,
628    E::Aggregate: AggOr<F::Aggregate>,
629{
630    SQLExpr::new(SQL::func(
631        "TO_NUMBER",
632        expr.into_sql().push(Token::COMMA).append(format.into_sql()),
633    ))
634}
635
636// =============================================================================
637// DATE_BIN (PostgreSQL 14+)
638// =============================================================================
639
640/// `DATE_BIN` - bins timestamps into intervals (`PostgreSQL` 14+).
641///
642/// Rounds a timestamp down to the nearest multiple of `stride` from `origin`.
643/// Useful for time-series bucketing.
644///
645/// # Example
646///
647/// ```rust
648/// # let _ = r####"
649/// use drizzle_core::expr::date_bin;
650///
651/// // SELECT DATE_BIN('15 minutes', events.created_at, TIMESTAMP '2001-01-01')
652/// let bucketed = date_bin("15 minutes", events.created_at, "2001-01-01");
653/// # "####;
654/// ```
655#[allow(clippy::type_complexity)]
656pub fn date_bin<'a, V, S, E, O>(
657    stride: S,
658    source: E,
659    origin: O,
660) -> SQLExpr<
661    'a,
662    V,
663    E::SQLType,
664    <<S::Nullable as NullOr<E::Nullable>>::Output as NullOr<O::Nullable>>::Output,
665    <<S::Aggregate as AggOr<E::Aggregate>>::Output as AggOr<O::Aggregate>>::Output,
666>
667where
668    V: SQLParam + 'a,
669    V::DialectMarker: PostgresDateTimeSupport,
670    S: Expr<'a, V>,
671    E: Expr<'a, V>,
672    E::SQLType: Temporal,
673    O: Expr<'a, V>,
674    O::SQLType: Temporal,
675    S::Nullable: NullOr<E::Nullable>,
676    E::Nullable: Nullability,
677    <S::Nullable as NullOr<E::Nullable>>::Output: NullOr<O::Nullable>,
678    O::Nullable: Nullability,
679    S::Aggregate: AggOr<E::Aggregate>,
680    <S::Aggregate as AggOr<E::Aggregate>>::Output: AggOr<O::Aggregate>,
681    O::Aggregate: super::AggregateKind,
682{
683    // The stride parameter binds as text; PostgreSQL resolves the overload at
684    // prepare time, so the cast keeps the bound and inferred types aligned.
685    SQLExpr::new(SQL::func(
686        "DATE_BIN",
687        super::math::pg_cast(stride.into_sql(), "INTERVAL")
688            .push(Token::COMMA)
689            .append(source.into_sql())
690            .push(Token::COMMA)
691            .append(origin.into_sql()),
692    ))
693}
694
695// =============================================================================
696// MAKE_DATE / MAKE_TIMESTAMP (PostgreSQL)
697// =============================================================================
698
699/// `MAKE_DATE` - constructs a date from year, month, day (`PostgreSQL`).
700///
701/// # Example
702///
703/// ```rust
704/// # let _ = r####"
705/// use drizzle_core::expr::make_date;
706///
707/// // SELECT MAKE_DATE(2024, 1, 15)
708/// let d = make_date(2024, 1, 15);
709/// # "####;
710/// ```
711#[allow(clippy::type_complexity)]
712pub fn make_date<'a, V, Y, M, D>(
713    year: Y,
714    month: M,
715    day: D,
716) -> SQLExpr<
717    'a,
718    V,
719    <V::DialectMarker as DialectTypes>::Date,
720    <<Y::Nullable as NullOr<M::Nullable>>::Output as NullOr<D::Nullable>>::Output,
721    <<Y::Aggregate as AggOr<M::Aggregate>>::Output as AggOr<D::Aggregate>>::Output,
722>
723where
724    V: SQLParam + 'a,
725    V::DialectMarker: PostgresDateTimeSupport,
726    Y: Expr<'a, V>,
727    Y::SQLType: Numeric,
728    M: Expr<'a, V>,
729    M::SQLType: Numeric,
730    D: Expr<'a, V>,
731    D::SQLType: Numeric,
732    Y::Nullable: NullOr<M::Nullable>,
733    M::Nullable: Nullability,
734    <Y::Nullable as NullOr<M::Nullable>>::Output: NullOr<D::Nullable>,
735    D::Nullable: Nullability,
736    Y::Aggregate: AggOr<M::Aggregate>,
737    <Y::Aggregate as AggOr<M::Aggregate>>::Output: AggOr<D::Aggregate>,
738    D::Aggregate: super::AggregateKind,
739{
740    SQLExpr::new(SQL::func(
741        "MAKE_DATE",
742        year.into_sql()
743            .push(Token::COMMA)
744            .append(month.into_sql())
745            .push(Token::COMMA)
746            .append(day.into_sql()),
747    ))
748}
749
750/// `MAKE_TIMESTAMP` - constructs a timestamp from components (`PostgreSQL`).
751///
752/// # Example
753///
754/// ```rust
755/// # let _ = r####"
756/// use drizzle_core::expr::make_timestamp;
757///
758/// // SELECT MAKE_TIMESTAMP(2024, 1, 15, 10, 30, 0.0)
759/// let ts = make_timestamp(2024, 1, 15, 10, 30, 0.0);
760/// # "####;
761/// ```
762#[allow(clippy::type_complexity)]
763pub fn make_timestamp<'a, V, Y, Mo, D, H, Mi, S>(
764    year: Y,
765    month: Mo,
766    day: D,
767    hour: H,
768    minute: Mi,
769    second: S,
770) -> SQLExpr<
771    'a,
772    V,
773    <V::DialectMarker as DialectTypes>::Timestamp,
774    <<<<Y::Nullable as NullOr<Mo::Nullable>>::Output as NullOr<D::Nullable>>::Output as NullOr<
775        H::Nullable,
776    >>::Output as NullOr<<Mi::Nullable as NullOr<S::Nullable>>::Output>>::Output,
777    <<<<Y::Aggregate as AggOr<Mo::Aggregate>>::Output as AggOr<D::Aggregate>>::Output as AggOr<
778        H::Aggregate,
779    >>::Output as AggOr<<Mi::Aggregate as AggOr<S::Aggregate>>::Output>>::Output,
780>
781where
782    V: SQLParam + 'a,
783    V::DialectMarker: PostgresDateTimeSupport,
784    Y: Expr<'a, V>,
785    Y::SQLType: Numeric,
786    Mo: Expr<'a, V>,
787    Mo::SQLType: Numeric,
788    D: Expr<'a, V>,
789    D::SQLType: Numeric,
790    H: Expr<'a, V>,
791    H::SQLType: Numeric,
792    Mi: Expr<'a, V>,
793    Mi::SQLType: Numeric,
794    S: Expr<'a, V>,
795    S::SQLType: Numeric,
796    Y::Nullable: NullOr<Mo::Nullable>,
797    <Y::Nullable as NullOr<Mo::Nullable>>::Output: NullOr<D::Nullable>,
798    <<Y::Nullable as NullOr<Mo::Nullable>>::Output as NullOr<D::Nullable>>::Output:
799        NullOr<H::Nullable>,
800    Mi::Nullable: NullOr<S::Nullable>,
801    <<<Y::Nullable as NullOr<Mo::Nullable>>::Output as NullOr<D::Nullable>>::Output as NullOr<
802        H::Nullable,
803    >>::Output: NullOr<<Mi::Nullable as NullOr<S::Nullable>>::Output>,
804    H::Nullable: Nullability,
805    D::Nullable: Nullability,
806    Mo::Nullable: Nullability,
807    S::Nullable: Nullability,
808    Y::Aggregate: AggOr<Mo::Aggregate>,
809    <Y::Aggregate as AggOr<Mo::Aggregate>>::Output: AggOr<D::Aggregate>,
810    <<Y::Aggregate as AggOr<Mo::Aggregate>>::Output as AggOr<D::Aggregate>>::Output:
811        AggOr<H::Aggregate>,
812    Mi::Aggregate: AggOr<S::Aggregate>,
813    <<<Y::Aggregate as AggOr<Mo::Aggregate>>::Output as AggOr<D::Aggregate>>::Output as AggOr<
814        H::Aggregate,
815    >>::Output: AggOr<<Mi::Aggregate as AggOr<S::Aggregate>>::Output>,
816    H::Aggregate: super::AggregateKind,
817    D::Aggregate: super::AggregateKind,
818    S::Aggregate: super::AggregateKind,
819{
820    SQLExpr::new(SQL::func(
821        "MAKE_TIMESTAMP",
822        year.into_sql()
823            .push(Token::COMMA)
824            .append(month.into_sql())
825            .push(Token::COMMA)
826            .append(day.into_sql())
827            .push(Token::COMMA)
828            .append(hour.into_sql())
829            .push(Token::COMMA)
830            .append(minute.into_sql())
831            .push(Token::COMMA)
832            .append(second.into_sql()),
833    ))
834}
835
836// =============================================================================
837// Current Time (PostgreSQL-specific)
838// =============================================================================
839
840/// LOCALTIME - returns the current time without time zone (`PostgreSQL`).
841///
842/// # Example
843///
844/// ```rust
845/// # let _ = r####"
846/// use drizzle_core::expr::localtime;
847///
848/// // SELECT LOCALTIME
849/// let now_time = localtime::<PostgresValue>();
850/// # "####;
851/// ```
852#[must_use]
853pub fn localtime<'a, V>()
854-> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Time, super::NonNull, Scalar>
855where
856    V: SQLParam + 'a,
857    V::DialectMarker: PostgresDateTimeSupport,
858{
859    SQLExpr::new(SQL::raw("LOCALTIME"))
860}
861
862/// LOCALTIMESTAMP - returns the current timestamp without time zone (`PostgreSQL`).
863///
864/// # Example
865///
866/// ```rust
867/// # let _ = r####"
868/// use drizzle_core::expr::localtimestamp;
869///
870/// // SELECT LOCALTIMESTAMP
871/// let now_ts = localtimestamp::<PostgresValue>();
872/// # "####;
873/// ```
874#[must_use]
875pub fn localtimestamp<'a, V>() -> SQLExpr<'a, V, PgTimestamp, super::NonNull, Scalar>
876where
877    V: SQLParam + 'a,
878    V::DialectMarker: PostgresDateTimeSupport,
879{
880    SQLExpr::new(SQL::raw("LOCALTIMESTAMP"))
881}
882
883/// `CLOCK_TIMESTAMP` - returns the actual wall-clock time (`PostgreSQL`).
884///
885/// Unlike `NOW()` or `CURRENT_TIMESTAMP`, this changes during a transaction.
886///
887/// # Example
888///
889/// ```rust
890/// # let _ = r####"
891/// use drizzle_core::expr::clock_timestamp;
892///
893/// // SELECT CLOCK_TIMESTAMP()
894/// let wall_clock = clock_timestamp::<PostgresValue>();
895/// # "####;
896/// ```
897#[must_use]
898pub fn clock_timestamp<'a, V>() -> SQLExpr<'a, V, PgTimestamptz, super::NonNull, Scalar>
899where
900    V: SQLParam + 'a,
901    V::DialectMarker: PostgresDateTimeSupport,
902{
903    SQLExpr::new(SQL::raw("CLOCK_TIMESTAMP()"))
904}
905
906// =============================================================================
907// TIMEDIFF (SQLite 3.43+)
908// =============================================================================
909
910/// TIMEDIFF - computes the difference between two temporal values (`SQLite` 3.43+).
911///
912/// Returns a text representation of the time difference.
913///
914/// # Example
915///
916/// ```rust
917/// # let _ = r####"
918/// use drizzle_core::expr::timediff;
919///
920/// // SELECT TIMEDIFF(events.end_time, events.start_time)
921/// let duration = timediff(events.end_time, events.start_time);
922/// # "####;
923/// ```
924#[allow(clippy::type_complexity)]
925pub fn timediff<'a, V, E1, E2>(
926    time1: E1,
927    time2: E2,
928) -> SQLExpr<
929    'a,
930    V,
931    <V::DialectMarker as DialectTypes>::Text,
932    <E1::Nullable as NullOr<E2::Nullable>>::Output,
933    <E1::Aggregate as AggOr<E2::Aggregate>>::Output,
934>
935where
936    V: SQLParam + 'a,
937    V::DialectMarker: SQLiteDateTimeSupport,
938    E1: Expr<'a, V>,
939    E1::SQLType: Temporal,
940    E2: Expr<'a, V>,
941    E2::SQLType: Temporal,
942    E1::Nullable: NullOr<E2::Nullable>,
943    E2::Nullable: Nullability,
944    E1::Aggregate: AggOr<E2::Aggregate>,
945{
946    SQLExpr::new(SQL::func(
947        "TIMEDIFF",
948        time1.into_sql().push(Token::COMMA).append(time2.into_sql()),
949    ))
950}