cxx_qt_lib/core/
qdate.rs

1// SPDX-FileCopyrightText: 2022 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
2// SPDX-FileContributor: Andrew Hayzen <andrew.hayzen@kdab.com>
3//
4// SPDX-License-Identifier: MIT OR Apache-2.0
5
6use cxx::{type_id, ExternType};
7use std::fmt;
8
9#[cxx::bridge]
10mod ffi {
11    #[namespace = "Qt"]
12    unsafe extern "C++" {
13        include!("cxx-qt-lib/qt.h");
14        type DateFormat = crate::DateFormat;
15    }
16
17    unsafe extern "C++" {
18        include!("cxx-qt-lib/qstring.h");
19        type QString = crate::QString;
20
21        include!("cxx-qt-lib/qdate.h");
22        type QDate = super::QDate;
23
24        /// Returns a QDate object containing a date nmonths later than the date of this object (or earlier if nmonths is negative).
25        #[rust_name = "add_months"]
26        fn addMonths(self: &QDate, nmonths: i32) -> QDate;
27
28        /// Returns a QDate object containing a date nyears later than the date of this object (or earlier if nyears is negative).
29        #[rust_name = "add_years"]
30        fn addYears(self: &QDate, nyears: i32) -> QDate;
31
32        /// Returns the day of the month for this date.
33        fn day(self: &QDate) -> i32;
34
35        /// Returns the weekday (1 = Monday to 7 = Sunday) for this date.
36        #[rust_name = "day_of_week"]
37        fn dayOfWeek(self: &QDate) -> i32;
38
39        /// Returns the day of the year (1 for the first day) for this date.
40        #[rust_name = "day_of_year"]
41        fn dayOfYear(self: &QDate) -> i32;
42
43        /// Returns the number of days in the month for this date.
44        #[rust_name = "days_in_monyth"]
45        fn daysInMonth(self: &QDate) -> i32;
46
47        /// Returns the number of days in the year for this date.
48        #[rust_name = "days_in_year"]
49        fn daysInYear(self: &QDate) -> i32;
50
51        /// Returns true if the date is null; otherwise returns false. A null date is invalid.
52        #[rust_name = "is_null"]
53        fn isNull(self: &QDate) -> bool;
54
55        /// Returns true if this date is valid; otherwise returns false.
56        #[rust_name = "is_valid"]
57        fn isValid(self: &QDate) -> bool;
58
59        /// Returns the month-number for the date.
60        ///
61        /// Numbers the months of the year starting with 1 for the first
62        fn month(self: &QDate) -> i32;
63
64        /// Sets this to represent the date, in the Gregorian calendar, with the given year, month and day numbers.
65        /// Returns true if the resulting date is valid, otherwise it sets this to represent an invalid date and returns false.
66        #[rust_name = "set_date"]
67        fn setDate(self: &mut QDate, y: i32, m: i32, d: i32) -> bool;
68
69        /// Returns the time as a string. The format parameter determines the format of the string.
70        #[rust_name = "format_enum"]
71        fn toString(self: &QDate, format: DateFormat) -> QString;
72
73        /// Returns the year of this date.
74        fn year(self: &QDate) -> i32;
75    }
76
77    #[namespace = "rust::cxxqtlib1"]
78    unsafe extern "C++" {
79        #[doc(hidden)]
80        #[rust_name = "qdate_add_days"]
81        fn qdateAddDays(date: &QDate, ndays: i64) -> QDate;
82
83        #[doc(hidden)]
84        #[rust_name = "qdate_current_date"]
85        fn qdateCurrentDate() -> QDate;
86
87        #[doc(hidden)]
88        #[rust_name = "qdate_days_to"]
89        fn qdateDaysTo(date: &QDate, d: QDate) -> i64;
90
91        #[doc(hidden)]
92        #[rust_name = "qdate_from_string"]
93        fn qdateFromString(string: &QString, format: &QString) -> QDate;
94        #[doc(hidden)]
95        #[rust_name = "qdate_from_string_enum"]
96        fn qdateFromString(string: &QString, format: DateFormat) -> QDate;
97
98        #[doc(hidden)]
99        #[rust_name = "qdate_is_leap_year"]
100        fn qdateIsLeapYear(year: i32) -> bool;
101
102        #[doc(hidden)]
103        #[rust_name = "qdate_to_format"]
104        fn qdateToFormat(date: &QDate, format: &QString) -> QString;
105    }
106
107    #[namespace = "rust::cxxqtlib1"]
108    unsafe extern "C++" {
109        include!("cxx-qt-lib/common.h");
110
111        #[doc(hidden)]
112        #[rust_name = "qdate_init_default"]
113        fn construct() -> QDate;
114        #[doc(hidden)]
115        #[rust_name = "qdate_init"]
116        fn construct(y: i32, m: i32, d: i32) -> QDate;
117        #[doc(hidden)]
118        #[rust_name = "qdate_to_qstring"]
119        fn toQString(value: &QDate) -> QString;
120    }
121}
122
123/// The QDate class provides date functions.
124#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
125#[repr(C)]
126pub struct QDate {
127    jd: i64,
128}
129
130impl Default for QDate {
131    /// Constructs a null date. Null dates are invalid.
132    fn default() -> Self {
133        ffi::qdate_init_default()
134    }
135}
136
137impl fmt::Display for QDate {
138    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
139        write!(f, "{}", ffi::qdate_to_qstring(self))
140    }
141}
142
143impl fmt::Debug for QDate {
144    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
145        write!(f, "{self}")
146    }
147}
148
149impl QDate {
150    /// Returns a QDate object containing a date ndays later than the date of this object (or earlier if ndays is negative).
151    ///
152    /// Returns a null date if the current date is invalid or the new date is out of range.
153    pub fn add_days(&self, ndays: i64) -> Self {
154        ffi::qdate_add_days(self, ndays)
155    }
156
157    // Returns the current date, as reported by the system clock.
158    pub fn current_date() -> Self {
159        ffi::qdate_current_date()
160    }
161
162    /// Returns the number of days from this date to d (which is negative if d is earlier than this date).
163    ///
164    /// Returns 0 if either date is invalid.
165    pub fn days_to(&self, date: Self) -> i64 {
166        ffi::qdate_days_to(self, date)
167    }
168
169    /// Returns the time as a string. The format parameter determines the format of the result string.
170    pub fn format(&self, format: &ffi::QString) -> ffi::QString {
171        ffi::qdate_to_format(self, format)
172    }
173
174    /// Converts the Julian day jd to a QDate.
175    pub fn from_julian_day(jd: i64) -> Self {
176        Self { jd }
177    }
178
179    /// Returns the QTime represented by the string, using the format given, or None if the string cannot be parsed.
180    pub fn from_string(string: &ffi::QString, format: &ffi::QString) -> Option<Self> {
181        let date = ffi::qdate_from_string(string, format);
182        if date.is_valid() {
183            Some(date)
184        } else {
185            None
186        }
187    }
188
189    /// Returns the time represented in the string as a QTime using the format given, or None if this is not possible.
190    pub fn from_string_enum(string: &ffi::QString, format: ffi::DateFormat) -> Option<Self> {
191        let date = ffi::qdate_from_string_enum(string, format);
192        if date.is_valid() {
193            Some(date)
194        } else {
195            None
196        }
197    }
198
199    /// Returns true if the specified year is a leap year in the Gregorian calendar; otherwise returns false.
200    pub fn is_leap_year(year: i32) -> bool {
201        ffi::qdate_is_leap_year(year)
202    }
203
204    /// Constructs a date with year y, month m and day d.
205    pub fn new(y: i32, m: i32, d: i32) -> Self {
206        ffi::qdate_init(y, m, d)
207    }
208
209    /// Converts the date to a Julian day.
210    pub fn to_julian_day(&self) -> i64 {
211        self.jd
212    }
213}
214
215// Safety:
216//
217// Static checks on the C++ side ensure that QDate is trivial.
218unsafe impl ExternType for QDate {
219    type Id = type_id!("QDate");
220    type Kind = cxx::kind::Trivial;
221}
222
223#[cfg(feature = "chrono")]
224use chrono::Datelike;
225
226#[cfg(feature = "chrono")]
227impl From<chrono::NaiveDate> for QDate {
228    fn from(value: chrono::NaiveDate) -> Self {
229        QDate::new(value.year(), value.month() as i32, value.day() as i32)
230    }
231}
232
233#[cfg(feature = "chrono")]
234impl TryFrom<QDate> for chrono::NaiveDate {
235    type Error = &'static str;
236
237    fn try_from(value: QDate) -> Result<Self, Self::Error> {
238        chrono::NaiveDate::from_ymd_opt(value.year(), value.month() as u32, value.day() as u32)
239            .ok_or("out-of-range date, invalid month and/or day")
240    }
241}
242
243#[cfg(feature = "time")]
244impl From<time::Date> for QDate {
245    fn from(value: time::Date) -> Self {
246        QDate::new(
247            value.year(),
248            Into::<u8>::into(value.month()) as i32,
249            value.day() as i32,
250        )
251    }
252}
253
254#[cfg(feature = "time")]
255impl TryFrom<QDate> for time::Date {
256    type Error = time::error::ComponentRange;
257
258    fn try_from(value: QDate) -> Result<Self, Self::Error> {
259        time::Date::from_calendar_date(
260            value.year(),
261            time::Month::try_from(value.month() as u8)?,
262            value.day() as u8,
263        )
264    }
265}
266
267#[cfg(test)]
268mod test {
269    use super::*;
270
271    #[test]
272    fn qdate_current_date() {
273        let date_a = QDate::current_date();
274        let date_b = date_a.add_days(100);
275        assert_eq!(date_a.days_to(date_b), 100);
276    }
277
278    #[test]
279    fn qdate_julian_day() {
280        let date_a = QDate::from_julian_day(1000);
281        let date_b = QDate::from_julian_day(1010);
282        assert_eq!(date_a.days_to(date_b), 10);
283    }
284
285    #[cfg(feature = "chrono")]
286    #[test]
287    fn qdate_from_chrono_naive() {
288        let naive = chrono::NaiveDate::from_ymd_opt(2023, 1, 1).unwrap();
289        let qdate = QDate::new(2023, 1, 1);
290        assert_eq!(QDate::from(naive), qdate);
291    }
292
293    #[cfg(feature = "chrono")]
294    #[test]
295    fn qdate_to_chrono_naive() {
296        let naive = chrono::NaiveDate::from_ymd_opt(2023, 1, 1).unwrap();
297        let qdate = QDate::new(2023, 1, 1);
298        assert_eq!(chrono::NaiveDate::try_from(qdate).unwrap(), naive);
299    }
300
301    #[cfg(feature = "time")]
302    #[test]
303    fn qdate_from_time_date() {
304        let time_date = time::Date::from_calendar_date(2023, time::Month::January, 1).unwrap();
305        let qdate = QDate::new(2023, 1, 1);
306        assert_eq!(QDate::from(time_date), qdate);
307    }
308
309    #[cfg(feature = "time")]
310    #[test]
311    fn qdate_to_time_date() {
312        let time_date = time::Date::from_calendar_date(2023, time::Month::January, 1).unwrap();
313        let qdate = QDate::new(2023, 1, 1);
314        assert_eq!(time::Date::try_from(qdate).unwrap(), time_date);
315    }
316}