Skip to main content

reifydb_function/date/
week.rs

1// SPDX-License-Identifier: AGPL-3.0-or-later
2// Copyright (c) 2025 ReifyDB
3
4use reifydb_core::value::column::data::ColumnData;
5use reifydb_type::value::{date::Date, r#type::Type};
6
7use crate::{
8	ScalarFunction, ScalarFunctionContext,
9	error::{ScalarFunctionError, ScalarFunctionResult},
10	propagate_options,
11};
12
13pub struct DateWeek;
14
15impl DateWeek {
16	pub fn new() -> Self {
17		Self
18	}
19}
20
21/// Compute the ISO 8601 week number for a date.
22///
23/// ISO 8601 rules:
24/// - Weeks start on Monday
25/// - Week 1 is the week containing January 4th
26/// - A year has 52 or 53 weeks
27/// - Jan 1-3 may belong to week 52/53 of the previous year
28/// - Dec 29-31 may belong to week 1 of the next year
29fn iso_week_number(date: &Date) -> i32 {
30	let days = date.to_days_since_epoch();
31
32	// ISO day of week: Mon=1..Sun=7
33	let dow = ((days % 7 + 3) % 7 + 7) % 7 + 1;
34
35	// Find the Thursday of this date's week (ISO weeks are identified by their Thursday)
36	let thursday = days + (4 - dow);
37
38	// Find Jan 1 of the year containing that Thursday
39	let thursday_ymd = {
40		let d = Date::from_days_since_epoch(thursday).unwrap();
41		d.year()
42	};
43	let jan1 = Date::new(thursday_ymd, 1, 1).unwrap();
44	let jan1_days = jan1.to_days_since_epoch();
45
46	// Week number = how many weeks between Jan 1 of that year and the Thursday
47	let week = (thursday - jan1_days) / 7 + 1;
48
49	week
50}
51
52impl ScalarFunction for DateWeek {
53	fn scalar(&self, ctx: ScalarFunctionContext) -> ScalarFunctionResult<ColumnData> {
54		if let Some(result) = propagate_options(self, &ctx) {
55			return result;
56		}
57
58		let columns = ctx.columns;
59		let row_count = ctx.row_count;
60
61		if columns.len() != 1 {
62			return Err(ScalarFunctionError::ArityMismatch {
63				function: ctx.fragment.clone(),
64				expected: 1,
65				actual: columns.len(),
66			});
67		}
68
69		let col = columns.get(0).unwrap();
70
71		match col.data() {
72			ColumnData::Date(container) => {
73				let mut data = Vec::with_capacity(row_count);
74				let mut bitvec = Vec::with_capacity(row_count);
75
76				for i in 0..row_count {
77					if let Some(date) = container.get(i) {
78						data.push(iso_week_number(&date));
79						bitvec.push(true);
80					} else {
81						data.push(0);
82						bitvec.push(false);
83					}
84				}
85
86				Ok(ColumnData::int4_with_bitvec(data, bitvec))
87			}
88			other => Err(ScalarFunctionError::InvalidArgumentType {
89				function: ctx.fragment.clone(),
90				argument_index: 0,
91				expected: vec![Type::Date],
92				actual: other.get_type(),
93			}),
94		}
95	}
96
97	fn return_type(&self, _input_types: &[Type]) -> Type {
98		Type::Int4
99	}
100}