Skip to content

Commit d93439e

Browse files
committed
Use timestamps with timezones
It generally appears to be recommended to use `timestamptz` (timestamp with time zone) instead of `timestamp` (timestamp without time zone), unless the saved timestamp explicitly should not have a timezone. Since we always treat these timestamps as UTC though, we should be using `timestamptz` instead. This commit accordingly also switches `NaiveDateTime` for `DateTime<Utc>` in most cases in our codebase. This change also allowed us to remove the `rfc3339` serde helper module, since we no longer need the `DateTime<Utc> -> NaiveDateTime` conversion performed by this module.
1 parent 3e6af81 commit d93439e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+349
-325
lines changed

crates/crates_io_database/src/models/action.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::models::{ApiToken, User, Version};
22
use crate::schema::*;
33
use bon::Builder;
4-
use chrono::NaiveDateTime;
4+
use chrono::{DateTime, Utc};
55
use crates_io_diesel_helpers::pg_enum;
66
use diesel::prelude::*;
77
use diesel_async::{AsyncPgConnection, RunQueryDsl};
@@ -49,7 +49,7 @@ pub struct VersionOwnerAction {
4949
pub user_id: i32,
5050
pub api_token_id: Option<i32>,
5151
pub action: VersionAction,
52-
pub time: NaiveDateTime,
52+
pub time: DateTime<Utc>,
5353
}
5454

5555
impl VersionOwnerAction {

crates/crates_io_database/src/models/category.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::models::Crate;
22
use crate::schema::*;
3-
use chrono::NaiveDateTime;
3+
use chrono::{DateTime, Utc};
44
use diesel::dsl;
55
use diesel::prelude::*;
66
use diesel_async::scoped_futures::ScopedFutureExt;
@@ -15,7 +15,7 @@ pub struct Category {
1515
pub slug: String,
1616
pub description: String,
1717
pub crates_cnt: i32,
18-
pub created_at: NaiveDateTime,
18+
pub created_at: DateTime<Utc>,
1919
}
2020

2121
type WithSlug<'a> = dsl::Eq<categories::slug, crates_io_diesel_helpers::lower<&'a str>>;

crates/crates_io_database/src/models/crate_owner_invitation.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use chrono::{DateTime, NaiveDateTime, Utc};
1+
use chrono::{DateTime, Utc};
22
use diesel::prelude::*;
33
use diesel_async::scoped_futures::ScopedFutureExt;
44
use diesel_async::{AsyncConnection, AsyncPgConnection, RunQueryDsl};
@@ -63,7 +63,7 @@ pub struct CrateOwnerInvitation {
6363
pub invited_user_id: i32,
6464
pub invited_by_user_id: i32,
6565
pub crate_id: i32,
66-
pub created_at: NaiveDateTime,
66+
pub created_at: DateTime<Utc>,
6767
#[diesel(deserialize_as = String)]
6868
pub token: SecretString,
6969
pub expires_at: DateTime<Utc>,

crates/crates_io_database/src/models/email.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use bon::Builder;
2-
use chrono::NaiveDateTime;
2+
use chrono::{DateTime, Utc};
33
use diesel::prelude::*;
44
use diesel_async::{AsyncPgConnection, RunQueryDsl};
55
use secrecy::SecretString;
@@ -16,7 +16,7 @@ pub struct Email {
1616
pub verified: bool,
1717
#[diesel(deserialize_as = String, serialize_as = String)]
1818
pub token: SecretString,
19-
pub token_generated_at: Option<NaiveDateTime>,
19+
pub token_generated_at: Option<DateTime<Utc>>,
2020
}
2121

2222
#[derive(Debug, Insertable, AsChangeset, Builder)]

crates/crates_io_database/src/models/keyword.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use chrono::NaiveDateTime;
1+
use chrono::{DateTime, Utc};
22
use diesel::prelude::*;
33
use diesel_async::scoped_futures::ScopedFutureExt;
44
use diesel_async::{AsyncConnection, AsyncPgConnection, RunQueryDsl};
@@ -12,7 +12,7 @@ pub struct Keyword {
1212
pub id: i32,
1313
pub keyword: String,
1414
pub crates_cnt: i32,
15-
pub created_at: NaiveDateTime,
15+
pub created_at: DateTime<Utc>,
1616
}
1717

1818
#[derive(Associations, Insertable, Identifiable, Debug, Clone, Copy)]

crates/crates_io_database/src/models/krate.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use crate::models::helpers::with_count::*;
22
use crate::models::version::TopVersions;
33
use crate::models::{CrateOwner, Owner, OwnerKind, ReverseDependency, User, Version};
44
use crate::schema::*;
5-
use chrono::NaiveDateTime;
5+
use chrono::{DateTime, Utc};
66
use crates_io_diesel_helpers::canon_crate_name;
77
use diesel::associations::Identifiable;
88
use diesel::dsl;
@@ -40,8 +40,8 @@ pub struct CrateName {
4040
pub struct Crate {
4141
pub id: i32,
4242
pub name: String,
43-
pub updated_at: NaiveDateTime,
44-
pub created_at: NaiveDateTime,
43+
pub updated_at: DateTime<Utc>,
44+
pub created_at: DateTime<Utc>,
4545
pub description: Option<String>,
4646
pub homepage: Option<String>,
4747
pub documentation: Option<String>,

crates/crates_io_database/src/models/token.rs

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
mod scopes;
22

33
use bon::Builder;
4-
use chrono::NaiveDateTime;
4+
use chrono::{DateTime, Utc};
55
use diesel::dsl::now;
66
use diesel::prelude::*;
7+
use diesel::sql_types::Timestamptz;
78
use diesel_async::scoped_futures::ScopedFutureExt;
89
use diesel_async::{AsyncConnection, AsyncPgConnection, RunQueryDsl};
910

1011
pub use self::scopes::{CrateScope, EndpointScope};
1112
use crate::models::User;
1213
use crate::schema::api_tokens;
13-
use crate::utils::rfc3339;
1414
use crate::utils::token::{HashedToken, PlainToken};
1515

1616
#[derive(Debug, Insertable, Builder)]
@@ -25,7 +25,7 @@ pub struct NewApiToken {
2525
pub crate_scopes: Option<Vec<CrateScope>>,
2626
/// A list of endpoint scopes or `None` for the `legacy` endpoint scope (see RFC #2947)
2727
pub endpoint_scopes: Option<Vec<EndpointScope>>,
28-
pub expired_at: Option<NaiveDateTime>,
28+
pub expired_at: Option<DateTime<Utc>>,
2929
}
3030

3131
impl NewApiToken {
@@ -46,18 +46,15 @@ pub struct ApiToken {
4646
#[serde(skip)]
4747
pub user_id: i32,
4848
pub name: String,
49-
#[serde(with = "rfc3339")]
50-
pub created_at: NaiveDateTime,
51-
#[serde(with = "rfc3339::option")]
52-
pub last_used_at: Option<NaiveDateTime>,
49+
pub created_at: DateTime<Utc>,
50+
pub last_used_at: Option<DateTime<Utc>>,
5351
#[serde(skip)]
5452
pub revoked: bool,
5553
/// `None` or a list of crate scope patterns (see RFC #2947)
5654
pub crate_scopes: Option<Vec<CrateScope>>,
5755
/// A list of endpoint scopes or `None` for the `legacy` endpoint scope (see RFC #2947)
5856
pub endpoint_scopes: Option<Vec<EndpointScope>>,
59-
#[serde(with = "rfc3339::option")]
60-
pub expired_at: Option<NaiveDateTime>,
57+
pub expired_at: Option<DateTime<Utc>>,
6158
}
6259

6360
impl ApiToken {
@@ -80,7 +77,7 @@ impl ApiToken {
8077
.transaction(|conn| {
8178
async move {
8279
diesel::update(tokens)
83-
.set(api_tokens::last_used_at.eq(now.nullable()))
80+
.set(api_tokens::last_used_at.eq(now.into_sql::<Timestamptz>().nullable()))
8481
.returning(ApiToken::as_returning())
8582
.get_result(conn)
8683
.await
@@ -111,20 +108,23 @@ mod tests {
111108
created_at: NaiveDate::from_ymd_opt(2017, 1, 6)
112109
.unwrap()
113110
.and_hms_opt(14, 23, 11)
114-
.unwrap(),
115-
last_used_at: NaiveDate::from_ymd_opt(2017, 1, 6)
116111
.unwrap()
117-
.and_hms_opt(14, 23, 12),
112+
.and_utc(),
113+
last_used_at: Some(
114+
NaiveDate::from_ymd_opt(2017, 1, 6)
115+
.unwrap()
116+
.and_hms_opt(14, 23, 12)
117+
.unwrap()
118+
.and_utc(),
119+
),
118120
crate_scopes: None,
119121
endpoint_scopes: None,
120122
expired_at: None,
121123
};
122124
let json = serde_json::to_string(&tok).unwrap();
125+
assert_some!(json.as_str().find(r#""created_at":"2017-01-06T14:23:11Z""#));
123126
assert_some!(json
124127
.as_str()
125-
.find(r#""created_at":"2017-01-06T14:23:11+00:00""#));
126-
assert_some!(json
127-
.as_str()
128-
.find(r#""last_used_at":"2017-01-06T14:23:12+00:00""#));
128+
.find(r#""last_used_at":"2017-01-06T14:23:12Z""#));
129129
}
130130
}

crates/crates_io_database/src/models/user.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use bon::Builder;
2-
use chrono::NaiveDateTime;
2+
use chrono::{DateTime, Utc};
33
use diesel::dsl::sql;
44
use diesel::prelude::*;
55
use diesel::sql_types::Integer;
@@ -22,7 +22,7 @@ pub struct User {
2222
pub gh_avatar: Option<String>,
2323
pub gh_id: i32,
2424
pub account_lock_reason: Option<String>,
25-
pub account_lock_until: Option<NaiveDateTime>,
25+
pub account_lock_until: Option<DateTime<Utc>>,
2626
pub is_admin: bool,
2727
pub publish_notifications: bool,
2828
}

crates/crates_io_database/src/models/version.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::collections::BTreeMap;
22

33
use bon::Builder;
4-
use chrono::NaiveDateTime;
4+
use chrono::{DateTime, Utc};
55
use crates_io_index::features::FeaturesMap;
66
use diesel::prelude::*;
77
use diesel_async::scoped_futures::ScopedFutureExt;
@@ -18,8 +18,8 @@ pub struct Version {
1818
pub id: i32,
1919
pub crate_id: i32,
2020
pub num: String,
21-
pub updated_at: NaiveDateTime,
22-
pub created_at: NaiveDateTime,
21+
pub updated_at: DateTime<Utc>,
22+
pub created_at: DateTime<Utc>,
2323
pub downloads: i32,
2424
pub features: serde_json::Value,
2525
pub yanked: bool,
@@ -84,7 +84,7 @@ pub struct NewVersion<'a> {
8484
num: &'a str,
8585
#[builder(default = strip_build_metadata(num))]
8686
pub num_no_build: &'a str,
87-
created_at: Option<&'a NaiveDateTime>,
87+
created_at: Option<&'a DateTime<Utc>>,
8888
yanked: Option<bool>,
8989
#[builder(default = serde_json::Value::Object(Default::default()))]
9090
features: serde_json::Value,
@@ -170,10 +170,10 @@ impl TopVersions {
170170
/// highest version (in semver order) for a collection of date/version pairs.
171171
pub fn from_date_version_pairs<T>(pairs: T) -> Self
172172
where
173-
T: IntoIterator<Item = (NaiveDateTime, String)>,
173+
T: IntoIterator<Item = (DateTime<Utc>, String)>,
174174
{
175175
// filter out versions that we can't parse
176-
let pairs: Vec<(NaiveDateTime, semver::Version)> = pairs
176+
let pairs: Vec<(DateTime<Utc>, semver::Version)> = pairs
177177
.into_iter()
178178
.filter_map(|(date, version)| {
179179
semver::Version::parse(&version)
@@ -201,12 +201,12 @@ impl TopVersions {
201201

202202
#[cfg(test)]
203203
mod tests {
204-
use super::TopVersions;
204+
use super::*;
205205
use chrono::NaiveDateTime;
206206

207207
#[track_caller]
208-
fn date(str: &str) -> NaiveDateTime {
209-
str.parse().unwrap()
208+
fn date(str: &str) -> DateTime<Utc> {
209+
str.parse::<NaiveDateTime>().unwrap().and_utc()
210210
}
211211

212212
#[track_caller]

crates/crates_io_database/src/schema.patch

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,11 @@
2222
+ endpoint_scopes -> Nullable<Array<Text>>,
2323
/// The `expired_at` column of the `api_tokens` table.
2424
///
25-
/// Its SQL type is `Nullable<Timestamp>`.
25+
/// Its SQL type is `Nullable<Timestamptz>`.
2626
@@ -180,12 +178,6 @@
2727
///
2828
/// (Automatically generated by Diesel.)
29-
created_at -> Timestamp,
29+
created_at -> Timestamptz,
3030
- /// The `path` column of the `categories` table.
3131
- ///
3232
- /// Its SQL type is `Ltree`.

0 commit comments

Comments
 (0)