diff --git a/Cargo.toml b/Cargo.toml index f3457e4..bcfdfbc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ include = ["/src", "README.md", "LICENSE", "!snapshots"] members = [".", "codegen", "cli"] [features] -default = ["default-tls"] +default = ["default-tls", "rss"] all = ["rss", "html"] rss = ["quick-xml"] @@ -38,8 +38,7 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0.82" serde_with = {version = "2.0.0", features = ["json"] } rand = "0.8.5" -chrono = {version = "0.4.19", default-features = false, features = ["clock", "serde"]} -chronoutil = "0.2.3" +time = {version = "0.3.15", features = ["macros", "serde", "serde-well-known"]} futures = "0.3.21" indicatif = "0.17.0" filenamify = "0.1.0" diff --git a/src/client/channel.rs b/src/client/channel.rs index 014b369..6b90a7f 100644 --- a/src/client/channel.rs +++ b/src/client/channel.rs @@ -1,6 +1,7 @@ use std::borrow::Cow; use serde::Serialize; +use time::OffsetDateTime; use url::Url; use crate::{ @@ -191,7 +192,8 @@ impl MapResponse> for response::Channel { lang, &meta.joined_date_text, &mut warnings, - ), + ) + .map(OffsetDateTime::date), view_count: meta .view_count_text .and_then(|txt| util::parse_numeric_or_warn(&txt, &mut warnings)), diff --git a/src/client/mod.rs b/src/client/mod.rs index b8a9276..d5f9f3e 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -18,13 +18,13 @@ mod channel_rss; use std::sync::Arc; use std::{borrow::Cow, fmt::Debug}; -use chrono::{DateTime, Duration, Utc}; use fancy_regex::Regex; use log::{debug, error, warn}; use once_cell::sync::Lazy; use rand::Rng; use reqwest::{header, Client, ClientBuilder, Request, RequestBuilder, Response}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use time::{Duration, OffsetDateTime}; use tokio::sync::RwLock; use crate::{ @@ -240,7 +240,8 @@ enum CacheEntry { #[default] None, Some { - last_update: DateTime, + #[serde(with = "time::serde::rfc3339")] + last_update: OffsetDateTime, data: T, }, } @@ -254,7 +255,7 @@ impl CacheEntry { fn get(&self) -> Option<&T> { match self { CacheEntry::Some { last_update, data } => { - if last_update < &(Utc::now() - Duration::hours(24)) { + if last_update < &(OffsetDateTime::now_utc() - Duration::hours(24)) { None } else { Some(data) @@ -268,7 +269,7 @@ impl CacheEntry { impl From for CacheEntry { fn from(f: T) -> Self { Self::Some { - last_update: Utc::now(), + last_update: OffsetDateTime::now_utc(), data: f, } } diff --git a/src/client/playlist.rs b/src/client/playlist.rs index 03ee9a7..73180f6 100644 --- a/src/client/playlist.rs +++ b/src/client/playlist.rs @@ -1,5 +1,7 @@ use std::{borrow::Cow, convert::TryFrom}; +use time::OffsetDateTime; + use crate::{ deobfuscate::Deobfuscator, error::{Error, ExtractionError}, @@ -152,9 +154,9 @@ impl MapResponse for response::Playlist { .and_then(|link| ChannelId::try_from(link).ok()); let mut warnings = video_items.warnings; - let last_update = last_update_txt - .as_ref() - .and_then(|txt| timeago::parse_textual_date_or_warn(lang, txt, &mut warnings)); + let last_update = last_update_txt.as_ref().and_then(|txt| { + timeago::parse_textual_date_or_warn(lang, txt, &mut warnings).map(OffsetDateTime::date) + }); Ok(MapResult { c: Playlist { diff --git a/src/client/response/channel_rss.rs b/src/client/response/channel_rss.rs index 456aec4..4e24fd8 100644 --- a/src/client/response/channel_rss.rs +++ b/src/client/response/channel_rss.rs @@ -1,5 +1,5 @@ -use chrono::{DateTime, Utc}; use serde::Deserialize; +use time::OffsetDateTime; use super::Thumbnail; @@ -9,8 +9,8 @@ pub(crate) struct ChannelRss { pub channel_id: String, #[serde(rename = "$unflatten=title")] pub title: String, - #[serde(rename = "$unflatten=published")] - pub create_date: DateTime, + #[serde(rename = "$unflatten=published", with = "time::serde::rfc3339")] + pub create_date: OffsetDateTime, pub entry: Vec, } @@ -20,10 +20,10 @@ pub(crate) struct Entry { pub video_id: String, #[serde(rename = "$unflatten=title")] pub title: String, - #[serde(rename = "$unflatten=published")] - pub published: DateTime, - #[serde(rename = "$unflatten=updated")] - pub updated: DateTime, + #[serde(rename = "$unflatten=published", with = "time::serde::rfc3339")] + pub published: OffsetDateTime, + #[serde(rename = "$unflatten=updated", with = "time::serde::rfc3339")] + pub updated: OffsetDateTime, #[serde(rename = "$unflatten=media:group")] pub media_group: MediaGroup, } diff --git a/src/client/response/video_item.rs b/src/client/response/video_item.rs index fd51b90..0228294 100644 --- a/src/client/response/video_item.rs +++ b/src/client/response/video_item.rs @@ -1,6 +1,6 @@ -use chrono::TimeZone; use serde::Deserialize; use serde_with::{json::JsonString, serde_as, DefaultOnError, VecSkipError}; +use time::OffsetDateTime; use super::{ChannelBadge, ContinuationEndpoint, Thumbnails}; use crate::{ @@ -342,12 +342,7 @@ impl YouTubeListMapper { publish_date: video .upcoming_event_data .as_ref() - .map(|upc| { - chrono::Local.from_utc_datetime(&chrono::NaiveDateTime::from_timestamp( - upc.start_time, - 0, - )) - }) + .and_then(|upc| OffsetDateTime::from_unix_timestamp(upc.start_time).ok()) .or_else(|| { video .published_time_text diff --git a/src/client/snapshots/rustypipe__client__channel__tests__map_channel_info.snap b/src/client/snapshots/rustypipe__client__channel__tests__map_channel_info.snap index 6d71013..1f6ba8d 100644 --- a/src/client/snapshots/rustypipe__client__channel__tests__map_channel_info.snap +++ b/src/client/snapshots/rustypipe__client__channel__tests__map_channel_info.snap @@ -143,7 +143,7 @@ Channel( ), ], content: ChannelInfo( - create_date: Some("2009-04-04T00:00:00+02:00"), + create_date: Some("2009-04-04"), view_count: Some(186854342), links: [ ("EEVblog Web Site", "http://www.eevblog.com/"), diff --git a/src/client/snapshots/rustypipe__client__channel__tests__map_channel_videos_upcoming.snap b/src/client/snapshots/rustypipe__client__channel__tests__map_channel_videos_upcoming.snap index 17d784b..67b9a35 100644 --- a/src/client/snapshots/rustypipe__client__channel__tests__map_channel_videos_upcoming.snap +++ b/src/client/snapshots/rustypipe__client__channel__tests__map_channel_videos_upcoming.snap @@ -160,7 +160,7 @@ Channel( ), ], channel: None, - publish_date: Some("2022-09-27T18:00:00+02:00"), + publish_date: Some("2022-09-27T16:00:00Z"), publish_date_txt: None, view_count: Some(237), is_live: false, diff --git a/src/model/mod.rs b/src/model/mod.rs index 074a80d..fb8bb4b 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -5,13 +5,14 @@ mod paginator; pub mod richtext; pub use paginator::Paginator; +use serde_with::serde_as; use std::ops::Range; -use chrono::{DateTime, Local, Utc}; use serde::{Deserialize, Serialize}; +use time::{Date, OffsetDateTime}; -use crate::{error::Error, util}; +use crate::{error::Error, serializer::DateYmd, util}; use self::richtext::RichText; @@ -437,6 +438,7 @@ pub struct Subtitle { */ /// YouTube playlist +#[serde_as] #[derive(Clone, Debug, Serialize, Deserialize)] #[non_exhaustive] pub struct Playlist { @@ -455,7 +457,8 @@ pub struct Playlist { /// Channel of the playlist pub channel: Option, /// Last update date - pub last_update: Option>, + #[serde_as(as = "Option")] + pub last_update: Option, /// Textual last update date pub last_update_txt: Option, } @@ -512,7 +515,8 @@ pub struct VideoDetails { /// Video publishing date. Start date in case of a livestream. /// /// [`None`] if the date could not be parsed. - pub publish_date: Option>, + #[serde(with = "time::serde::rfc3339::option")] + pub publish_date: Option, /// Textual video publishing date (e.g. `Aug 2, 2013`, depends on language) pub publish_date_txt: String, /// Is the video a livestream? @@ -616,7 +620,8 @@ pub struct Comment { /// Comment publishing date. /// /// [`None`] if the date could not be parsed. - pub publish_date: Option>, + #[serde(with = "time::serde::rfc3339::option")] + pub publish_date: Option, /// Textual comment publish date (e.g. `14 hours ago`), depends on language setting pub publish_date_txt: String, /// Number of comment likes @@ -675,11 +680,13 @@ pub struct Channel { } /// Additional channel metadata fetched from the "About" tab. +#[serde_as] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[non_exhaustive] pub struct ChannelInfo { /// Channel creation date - pub create_date: Option>, + #[serde_as(as = "Option")] + pub create_date: Option, /// Channel view count pub view_count: Option, /// Links to other websites or social media profiles @@ -697,7 +704,8 @@ pub struct ChannelRss { /// List of the latest channel videos pub videos: Vec, /// Channel creation date (second-accurate). - pub create_date: DateTime, + #[serde(with = "time::serde::rfc3339")] + pub create_date: OffsetDateTime, } /// YouTube video fetched from a channel's RSS feed @@ -713,9 +721,11 @@ pub struct ChannelRssVideo { /// Video thumbnail pub thumbnail: Thumbnail, /// Video publishing date (second-accurate). - pub publish_date: DateTime, + #[serde(with = "time::serde::rfc3339")] + pub publish_date: OffsetDateTime, /// Date and time when the RSS feed entry was last updated. - pub update_date: DateTime, + #[serde(with = "time::serde::rfc3339")] + pub update_date: OffsetDateTime, /// Number of views / current viewers in case of a livestream. pub view_count: u64, /// Number of likes @@ -767,7 +777,8 @@ pub struct VideoItem { /// Video publishing date. /// /// [`None`] if the date could not be parsed. - pub publish_date: Option>, + #[serde(with = "time::serde::rfc3339::option")] + pub publish_date: Option, /// Textual video publish date (e.g. `11 months ago`, depends on language) /// /// Is [`None`] for livestreams. diff --git a/src/report.rs b/src/report.rs index a7ab00a..979d13b 100644 --- a/src/report.rs +++ b/src/report.rs @@ -6,13 +6,17 @@ use std::{ path::{Path, PathBuf}, }; -use chrono::{DateTime, Local}; use log::error; use serde::{Deserialize, Serialize}; +use time::macros::format_description; +use time::OffsetDateTime; use crate::deobfuscate::DeobfData; use crate::error::Error; +const FILENAME_FORMAT: &[time::format_description::FormatItem] = + format_description!("[year]-[month]-[day]_[hour]-[minute]-[second]"); + #[derive(Debug, Clone, Serialize, Deserialize)] #[non_exhaustive] pub struct Report { @@ -40,7 +44,8 @@ pub struct Info { /// Package version (`0.1.0`) pub version: String, /// Date/Time when the event occurred - pub date: DateTime, + #[serde(with = "time::serde::rfc3339")] + pub date: OffsetDateTime, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -76,7 +81,7 @@ impl Default for Info { Self { package: "rustypipe".to_owned(), version: "0.1.0".to_owned(), - date: chrono::Local::now(), + date: OffsetDateTime::now_utc(), } } } @@ -126,7 +131,7 @@ fn get_report_path(root: &Path, report: &Report, ext: &str) -> Result for DateYmd { + fn serialize_as(date: &Date, serializer: S) -> Result + where + S: serde::Serializer, + { + date.format(YMD_FORMAT) + .map_err(ser::Error::custom)? + .serialize(serializer) + } +} + +impl<'de> DeserializeAs<'de, Date> for DateYmd { + fn deserialize_as(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + struct DateYmdVisitor; + + impl<'de> Visitor<'de> for DateYmdVisitor { + type Value = Date; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a YYYY-MM-DD formatted date") + } + + fn visit_str(self, v: &str) -> Result + where + E: serde::de::Error, + { + Date::parse(v, YMD_FORMAT).map_err(|_| { + de::Error::invalid_value(de::Unexpected::Str(v), &"a YYYY-MM-DD formatted date") + }) + } + } + + deserializer.deserialize_str(DateYmdVisitor) + } +} diff --git a/src/serializer/mod.rs b/src/serializer/mod.rs index 719314e..6e86ae5 100644 --- a/src/serializer/mod.rs +++ b/src/serializer/mod.rs @@ -1,8 +1,10 @@ pub mod text; +mod date; mod range; mod vec_log_err; +pub use date::DateYmd; pub use range::Range; pub use vec_log_err::VecLogError; diff --git a/src/timeago.rs b/src/timeago.rs index fbd648e..8ae4bec 100644 --- a/src/timeago.rs +++ b/src/timeago.rs @@ -17,8 +17,8 @@ use std::ops::Mul; -use chrono::{DateTime, Duration, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone}; use serde::{Deserialize, Serialize}; +use time::{Date, Duration, OffsetDateTime}; use crate::{ param::Language, @@ -42,7 +42,7 @@ pub struct TimeAgo { /// - "2 months ago" => `ParsedDate::Relative(TimeAgo {n: 2, unit: TimeUnit::Month})` #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum ParsedDate { - Absolute(NaiveDate), + Absolute(Date), Relative(TimeAgo), } @@ -82,27 +82,25 @@ impl Mul for TimeAgo { } } -impl From for DateTime { +impl From for OffsetDateTime { fn from(ta: TimeAgo) -> Self { - let ts = Local::now(); + let ts = OffsetDateTime::now_utc(); match ta.unit { TimeUnit::Second => ts - Duration::seconds(ta.n as i64), TimeUnit::Minute => ts - Duration::minutes(ta.n as i64), TimeUnit::Hour => ts - Duration::hours(ta.n as i64), TimeUnit::Day => ts - Duration::days(ta.n as i64), TimeUnit::Week => ts - Duration::weeks(ta.n as i64), - TimeUnit::Month => chronoutil::shift_months(ts, -(ta.n as i32)), - TimeUnit::Year => chronoutil::shift_years(ts, -(ta.n as i32)), + TimeUnit::Month => ts.replace_date(util::shift_months(ts.date(), -(ta.n as i32))), + TimeUnit::Year => ts.replace_date(util::shift_years(ts.date(), -(ta.n as i32))), } } } -impl From for DateTime { +impl From for OffsetDateTime { fn from(date: ParsedDate) -> Self { match date { - ParsedDate::Absolute(date) => Local - .from_local_datetime(&NaiveDateTime::new(date, NaiveTime::from_hms(0, 0, 0))) - .unwrap(), + ParsedDate::Absolute(date) => date.with_hms(0, 0, 0).unwrap().assume_utc(), ParsedDate::Relative(timeago) => timeago.into(), } } @@ -180,7 +178,7 @@ pub fn parse_timeago(lang: Language, textual_date: &str) -> Option { /// Parse a TimeAgo string (e.g. "29 minutes ago") into a Chrono DateTime object. /// /// Returns None if the date could not be parsed. -pub fn parse_timeago_to_dt(lang: Language, textual_date: &str) -> Option> { +pub fn parse_timeago_to_dt(lang: Language, textual_date: &str) -> Option { parse_timeago(lang, textual_date).map(|ta| ta.into()) } @@ -219,10 +217,9 @@ pub fn parse_textual_date(lang: Language, textual_date: &str) -> Option { - NaiveDate::from_ymd_opt(y.into(), m.into(), d.into()) - .map(ParsedDate::Absolute) - } + (Some(y), Some(m), Some(d)) => util::month_from_n(m as u8) + .and_then(|m| Date::from_calendar_date(y.into(), m, d as u8).ok()) + .map(ParsedDate::Absolute), _ => None, } } else { @@ -236,7 +233,7 @@ pub fn parse_textual_date(lang: Language, textual_date: &str) -> Option Option> { +pub fn parse_textual_date_to_dt(lang: Language, textual_date: &str) -> Option { parse_textual_date(lang, textual_date).map(|ta| ta.into()) } @@ -244,7 +241,7 @@ pub(crate) fn parse_textual_date_or_warn( lang: Language, textual_date: &str, warnings: &mut Vec, -) -> Option> { +) -> Option { let res = parse_textual_date_to_dt(lang, textual_date); if res.is_none() { warnings.push(format!("could not parse timeago `{}`", textual_date)); @@ -256,8 +253,8 @@ pub(crate) fn parse_textual_date_or_warn( mod tests { use std::{collections::BTreeMap, fs::File, io::BufReader, path::Path}; - use chrono::Datelike; use rstest::rstest; + use time::macros::{date, datetime}; use super::*; @@ -494,7 +491,7 @@ mod tests { #[case( Language::En, "Last updated on Jun 04, 2003", - Some(ParsedDate::Absolute(NaiveDate::from_ymd(2003, 6, 4))) + Some(ParsedDate::Absolute(date!(2003-6-4))) )] fn t_parse_date( #[case] lang: Language, @@ -546,73 +543,73 @@ mod tests { ); assert_eq!( parse_textual_date(*lang, samples.get("Jan").unwrap()), - Some(ParsedDate::Absolute(NaiveDate::from_ymd(2020, 1, 3))), + Some(ParsedDate::Absolute(date!(2020 - 1 - 3))), "lang: {}", lang ); assert_eq!( parse_textual_date(*lang, samples.get("Feb").unwrap()), - Some(ParsedDate::Absolute(NaiveDate::from_ymd(2016, 2, 7))), + Some(ParsedDate::Absolute(date!(2016 - 2 - 7))), "lang: {}", lang ); assert_eq!( parse_textual_date(*lang, samples.get("Mar").unwrap()), - Some(ParsedDate::Absolute(NaiveDate::from_ymd(2015, 3, 9))), + Some(ParsedDate::Absolute(date!(2015 - 3 - 9))), "lang: {}", lang ); assert_eq!( parse_textual_date(*lang, samples.get("Apr").unwrap()), - Some(ParsedDate::Absolute(NaiveDate::from_ymd(2017, 4, 2))), + Some(ParsedDate::Absolute(date!(2017 - 4 - 2))), "lang: {}", lang ); assert_eq!( parse_textual_date(*lang, samples.get("May").unwrap()), - Some(ParsedDate::Absolute(NaiveDate::from_ymd(2014, 5, 22))), + Some(ParsedDate::Absolute(date!(2014 - 5 - 22))), "lang: {}", lang ); assert_eq!( parse_textual_date(*lang, samples.get("Jun").unwrap()), - Some(ParsedDate::Absolute(NaiveDate::from_ymd(2014, 6, 28))), + Some(ParsedDate::Absolute(date!(2014 - 6 - 28))), "lang: {}", lang ); assert_eq!( parse_textual_date(*lang, samples.get("Jul").unwrap()), - Some(ParsedDate::Absolute(NaiveDate::from_ymd(2014, 7, 2))), + Some(ParsedDate::Absolute(date!(2014 - 7 - 2))), "lang: {}", lang ); assert_eq!( parse_textual_date(*lang, samples.get("Aug").unwrap()), - Some(ParsedDate::Absolute(NaiveDate::from_ymd(2015, 8, 23))), + Some(ParsedDate::Absolute(date!(2015 - 8 - 23))), "lang: {}", lang ); assert_eq!( parse_textual_date(*lang, samples.get("Sep").unwrap()), - Some(ParsedDate::Absolute(NaiveDate::from_ymd(2018, 9, 16))), + Some(ParsedDate::Absolute(date!(2018 - 9 - 16))), "lang: {}", lang ); assert_eq!( parse_textual_date(*lang, samples.get("Oct").unwrap()), - Some(ParsedDate::Absolute(NaiveDate::from_ymd(2014, 10, 31))), + Some(ParsedDate::Absolute(date!(2014 - 10 - 31))), "lang: {}", lang ); assert_eq!( parse_textual_date(*lang, samples.get("Nov").unwrap()), - Some(ParsedDate::Absolute(NaiveDate::from_ymd(2016, 11, 3))), + Some(ParsedDate::Absolute(date!(2016 - 11 - 3))), "lang: {}", lang ); assert_eq!( parse_textual_date(*lang, samples.get("Dec").unwrap()), - Some(ParsedDate::Absolute(NaiveDate::from_ymd(2021, 12, 24))), + Some(ParsedDate::Absolute(date!(2021 - 12 - 24))), "lang: {}", lang ); @@ -623,19 +620,11 @@ mod tests { fn t_to_datetime() { // Absolute date let date = parse_textual_date_to_dt(Language::En, "Last updated on Jan 3, 2020").unwrap(); - assert_eq!( - date, - Local - .from_local_datetime(&NaiveDateTime::new( - NaiveDate::from_ymd(2020, 1, 3), - NaiveTime::from_hms(0, 0, 0) - )) - .unwrap() - ); + assert_eq!(date, datetime!(2020-1-3 0:00 +0)); // Relative date let date = parse_textual_date_to_dt(Language::En, "1 year ago").unwrap(); - let now = Local::now(); + let now = OffsetDateTime::now_utc(); assert_eq!(date.year(), now.year() - 1); } } diff --git a/src/util/date.rs b/src/util/date.rs new file mode 100644 index 0000000..a161565 --- /dev/null +++ b/src/util/date.rs @@ -0,0 +1,44 @@ +use time::{Date, Month}; + +pub const fn month_from_n(n: u8) -> Option { + match n { + 1 => Some(Month::January), + 2 => Some(Month::February), + 3 => Some(Month::March), + 4 => Some(Month::April), + 5 => Some(Month::May), + 6 => Some(Month::June), + 7 => Some(Month::July), + 8 => Some(Month::August), + 9 => Some(Month::September), + 10 => Some(Month::October), + 11 => Some(Month::November), + 12 => Some(Month::December), + _ => None, + } +} + +/// Shift a date by the given number of months. +/// Ambiguous month-ends are shifted backwards as necessary. +pub fn shift_months(date: Date, months: i32) -> Date { + let mut year = date.year() + (date.month() as i32 + months) / 12; + let mut month = (date.month() as i32 + months) % 12; + let mut day = date.day(); + + if month < 1 { + year -= 1; + month += 12; + } + + let month = month_from_n(month as u8).unwrap(); + let month_days = time::util::days_in_year_month(year, month); + + day = day.min(month_days); + Date::from_calendar_date(year, month, day).unwrap() +} + +/// Shift a date by the given number of years. +/// Ambiguous month-ends are shifted backwards as necessary. +pub fn shift_years(date: Date, years: i32) -> Date { + shift_months(date, years * 12) +} diff --git a/src/util/mod.rs b/src/util/mod.rs index 82295e7..9c6ab8f 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -1,7 +1,9 @@ +mod date; mod protobuf; pub mod dictionary; +pub use date::{month_from_n, shift_months, shift_years}; pub use protobuf::ProtoBuilder; use std::{ diff --git a/tests/youtube.rs b/tests/youtube.rs index 232ab3d..b2c31ba 100644 --- a/tests/youtube.rs +++ b/tests/youtube.rs @@ -1,7 +1,8 @@ use std::collections::HashSet; -use chrono::Datelike; use rstest::rstest; +use time::macros::{date, datetime}; +use time::OffsetDateTime; use rustypipe::client::{ClientType, RustyPipe}; use rustypipe::error::{Error, ExtractionError}; @@ -417,9 +418,7 @@ async fn get_video_details() { ); let date = details.publish_date.unwrap(); - assert_eq!(date.year(), 2020); - assert_eq!(date.month(), 11); - assert_eq!(date.day(), 17); + assert_eq!(date.date(), date!(2020 - 11 - 17)); assert!(!details.is_live); assert!(!details.is_ccommons); @@ -470,9 +469,7 @@ async fn get_video_details_music() { ); let date = details.publish_date.unwrap(); - assert_eq!(date.year(), 2020); - assert_eq!(date.month(), 8); - assert_eq!(date.day(), 6); + assert_eq!(date.date(), date!(2020 - 8 - 6)); assert!(!details.is_live); assert!(!details.is_ccommons); @@ -528,9 +525,7 @@ async fn get_video_details_ccommons() { ); let date = details.publish_date.unwrap(); - assert_eq!(date.year(), 2019); - assert_eq!(date.month(), 12); - assert_eq!(date.day(), 29); + assert_eq!(date.date(), date!(2019 - 12 - 29)); assert!(!details.is_live); assert!(details.is_ccommons); @@ -585,9 +580,7 @@ async fn get_video_details_chapters() { ); let date = details.publish_date.unwrap(); - assert_eq!(date.year(), 2022); - assert_eq!(date.month(), 9); - assert_eq!(date.day(), 15); + assert_eq!(date.date(), date!(2022 - 9 - 15)); assert!(!details.is_live); assert!(!details.is_ccommons); @@ -728,9 +721,7 @@ async fn get_video_details_live() { ); let date = details.publish_date.unwrap(); - assert_eq!(date.year(), 2021); - assert_eq!(date.month(), 9); - assert_eq!(date.day(), 23); + assert_eq!(date.date(), date!(2021 - 9 - 23)); assert!(details.is_live); assert!(!details.is_ccommons); @@ -777,9 +768,7 @@ async fn get_video_details_agegate() { assert!(details.like_count.is_none(), "like count not hidden"); let date = details.publish_date.unwrap(); - assert_eq!(date.year(), 2019); - assert_eq!(date.month(), 1); - assert_eq!(date.day(), 2); + assert_eq!(date.date(), date!(2019 - 1 - 2)); assert!(!details.is_live); assert!(!details.is_ccommons); @@ -885,7 +874,7 @@ async fn channel_videos(#[case] order: ChannelOrder) { let first_video = &channel.content.items[0]; let first_video_date = first_video.publish_date.unwrap(); - let age_days = (chrono::Local::now() - first_video_date).num_days(); + let age_days = (OffsetDateTime::now_utc() - first_video_date).whole_days(); match order { ChannelOrder::Latest => { @@ -946,9 +935,7 @@ async fn channel_info() { assert_channel_eevblog(&channel); let created = channel.content.create_date.unwrap(); - assert_eq!(created.year(), 2009); - assert_eq!(created.month(), 4); - assert_eq!(created.day(), 4); + assert_eq!(created, date!(2009 - 4 - 4)); assert!( channel.content.view_count.unwrap() > 186854340, @@ -1072,8 +1059,6 @@ async fn channel_not_found(#[case] id: &str) { mod channel_rss { use super::*; - use chrono::Timelike; - #[tokio::test] async fn get_channel_rss() { let rp = RustyPipe::builder().strict().build(); @@ -1085,11 +1070,7 @@ mod channel_rss { assert_eq!(channel.id, "UCHnyfMqiRRG1u-2MsSQLbXA"); assert_eq!(channel.name, "Veritasium"); - assert_eq!(channel.create_date.year(), 2010); - assert_eq!(channel.create_date.month(), 7); - assert_eq!(channel.create_date.day(), 21); - assert_eq!(channel.create_date.hour(), 7); - assert_eq!(channel.create_date.minute(), 18); + assert_eq!(channel.create_date, datetime!(2010-07-21 7:18:02 +0)); assert!(!channel.videos.is_empty()); }