I am trying to implement a struct to represent a date, which can be used with range syntax (for d in start..end { }
). I know there are already crates out there that handle dates, but I am doing this as an exercise.
Here is the struct:
type DayOfMonth = u8;
type Month = u8;
type Year = u16;
#[derive(PartialEq, Eq, Clone)]
pub struct Date {
pub year: Year,
pub month: Month,
pub day: DayOfMonth
}
Here is how I would like to use it:
fn print_dates() {
let start = Date { year: 1999, month: 1, day: 1 };
let end = Date { year: 1999, month: 12, day: 31 };
for d in start..end {
println!("{}-{}-{}", d.year, d.month, d.day);
}
}
I originally tried implementing the Iterator
trait, but then when I tried to use range syntax, I got a compiler error saying I needed to implement Step
instead.
The documentation shows this signature for the Step
trait.
pub trait Step: PartialOrd<Self> + Clone {
fn steps_between(start: &Self, end: &Self) -> Option<usize>;
fn replace_one(&mut self) -> Self;
fn replace_zero(&mut self) -> Self;
fn add_one(&self) -> Self;
fn sub_one(&self) -> Self;
fn add_usize(&self, n: usize) -> Option<Self>;
}
I've already implemented Ord
and PartialOrd
:
impl Ord for Date {
fn cmp(&self, other: &Self) -> Ordering {
match self.year.cmp(&other.year) {
Ordering::Equal =>
match self.month.cmp(&other.month) {
Ordering::Equal =>
self.day.cmp(&other.day),
ord => ord
},
ord => ord
}
}
}
impl PartialOrd for Date {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
I am implementing Clone
automatically with #[derive(Clone)]
.
I started implementing Step
, but there are some methods that I cannot figure out what to do with. Here is what I have so far:
impl Step for Date {
fn steps_between(start: &self, end: &self) -> Option<usize> {
//is_valid_date checks that the month is not > 12 and other rules like that
if is_valid_date(start) && is_valid_date(end) {
//get_epoch_day_number gets the number of days since 1900-01-01
let diff = get_epoch_day_number(end) - get_epoch_day_number(start);
Some(diff)
}
else { None }
}
fn add_one(&self) -> Self {
//Try the next day
let mut next = Date {
year: self.year,
month: self.month,
day: self.day + 1
};
//If not valid, try the 1st of the next month
if !is_valid_date(&next) {
next = Date {
year: self.year,
month: self.month + 1,
day: 1
};
}
//If not valid, try the 1st of the next year
if !is_valid_date(&next) {
next = Date {
year: self.year + 1,
month: 1,
day: 1
};
}
next
}
fn sub_one(&self) -> Self {
//Try the prev day
let mut prev = Date {
year: self.year,
month: self.month,
day: self.day - 1
};
//If not valid, try the last of the prev month
if !is_valid_date(&prev) {
let m = self.month - 1;
prev = Date {
year: self.year,
month: m,
day: get_month_length(self.year, m)
};
}
//If not valid, try the last of the prev year
if !is_valid_date(&prev) {
prev = Date {
year: self.year - 1,
month: 12,
day: 31
};
}
prev
}
fn add_usize(&self, n: usize) -> Self {
//This is really inefficient, but that's not important
let mut result = self;
for i in 1..n+1 {
result = result.add_one();
}
result
}
fn replace_one(&mut self) -> Self {
// ?
}
fn replace_zero(&mut self) -> Self {
// ?
}
}
I am really stumped on what replace_one
and replace_zero
are supposed to do. The documentation says:
Replaces this step with 1
, returning itself. and Replaces this step with 0
, returning itself.
Does my struct need to have zero
and one
identity values just to be used in a range? Shouldn't add_one
be enough?
The wording used by the documentation is also a little unclear. If we replace x
with 1
and return "itself", is "it" x
or 1
?
I just looked at Rust's code where those methods are used. The only uses in whole of rustc's repository are to implement RangeInclusive
operations. An empty RangeInclusive
is represented as a range from 1
to 0
, so the next
, next_back
and nth
methods need to be able to get those somehow, and that's what replace_one
and replace_zero
are for.
I would suggest opening an issue on rustc's GitHub to make the documentation better, and possibly change the name of these methods.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With