dbt-labs/dbt-core · error

'ExpirationTime' does not conform to RFC-3339. This is a dri

Error message

'ExpirationTime' does not conform to RFC-3339. This is a driver bug.

What it means

While building the refresh/expiry component of a BigQuery materialized view config, the 'ExpirationTime' schema metadata is parsed with DateTime::parse_from_rfc3339 and .expect, so any timestamp not in strict RFC-3339 format panics. The message asserts the driver should always deliver an RFC-3339 ExpirationTime; malformed values indicate a driver bug rather than user error.

Source

Thrown at crates/dbt-adapter/src/relation/bigquery/config/components/refresh.rs:97

            .map(|s| {
                parse_duration(s)
                    .map(|v| v.as_secs() as f64 / 60.0)
                    .unwrap_or(DEFAULT_INTERVAL_MIN)
            })
            .unwrap_or(DEFAULT_INTERVAL_MIN),
        // NOTE: dbt set this to None, but the ADBC driver provides it under
        // MaterializedView.MaxStaleness.
        //
        // This is a deviation from Core.
        // https://github.com/dbt-labs/dbt-adapters/blob/2a94cc75dba1f98fa5caff1f396f5af7ee444598/dbt-bigquery/src/dbt/adapters/bigquery/relation_configs/_options.py#L142
        max_staleness: schema
            .metadata
            .get("MaterializedView.MaxStaleness")
            .map(|s| s.to_owned())
            .unwrap_or_else(|| "".to_owned()),
        expiration: schema.metadata.get("ExpirationTime").map(|s| {
            DateTime::parse_from_rfc3339(s)
                .expect("'ExpirationTime' does not conform to RFC-3339. This is a driver bug.")
                .to_utc()
        }),
    };

    Ok(new_component(cfg))
}

fn from_local_config(relation_config: &dyn InternalDbtNodeAttributes) -> AdapterResult<Refresh> {
    let cfg = match relation_config.as_any().downcast_ref::<DbtModel>() {
        None => Config {
            enable: DEFAULT_ENABLE,
            interval_min: DEFAULT_INTERVAL_MIN,
            ..Default::default()
        },
        Some(model) => {
            let model_cfg = model
                .__adapter_attr__
                .bigquery_attr

View on GitHub (pinned to 0267ce9170)

Solutions

  1. Check the raw 'ExpirationTime' value and fix/upgrade the driver to emit RFC-3339 (e.g. '2026-01-01T00:00:00Z').
  2. Re-fetch metadata from the live BigQuery API to replace values written by older tooling.
  3. If epochs must be supported, branch on format: try parse_from_rfc3339 first, then fall back to numeric epoch via DateTime::from_timestamp.
  4. Replace .expect with a mapped AdapterError so malformed values fail gracefully with the offending value in the message.

Example fix

// before
DateTime::parse_from_rfc3339(s)
    .expect("'ExpirationTime' does not conform to RFC-3339. This is a driver bug.")
// after
DateTime::parse_from_rfc3339(s).map_err(|e| AdapterError::new(
    AdapterErrorKind::Internal,
    format!("invalid ExpirationTime '{s}': {e}")))?.to_utc()
Defensive patterns

Strategy: validation

Validate before calling

use chrono::DateTime;
fn valid_expiration(meta: &HashMap<String, String>) -> bool {
    meta.get("ExpirationTime")
        .map(|s| DateTime::parse_from_rfc3339(s).is_ok())
        .unwrap_or(true)
}

Prevention

When it happens

Trigger: from_remote_state (Config component) reads 'ExpirationTime' metadata containing a non-RFC-3339 timestamp — e.g. epoch millis ('1735689600'), epoch seconds as string, 'Z'-less datetimes without offset, or BigQuery's TIMESTAMP float-string form.

Common situations: Driver serializing the expiration as a numeric epoch instead of an ISO-8601 string; cached metadata written by an older adapter version; hand-built schemas in tests lacking proper formatting; timezone offset formats chrono's strict RFC-3339 parser rejects (e.g. '+00' instead of '+00:00').

Related errors


AI-assisted analysis of dbt-labs/dbt-core@0267ce9170 (2026-09-07). Data as JSON: /api/errors/4adcf360f8115111. Report an issue: GitHub.