Skip to content

feat: add start_of / end_of period convenience methods #53

Description

@ZialeHub

Summary

clear_unit currently resets a field to its minimum value (day → 1, month → 1, hour → 0). This covers start_of_* semantics. However, the equally important end_of_* operations are missing entirely, and consumers are forced to reimplement non-trivial calendar logic — particularly end_of_month, which must handle varying month lengths and leap years correctly.


Proposed API

On Date

date.start_of_month()? -> Date   // 2024-10-15 → 2024-10-01
date.end_of_month()?   -> Date   // 2024-10-15 → 2024-10-31
                                 // 2024-02-10 → 2024-02-29  (leap year)
                                 // 2023-02-10 → 2023-02-28

date.start_of_year()?  -> Date   // 2024-10-15 → 2024-01-01
date.end_of_year()?    -> Date   // 2024-10-15 → 2024-12-31

date.start_of_week()?  -> Date   // ISO: Monday of current week
date.end_of_week()?    -> Date   // ISO: Sunday of current week

date.start_of_quarter()? -> Date // 2024-11-15 → 2024-10-01  (Q4)
date.end_of_quarter()?   -> Date // 2024-11-15 → 2024-12-31  (Q4)

On DateTime

Same methods, preserving or zeroing time components as appropriate:

datetime.start_of_month()? -> DateTime  // sets day=1, time=00:00:00
datetime.end_of_month()?   -> DateTime  // sets day=last, time=23:59:59
datetime.start_of_day()?   -> DateTime  // sets time=00:00:00  (alias for clear_time)
datetime.end_of_day()?     -> DateTime  // sets time=23:59:59

Why Not Just Use clear_unit?

clear_unit is a low-level building block. These methods are higher-level semantic operations:

Goal With clear_unit With new methods
First day of month date.clear_unit(DateUnit::Day)? date.start_of_month()?
Last day of month Manual: compute days in month, then set date.end_of_month()?
First day of quarter Manual: compute quarter start month, then set date.start_of_quarter()?
Last second of day Manual: update to 23:59:59 datetime.end_of_day()?

end_of_month in particular is non-trivial: it requires knowing the number of days in the month, which depends on the year (leap year handling for February). This is exactly the kind of logic timeflow should own.


Implementation Notes

  • end_of_month should use chrono's NaiveDate::from_ymd_opt with the next month's first day minus one day — the idiomatic approach that handles all edge cases automatically.
  • start_of_week / end_of_week should follow ISO 8601 (Monday = start, Sunday = end).
  • Quarter boundaries: Q1 = Jan–Mar, Q2 = Apr–Jun, Q3 = Jul–Sep, Q4 = Oct–Dec.
  • All methods return Result<Self, SpanError> for consistency with the existing API.
  • These methods can be implemented in terms of existing primitives (clear_unit, update, new) to minimise new code surface.

Acceptance Criteria

  • start_of_month, end_of_month, start_of_year, end_of_year implemented on Date and DateTime
  • start_of_week, end_of_week implemented (ISO Monday–Sunday)
  • start_of_quarter, end_of_quarter implemented
  • start_of_day, end_of_day implemented on DateTime
  • end_of_month correctly handles February in leap and non-leap years
  • end_of_month correctly handles all 28/29/30/31-day months
  • Each method has a doc comment with at least one rust,ignore example
  • Full test coverage including leap year February, Q4 end, week crossing year boundary

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-domainbusiness logic / coreB-ideaDiscussion; or implementation attempt, to be reviewed before further work

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions