Current section
Files
Jump to
Current section
Files
src/calendar/date_range.gleam
// Date range utilities for calendar operations
import calendar/date.{type Date}
import gleam/list
pub type DateRange {
DateRange(first: Date, last: Date, step: Int)
}
pub type DateRangeError {
InvalidRange
}
/// Creates a date range with step 1.
pub fn new(first: Date, last: Date) -> Result(DateRange, DateRangeError) {
new_with_step(first, last, 1)
}
/// Creates a date range with a custom step.
pub fn new_with_step(
first: Date,
last: Date,
step: Int,
) -> Result(DateRange, DateRangeError) {
case step == 0 {
True -> Error(InvalidRange)
False -> {
case first.calendar == last.calendar {
False -> Error(InvalidRange)
True -> Ok(DateRange(first: first, last: last, step: step))
}
}
}
}
/// Converts a date range to a list of dates.
pub fn to_list(range: DateRange) -> List(Date) {
let first_days = date.to_gregorian_days(range.first)
let last_days = date.to_gregorian_days(range.last)
date_range_to_list(range.first, first_days, last_days, range.step, [])
|> list.reverse
}
/// Returns the number of dates in the range.
pub fn size(range: DateRange) -> Int {
let first_days = date.to_gregorian_days(range.first)
let last_days = date.to_gregorian_days(range.last)
let diff = last_days - first_days
case range.step > 0 && diff >= 0 {
True -> diff / range.step + 1
False ->
case range.step < 0 && diff <= 0 {
True -> { -diff } / { -range.step } + 1
False -> 0
}
}
}
/// Checks if a date is in the range.
pub fn member(range: DateRange, d: Date) -> Bool {
let d_days = date.to_gregorian_days(d)
let first_days = date.to_gregorian_days(range.first)
let last_days = date.to_gregorian_days(range.last)
let diff = d_days - first_days
case range.step > 0 {
True ->
d_days >= first_days && d_days <= last_days && diff % range.step == 0
False ->
d_days <= first_days && d_days >= last_days && diff % range.step == 0
}
}
fn date_range_to_list(
current: Date,
current_days: Int,
target_days: Int,
step: Int,
acc: List(Date),
) -> List(Date) {
case
step > 0
&& current_days > target_days
|| step < 0
&& current_days < target_days
{
True -> acc
False -> {
let next = date.add(current, step)
date_range_to_list(next, current_days + step, target_days, step, [
current,
..acc
])
}
}
}