diff --git a/sdk/core/azure_core/src/request_options/if_source_modified_since_condition.rs b/sdk/core/azure_core/src/request_options/if_source_modified_since_condition.rs index 139c725393..7340308534 100644 --- a/sdk/core/azure_core/src/request_options/if_source_modified_since_condition.rs +++ b/sdk/core/azure_core/src/request_options/if_source_modified_since_condition.rs @@ -24,7 +24,7 @@ impl Header for IfSourceModifiedSinceCondition { fn value(&self) -> headers::HeaderValue { match self { IfSourceModifiedSinceCondition::Modified(date) - | IfSourceModifiedSinceCondition::Unmodified(date) => date::to_rfc1123(date).into(), + | IfSourceModifiedSinceCondition::Unmodified(date) => date::to_rfc7231(date).into(), } } } diff --git a/sdk/cosmos/azure_data_cosmos/src/pipeline/authorization_policy.rs b/sdk/cosmos/azure_data_cosmos/src/pipeline/authorization_policy.rs index e42918ffd9..8d0fd14669 100644 --- a/sdk/cosmos/azure_data_cosmos/src/pipeline/authorization_policy.rs +++ b/sdk/cosmos/azure_data_cosmos/src/pipeline/authorization_policy.rs @@ -70,7 +70,7 @@ impl Policy for AuthorizationPolicy { ); // x-ms-date and the string used in the signature must be exactly the same, so just generate it here once. - let date_string = date::to_rfc1123(&OffsetDateTime::now_utc()).to_lowercase(); + let date_string = date::to_rfc7231(&OffsetDateTime::now_utc()).to_lowercase(); let resource_link: &ResourceLink = ctx .value() @@ -179,7 +179,7 @@ mod tests { #[tokio::test] async fn generate_authorization_for_token_credential() { let time_nonce = date::parse_rfc3339("1900-01-01T01:00:00.000000000+00:00").unwrap(); - let date_string = date::to_rfc1123(&time_nonce).to_lowercase(); + let date_string = date::to_rfc7231(&time_nonce).to_lowercase(); let cred = Arc::new(TestTokenCredential("test_token".to_string())); let auth_token = Credential::Token(cred); @@ -209,7 +209,7 @@ mod tests { #[cfg(feature = "key_auth")] async fn generate_authorization_for_primary_key_0() { let time_nonce = date::parse_rfc3339("1900-01-01T01:00:00.000000000+00:00").unwrap(); - let date_string = date::to_rfc1123(&time_nonce).to_lowercase(); + let date_string = date::to_rfc7231(&time_nonce).to_lowercase(); let auth_token = Credential::PrimaryKey( "8F8xXXOptJxkblM1DBXW7a6NMI5oE8NnwPGYBmwxLCKfejOK7B7yhcCHMGvN3PBrlMLIOeol1Hv9RCdzAZR5sg==".into(), @@ -243,7 +243,7 @@ mod tests { #[cfg(feature = "key_auth")] async fn generate_authorization_for_primary_key_1() { let time_nonce = date::parse_rfc3339("2017-04-27T00:51:12.000000000+00:00").unwrap(); - let date_string = date::to_rfc1123(&time_nonce).to_lowercase(); + let date_string = date::to_rfc7231(&time_nonce).to_lowercase(); let auth_token = Credential::PrimaryKey( "dsZQi3KtZmCv1ljt3VNWNm7sQUF1y5rJfC6kv5JiwvW0EndXdDku/dkKBp8/ufDToSxL".into(), diff --git a/sdk/cosmos/azure_data_cosmos/src/pipeline/signature_target.rs b/sdk/cosmos/azure_data_cosmos/src/pipeline/signature_target.rs index f142140930..57c6d6866e 100644 --- a/sdk/cosmos/azure_data_cosmos/src/pipeline/signature_target.rs +++ b/sdk/cosmos/azure_data_cosmos/src/pipeline/signature_target.rs @@ -82,7 +82,7 @@ mod tests { #[test] fn into_signable_string_generates_correct_value() { let time_nonce = date::parse_rfc3339("1900-01-01T01:00:00.000000000+00:00").unwrap(); - let date_string = date::to_rfc1123(&time_nonce).to_lowercase(); + let date_string = date::to_rfc7231(&time_nonce).to_lowercase(); let ret = SignatureTarget::new( azure_core::Method::Get, diff --git a/sdk/typespec/typespec_client_core/src/date/iso8601.rs b/sdk/typespec/typespec_client_core/src/date/iso8601.rs index 59863ce46d..aaaa680e99 100644 --- a/sdk/typespec/typespec_client_core/src/date/iso8601.rs +++ b/sdk/typespec/typespec_client_core/src/date/iso8601.rs @@ -8,7 +8,7 @@ use time::{ iso8601::{Config, EncodedConfig, TimePrecision}, Iso8601, }, - OffsetDateTime, UtcOffset, + OffsetDateTime, }; use typespec::error::{ErrorKind, ResultExt}; @@ -49,7 +49,6 @@ pub fn serialize(date: &OffsetDateTime, serializer: S) -> Result - use std::time::Duration; pub use time::{error::ComponentRange, OffsetDateTime}; use time::{ format_description::{well_known::Rfc3339, FormatItem}, macros::format_description, - PrimitiveDateTime, UtcOffset, + PrimitiveDateTime, }; use typespec::error::{ErrorKind, ResultExt}; // Serde modules. pub use time::serde::rfc3339; pub use time::serde::timestamp; + +// RFC 3339 vs ISO 8601: pub mod iso8601; -pub mod rfc1123; +pub mod rfc7231; /// RFC 3339: Date and Time on the Internet: Timestamps. /// /// /// -/// In REST API specifications it is specified as `"format": "date-time"`. +/// In [TypeSpec](https://aka.ms/typespec) properties are specified as `utcDateTime` or `offsetDateTime`. +/// In OpenAPI 2.0 specifications properties are specified as `"format": "date-time"`. /// -/// 1985-04-12T23:20:50.52Z +/// Example string: `1985-04-12T23:20:50.52Z`. pub fn parse_rfc3339(s: &str) -> crate::Result { OffsetDateTime::parse(s, &Rfc3339).with_context(ErrorKind::DataConversion, || { format!("unable to parse rfc3339 date '{s}") @@ -38,59 +38,57 @@ pub fn parse_rfc3339(s: &str) -> crate::Result { /// /// /// -/// In REST API specifications it is specified as `"format": "date-time"`. +/// In [TypeSpec](https://aka.ms/typespec) properties are specified as `utcDateTime` or `offsetDateTime`. +/// In OpenAPI 2.0 specifications properties are specified as `"format": "date-time"`. /// -/// 1985-04-12T23:20:50.52Z +/// Example string: `1985-04-12T23:20:50.52Z`. pub fn to_rfc3339(date: &OffsetDateTime) -> String { // known format does not panic date.format(&Rfc3339).unwrap() } -/// RFC 1123: Requirements for Internet Hosts - Application and Support. -/// -/// +/// RFC 7231: Requirements for Internet Hosts - Application and Support. /// -/// In REST API specifications it is specified as `"format": "date-time-rfc1123"`. +/// /// -/// In .NET it is the `rfc1123pattern`. -/// +/// In [TypeSpec](https://aka.ms/typespec) headers are specified as `utcDateTime`. +/// In REST API specifications headers are specified as `"format": "date-time-rfc1123"`. /// -/// This format is also the preferred HTTP date format. -/// +/// This format is also the preferred HTTP date-based header format. +/// * +/// * /// -/// Sun, 06 Nov 1994 08:49:37 GMT -pub fn parse_rfc1123(s: &str) -> crate::Result { - Ok(PrimitiveDateTime::parse(s, RFC1123_FORMAT) +/// Example string: `Sun, 06 Nov 1994 08:49:37 GMT`. +pub fn parse_rfc7231(s: &str) -> crate::Result { + Ok(PrimitiveDateTime::parse(s, RFC7231_FORMAT) .with_context(ErrorKind::DataConversion, || { - format!("unable to parse rfc1123 date '{s}") + format!("unable to parse rfc7231 date '{s}") })? .assume_utc()) } -const RFC1123_FORMAT: &[FormatItem] = format_description!( +const RFC7231_FORMAT: &[FormatItem] = format_description!( "[weekday repr:short], [day] [month repr:short] [year] [hour]:[minute]:[second] GMT" ); -/// RFC 1123: Requirements for Internet Hosts - Application and Support. +/// RFC 7231: Requirements for Internet Hosts - Application and Support. /// -/// +/// /// -/// In REST API specifications it is specified as `"format": "date-time-rfc1123"`. +/// In [TypeSpec](https://aka.ms/typespec) headers are specified as `utcDateTime`. +/// In REST API specifications headers are specified as `"format": "date-time-rfc1123"`. /// -/// In .NET it is the `rfc1123pattern`. -/// +/// This format is also the preferred HTTP date-based header format. +/// * +/// * /// -/// This format is also the preferred HTTP date format. -/// -/// -/// Sun, 06 Nov 1994 08:49:37 GMT -pub fn to_rfc1123(date: &OffsetDateTime) -> String { - date.to_offset(UtcOffset::UTC); +/// Example string: `Sun, 06 Nov 1994 08:49:37 GMT`. +pub fn to_rfc7231(date: &OffsetDateTime) -> String { // known format does not panic - date.format(&RFC1123_FORMAT).unwrap() + date.format(&RFC7231_FORMAT).unwrap() } -/// Similar to RFC 1123, but includes milliseconds. +/// Similar to RFC 7231, but includes milliseconds. /// /// /// @@ -114,7 +112,6 @@ const LAST_STATE_CHANGE_FORMAT: &[FormatItem] = format_description!( /// /// x-ms-last-state-change-utc: Fri, 25 Mar 2016 21:27:20.035 GMT pub fn to_last_state_change(date: &OffsetDateTime) -> String { - date.to_offset(UtcOffset::UTC); // known format does not panic date.format(LAST_STATE_CHANGE_FORMAT).unwrap() } @@ -157,7 +154,7 @@ mod tests { } #[test] - fn test_roundtrip_rfc3339() -> crate::Result<()> { + fn roundtrip_rfc3339() -> crate::Result<()> { let s = "2019-10-12T07:20:50.52Z"; let dt = parse_rfc3339(s)?; assert_eq!(s, to_rfc3339(&dt)); @@ -165,7 +162,16 @@ mod tests { } #[test] - fn test_device_update_dates() -> crate::Result<()> { + fn roundtrip_rfc3339_offset() -> crate::Result<()> { + let s = "2019-10-12T00:20:50.52-08:00"; + let dt = parse_rfc3339(s)?; + assert!(!dt.offset().is_utc()); + assert_eq!(s, to_rfc3339(&dt)); + Ok(()) + } + + #[test] + fn device_update_dates() -> crate::Result<()> { let created = parse_rfc3339("1999-09-10T21:59:22Z")?; let last_action = parse_rfc3339("1999-09-10T03:05:07.3845533+01:00")?; assert_eq!(created, datetime!(1999-09-10 21:59:22 UTC)); @@ -174,16 +180,39 @@ mod tests { } #[test] - fn test_to_rfc1123() -> crate::Result<()> { + fn test_to_rfc7231() -> crate::Result<()> { let dt = datetime!(1994-11-06 08:49:37 UTC); - assert_eq!("Sun, 06 Nov 1994 08:49:37 GMT", to_rfc1123(&dt)); + assert_eq!("Sun, 06 Nov 1994 08:49:37 GMT", to_rfc7231(&dt)); Ok(()) } #[test] - fn test_parse_rfc1123() -> crate::Result<()> { + fn test_parse_rfc7231() -> crate::Result<()> { let dt = datetime!(1994-11-06 08:49:37 UTC); - assert_eq!(parse_rfc1123("Sun, 06 Nov 1994 08:49:37 GMT")?, dt); + assert_eq!(parse_rfc7231("Sun, 06 Nov 1994 08:49:37 GMT")?, dt); + Ok(()) + } + + #[test] + fn parse_rfc7231_offset() { + assert!(parse_rfc7231("Sun, 06 Nov 1994 00:49:37 PST").is_err()); + } + + #[test] + fn roundtrip_rfc7231() -> crate::Result<()> { + let s = "Sat, 12 Oct 2019 07:20:50 GMT"; + let dt = parse_rfc7231(s)?; + assert_eq!(s, to_rfc7231(&dt)); + Ok(()) + } + + #[test] + #[ignore = "https://github.com/Azure/azure-sdk-for-rust/issues/1982"] + fn roundtrip_rfc7231_offset() -> crate::Result<()> { + let s = "Sat, 12 Oct 2019 07:20:50 PST"; + let dt = parse_rfc7231(s)?; + assert!(!dt.offset().is_utc()); + assert_eq!(s, to_rfc7231(&dt)); Ok(()) } @@ -197,17 +226,17 @@ mod tests { } #[test] - fn test_list_blob_creation_time() -> crate::Result<()> { + fn list_blob_creation_time() -> crate::Result<()> { let creation_time = "Thu, 01 Jul 2021 10:45:02 GMT"; assert_eq!( datetime!(2021-07-01 10:45:02 UTC), - parse_rfc1123(creation_time)? + parse_rfc7231(creation_time)? ); Ok(()) } #[test] - fn test_serde_rfc3339_none_optional() -> crate::Result<()> { + fn serde_rfc3339_none_optional() -> crate::Result<()> { let json_state = r#"{ "created_time": "2021-07-01T10:45:02Z" }"#; @@ -221,7 +250,7 @@ mod tests { } #[test] - fn test_serde_rfc3339_some_optional() -> crate::Result<()> { + fn serde_rfc3339_some_optional() -> crate::Result<()> { let json_state = r#"{ "created_time": "2021-07-01T10:45:02Z", "deleted_time": "2022-03-28T11:05:31Z" diff --git a/sdk/typespec/typespec_client_core/src/date/rfc1123.rs b/sdk/typespec/typespec_client_core/src/date/rfc7231.rs similarity index 64% rename from sdk/typespec/typespec_client_core/src/date/rfc1123.rs rename to sdk/typespec/typespec_client_core/src/date/rfc7231.rs index 0a8937e4de..9debda93a6 100644 --- a/sdk/typespec/typespec_client_core/src/date/rfc1123.rs +++ b/sdk/typespec/typespec_client_core/src/date/rfc7231.rs @@ -1,50 +1,50 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -//! RFC 1123 date and time parsing and formatting functions. -use crate::date::{parse_rfc1123, to_rfc1123}; +//! RFC 7231 date and time parsing and formatting functions. +use crate::date::{parse_rfc7231, to_rfc7231}; use serde::{de, Deserialize, Deserializer, Serializer}; use time::OffsetDateTime; -/// Deserialize an RFC 1123 date and time string into an [`OffsetDateTime`]. +/// Deserialize an RFC 7231 date and time string into an [`OffsetDateTime`]. pub fn deserialize<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, { let s = String::deserialize(deserializer)?; - parse_rfc1123(&s).map_err(de::Error::custom) + parse_rfc7231(&s).map_err(de::Error::custom) } -/// Serialize an [`OffsetDateTime`] to an RFC 1123 date and time string. +/// Serialize an [`OffsetDateTime`] to an RFC 7231 date and time string. pub fn serialize(date: &OffsetDateTime, serializer: S) -> Result where S: Serializer, { - serializer.serialize_str(&to_rfc1123(date)) + serializer.serialize_str(&to_rfc7231(date)) } pub mod option { - use crate::date::{parse_rfc1123, to_rfc1123}; + use crate::date::{parse_rfc7231, to_rfc7231}; use serde::{Deserialize, Deserializer, Serializer}; use time::OffsetDateTime; - /// Deserialize an RFC 1123 date and time string into an optional [`OffsetDateTime`]. + /// Deserialize an RFC 7231 date and time string into an optional [`OffsetDateTime`]. pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, { let s: Option = Option::deserialize(deserializer)?; - s.map(|s| parse_rfc1123(&s).map_err(serde::de::Error::custom)) + s.map(|s| parse_rfc7231(&s).map_err(serde::de::Error::custom)) .transpose() } - /// Serialize an optional [`OffsetDateTime`] to an RFC 1123 date and time string. + /// Serialize an optional [`OffsetDateTime`] to an RFC 7231 date and time string. pub fn serialize(date: &Option, serializer: S) -> Result where S: Serializer, { if let Some(date) = date { - serializer.serialize_str(&to_rfc1123(date)) + serializer.serialize_str(&to_rfc7231(date)) } else { serializer.serialize_none() } diff --git a/sdk/typespec/typespec_client_core/src/http/policies/retry/mod.rs b/sdk/typespec/typespec_client_core/src/http/policies/retry/mod.rs index 9c86eb529f..5d909bc874 100644 --- a/sdk/typespec/typespec_client_core/src/http/policies/retry/mod.rs +++ b/sdk/typespec/typespec_client_core/src/http/policies/retry/mod.rs @@ -24,10 +24,10 @@ use std::{sync::Arc, time::Duration}; use tracing::{debug, trace}; use typespec::error::{Error, ErrorKind, ResultExt}; -/// Attempts to parse the supplied string as an HTTP date, of the form defined by RFC 1123 (e.g. `Fri, 01 Jan 2021 00:00:00 GMT`). +/// Attempts to parse the supplied string as an HTTP date, of the form defined by RFC 7231 (e.g. `Fri, 01 Jan 2021 00:00:00 GMT`). /// Returns `None` if the string is not a valid HTTP date. fn try_parse_retry_after_http_date(http_date: &str) -> Option { - crate::date::parse_rfc1123(http_date).ok() + crate::date::parse_rfc7231(http_date).ok() } /// A function that returns an `OffsetDateTime`. diff --git a/sdk/typespec/typespec_client_core/src/http/request/options/if_modified_since.rs b/sdk/typespec/typespec_client_core/src/http/request/options/if_modified_since.rs index 4068ecc48b..5eec8636b1 100644 --- a/sdk/typespec/typespec_client_core/src/http/request/options/if_modified_since.rs +++ b/sdk/typespec/typespec_client_core/src/http/request/options/if_modified_since.rs @@ -24,7 +24,7 @@ impl Header for IfModifiedSince { } fn value(&self) -> headers::HeaderValue { - date::to_rfc1123(&self.0).into() + date::to_rfc7231(&self.0).into() } } diff --git a/sdk/typespec/typespec_client_core/src/http/request/options/if_modified_since_condition.rs b/sdk/typespec/typespec_client_core/src/http/request/options/if_modified_since_condition.rs index 3c65f970dc..e6d5bfe697 100644 --- a/sdk/typespec/typespec_client_core/src/http/request/options/if_modified_since_condition.rs +++ b/sdk/typespec/typespec_client_core/src/http/request/options/if_modified_since_condition.rs @@ -26,7 +26,7 @@ impl Header for IfModifiedSinceCondition { fn value(&self) -> headers::HeaderValue { match self { IfModifiedSinceCondition::Modified(date) - | IfModifiedSinceCondition::Unmodified(date) => date::to_rfc1123(date), + | IfModifiedSinceCondition::Unmodified(date) => date::to_rfc7231(date), } .into() } diff --git a/sdk/typespec/typespec_client_core/src/parsing.rs b/sdk/typespec/typespec_client_core/src/parsing.rs index 6b917672bd..41077910f3 100644 --- a/sdk/typespec/typespec_client_core/src/parsing.rs +++ b/sdk/typespec/typespec_client_core/src/parsing.rs @@ -36,7 +36,7 @@ impl FromStringOptional for bool { impl FromStringOptional for date::OffsetDateTime { fn from_str_optional(s: &str) -> crate::Result { - date::parse_rfc1123(s).with_context(ErrorKind::DataConversion, || { + date::parse_rfc7231(s).with_context(ErrorKind::DataConversion, || { format!("error parsing date time '{s}'") }) }