feat: parse userPlayDate as NaiveDateTime directly

This commit is contained in:
beerpiss 2024-06-27 10:37:37 +07:00
parent ccb747cd33
commit fc04a5ee01
4 changed files with 57 additions and 13 deletions

View File

@ -1,3 +1,5 @@
Write-Output "Generating release for git SHA $(git rev-parse HEAD)"
cargo build --target i686-pc-windows-msvc --release cargo build --target i686-pc-windows-msvc --release
if (!(Test-Path ./saekawa.pfx)) { if (!(Test-Path ./saekawa.pfx)) {

View File

@ -29,3 +29,43 @@ where
BooleanishTypes::Number(n) => Ok(n > 0), BooleanishTypes::Number(n) => Ok(n > 0),
} }
} }
mod serde_user_play_date {
use chrono::NaiveDateTime;
use serde::{ser, de};
const DT_FORMAT: &str = "%Y-%m-%d %H:%M:%S";
#[derive(Debug)]
struct UserPlayDateVisitor;
pub fn serialize<S>(dt: &NaiveDateTime, serializer: S) -> Result<S::Ok, S::Error>
where
S: ser::Serializer,
{
serializer.serialize_str(&dt.format(DT_FORMAT).to_string())
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<NaiveDateTime, D::Error>
where
D: de::Deserializer<'de>,
{
deserializer.deserialize_str(UserPlayDateVisitor)
}
impl<'de> de::Visitor<'de> for UserPlayDateVisitor {
type Value = NaiveDateTime;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(formatter, "a string in the format of \"{}\"", DT_FORMAT)
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
NaiveDateTime::parse_from_str(v, DT_FORMAT)
.map_err(E::custom)
}
}
}

View File

@ -1,7 +1,8 @@
use chrono::NaiveDateTime;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_aux::prelude::*; use serde_aux::prelude::*;
use super::deserialize_bool; use super::{deserialize_bool, serde_user_play_date};
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
@ -46,15 +47,17 @@ pub struct UserDataEx {
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct UserPlaylog { pub struct UserPlaylog {
// This decides what `level` indices mean. /// This decides what `level` indices mean.
// rom version 1.xx.yy: 0->4 for BASIC/ADVANCED/EXPERT/MASTER/WORLD'S END /// rom version 1.xx.yy: 0->4 for BASIC/ADVANCED/EXPERT/MASTER/WORLD'S END
// rom version 2.xx.yy: 0->5 for BASIC/ADVANCED/EXPERT/MASTER/ULTIMA/WORLD'S END /// rom version 2.xx.yy: 0->5 for BASIC/ADVANCED/EXPERT/MASTER/ULTIMA/WORLD'S END
pub rom_version: String, pub rom_version: String,
pub music_id: String, pub music_id: String,
// This is in UTC+9 /// The date and time the player set this score with, in the local time
pub user_play_date: String, /// perceived by the game. On most setups this will be UTC+9.
#[serde(with = "serde_user_play_date")]
pub user_play_date: NaiveDateTime,
#[serde(deserialize_with = "deserialize_number_from_string")] #[serde(deserialize_with = "deserialize_number_from_string")]
pub level: u32, pub level: u32,
@ -90,6 +93,10 @@ pub struct UserPlaylog {
#[serde(deserialize_with = "deserialize_bool")] #[serde(deserialize_with = "deserialize_bool")]
pub is_full_combo: bool, pub is_full_combo: bool,
/// In CHUNITHM SUN+ and beyond this is actually an integer, with different
/// indexes for different clear lamps, ranging from a normal CLEAR to a CATASTROPHY
/// (similar to EX HARD CLEAR). To keep things simple it's all smushed to a boolean,
/// since Tachi doesn't implement those clear lamps.
#[serde(deserialize_with = "deserialize_bool")] #[serde(deserialize_with = "deserialize_bool")]
pub is_clear: bool, pub is_clear: bool,
} }

View File

@ -1,7 +1,7 @@
pub mod chuni; pub mod chuni;
pub mod tachi; pub mod tachi;
use chrono::{FixedOffset, NaiveDateTime, TimeZone}; use chrono::{FixedOffset, TimeZone};
use num_enum::TryFromPrimitiveError; use num_enum::TryFromPrimitiveError;
use snafu::{ResultExt, Snafu}; use snafu::{ResultExt, Snafu};
@ -20,9 +20,6 @@ pub enum ScoreConversionError {
InvalidDifficulty { InvalidDifficulty {
source: TryFromPrimitiveError<Difficulty>, source: TryFromPrimitiveError<Difficulty>,
}, },
#[snafu(display("Invalid play date."))]
InvalidPlayDate { source: chrono::format::ParseError },
} }
impl UserPlaylog { impl UserPlaylog {
@ -60,10 +57,8 @@ impl UserPlaylog {
Difficulty::try_from(self.level).context(InvalidDifficultySnafu)? Difficulty::try_from(self.level).context(InvalidDifficultySnafu)?
}; };
let datetime = NaiveDateTime::parse_from_str(&self.user_play_date, "%Y-%m-%d %H:%M:%S")
.context(InvalidPlayDateSnafu)?;
let jst_offset = FixedOffset::east_opt(9 * 3600).expect("chrono should parse JST timezone"); let jst_offset = FixedOffset::east_opt(9 * 3600).expect("chrono should parse JST timezone");
let jst_time = jst_offset.from_local_datetime(&datetime).unwrap(); let jst_time = jst_offset.from_local_datetime(&self.user_play_date).unwrap();
Ok(BatchManualScore { Ok(BatchManualScore {
score: self.score, score: self.score,