Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Do not convert dates #1986

Merged
merged 3 commits into from
Jan 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
3 changes: 1 addition & 2 deletions sdk/typespec/typespec_client_core/src/date/iso8601.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use time::{
iso8601::{Config, EncodedConfig, TimePrecision},
Iso8601,
},
OffsetDateTime, UtcOffset,
OffsetDateTime,
};
use typespec::error::{ErrorKind, ResultExt};

Expand Down Expand Up @@ -49,7 +49,6 @@ pub fn serialize<S>(date: &OffsetDateTime, serializer: S) -> Result<S::Ok, S::Er
where
S: Serializer,
{
date.to_offset(UtcOffset::UTC);
let as_str = to_iso8601(date).map_err(serde::ser::Error::custom)?;
serializer.serialize_str(&as_str)
}
Expand Down
121 changes: 75 additions & 46 deletions sdk/typespec/typespec_client_core/src/date/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,31 @@

//! Date and time parsing and formatting functions.

// RFC 3339 vs ISO 8601
// <https://ijmacd.github.io/rfc3339-iso8601/>

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: <https://ijmacd.github.io/rfc3339-iso8601/>
pub mod iso8601;
pub mod rfc1123;
pub mod rfc7231;

/// RFC 3339: Date and Time on the Internet: Timestamps.
///
/// <https://www.rfc-editor.org/rfc/rfc3339>
///
/// 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> {
OffsetDateTime::parse(s, &Rfc3339).with_context(ErrorKind::DataConversion, || {
format!("unable to parse rfc3339 date '{s}")
Expand All @@ -38,59 +38,57 @@ pub fn parse_rfc3339(s: &str) -> crate::Result<OffsetDateTime> {
///
/// <https://www.rfc-editor.org/rfc/rfc3339>
///
/// 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.
///
/// <https://www.rfc-editor.org/rfc/rfc1123>
/// RFC 7231: Requirements for Internet Hosts - Application and Support.
///
/// In REST API specifications it is specified as `"format": "date-time-rfc1123"`.
/// <https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.1.1>
///
/// In .NET it is the `rfc1123pattern`.
/// <https://learn.microsoft.com/dotnet/api/system.globalization.datetimeformatinfo.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.
/// <https://httpwg.org/specs/rfc9110.html#http.date>
/// This format is also the preferred HTTP date-based header format.
/// * <https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.1.2>
/// * <https://datatracker.ietf.org/doc/html/rfc7232>
///
/// Sun, 06 Nov 1994 08:49:37 GMT
pub fn parse_rfc1123(s: &str) -> crate::Result<OffsetDateTime> {
Ok(PrimitiveDateTime::parse(s, RFC1123_FORMAT)
/// Example string: `Sun, 06 Nov 1994 08:49:37 GMT`.
pub fn parse_rfc7231(s: &str) -> crate::Result<OffsetDateTime> {
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.
///
/// <https://www.rfc-editor.org/rfc/rfc1123>
/// <https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.1.1>
///
/// 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`.
/// <https://learn.microsoft.com/dotnet/api/system.globalization.datetimeformatinfo.rfc1123pattern>
/// This format is also the preferred HTTP date-based header format.
/// * <https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.1.2>
/// * <https://datatracker.ietf.org/doc/html/rfc7232>
///
/// This format is also the preferred HTTP date format.
/// <https://httpwg.org/specs/rfc9110.html#http.date>
///
/// 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.
///
/// <https://learn.microsoft.com/rest/api/cosmos-db/patch-a-document>
///
Expand All @@ -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()
}
Expand Down Expand Up @@ -157,15 +154,24 @@ 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));
Ok(())
}

#[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));
Expand All @@ -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(())
}

Expand All @@ -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"
}"#;
Expand All @@ -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"
Expand Down
Original file line number Diff line number Diff line change
@@ -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<OffsetDateTime, D::Error>
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<S>(date: &OffsetDateTime, serializer: S) -> Result<S::Ok, S::Error>
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<Option<OffsetDateTime>, D::Error>
where
D: Deserializer<'de>,
{
let s: Option<String> = 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<S>(date: &Option<OffsetDateTime>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
if let Some(date) = date {
serializer.serialize_str(&to_rfc1123(date))
serializer.serialize_str(&to_rfc7231(date))
} else {
serializer.serialize_none()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<OffsetDateTime> {
crate::date::parse_rfc1123(http_date).ok()
crate::date::parse_rfc7231(http_date).ok()
}

/// A function that returns an `OffsetDateTime`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Expand Down
Loading