#![forbid(unsafe_code)]
use crate::dataset::all_datasets;
use chrono::NaiveDate;
type PrefCode = u8;
#[derive(Debug, Clone)]
pub(crate) struct YearDataset {
pub year: u16,
pub effective_from: NaiveDate, pub rates: Vec<(PrefCode, NaiveDate, u16)>, }
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MinimumWageJpCompliance {
Compliant,
Short {
shortage_yen: u32,
required_yen: u32,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MinimumWageJpSuggestion {
pub year: u16,
pub effective_from: NaiveDate,
pub rate: u16,
}
#[derive(thiserror::Error, Debug, Eq, PartialEq)]
pub enum MinimumWageJpErr {
#[error("invalid pref code: {0}")]
InvalidPrefCode(PrefCode),
#[error("no dataset found for date {date}; prev={prev:?} next={next:?}")]
DatasetNotFoundForDate {
date: NaiveDate,
prev: Option<MinimumWageJpSuggestion>,
next: Option<MinimumWageJpSuggestion>,
},
#[error("no dataset found for year {year}")]
DatasetNotFoundForYear { year: u16 },
}
mod dataset;
pub struct MinimumWageJp;
impl MinimumWageJp {
pub fn rate(pref: PrefCode) -> Result<u16, MinimumWageJpErr> {
Self::rate_on_date(today(), pref)
}
pub fn is_compliant(
pref: PrefCode,
hourly_yen: u32,
) -> Result<MinimumWageJpCompliance, MinimumWageJpErr> {
Self::is_compliant_on_date(today(), pref, hourly_yen)
}
pub fn rate_for_revision(year: u16, pref: PrefCode) -> Result<u16, MinimumWageJpErr> {
check_pref(pref)?;
let ds = all_datasets()
.iter()
.find(|d| d.year == year)
.ok_or(MinimumWageJpErr::DatasetNotFoundForYear { year })?;
let rate = ds
.rates
.iter()
.find(|(p, _, _)| *p == pref)
.map(|(_, _, r)| *r)
.ok_or(MinimumWageJpErr::InvalidPrefCode(pref))?;
Ok(rate)
}
pub fn rate_on_date(date: NaiveDate, pref: PrefCode) -> Result<u16, MinimumWageJpErr> {
check_pref(pref)?;
let ds = match dataset_prev_or_equal(date) {
Some(d) => d,
None => {
let next = dataset_next_after(date).map(|n| {
let rate = n
.rates
.iter()
.find(|(p, _, _)| *p == pref)
.map(|(_, _, r)| *r)
.unwrap_or(0);
MinimumWageJpSuggestion {
year: n.year,
effective_from: n.effective_from,
rate,
}
});
return Err(MinimumWageJpErr::DatasetNotFoundForDate {
date,
prev: None,
next,
});
}
};
let (_, eff, rate) = ds
.rates
.iter()
.find(|(p, _, _)| *p == pref)
.copied()
.ok_or(MinimumWageJpErr::InvalidPrefCode(pref))?;
if date < eff {
if let Some(prev) = dataset_prev_before(ds.effective_from) {
let rate_prev = prev
.rates
.iter()
.find(|(p, _, _)| *p == pref)
.map(|(_, _, r)| *r)
.ok_or(MinimumWageJpErr::InvalidPrefCode(pref))?;
return Ok(rate_prev);
}
}
Ok(rate)
}
pub fn is_compliant_on_date(
date: NaiveDate,
pref: PrefCode,
hourly_yen: u32,
) -> Result<MinimumWageJpCompliance, MinimumWageJpErr> {
match Self::rate_on_date(date, pref) {
Ok(rate) => {
let rate_u32 = rate as u32;
if hourly_yen >= rate_u32 {
Ok(MinimumWageJpCompliance::Compliant)
} else {
Ok(MinimumWageJpCompliance::Short {
shortage_yen: rate_u32 - hourly_yen,
required_yen: rate_u32,
})
}
}
Err(e) => Err(e),
}
}
}
fn today() -> NaiveDate {
chrono::Local::now().date_naive()
}
fn check_pref(pref: PrefCode) -> Result<(), MinimumWageJpErr> {
if (1..=47).contains(&pref) {
Ok(())
} else {
Err(MinimumWageJpErr::InvalidPrefCode(pref))
}
}
fn dataset_prev_or_equal(date: NaiveDate) -> Option<&'static YearDataset> {
let mut sel: Option<&YearDataset> = None;
for ds in all_datasets() {
if ds.effective_from <= date {
sel = Some(ds);
} else {
break;
}
}
sel
}
fn dataset_next_after(date: NaiveDate) -> Option<&'static YearDataset> {
for ds in all_datasets() {
if ds.effective_from > date {
return Some(ds);
}
}
None
}
fn dataset_prev_before(effective_from: NaiveDate) -> Option<&'static YearDataset> {
let mut latest: Option<&YearDataset> = None;
for ds in all_datasets() {
if ds.effective_from < effective_from {
latest = Some(ds);
} else {
break;
}
}
latest
}
#[cfg(test)]
mod tests {
use super::*;
use crate::dataset::nd;
#[test]
fn test_rate_for_revision() {
assert_eq!(
MinimumWageJp::rate_for_revision(2020, 1).unwrap_err(),
MinimumWageJpErr::DatasetNotFoundForYear { year: 2020 }
);
assert_eq!(MinimumWageJp::rate_for_revision(2025, 1).unwrap(), 1075);
assert_eq!(MinimumWageJp::rate_for_revision(2025, 13).unwrap(), 1226);
assert_eq!(MinimumWageJp::rate_for_revision(2025, 47).unwrap(), 1023);
}
#[test]
fn test_rate_at() {
assert!(MinimumWageJp::rate_on_date(nd(2024, 1, 1), 1).is_err(),);
assert_eq!(
MinimumWageJp::rate_on_date(nd(2025, 10, 2), 1).unwrap(),
1010
);
assert_eq!(
MinimumWageJp::rate_on_date(nd(2025, 10, 4), 1).unwrap(),
1075
);
assert_eq!(
MinimumWageJp::rate_on_date(nd(2025, 10, 1), 13).unwrap(),
1226
);
}
#[test]
fn test_is_compliant() {
for i in 1..=47 {
match MinimumWageJp::is_compliant_on_date(nd(2025, 10, 1), i, 1000).unwrap() {
MinimumWageJpCompliance::Compliant => {
assert!(false, "should not be compliant: {}", i)
}
MinimumWageJpCompliance::Short { .. } => {
assert!(true)
}
}
}
for i in 1..=47 {
match MinimumWageJp::is_compliant_on_date(nd(2030, 10, 1), i, 3000).unwrap() {
MinimumWageJpCompliance::Compliant => {
assert!(true)
}
MinimumWageJpCompliance::Short { .. } => {
assert!(false, "should not be compliant: {}", i)
}
}
}
assert_eq!(
MinimumWageJp::is_compliant_on_date(nd(2025, 10, 3), 1, 1010).unwrap(),
MinimumWageJpCompliance::Compliant
);
assert_eq!(
MinimumWageJp::is_compliant_on_date(nd(2025, 10, 4), 1, 1020).unwrap(),
MinimumWageJpCompliance::Short {
shortage_yen: 55,
required_yen: 1075
}
);
assert_eq!(
MinimumWageJp::is_compliant_on_date(nd(2025, 10, 4), 1, 1075).unwrap(),
MinimumWageJpCompliance::Compliant
);
assert_eq!(
MinimumWageJp::is_compliant_on_date(nd(2025, 10, 4), 1, 1074).unwrap(),
MinimumWageJpCompliance::Short {
shortage_yen: 1,
required_yen: 1075
}
);
assert_eq!(
MinimumWageJp::is_compliant_on_date(nd(2025, 10, 4), 1, 1000).unwrap(),
MinimumWageJpCompliance::Short {
shortage_yen: 75,
required_yen: 1075
}
);
}
#[test]
fn test_errors() {
assert!(matches!(
MinimumWageJp::rate_for_revision(2025, 0),
Err(MinimumWageJpErr::InvalidPrefCode(0))
));
assert!(matches!(
MinimumWageJp::rate_for_revision(2025, 48),
Err(MinimumWageJpErr::InvalidPrefCode(48))
));
assert!(matches!(
MinimumWageJp::rate_for_revision(2023, 1),
Err(MinimumWageJpErr::DatasetNotFoundForYear { year: 2023 })
));
}
}