Skip to main content

common_time/
date.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::fmt::{Display, Formatter, Write};
16
17use chrono::{Datelike, Days, LocalResult, Months, NaiveDate, NaiveTime, TimeDelta, TimeZone};
18use serde::{Deserialize, Serialize};
19use serde_json::Value;
20use snafu::ResultExt;
21
22use crate::Timezone;
23use crate::error::{InvalidDateStrSnafu, ParseDateStrSnafu, Result};
24use crate::interval::{IntervalDayTime, IntervalMonthDayNano, IntervalYearMonth};
25use crate::timezone::get_timezone;
26use crate::util::datetime_to_utc;
27
28const UNIX_EPOCH_FROM_CE: i32 = 719_163;
29
30/// ISO 8601 [Date] values. The inner representation is a signed 32 bit integer that represents the
31/// **days since "1970-01-01 00:00:00 UTC" (UNIX Epoch)**.
32#[derive(
33    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Deserialize, Serialize,
34)]
35pub struct Date(i32);
36
37impl From<Date> for Value {
38    fn from(d: Date) -> Self {
39        Value::String(d.to_string())
40    }
41}
42
43impl From<i32> for Date {
44    fn from(v: i32) -> Self {
45        Self(v)
46    }
47}
48
49impl From<NaiveDate> for Date {
50    fn from(date: NaiveDate) -> Self {
51        Self(date.num_days_from_ce() - UNIX_EPOCH_FROM_CE)
52    }
53}
54
55impl Display for Date {
56    /// [Date] is formatted according to ISO-8601 standard.
57    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
58        if let Some(abs_date) = self.to_chrono_date() {
59            write!(f, "{}", abs_date.format("%F"))
60        } else {
61            write!(f, "Date({})", self.0)
62        }
63    }
64}
65
66impl Date {
67    /// Try parsing a string into [`Date`] with UTC timezone.
68    pub fn from_str_utc(s: &str) -> Result<Self> {
69        Self::from_str(s, None)
70    }
71
72    /// Try parsing a string into [`Date`] with given timezone.
73    pub fn from_str(s: &str, timezone: Option<&Timezone>) -> Result<Self> {
74        let s = s.trim();
75        let date = NaiveDate::parse_from_str(s, "%F").context(ParseDateStrSnafu { raw: s })?;
76        let Some(timezone) = timezone else {
77            return Ok(Self(date.num_days_from_ce() - UNIX_EPOCH_FROM_CE));
78        };
79
80        let datetime = date.and_time(NaiveTime::default());
81        match datetime_to_utc(&datetime, timezone) {
82            LocalResult::None => InvalidDateStrSnafu { raw: s }.fail(),
83            LocalResult::Single(utc) | LocalResult::Ambiguous(utc, _) => Ok(Date::from(utc.date())),
84        }
85    }
86
87    pub fn new(val: i32) -> Self {
88        Self(val)
89    }
90
91    pub fn val(&self) -> i32 {
92        self.0
93    }
94
95    pub fn to_chrono_date(&self) -> Option<NaiveDate> {
96        UNIX_EPOCH_FROM_CE
97            .checked_add(self.0)
98            .and_then(NaiveDate::from_num_days_from_ce_opt)
99    }
100
101    /// Format Date for given format and timezone.
102    /// If `tz==None`, the server default timezone will used.
103    pub fn as_formatted_string(
104        self,
105        pattern: &str,
106        timezone: Option<&Timezone>,
107    ) -> Result<Option<String>> {
108        if let Some(v) = self.to_chrono_date() {
109            // Safety: always success
110            let time = NaiveTime::from_hms_nano_opt(0, 0, 0, 0).unwrap();
111            let v = v.and_time(time);
112            let mut formatted = String::new();
113
114            match get_timezone(timezone) {
115                Timezone::Offset(offset) => {
116                    write!(
117                        formatted,
118                        "{}",
119                        offset.from_utc_datetime(&v).format(pattern)
120                    )
121                    .context(crate::error::FormatSnafu { pattern })?;
122                }
123                Timezone::Named(tz) => {
124                    write!(formatted, "{}", tz.from_utc_datetime(&v).format(pattern))
125                        .context(crate::error::FormatSnafu { pattern })?;
126                }
127            }
128
129            return Ok(Some(formatted));
130        }
131
132        Ok(None)
133    }
134
135    pub fn to_secs(&self) -> i64 {
136        (self.0 as i64) * 24 * 3600
137    }
138
139    // FIXME(yingwen): remove add/sub intervals later
140    /// Adds given [IntervalYearMonth] to the current date.
141    pub fn add_year_month(&self, interval: IntervalYearMonth) -> Option<Date> {
142        let naive_date = self.to_chrono_date()?;
143
144        naive_date
145            .checked_add_months(Months::new(interval.months as u32))
146            .map(Into::into)
147    }
148
149    /// Adds given [IntervalDayTime] to the current date.
150    pub fn add_day_time(&self, interval: IntervalDayTime) -> Option<Date> {
151        let naive_date = self.to_chrono_date()?;
152
153        naive_date
154            .checked_add_days(Days::new(interval.days as u64))?
155            .checked_add_signed(TimeDelta::milliseconds(interval.milliseconds as i64))
156            .map(Into::into)
157    }
158
159    /// Adds given [IntervalMonthDayNano] to the current date.
160    pub fn add_month_day_nano(&self, interval: IntervalMonthDayNano) -> Option<Date> {
161        let naive_date = self.to_chrono_date()?;
162
163        naive_date
164            .checked_add_months(Months::new(interval.months as u32))?
165            .checked_add_days(Days::new(interval.days as u64))?
166            .checked_add_signed(TimeDelta::nanoseconds(interval.nanoseconds))
167            .map(Into::into)
168    }
169
170    /// Subtracts given [IntervalYearMonth] to the current date.
171    pub fn sub_year_month(&self, interval: IntervalYearMonth) -> Option<Date> {
172        let naive_date = self.to_chrono_date()?;
173
174        naive_date
175            .checked_sub_months(Months::new(interval.months as u32))
176            .map(Into::into)
177    }
178
179    /// Subtracts given [IntervalDayTime] to the current date.
180    pub fn sub_day_time(&self, interval: IntervalDayTime) -> Option<Date> {
181        let naive_date = self.to_chrono_date()?;
182
183        naive_date
184            .checked_sub_days(Days::new(interval.days as u64))?
185            .checked_sub_signed(TimeDelta::milliseconds(interval.milliseconds as i64))
186            .map(Into::into)
187    }
188
189    /// Subtracts given [IntervalMonthDayNano] to the current date.
190    pub fn sub_month_day_nano(&self, interval: IntervalMonthDayNano) -> Option<Date> {
191        let naive_date = self.to_chrono_date()?;
192
193        naive_date
194            .checked_sub_months(Months::new(interval.months as u32))?
195            .checked_sub_days(Days::new(interval.days as u64))?
196            .checked_sub_signed(TimeDelta::nanoseconds(interval.nanoseconds))
197            .map(Into::into)
198    }
199
200    pub fn negative(&self) -> Self {
201        Self(-self.0)
202    }
203
204    pub fn checked_negative(&self) -> Option<Self> {
205        self.0.checked_neg().map(Self)
206    }
207}
208
209#[cfg(test)]
210mod tests {
211    use chrono::Utc;
212
213    use super::*;
214
215    #[test]
216    pub fn test_print_date2() {
217        assert_eq!("1969-12-31", Date::new(-1).to_string());
218        assert_eq!("1970-01-01", Date::new(0).to_string());
219        assert_eq!("1970-02-12", Date::new(42).to_string());
220    }
221
222    #[test]
223    fn test_to_chrono_date_out_of_range() {
224        assert_eq!(None, Date::new(i32::MAX).to_chrono_date());
225        assert_eq!("Date(2147483647)", Date::new(i32::MAX).to_string());
226    }
227
228    #[test]
229    pub fn test_date_parse() {
230        assert_eq!(
231            "1970-01-01",
232            Date::from_str("1970-01-01", None).unwrap().to_string()
233        );
234
235        assert_eq!(
236            "1969-01-01",
237            Date::from_str("1969-01-01", None).unwrap().to_string()
238        );
239
240        assert_eq!(
241            "1969-01-01",
242            Date::from_str("     1969-01-01       ", None)
243                .unwrap()
244                .to_string()
245        );
246
247        let now = Utc::now().date_naive().format("%F").to_string();
248        assert_eq!(now, Date::from_str(&now, None).unwrap().to_string());
249
250        // with timezone
251        assert_eq!(
252            "1969-12-31",
253            Date::from_str(
254                "1970-01-01",
255                Some(&Timezone::from_tz_string("Asia/Shanghai").unwrap())
256            )
257            .unwrap()
258            .to_string()
259        );
260
261        assert_eq!(
262            "1969-12-31",
263            Date::from_str(
264                "1970-01-01",
265                Some(&Timezone::from_tz_string("+16:00").unwrap())
266            )
267            .unwrap()
268            .to_string()
269        );
270
271        assert_eq!(
272            "1970-01-01",
273            Date::from_str(
274                "1970-01-01",
275                Some(&Timezone::from_tz_string("-8:00").unwrap())
276            )
277            .unwrap()
278            .to_string()
279        );
280
281        assert_eq!(
282            "1970-01-01",
283            Date::from_str(
284                "1970-01-01",
285                Some(&Timezone::from_tz_string("-16:00").unwrap())
286            )
287            .unwrap()
288            .to_string()
289        );
290    }
291
292    #[test]
293    fn test_add_sub_interval() {
294        let date = Date::new(1000);
295
296        let interval = IntervalYearMonth::new(3);
297
298        let new_date = date.add_year_month(interval).unwrap();
299        assert_eq!(new_date.val(), 1091);
300
301        assert_eq!(date, new_date.sub_year_month(interval).unwrap());
302    }
303
304    #[test]
305    pub fn test_min_max() {
306        let mut date = Date::from_str("9999-12-31", None).unwrap();
307        date.0 += 1000;
308        assert_eq!(date, Date::from_str(&date.to_string(), None).unwrap());
309    }
310
311    #[test]
312    fn test_as_formatted_string() {
313        let d: Date = 42.into();
314
315        assert_eq!(
316            "1970-02-12",
317            d.as_formatted_string("%Y-%m-%d", None).unwrap().unwrap()
318        );
319        assert_eq!(
320            "1970-02-12 00:00:00",
321            d.as_formatted_string("%Y-%m-%d %H:%M:%S", None)
322                .unwrap()
323                .unwrap()
324        );
325        assert_eq!(
326            "1970-02-12T00:00:00:000",
327            d.as_formatted_string("%Y-%m-%dT%H:%M:%S:%3f", None)
328                .unwrap()
329                .unwrap()
330        );
331        assert_eq!(
332            "1970-02-12T08:00:00:000",
333            d.as_formatted_string(
334                "%Y-%m-%dT%H:%M:%S:%3f",
335                Some(&Timezone::from_tz_string("Asia/Shanghai").unwrap())
336            )
337            .unwrap()
338            .unwrap()
339        );
340    }
341
342    #[test]
343    pub fn test_from() {
344        let d: Date = 42.into();
345        assert_eq!(42, d.val());
346    }
347
348    #[test]
349    fn test_to_secs() {
350        let d = Date::from_str("1970-01-01", None).unwrap();
351        assert_eq!(d.to_secs(), 0);
352        let d = Date::from_str("1970-01-02", None).unwrap();
353        assert_eq!(d.to_secs(), 24 * 3600);
354        let d = Date::from_str("1970-01-03", None).unwrap();
355        assert_eq!(d.to_secs(), 2 * 24 * 3600);
356    }
357}