diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6053c35 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.snfoundry_cache +target diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000..1d32f85 --- /dev/null +++ b/.tool-versions @@ -0,0 +1,2 @@ +scarb 2.11.3 +starknet-foundry 0.39.0 diff --git a/examples/cairo/scripts/datetime/Scarb.lock b/Scarb.lock similarity index 58% rename from examples/cairo/scripts/datetime/Scarb.lock rename to Scarb.lock index 5595567..f6e5591 100644 --- a/examples/cairo/scripts/datetime/Scarb.lock +++ b/Scarb.lock @@ -2,7 +2,7 @@ version = 1 [[package]] -name = "datetime" +name = "chrono" version = "0.1.0" dependencies = [ "snforge_std", @@ -10,15 +10,15 @@ dependencies = [ [[package]] name = "snforge_scarb_plugin" -version = "0.38.3" +version = "0.39.0" source = "registry+https://scarbs.xyz/" -checksum = "sha256:0cd914b547acd96b4cad99a78e95c0eda001d0c280da4969b2161e286079cf46" +checksum = "sha256:61ae2333de26d9e6e4b535916f8da7916bfd95c951c765c6fbd36038289a636c" [[package]] name = "snforge_std" -version = "0.38.3" +version = "0.39.0" source = "registry+https://scarbs.xyz/" -checksum = "sha256:d376526fbbe22535ad89ed630b11d6e209f22c50168de6c6430c0591c81c3174" +checksum = "sha256:31f118f54e5d87393e50c07e8c052f03ff4af945a2b65b56e6edbd3b5b7fd127" dependencies = [ "snforge_scarb_plugin", ] diff --git a/Scarb.toml b/Scarb.toml new file mode 100644 index 0000000..d053cc0 --- /dev/null +++ b/Scarb.toml @@ -0,0 +1,28 @@ +[workspace] +members = ["packages/chrono"] + +[workspace.package] +version = "0.1.0" +edition = "2024_07" +cairo-version = "2.11.2" +scarb-version = "2.11.3" +authors = ["KaizeNodeLabs Community"] +description = "Community maintained Cairo and Starknet libraries" +documentation = "https://github.com/KaizeNodeLabs/starkiro" +homepage = "https://github.com/KaizeNodeLabs/starkiro" +repository = "https://github.com/KaizeNodeLabs/starkiro" +license-file = "LICENSE" + +[workspace.dependencies] +starknet = "2.11.2" +assert_macros = "2.11.2" +snforge_std = "0.39.0" + +[workspace.tool.fmt] +sort-module-level-items = true + +[workspace.tool.scarb] +allow-prebuilt-plugins = ["snforge_std"] + +[workspace.scripts] +test = "snforge test --max-n-steps 1000000000" diff --git a/examples/cairo/scripts/datetime/.gitignore b/examples/cairo/scripts/datetime/.gitignore deleted file mode 100644 index eb5a316..0000000 --- a/examples/cairo/scripts/datetime/.gitignore +++ /dev/null @@ -1 +0,0 @@ -target diff --git a/examples/cairo/scripts/datetime/.tool-versions b/examples/cairo/scripts/datetime/.tool-versions deleted file mode 100644 index cf929ab..0000000 --- a/examples/cairo/scripts/datetime/.tool-versions +++ /dev/null @@ -1,2 +0,0 @@ -scarb 2.11.1 -starknet-foundry 0.38.3 diff --git a/examples/cairo/scripts/datetime/Scarb.toml b/examples/cairo/scripts/datetime/Scarb.toml deleted file mode 100644 index 9997f19..0000000 --- a/examples/cairo/scripts/datetime/Scarb.toml +++ /dev/null @@ -1,52 +0,0 @@ -[package] -name = "datetime" -version = "0.1.0" -edition = "2024_07" - -# See more keys and their definitions at https://docs.swmansion.com/scarb/docs/reference/manifest.html - -[dependencies] -starknet = "2.11.1" - -[dev-dependencies] -snforge_std = "0.38.3" -assert_macros = "2.11.1" - -[[target.starknet-contract]] -sierra = true - -[scripts] -test = "snforge test --max-n-steps 1000000000" - -[tool.scarb] -allow-prebuilt-plugins = ["snforge_std"] - -# Visit https://foundry-rs.github.io/starknet-foundry/appendix/scarb-toml.html for more information - -# [tool.snforge] # Define `snforge` tool section -# exit_first = true # Stop tests execution immediately upon the first failure -# fuzzer_runs = 1234 # Number of runs of the random fuzzer -# fuzzer_seed = 1111 # Seed for the random fuzzer - -# [[tool.snforge.fork]] # Used for fork testing -# name = "SOME_NAME" # Fork name -# url = "http://your.rpc.url" # Url of the RPC provider -# block_id.tag = "latest" # Block to fork from (block tag) - -# [[tool.snforge.fork]] -# name = "SOME_SECOND_NAME" -# url = "http://your.second.rpc.url" -# block_id.number = "123" # Block to fork from (block number) - -# [[tool.snforge.fork]] -# name = "SOME_THIRD_NAME" -# url = "http://your.third.rpc.url" -# block_id.hash = "0x123" # Block to fork from (block hash) - -# [profile.dev.cairo] # Configure Cairo compiler -# unstable-add-statements-code-locations-debug-info = true # Should be used if you want to use coverage -# unstable-add-statements-functions-debug-info = true # Should be used if you want to use coverage/profiler -# inlining-strategy = "avoid" # Should be used if you want to use coverage - -# [features] # Used for conditional compilation -# enable_for_tests = [] # Feature name and list of other features that should be enabled with it diff --git a/examples/cairo/scripts/datetime/snfoundry.toml b/examples/cairo/scripts/datetime/snfoundry.toml deleted file mode 100644 index 306a097..0000000 --- a/examples/cairo/scripts/datetime/snfoundry.toml +++ /dev/null @@ -1,11 +0,0 @@ -# Visit https://foundry-rs.github.io/starknet-foundry/appendix/snfoundry-toml.html -# and https://foundry-rs.github.io/starknet-foundry/projects/configuration.html for more information - -# [sncast.default] # Define a profile name -# url = "https://free-rpc.nethermind.io/sepolia-juno/v0_7" # Url of the RPC provider -# accounts-file = "../account-file" # Path to the file with the account data -# account = "mainuser" # Account from `accounts_file` or default account file that will be used for the transactions -# keystore = "~/keystore" # Path to the keystore file -# wait-params = { timeout = 300, retry-interval = 10 } # Wait for submitted transaction parameters -# block-explorer = "StarkScan" # Block explorer service used to display links to transaction details -# show-explorer-links = true # Print links pointing to pages with transaction details in the chosen block explorer diff --git a/examples/cairo/scripts/datetime/src/time_delta.cairo b/examples/cairo/scripts/datetime/src/time_delta.cairo deleted file mode 100644 index a94756b..0000000 --- a/examples/cairo/scripts/datetime/src/time_delta.cairo +++ /dev/null @@ -1,189 +0,0 @@ -use core::fmt::{Debug, Display, Error, Formatter}; -use core::num::traits::Bounded; -use datetime::utils::abs; - -#[derive(Clone, Copy, PartialEq, Drop)] -pub struct TimeDelta { - secs: i64, -} - -/// The number of milliseconds per second. -pub const MILLIS_PER_SEC: i64 = 1000; -// The number of seconds in a minute. -pub const SECS_PER_MINUTE: i64 = 60; -/// The number of seconds in an hour. -pub const SECS_PER_HOUR: i64 = 3600; -/// The number of (non-leap) seconds in days. -pub const SECS_PER_DAY: i64 = 86_400; -/// The number of (non-leap) seconds in a week. -pub const SECS_PER_WEEK: i64 = 604_800; - -fn checked_mul(lhs: i64, rhs: i64) -> Option { - // TODO needs proper check - Some(lhs * rhs) -} - -#[generate_trait] -pub impl TimeDeltaImpl of TimeDeltaTrait { - fn new(secs: i64) -> Option { - if secs < Self::MIN.secs || secs > Self::MAX.secs { - return None; - } - Some(TimeDelta { secs }) - } - - fn weeks(weeks: i64) -> TimeDelta { - Self::try_weeks(weeks).unwrap() - } - - fn try_weeks(weeks: i64) -> Option { - Self::try_seconds(checked_mul(weeks, SECS_PER_WEEK)?) - } - - fn days(days: i64) -> TimeDelta { - Self::try_days(days).unwrap() - } - - fn try_days(days: i64) -> Option { - Self::try_seconds(checked_mul(days, SECS_PER_DAY)?) - } - - fn hours(hours: i64) -> TimeDelta { - Self::try_hours(hours).unwrap() - } - - fn try_hours(hours: i64) -> Option { - Self::try_seconds(checked_mul(hours, SECS_PER_HOUR)?) - } - - fn minutes(minutes: i64) -> TimeDelta { - Self::try_minutes(minutes).unwrap() - } - - fn try_minutes(minutes: i64) -> Option { - Self::try_seconds(checked_mul(minutes, SECS_PER_MINUTE)?) - } - - fn seconds(seconds: i64) -> TimeDelta { - Self::try_seconds(seconds).unwrap() - } - - fn try_seconds(seconds: i64) -> Option { - Self::new(seconds) - } - - fn num_weeks(self: TimeDelta) -> i64 { - self.num_days() / 7 - } - - fn num_days(self: TimeDelta) -> i64 { - self.num_seconds() / SECS_PER_DAY - } - - fn num_hours(self: TimeDelta) -> i64 { - self.num_seconds() / SECS_PER_HOUR - } - - fn num_minutes(self: TimeDelta) -> i64 { - self.num_seconds() / SECS_PER_MINUTE - } - - fn num_seconds(self: @TimeDelta) -> i64 { - *self.secs - } - - /// Add two `TimeDelta`s, returning `None` if overflow occurred. - fn checked_add(self: @TimeDelta, rhs: TimeDelta) -> Option { - // No overflow checks here because we stay comfortably within the range of an `i64`. - // Range checks happen in `TimeDelta::new`. - Self::new(*self.secs + rhs.secs) - } - - /// Subtract two `TimeDelta`s, returning `None` if overflow occurred. - fn checked_sub(self: @TimeDelta, rhs: TimeDelta) -> Option { - // No overflow checks here because we stay comfortably within the range of an `i64`. - // Range checks happen in `TimeDelta::new`. - Self::new(*self.secs - rhs.secs) - } - - /// Returns the `TimeDelta` as an absolute (non-negative) value. - fn abs(self: @TimeDelta) -> TimeDelta { - TimeDelta { secs: abs(*self.secs) } - } - - fn zero() -> TimeDelta { - TimeDelta { secs: 0 } - } - - const MIN: TimeDelta = TimeDelta { secs: -Bounded::::MAX / MILLIS_PER_SEC }; - - const MAX: TimeDelta = TimeDelta { secs: Bounded::::MAX / MILLIS_PER_SEC }; -} - -impl TimeDeltaDefault of Default { - fn default() -> TimeDelta { - TimeDeltaTrait::zero() - } -} - -impl TimeDeltaNeg of Neg { - fn neg(a: TimeDelta) -> TimeDelta { - TimeDelta { secs: -a.secs } - } -} - -impl TimeDeltaAdd of Add { - fn add(lhs: TimeDelta, rhs: TimeDelta) -> TimeDelta { - lhs.checked_add(rhs).unwrap() - } -} - -impl TimeDeltaSub of Sub { - fn sub(lhs: TimeDelta, rhs: TimeDelta) -> TimeDelta { - lhs.checked_sub(rhs).unwrap() - } -} - -impl TimeDeltaPartialOrd of PartialOrd { - fn lt(lhs: TimeDelta, rhs: TimeDelta) -> bool { - lhs.secs < rhs.secs - } - fn ge(lhs: TimeDelta, rhs: TimeDelta) -> bool { - lhs.secs >= rhs.secs - } -} - -impl TimeDeltaDebug of Debug { - /// Format a `TimeDelta` using the [ISO 8601] format - /// - /// [ISO 8601]: https://en.wikipedia.org/wiki/ISO_8601#Durations - fn fmt(self: @TimeDelta, ref f: Formatter) -> Result<(), Error> { - // technically speaking, negative duration is not valid ISO 8601, - // but we need to print it anyway. - let (abs, sign) = if *self.secs < 0 { - (-*self, '-') - } else { - (*self, '') - }; - - if sign == '-' { - f.buffer.append_byte('-'); - } - f.buffer.append_byte('P'); - // Plenty of ways to encode an empty string. `P0D` is short and not too strange. - if abs.secs == 0 { - return write!(f, "0D"); - } - - write!(f, "T{}", abs.secs)?; - - f.buffer.append_byte('S'); - Result::Ok(()) - } -} - -impl TimeDeltaDisplay of Display { - fn fmt(self: @TimeDelta, ref f: Formatter) -> Result<(), Error> { - Debug::fmt(self, ref f) - } -} diff --git a/examples/cairo/scripts/datetime/src/utils.cairo b/examples/cairo/scripts/datetime/src/utils.cairo deleted file mode 100644 index 5139250..0000000 --- a/examples/cairo/scripts/datetime/src/utils.cairo +++ /dev/null @@ -1,94 +0,0 @@ -use core::num::traits::zero::Zero; -use core::num::traits::{CheckedSub, Pow}; - -const TWO_POW_0: u32 = 2_u32.pow(0); -const TWO_POW_1: u32 = 2_u32.pow(1); -const TWO_POW_2: u32 = 2_u32.pow(2); -const TWO_POW_3: u32 = 2_u32.pow(3); -const TWO_POW_4: u32 = 2_u32.pow(4); -const TWO_POW_5: u32 = 2_u32.pow(5); -const TWO_POW_6: u32 = 2_u32.pow(6); -const TWO_POW_7: u32 = 2_u32.pow(7); -const TWO_POW_8: u32 = 2_u32.pow(8); -const TWO_POW_9: u32 = 2_u32.pow(9); -const TWO_POW_10: u32 = 2_u32.pow(10); -const TWO_POW_11: u32 = 2_u32.pow(11); -const TWO_POW_12: u32 = 2_u32.pow(12); -const TWO_POW_13: u32 = 2_u32.pow(13); -const TWO_POW_14: u32 = 2_u32.pow(14); -const TWO_POW_15: u32 = 2_u32.pow(15); - -fn shift_to_power_of_2(shift: u8) -> u32 { - match shift { - 0 => TWO_POW_0, - 1 => TWO_POW_1, - 2 => TWO_POW_2, - 3 => TWO_POW_3, - 4 => TWO_POW_4, - 5 => TWO_POW_5, - 6 => TWO_POW_6, - 7 => TWO_POW_7, - 8 => TWO_POW_8, - 9 => TWO_POW_9, - 10 => TWO_POW_10, - 11 => TWO_POW_11, - 12 => TWO_POW_12, - 13 => TWO_POW_13, - 14 => TWO_POW_14, - 15 => TWO_POW_15, - _ => 0, - } -} - -pub fn ushl(val: u32, shift: u8) -> u32 { - val * shift_to_power_of_2(shift) -} - -pub fn ushr(val: u32, shift: u8) -> u32 { - val / shift_to_power_of_2(shift) -} - -pub fn shl(val: i32, shift: u8) -> i32 { - val * shift_to_power_of_2(shift).try_into().unwrap() -} - -pub fn shr(val: i32, shift: u8) -> i32 { - val / shift_to_power_of_2(shift).try_into().unwrap() -} - -pub fn abs, +Neg, +Zero, +Copy, +Drop>(n: T) -> T { - if n < Zero::::zero() { - -n - } else { - n - } -} - -pub fn rem_euclid, +Add, +PartialOrd, +Neg, +Zero, +Copy, +Drop>( - val: T, div: T, -) -> T { - let val_mod_div = val % div; - if val_mod_div < Zero::::zero() { - val_mod_div + abs(div) - } else { - val_mod_div - } -} - -pub fn div_euclid< - T, - +CheckedSub, - +Div, - +Rem, - +Add, - +PartialOrd, - +Neg, - +Zero, - +Copy, - +Drop, ->( - val: T, div: T, -) -> Option { - let r = rem_euclid(val, div); - Some((val.checked_sub(r)?) / div) -} diff --git a/examples/cairo/scripts/datetime/tests/internals.cairo b/examples/cairo/scripts/datetime/tests/internals.cairo deleted file mode 100644 index 5edd1b7..0000000 --- a/examples/cairo/scripts/datetime/tests/internals.cairo +++ /dev/null @@ -1,247 +0,0 @@ -use core::num::traits::Bounded; -use datetime::internals::{ - A, AG, B, BA, C, CB, D, DC, E, ED, F, FE, G, GF, MDL_TO_OL, Mdf, MdfTrait, YearFlags, - YearFlagsTrait, -}; - -#[test] -fn test_year_flags_ndays_from_year() { - assert_eq!(YearFlagsTrait::from_year(2014).ndays(), 365); - assert_eq!(YearFlagsTrait::from_year(2012).ndays(), 366); - assert_eq!(YearFlagsTrait::from_year(2000).ndays(), 366); - assert_eq!(YearFlagsTrait::from_year(1900).ndays(), 365); - assert_eq!(YearFlagsTrait::from_year(1600).ndays(), 366); - assert_eq!(YearFlagsTrait::from_year(1).ndays(), 365); - assert_eq!(YearFlagsTrait::from_year(0).ndays(), 366); // 1 BCE (proleptic Gregorian) - assert_eq!(YearFlagsTrait::from_year(-1).ndays(), 365); // 2 BCE - assert_eq!(YearFlagsTrait::from_year(-4).ndays(), 366); // 5 BCE - assert_eq!(YearFlagsTrait::from_year(-99).ndays(), 365); // 100 BCE - assert_eq!(YearFlagsTrait::from_year(-100).ndays(), 365); // 101 BCE - assert_eq!(YearFlagsTrait::from_year(-399).ndays(), 365); // 400 BCE - assert_eq!(YearFlagsTrait::from_year(-400).ndays(), 366); // 401 BCE -} - -#[test] -fn test_year_flags_nisoweeks() { - assert_eq!(A.nisoweeks(), 52); - assert_eq!(B.nisoweeks(), 52); - assert_eq!(C.nisoweeks(), 52); - assert_eq!(D.nisoweeks(), 53); - assert_eq!(E.nisoweeks(), 52); - assert_eq!(F.nisoweeks(), 52); - assert_eq!(G.nisoweeks(), 52); - assert_eq!(AG.nisoweeks(), 52); - assert_eq!(BA.nisoweeks(), 52); - assert_eq!(CB.nisoweeks(), 52); - assert_eq!(DC.nisoweeks(), 53); - assert_eq!(ED.nisoweeks(), 53); - assert_eq!(FE.nisoweeks(), 52); - assert_eq!(GF.nisoweeks(), 52); -} - -const NONLEAP_FLAGS: [YearFlags; 7] = [A, B, C, D, E, F, G]; -const LEAP_FLAGS: [YearFlags; 7] = [AG, BA, CB, DC, ED, FE, GF]; -const FLAGS: [YearFlags; 14] = [A, B, C, D, E, F, G, AG, BA, CB, DC, ED, FE, GF]; - -fn is_valid_mdf(mdf: Mdf) -> bool { - *MDL_TO_OL.span()[mdf.mdf / 8] > 0 -} - -fn check_mdf_valid( - expected: bool, flags: YearFlags, month1: u32, day1: u32, month2: u32, day2: u32, -) { - for month in month1..month2 + 1 { - for day in day1..day2 + 1 { - let mdf_opt = MdfTrait::new(month, day, flags); - if mdf_opt.is_none() { - if !expected { - continue; - } - panic!("Mdf::new({}, {}, {:?}) returned None", month, day, flags); - } - let mdf = mdf_opt.unwrap(); - - assert!( - is_valid_mdf(mdf) == expected, - "month {} day {} = {:?} should be {} for dominical year {:?}", - month, - day, - mdf, - if expected { - 'valid' - } else { - 'invalid' - }, - flags, - ); - }; - }; -} - -#[test] -fn test_mdf_valid() { - for i in 0_usize..7 { - let flags = *NONLEAP_FLAGS.span()[i]; - check_mdf_valid(false, flags, 0, 0, 0, 1024); - check_mdf_valid(false, flags, 0, 0, 16, 0); - check_mdf_valid(true, flags, 1, 1, 1, 31); - check_mdf_valid(false, flags, 1, 32, 1, 1024); - check_mdf_valid(true, flags, 2, 1, 2, 28); - check_mdf_valid(false, flags, 2, 29, 2, 1024); - check_mdf_valid(true, flags, 3, 1, 3, 31); - check_mdf_valid(false, flags, 3, 32, 3, 1024); - check_mdf_valid(true, flags, 4, 1, 4, 30); - check_mdf_valid(false, flags, 4, 31, 4, 1024); - check_mdf_valid(true, flags, 5, 1, 5, 31); - check_mdf_valid(false, flags, 5, 32, 5, 1024); - check_mdf_valid(true, flags, 6, 1, 6, 30); - check_mdf_valid(false, flags, 6, 31, 6, 1024); - check_mdf_valid(true, flags, 7, 1, 7, 31); - check_mdf_valid(false, flags, 7, 32, 7, 1024); - check_mdf_valid(true, flags, 8, 1, 8, 31); - check_mdf_valid(false, flags, 8, 32, 8, 1024); - check_mdf_valid(true, flags, 9, 1, 9, 30); - check_mdf_valid(false, flags, 9, 31, 9, 1024); - check_mdf_valid(true, flags, 10, 1, 10, 31); - check_mdf_valid(false, flags, 10, 32, 10, 1024); - check_mdf_valid(true, flags, 11, 1, 11, 30); - check_mdf_valid(false, flags, 11, 31, 11, 1024); - check_mdf_valid(true, flags, 12, 1, 12, 31); - check_mdf_valid(false, flags, 12, 32, 12, 1024); - check_mdf_valid(false, flags, 13, 0, 16, 1024); - check_mdf_valid(false, flags, Bounded::::MAX - 1, 0, Bounded::::MAX - 1, 1024); - check_mdf_valid(false, flags, 0, Bounded::::MAX - 1, 16, Bounded::::MAX - 1); - check_mdf_valid( - false, - flags, - Bounded::::MAX - 1, - Bounded::::MAX - 1, - Bounded::::MAX - 1, - Bounded::::MAX - 1, - ); - } - for i in 0_usize..7 { - let flags = *LEAP_FLAGS.span()[i]; - check_mdf_valid(false, flags, 0, 0, 0, 1024); - check_mdf_valid(false, flags, 0, 0, 16, 0); - check_mdf_valid(true, flags, 1, 1, 1, 31); - check_mdf_valid(false, flags, 1, 32, 1, 1024); - check_mdf_valid(true, flags, 2, 1, 2, 29); - check_mdf_valid(false, flags, 2, 30, 2, 1024); - check_mdf_valid(true, flags, 3, 1, 3, 31); - check_mdf_valid(false, flags, 3, 32, 3, 1024); - check_mdf_valid(true, flags, 4, 1, 4, 30); - check_mdf_valid(false, flags, 4, 31, 4, 1024); - check_mdf_valid(true, flags, 5, 1, 5, 31); - check_mdf_valid(false, flags, 5, 32, 5, 1024); - check_mdf_valid(true, flags, 6, 1, 6, 30); - check_mdf_valid(false, flags, 6, 31, 6, 1024); - check_mdf_valid(true, flags, 7, 1, 7, 31); - check_mdf_valid(false, flags, 7, 32, 7, 1024); - check_mdf_valid(true, flags, 8, 1, 8, 31); - check_mdf_valid(false, flags, 8, 32, 8, 1024); - check_mdf_valid(true, flags, 9, 1, 9, 30); - check_mdf_valid(false, flags, 9, 31, 9, 1024); - check_mdf_valid(true, flags, 10, 1, 10, 31); - check_mdf_valid(false, flags, 10, 32, 10, 1024); - check_mdf_valid(true, flags, 11, 1, 11, 30); - check_mdf_valid(false, flags, 11, 31, 11, 1024); - check_mdf_valid(true, flags, 12, 1, 12, 31); - check_mdf_valid(false, flags, 12, 32, 12, 1024); - check_mdf_valid(false, flags, 13, 0, 16, 1024); - check_mdf_valid(false, flags, Bounded::::MAX - 1, 0, Bounded::::MAX - 1, 1024); - check_mdf_valid(false, flags, 0, Bounded::::MAX - 1, 16, Bounded::::MAX - 1); - check_mdf_valid( - false, - flags, - Bounded::::MAX - 1, - Bounded::::MAX - 1, - Bounded::::MAX - 1, - Bounded::::MAX - 1, - ); - }; -} - -#[test] -fn test_mdf_fields() { - for i in 0_usize..14 { - let flags = *FLAGS.span()[i]; - for month in 1_usize..13 { - for day in 1_usize..31 { - let mdf_opt = MdfTrait::new(month, day, flags); - if mdf_opt.is_none() { - continue; - } - let mdf = mdf_opt.unwrap(); - if is_valid_mdf(mdf) { - assert_eq!(mdf.month(), month); - assert_eq!(mdf.day(), day); - } - }; - }; - }; -} - -fn check_mdf_with_fields(flags: YearFlags, month: u32, day: u32) { - let mdf = MdfTrait::new(month, day, flags).unwrap(); - for month in 0_usize..17 { - let mdf_opt = mdf.with_month(month); - if mdf_opt.is_none() { - if month > 12 { - continue; - } - panic!("failed to create Mdf with month {}", month); - } - let mdf = mdf_opt.unwrap(); - - if is_valid_mdf(mdf) { - assert_eq!(mdf.month(), month); - assert_eq!(mdf.day(), day); - } - } - for day in 0_usize..1025 { - let mdf_opt = mdf.with_day(day); - if mdf_opt.is_none() { - if day > 31 { - continue; - } - // TODO panic!("failed to create Mdf with month {}", month), - panic!("failed to create Mdf with day {}", day); - } - let mdf = mdf_opt.unwrap(); - - if is_valid_mdf(mdf) { - assert_eq!(mdf.month(), month); - assert_eq!(mdf.day(), day); - } - }; -} - -#[test] -fn test_mdf_with_fields() { - for i in 0_usize..7 { - let flags = *NONLEAP_FLAGS.span()[i]; - check_mdf_with_fields(flags, 1, 1); - check_mdf_with_fields(flags, 1, 31); - check_mdf_with_fields(flags, 2, 1); - check_mdf_with_fields(flags, 2, 28); - check_mdf_with_fields(flags, 2, 29); - check_mdf_with_fields(flags, 12, 31); - } - for i in 0_usize..7 { - let flags = *LEAP_FLAGS.span()[i]; - check_mdf_with_fields(flags, 1, 1); - check_mdf_with_fields(flags, 1, 31); - check_mdf_with_fields(flags, 2, 1); - check_mdf_with_fields(flags, 2, 29); - check_mdf_with_fields(flags, 2, 30); - check_mdf_with_fields(flags, 12, 31); - }; -} - -#[test] -fn test_mdf_new_range() { - let flags = YearFlagsTrait::from_year(2023); - assert!(MdfTrait::new(13, 1, flags).is_none()); - assert!(MdfTrait::new(1, 32, flags).is_none()); -} diff --git a/examples/cairo/scripts/datetime/tests/time_delta.cairo b/examples/cairo/scripts/datetime/tests/time_delta.cairo deleted file mode 100644 index 3fc9824..0000000 --- a/examples/cairo/scripts/datetime/tests/time_delta.cairo +++ /dev/null @@ -1,129 +0,0 @@ -use core::num::traits::Bounded; -use datetime::time_delta::TimeDeltaTrait; - -#[test] -fn test_duration() { - assert!(TimeDeltaTrait::seconds(1) != TimeDeltaTrait::zero()); - assert_eq!(TimeDeltaTrait::seconds(1) + TimeDeltaTrait::seconds(2), TimeDeltaTrait::seconds(3)); - assert_eq!( - TimeDeltaTrait::seconds(86_399) + TimeDeltaTrait::seconds(4), - TimeDeltaTrait::days(1) + TimeDeltaTrait::seconds(3), - ); - assert_eq!( - TimeDeltaTrait::days(10) - TimeDeltaTrait::seconds(1000), TimeDeltaTrait::seconds(863_000), - ); - assert_eq!( - TimeDeltaTrait::days(10) - TimeDeltaTrait::seconds(1_000_000), - TimeDeltaTrait::seconds(-136_000), - ); - // assert_eq!( - // days(2) + seconds(86_399) + TimeDelta::nanoseconds(1_234_567_890), - // days(3) + TimeDelta::nanoseconds(234_567_890), - // ); - assert_eq!(-TimeDeltaTrait::days(3), TimeDeltaTrait::days(-3)); - assert_eq!( - -(TimeDeltaTrait::days(3) + TimeDeltaTrait::seconds(70)), - TimeDeltaTrait::days(-4) + TimeDeltaTrait::seconds(86_400 - 70), - ); - - assert_eq!( - Default::default() + TimeDeltaTrait::minutes(1) - TimeDeltaTrait::seconds(30), - TimeDeltaTrait::seconds(30), - ); -} - -#[test] -fn test_duration_num_days() { - assert_eq!(TimeDeltaTrait::zero().num_days(), 0); - assert_eq!(TimeDeltaTrait::days(1).num_days(), 1); - assert_eq!(TimeDeltaTrait::days(-1).num_days(), -1); - assert_eq!(TimeDeltaTrait::seconds(86_399).num_days(), 0); - assert_eq!(TimeDeltaTrait::seconds(86_401).num_days(), 1); - assert_eq!(TimeDeltaTrait::seconds(-86_399).num_days(), 0); - assert_eq!(TimeDeltaTrait::seconds(-86_401).num_days(), -1); - assert_eq!( - TimeDeltaTrait::days(Bounded::::MAX.try_into().unwrap()).num_days(), - Bounded::::MAX.try_into().unwrap(), - ); - assert_eq!( - TimeDeltaTrait::days(Bounded::::MIN.try_into().unwrap()).num_days(), - Bounded::::MIN.try_into().unwrap(), - ); -} - -#[test] -fn test_duration_num_seconds() { - assert_eq!(TimeDeltaTrait::zero().num_seconds(), 0); - assert_eq!(TimeDeltaTrait::seconds(1).num_seconds(), 1); - assert_eq!(TimeDeltaTrait::seconds(-1).num_seconds(), -1); -} - -#[test] -fn test_duration_seconds_max_allowed() { - let duration = TimeDeltaTrait::seconds(Bounded::::MAX / 1_000); - assert_eq!(duration.num_seconds(), Bounded::::MAX / 1_000); -} - -#[test] -fn test_duration_seconds_max_overflow() { - assert!(TimeDeltaTrait::try_seconds(Bounded::::MAX / 1_000 + 1).is_none()); -} - -#[test] -#[should_panic(expected: 'Option::unwrap failed.')] -fn test_duration_seconds_max_overflow_panic() { - let _ = TimeDeltaTrait::seconds(Bounded::::MAX / 1_000 + 1); -} - -#[test] -fn test_duration_seconds_min_allowed() { - let duration = TimeDeltaTrait::seconds( - Bounded::::MIN / 1_000, - ); // Same as -i64::MAX / 1_000 due to rounding - assert_eq!( - duration.num_seconds(), Bounded::::MIN / 1_000, - ); // Same as -i64::MAX / 1_000 due to rounding -} - -#[test] -fn test_duration_seconds_min_underflow() { - assert!(TimeDeltaTrait::try_seconds(-Bounded::::MAX / 1_000 - 1).is_none()); -} - -#[test] -#[should_panic(expected: 'Option::unwrap failed.')] -fn test_duration_seconds_min_underflow_panic() { - let _ = TimeDeltaTrait::seconds(-Bounded::::MAX / 1_000 - 1); -} - -#[test] -fn test_duration_ord() { - assert!(TimeDeltaTrait::seconds(1) < TimeDeltaTrait::seconds(2)); - assert!(TimeDeltaTrait::seconds(2) > TimeDeltaTrait::seconds(1)); - assert!(TimeDeltaTrait::seconds(-1) > TimeDeltaTrait::seconds(-2)); - assert!(TimeDeltaTrait::seconds(-2) < TimeDeltaTrait::seconds(-1)); - assert!(TimeDeltaTrait::seconds(-1) < TimeDeltaTrait::seconds(1)); - assert!(TimeDeltaTrait::seconds(1) > TimeDeltaTrait::seconds(-1)); - assert!(TimeDeltaTrait::seconds(0) < TimeDeltaTrait::seconds(1)); - assert!(TimeDeltaTrait::seconds(0) > TimeDeltaTrait::seconds(-1)); - assert!(TimeDeltaTrait::seconds(1_001) < TimeDeltaTrait::seconds(1_002)); - assert!(TimeDeltaTrait::seconds(-1_001) > TimeDeltaTrait::seconds(-1_002)); - assert!(TimeDeltaTrait::seconds(1_234_567_890) < TimeDeltaTrait::seconds(1_234_567_891)); - assert!(TimeDeltaTrait::seconds(-1_234_567_890) > TimeDeltaTrait::seconds(-1_234_567_891)); - // assert!(milliseconds(i64::MAX) > milliseconds(i64::MAX - 1)); -// assert!(milliseconds(-i64::MAX) < milliseconds(-i64::MAX + 1)); -} - -#[test] -fn test_duration_abs() { - assert_eq!(TimeDeltaTrait::seconds(1300).abs(), TimeDeltaTrait::seconds(1300)); - assert_eq!(TimeDeltaTrait::seconds(1000).abs(), TimeDeltaTrait::seconds(1000)); - assert_eq!(TimeDeltaTrait::seconds(300).abs(), TimeDeltaTrait::seconds(300)); - assert_eq!(TimeDeltaTrait::seconds(0).abs(), TimeDeltaTrait::seconds(0)); - assert_eq!(TimeDeltaTrait::seconds(-300).abs(), TimeDeltaTrait::seconds(300)); - assert_eq!(TimeDeltaTrait::seconds(-700).abs(), TimeDeltaTrait::seconds(700)); - assert_eq!(TimeDeltaTrait::seconds(-1000).abs(), TimeDeltaTrait::seconds(1000)); - assert_eq!(TimeDeltaTrait::seconds(-1300).abs(), TimeDeltaTrait::seconds(1300)); - assert_eq!(TimeDeltaTrait::seconds(-1700).abs(), TimeDeltaTrait::seconds(1700)); - // assert_eq!(TimeDeltaTrait::seconds(-i64::MAX).abs(), TimeDeltaTrait::seconds(i64::MAX)); -} diff --git a/packages/chrono/README.md b/packages/chrono/README.md new file mode 100644 index 0000000..f95f099 --- /dev/null +++ b/packages/chrono/README.md @@ -0,0 +1,130 @@ +# Chrono: Date and Time for Cairo + +Chrono aims to provide all functionality needed to do correct operations on dates and times in the [proleptic Gregorian calendar](https://en.wikipedia.org/wiki/Proleptic_Gregorian_calendar). +This library is a Cairo port of a subset of the [Rust library with the same name](https://docs.rs/chrono/latest/chrono/), without timezone support. + +## Overview + +### [Time Delta](#time-delta) + +Chrono has a `TimeDelta` type to represent the magnitude of a time span. This is an “accurate” duration represented as seconds, and does not represent “nominal” components such as days or months. + +### [Date and Time](#date-time) + +Chrono provides a `DateTime` type to represent an ISO 8601 combined date and time without timezone. + +You can create your own date and time using a rich combination of initialization methods. + +```cairo +use chrono::prelude::*; + +let ymdhms = |y, m, d, h, n, s| { + DateTrait::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap() +}; + +let dt = ymdhms(2014, 7, 8, 9, 10, 11); // `2014-07-08T09:10:11` +assert_eq!(format!("{}", dt), "2014-07-08 09:10:11"); +// July 8 is 188th day of the year 2014 (`o` for "ordinal") +assert_eq!(dt, DateTrait::from_yo_opt(2014, 189).unwrap().and_hms_opt(9, 10, 11).unwrap()); +// July 8 is Tuesday in ISO week 28 of the year 2014. +assert_eq!( + dt, + DateTrait::from_isoywd_opt(2014, 28, Weekday::Tue).unwrap().and_hms_opt(9, 10, 11).unwrap(), +); +``` + +Various properties are available to the date and time, and can be altered individually. Most of them are defined in the traits `Datelike` and `Timelike` which you should `use` before. Addition and subtraction is also supported. The following illustrates most supported operations to the date and time: + +```cairo +use chrono::prelude::*; + +let ymdhms = |y, m, d, h, n, s| { + DateTrait::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap() +}; + +// assume this returned `2014-11-28T21:45:59`: +let dt = ymdhms(2014, 11, 28, 21, 45, 59); +// property accessors +assert_eq!((dt.year(), dt.month(), dt.day()), (2014, 11, 28)); +assert_eq!((dt.month0(), dt.day0()), (10, 27)); // for unfortunate souls +assert_eq!((dt.hour(), dt.minute(), dt.second()), (21, 45, 59)); +assert_eq!(dt.weekday(), Weekday::Fri); +assert_eq!(dt.weekday().number_from_monday(), 5); // Mon=1, ..., Sun=7 +assert_eq!(dt.ordinal(), 332); // the day of year +assert_eq!(dt.num_days_from_ce(), 735565); // the number of days from and including Jan 1, 1 + +// a sample of property manipulations (validates dynamically) +assert_eq!(dt.with_day(29).unwrap().weekday(), Weekday::Sat); // 2014-11-29 is Saturday +assert_eq!(dt.with_day(32), None); + +// arithmetic operations +let dt1 = ymdhms(2014, 11, 14, 8, 9, 10); +let dt2 = ymdhms(2014, 11, 14, 10, 9, 8); +assert_eq!(dt1.signed_duration_since(dt2), TimeDeltaTrait::seconds(-2 * 3600 + 2)); +assert_eq!(dt2.signed_duration_since(dt1), TimeDeltaTrait::seconds(2 * 3600 - 2)); +assert_eq!( + ymdhms(1970, 1, 1, 0, 0, 0) + .checked_add_signed(TimeDeltaTrait::seconds(1_000_000_000)) + .unwrap(), + ymdhms(2001, 9, 9, 1, 46, 40), +); +assert_eq!( + ymdhms(1970, 1, 1, 0, 0, 0) + .checked_sub_signed(TimeDeltaTrait::seconds(1_000_000_000)) + .unwrap(), + ymdhms(1938, 4, 24, 22, 13, 20), +); +``` + +### [Formatting](#formatting) + +At the moment we only support simple formatting through the `Display` trait to format the date and time to a `ByteArray` using this format: `yyyy-MM-dd HH:mm:ss`. + +```cairo +use chrono::prelude::*; + +let dt: DateTime = Default::default(); +assert_eq!(format!("{}", dt), "1970-01-01 00:00:00"); +``` + +### [Conversion from and to EPOCH timestamps](#timestamps) + +Use `DateTimeTrait::from_timestamp(i64: secs)` to construct a `DateTime` from a UNIX timestamp (seconds that passed since January 1st 1970). + +Use `DateTimeTrait.timestamp` to get the timestamp (in seconds) from a `DateTime`. + +```cairo +use chrono::prelude::*; + +let ymdhms = |y, m, d, h, n, s| { + DateTrait::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap() +}; + +// Construct a datetime from epoch: +let dt = DateTimeTrait::from_timestamp(1_500_000_000).unwrap(); +assert_eq!(format!("{}", dt), "2017-07-14 02:40:00"); + +// Get epoch value from a datetime: +let dt = ymdhms(2017, 7, 14, 2, 40, 0); +assert_eq!(dt.timestamp(), 1_500_000_000); +``` + +Alternatively, you can use `DateTimeTrait::from_block_timestamp(u64: block_timestamp)` to construct a `DateTime` from a Starknet block timestamp. + +```cairo +start_cheat_block_timestamp_global(1707868800); +assert_eq!( + format!("{}", DateTimeTrait::from_block_timestamp(get_block_timestamp()).unwrap()), + "2024-02-14 00:00:00", +); +``` + +## Limitations + +- Only the proleptic Gregorian calendar (i.e. extended to support older dates) is supported. +- Date types are limited to about + 262,000 years from the common epoch. +- At the moment negative years are not supported. + +## Cairo version requirements + +The Minimum Supported Cairo Version is currently Cairo 2.11.2. diff --git a/packages/chrono/Scarb.toml b/packages/chrono/Scarb.toml new file mode 100644 index 0000000..aadde53 --- /dev/null +++ b/packages/chrono/Scarb.toml @@ -0,0 +1,39 @@ +[package] +name = "chrono" +readme = "README.md" +keywords = [ + "date", + "time", + "chrono", +] +version.workspace = true +edition.workspace = true +cairo-version.workspace = true +scarb-version.workspace = true +authors.workspace = true +description.workspace = true +documentation.workspace = true +homepage.workspace = true +repository.workspace = true +license-file.workspace = true + +[tool] +fmt.workspace = true +scarb.workspace = true + +[dependencies] +starknet.workspace = true + +[dev-dependencies] +assert_macros.workspace = true +snforge_std.workspace = true + +[lib] + +[[target.starknet-contract]] +allowed-libfuncs-list.name = "experimental" +sierra = true +casm = false + +[scripts] +test.workspace = true diff --git a/examples/cairo/scripts/datetime/src/date.cairo b/packages/chrono/src/date.cairo similarity index 78% rename from examples/cairo/scripts/datetime/src/date.cairo rename to packages/chrono/src/date.cairo index d1f3f9d..93bfedc 100644 --- a/examples/cairo/scripts/datetime/src/date.cairo +++ b/packages/chrono/src/date.cairo @@ -12,15 +12,19 @@ use core::fmt::{Debug, Display, Error, Formatter}; use core::num::traits::{Bounded, CheckedAdd, Pow}; -use datetime::Days; -use datetime::datetime::{DateTime, DateTimeTrait}; -use datetime::format::formatting::write_hundreds; -use datetime::internals::{Mdf, MdfTrait, YearFlags, YearFlagsTrait}; -use datetime::month::{Months, MonthsTrait}; -use datetime::time::{Time, TimeTrait}; -use datetime::time_delta::{TimeDelta, TimeDeltaTrait}; -use datetime::utils::{div_euclid, rem_euclid, shr, ushl, ushr}; -use datetime::weekday::{Weekday, WeekdayTrait}; +use core::ops::RangeInclusiveTrait; +use super::datetime::{DateTime, DateTimeTrait}; +use super::days::Days; +use super::format::formatting::write_hundreds; +use super::internals::{Mdf, MdfTrait, YearFlags, YearFlagsTrait}; +use super::isoweek::{IsoWeek, IsoWeekTrait}; +use super::months::{Months, MonthsTrait}; +use super::time::{Time, TimeTrait}; +use super::time_delta::{TimeDelta, TimeDeltaTrait}; +use super::traits::Datelike; +use super::utils::{div_euclid, rem_euclid, u32_shl, u32_shr}; +use super::week::{Week, WeekTrait}; +use super::weekday::{Weekday, WeekdayTrait}; /// ISO 8601 calendar date without timezone. /// Allows for every [proleptic Gregorian date] from Jan 1, 262145 BCE to Dec 31, 262143 CE. @@ -70,13 +74,20 @@ use datetime::weekday::{Weekday, WeekdayTrait}; /// This is currently the internal format of Chrono's date types. /// /// [proleptic Gregorian date]: crate::NaiveDate#calendar-date -#[derive(Clone, Copy, PartialEq, Drop)] +#[derive(Clone, Copy, PartialEq, Drop, Serde, starknet::Store)] pub struct Date { - pub yof: u32 // (year << 13) | of + pub(crate) yof: u32 // (year << 13) | of } #[generate_trait] pub impl DateImpl of DateTrait { + fn weeks_from(self: @Date, day: Weekday) -> i32 { + (self.ordinal().try_into().unwrap() + - self.weekday().days_since(day).try_into().unwrap() + + 6) + / 7 + } + /// Makes a new `NaiveDate` from year, ordinal and flags. /// Does not check whether the flags are correct for the provided year. fn from_ordinal_and_flags(year: u32, ordinal: u32, flags: YearFlags) -> Option { @@ -87,7 +98,7 @@ pub impl DateImpl of DateTrait { return None; // Invalid } // debug_assert!(YearFlags::from_year(year).0 == flags.0); - let yof = ushl(year, 13) | ushl(ordinal, 4) | flags.flags.into(); + let yof = u32_shl(year, 13) | u32_shl(ordinal, 4) | flags.flags.into(); match yof & OL_MASK <= MAX_OL { true => Some(Self::from_yof(yof)), false => None // Does not exist: Ordinal 366 in a common year. @@ -100,7 +111,7 @@ pub impl DateImpl of DateTrait { if year < MIN_YEAR || year > MAX_YEAR { return None; // Out-of-range } - Some(Self::from_yof(ushl(year, 13) | mdf.ordinal_and_flags()?)) + Some(Self::from_yof(u32_shl(year, 13) | mdf.ordinal_and_flags()?)) } /// Makes a new `NaiveDate` from the [calendar date](#calendar-date) @@ -312,291 +323,6 @@ pub impl DateImpl of DateTrait { Self::from_ymd_opt(year, month, day) } - /// Returns the packed month-day-flags. - fn mdf(self: @Date) -> Mdf { - let ol = ushr((self.yof() & OL_MASK), 3); - MdfTrait::from_ol(ol.try_into().unwrap(), self.year_flags()) - } - - /// Makes a new `NaiveDate` with the packed month-day-flags changed. - /// - /// Returns `None` when the resulting `NaiveDate` would be invalid. - fn with_mdf(self: @Date, mdf: Mdf) -> Option { - // debug_assert!(self.year_flags().0 == mdf.year_flags().0); - match mdf.ordinal() { - Some(ordinal) => { - Some(Self::from_yof((self.yof() & NOT_ORDINAL_MASK) | ushl(ordinal, 4))) - }, - None => None // Non-existing date - } - } - - /// Makes a new `NaiveDate` for the next calendar date. - /// - /// # Errors - /// - /// Returns `None` when `self` is the last representable date. - /// - /// # Example - /// - /// ``` - /// use chrono::NaiveDate; - /// - /// assert_eq!( - /// NaiveDate::from_ymd_opt(2015, 6, 3).unwrap().succ_opt(), - /// Some(NaiveDate::from_ymd_opt(2015, 6, 4).unwrap()) - /// ); - /// assert_eq!(NaiveDate::MAX.succ_opt(), None); - /// ``` - fn succ_opt(self: @Date) -> Option { - let new_ol = (self.yof() & OL_MASK) + ushl(1, 4); - match new_ol <= MAX_OL { - true => Some(Self::from_yof(self.yof() & NOT_OL_MASK | new_ol)), - false => Self::from_yo_opt(self.year() + 1, 1), - } - } - - /// Makes a new `NaiveDate` for the previous calendar date. - /// - /// # Errors - /// - /// Returns `None` when `self` is the first representable date. - /// - /// # Example - /// - /// ``` - /// use chrono::NaiveDate; - /// - /// assert_eq!( - /// NaiveDate::from_ymd_opt(2015, 6, 3).unwrap().pred_opt(), - /// Some(NaiveDate::from_ymd_opt(2015, 6, 2).unwrap()) - /// ); - /// assert_eq!(NaiveDate::MIN.pred_opt(), None); - /// ``` - fn pred_opt(self: @Date) -> Option { - let new_shifted_ordinal = (self.yof() & ORDINAL_MASK) - ushl(1, 4); - match new_shifted_ordinal > 0 { - true => Some(Self::from_yof(self.yof() & NOT_ORDINAL_MASK | new_shifted_ordinal)), - false => { - if self.year() == 0 { - return None; - } - Self::from_ymd_opt(self.year() - 1, 12, 31) - }, - } - } - - /// Adds the number of whole days in the given `TimeDelta` to the current date. - /// - /// # Errors - /// - /// Returns `None` if the resulting date would be out of range. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDate, TimeDelta}; - /// - /// let d = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap(); - /// assert_eq!( - /// d.checked_add_signed(TimeDelta::try_days(40).unwrap()), - /// Some(NaiveDate::from_ymd_opt(2015, 10, 15).unwrap()) - /// ); - /// assert_eq!( - /// d.checked_add_signed(TimeDelta::try_days(-40).unwrap()), - /// Some(NaiveDate::from_ymd_opt(2015, 7, 27).unwrap()) - /// ); - /// assert_eq!(d.checked_add_signed(TimeDelta::try_days(1_000_000_000).unwrap()), None); - /// assert_eq!(d.checked_add_signed(TimeDelta::try_days(-1_000_000_000).unwrap()), None); - /// assert_eq!(NaiveDate::MAX.checked_add_signed(TimeDelta::try_days(1).unwrap()), None); - /// ``` - fn checked_add_signed(self: @Date, rhs: TimeDelta) -> Option { - let days = rhs.num_days(); - if days < Bounded::::MIN.try_into().unwrap() - || days > Bounded::::MAX.try_into().unwrap() { - return None; - } - self.add_days(days.try_into().unwrap()) - } - - /// Subtracts the number of whole days in the given `TimeDelta` from the current date. - /// - /// # Errors - /// - /// Returns `None` if the resulting date would be out of range. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDate, TimeDelta}; - /// - /// let d = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap(); - /// assert_eq!( - /// d.checked_sub_signed(TimeDelta::try_days(40).unwrap()), - /// Some(NaiveDate::from_ymd_opt(2015, 7, 27).unwrap()) - /// ); - /// assert_eq!( - /// d.checked_sub_signed(TimeDelta::try_days(-40).unwrap()), - /// Some(NaiveDate::from_ymd_opt(2015, 10, 15).unwrap()) - /// ); - /// assert_eq!(d.checked_sub_signed(TimeDelta::try_days(1_000_000_000).unwrap()), None); - /// assert_eq!(d.checked_sub_signed(TimeDelta::try_days(-1_000_000_000).unwrap()), None); - /// assert_eq!(NaiveDate::MIN.checked_sub_signed(TimeDelta::try_days(1).unwrap()), None); - /// ``` - fn checked_sub_signed(self: @Date, rhs: TimeDelta) -> Option { - let days = -rhs.num_days(); - if days < Bounded::::MIN.try_into().unwrap() - || days > Bounded::::MAX.try_into().unwrap() { - return None; - } - self.add_days(days.try_into().unwrap()) - } - - /// Subtracts another `NaiveDate` from the current date. - /// Returns a `TimeDelta` of integral numbers. - /// - /// This does not overflow or underflow at all, - /// as all possible output fits in the range of `TimeDelta`. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDate, TimeDelta}; - /// - /// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); - /// let since = NaiveDate::signed_duration_since; - /// - /// assert_eq!(since(from_ymd(2014, 1, 1), from_ymd(2014, 1, 1)), TimeDelta::zero()); - /// assert_eq!( - /// since(from_ymd(2014, 1, 1), from_ymd(2013, 12, 31)), - /// TimeDelta::try_days(1).unwrap() - /// ); - /// assert_eq!(since(from_ymd(2014, 1, 1), from_ymd(2014, 1, 2)), - /// TimeDelta::try_days(-1).unwrap()); - /// assert_eq!( - /// since(from_ymd(2014, 1, 1), from_ymd(2013, 9, 23)), - /// TimeDelta::try_days(100).unwrap() - /// ); - /// assert_eq!( - /// since(from_ymd(2014, 1, 1), from_ymd(2013, 1, 1)), - /// TimeDelta::try_days(365).unwrap() - /// ); - /// assert_eq!( - /// since(from_ymd(2014, 1, 1), from_ymd(2010, 1, 1)), - /// TimeDelta::try_days(365 * 4 + 1).unwrap() - /// ); - /// assert_eq!( - /// since(from_ymd(2014, 1, 1), from_ymd(1614, 1, 1)), - /// TimeDelta::try_days(365 * 400 + 97).unwrap() - /// ); - /// ``` - fn signed_duration_since(self: @Date, rhs: Date) -> TimeDelta { - let year1 = self.year(); - let year2 = rhs.year(); - let (year1_div_400, year1_mod_400) = div_mod_floor(year1.try_into().unwrap(), 400); - let (year2_div_400, year2_mod_400) = div_mod_floor(year2.try_into().unwrap(), 400); - let cycle1: i64 = yo_to_cycle(year1_mod_400.try_into().unwrap(), self.ordinal()) - .try_into() - .unwrap(); - let cycle2: i64 = yo_to_cycle(year2_mod_400.try_into().unwrap(), rhs.ordinal()) - .try_into() - .unwrap(); - let days = (year1_div_400.try_into().unwrap() - year2_div_400.try_into().unwrap()) * 146_097 - + (cycle1 - cycle2); - // The range of `TimeDelta` is ca. 585 million years, the range of `NaiveDate` ca. 525.000 - // years. - TimeDeltaTrait::try_days(days).unwrap() - } - - /// Returns `true` if this is a leap year. - /// - /// ``` - /// # use chrono::NaiveDate; - /// assert_eq!(NaiveDate::from_ymd_opt(2000, 1, 1).unwrap().leap_year(), true); - /// assert_eq!(NaiveDate::from_ymd_opt(2001, 1, 1).unwrap().leap_year(), false); - /// assert_eq!(NaiveDate::from_ymd_opt(2002, 1, 1).unwrap().leap_year(), false); - /// assert_eq!(NaiveDate::from_ymd_opt(2003, 1, 1).unwrap().leap_year(), false); - /// assert_eq!(NaiveDate::from_ymd_opt(2004, 1, 1).unwrap().leap_year(), true); - /// assert_eq!(NaiveDate::from_ymd_opt(2100, 1, 1).unwrap().leap_year(), false); - /// ``` - fn leap_year(self: @Date) -> bool { - self.yof() & LEAP_YEAR_MASK == 0 - } - - // This duplicates `Datelike::year()`, because trait methods can't be const yet. - fn year(self: @Date) -> u32 { - ushr(self.yof(), 13) - } - - /// Returns the day of year starting from 1. - // This duplicates `Datelike::ordinal()`, because trait methods can't be const yet. - fn ordinal(self: @Date) -> u32 { - ushr(self.yof() & ORDINAL_MASK, 4) - } - - // This duplicates `Datelike::month()`, because trait methods can't be const yet. - fn month(self: @Date) -> u32 { - self.mdf().month() - } - - // This duplicates `Datelike::day()`, because trait methods can't be const yet. - fn day(self: @Date) -> u32 { - self.mdf().day() - } - - /// Returns the day of week. - // This duplicates `Datelike::weekday()`, because trait methods can't be const yet. - fn weekday(self: Date) -> Weekday { - match ((ushr(self.yof() & ORDINAL_MASK, 4)) + (self.yof() & WEEKDAY_FLAGS_MASK)) % 7 { - 0 => Weekday::Mon, - 1 => Weekday::Tue, - 2 => Weekday::Wed, - 3 => Weekday::Thu, - 4 => Weekday::Fri, - 5 => Weekday::Sat, - _ => Weekday::Sun, - } - } - - fn year_flags(self: @Date) -> YearFlags { - let flags = self.yof() & YEAR_FLAGS_MASK; - YearFlags { flags: flags.try_into().unwrap() } - } - - /// Counts the days in the proleptic Gregorian calendar, with January 1, Year 1 (CE) as day 1. - // This duplicates `Datelike::num_days_from_ce()`, because trait methods can't be const yet. - fn num_days_from_ce(self: @Date) -> i32 { - // we know this wouldn't overflow since year is limited to 1/2^13 of i32's full range. - let mut year: i32 = self.year().try_into().unwrap() - 1; - let mut ndays = 0; - if year < 0 { - let excess = 1 + (-year) / 400; - year += excess * 400; - ndays -= excess * 146_097; - } - let div_100 = year / 100; - ndays += (shr(year * 1461, 2)) - div_100 + shr(div_100, 2); - ndays + self.ordinal().try_into().unwrap() - } - - /// Get the raw year-ordinal-flags `i32`. - fn yof(self: @Date) -> u32 { - *self.yof - } - - /// Create a new `NaiveDate` from a raw year-ordinal-flags `i32`. - /// - /// In a valid value an ordinal is never `0`, and neither are the year flags. This method - /// doesn't do any validation in release builds. - fn from_yof(yof: u32) -> Date { - // The following are the invariants our ordinal and flags should uphold for a valid - // `NaiveDate`. - // debug_assert!(((yof & OL_MASK) >> 3) > 1); - // debug_assert!(((yof & OL_MASK) >> 3) <= MAX_OL); - // debug_assert!((yof & 0b111) != 000); - Date { yof } - } - /// Add a duration in [`Months`] to the date /// /// Uses the last day of the month if the day does not exist in the resulting month. @@ -771,7 +497,9 @@ pub impl DateImpl of DateTrait { }; if ordinal > 0 && ordinal <= 365 + leap_year { let year_and_flags = self.yof() & NOT_ORDINAL_MASK; - return Some(Self::from_yof(year_and_flags | ushl(ordinal.try_into().unwrap(), 4))); + return Some( + Self::from_yof(year_and_flags | u32_shl(ordinal.try_into().unwrap(), 4)), + ); } } // do the full check @@ -808,7 +536,8 @@ pub impl DateImpl of DateTrait { /// assert_eq!(dt.date(), d); /// assert_eq!(dt.time(), t); /// ``` - fn and_time(self: @Date, time: Time) -> DateTime { + #[inline] + const fn and_time(self: @Date, time: Time) -> DateTime { DateTimeTrait::new(*self, time) } @@ -832,13 +561,488 @@ pub impl DateImpl of DateTrait { /// assert!(d.and_hms_opt(12, 60, 56).is_none()); /// assert!(d.and_hms_opt(24, 34, 56).is_none()); /// ``` + #[inline] fn and_hms_opt(self: @Date, hour: u32, min: u32, sec: u32) -> Option { let time = TimeTrait::from_hms_opt(hour, min, sec)?; Some(self.and_time(time)) } - /// Makes a new `NaiveDate` with the year number changed, while keeping the same month and day. - /// + /// Returns the packed month-day-flags. + #[inline] + fn mdf(self: @Date) -> Mdf { + let ol = u32_shr((self.yof() & OL_MASK), 3); + MdfTrait::from_ol(ol.try_into().unwrap(), self.year_flags()) + } + + /// Makes a new `NaiveDate` with the packed month-day-flags changed. + /// + /// Returns `None` when the resulting `NaiveDate` would be invalid. + #[inline] + fn with_mdf(self: @Date, mdf: Mdf) -> Option { + // debug_assert!(self.year_flags().0 == mdf.year_flags().0); + match mdf.ordinal() { + Some(ordinal) => { + Some(Self::from_yof((self.yof() & NOT_ORDINAL_MASK) | u32_shl(ordinal, 4))) + }, + None => None // Non-existing date + } + } + + /// Makes a new `NaiveDate` for the next calendar date. + /// + /// # Errors + /// + /// Returns `None` when `self` is the last representable date. + /// + /// # Example + /// + /// ``` + /// use chrono::NaiveDate; + /// + /// assert_eq!( + /// NaiveDate::from_ymd_opt(2015, 6, 3).unwrap().succ_opt(), + /// Some(NaiveDate::from_ymd_opt(2015, 6, 4).unwrap()) + /// ); + /// assert_eq!(NaiveDate::MAX.succ_opt(), None); + /// ``` + #[inline] + fn succ_opt(self: @Date) -> Option { + let new_ol = (self.yof() & OL_MASK) + u32_shl(1, 4); + match new_ol <= MAX_OL { + true => Some(Self::from_yof(self.yof() & NOT_OL_MASK | new_ol)), + false => Self::from_yo_opt(self.year() + 1, 1), + } + } + + /// Makes a new `NaiveDate` for the previous calendar date. + /// + /// # Errors + /// + /// Returns `None` when `self` is the first representable date. + /// + /// # Example + /// + /// ``` + /// use chrono::NaiveDate; + /// + /// assert_eq!( + /// NaiveDate::from_ymd_opt(2015, 6, 3).unwrap().pred_opt(), + /// Some(NaiveDate::from_ymd_opt(2015, 6, 2).unwrap()) + /// ); + /// assert_eq!(NaiveDate::MIN.pred_opt(), None); + /// ``` + #[inline] + fn pred_opt(self: @Date) -> Option { + let new_shifted_ordinal = (self.yof() & ORDINAL_MASK) - u32_shl(1, 4); + match new_shifted_ordinal > 0 { + true => Some(Self::from_yof(self.yof() & NOT_ORDINAL_MASK | new_shifted_ordinal)), + false => { + if self.year() == 0 { + return None; + } + Self::from_ymd_opt(self.year() - 1, 12, 31) + }, + } + } + + /// Adds the number of whole days in the given `TimeDelta` to the current date. + /// + /// # Errors + /// + /// Returns `None` if the resulting date would be out of range. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveDate, TimeDelta}; + /// + /// let d = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap(); + /// assert_eq!( + /// d.checked_add_signed(TimeDelta::try_days(40).unwrap()), + /// Some(NaiveDate::from_ymd_opt(2015, 10, 15).unwrap()) + /// ); + /// assert_eq!( + /// d.checked_add_signed(TimeDelta::try_days(-40).unwrap()), + /// Some(NaiveDate::from_ymd_opt(2015, 7, 27).unwrap()) + /// ); + /// assert_eq!(d.checked_add_signed(TimeDelta::try_days(1_000_000_000).unwrap()), None); + /// assert_eq!(d.checked_add_signed(TimeDelta::try_days(-1_000_000_000).unwrap()), None); + /// assert_eq!(NaiveDate::MAX.checked_add_signed(TimeDelta::try_days(1).unwrap()), None); + /// ``` + fn checked_add_signed(self: @Date, rhs: TimeDelta) -> Option { + let days = rhs.num_days(); + if days < Bounded::::MIN.try_into().unwrap() + || days > Bounded::::MAX.try_into().unwrap() { + return None; + } + self.add_days(days.try_into().unwrap()) + } + + /// Subtracts the number of whole days in the given `TimeDelta` from the current date. + /// + /// # Errors + /// + /// Returns `None` if the resulting date would be out of range. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveDate, TimeDelta}; + /// + /// let d = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap(); + /// assert_eq!( + /// d.checked_sub_signed(TimeDelta::try_days(40).unwrap()), + /// Some(NaiveDate::from_ymd_opt(2015, 7, 27).unwrap()) + /// ); + /// assert_eq!( + /// d.checked_sub_signed(TimeDelta::try_days(-40).unwrap()), + /// Some(NaiveDate::from_ymd_opt(2015, 10, 15).unwrap()) + /// ); + /// assert_eq!(d.checked_sub_signed(TimeDelta::try_days(1_000_000_000).unwrap()), None); + /// assert_eq!(d.checked_sub_signed(TimeDelta::try_days(-1_000_000_000).unwrap()), None); + /// assert_eq!(NaiveDate::MIN.checked_sub_signed(TimeDelta::try_days(1).unwrap()), None); + /// ``` + fn checked_sub_signed(self: @Date, rhs: TimeDelta) -> Option { + let days = -rhs.num_days(); + if days < Bounded::::MIN.try_into().unwrap() + || days > Bounded::::MAX.try_into().unwrap() { + return None; + } + self.add_days(days.try_into().unwrap()) + } + + /// Subtracts another `NaiveDate` from the current date. + /// Returns a `TimeDelta` of integral numbers. + /// + /// This does not overflow or underflow at all, + /// as all possible output fits in the range of `TimeDelta`. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveDate, TimeDelta}; + /// + /// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); + /// let since = NaiveDate::signed_duration_since; + /// + /// assert_eq!(since(from_ymd(2014, 1, 1), from_ymd(2014, 1, 1)), TimeDelta::zero()); + /// assert_eq!( + /// since(from_ymd(2014, 1, 1), from_ymd(2013, 12, 31)), + /// TimeDelta::try_days(1).unwrap() + /// ); + /// assert_eq!(since(from_ymd(2014, 1, 1), from_ymd(2014, 1, 2)), + /// TimeDelta::try_days(-1).unwrap()); + /// assert_eq!( + /// since(from_ymd(2014, 1, 1), from_ymd(2013, 9, 23)), + /// TimeDelta::try_days(100).unwrap() + /// ); + /// assert_eq!( + /// since(from_ymd(2014, 1, 1), from_ymd(2013, 1, 1)), + /// TimeDelta::try_days(365).unwrap() + /// ); + /// assert_eq!( + /// since(from_ymd(2014, 1, 1), from_ymd(2010, 1, 1)), + /// TimeDelta::try_days(365 * 4 + 1).unwrap() + /// ); + /// assert_eq!( + /// since(from_ymd(2014, 1, 1), from_ymd(1614, 1, 1)), + /// TimeDelta::try_days(365 * 400 + 97).unwrap() + /// ); + /// ``` + fn signed_duration_since(self: @Date, rhs: Date) -> TimeDelta { + let year1 = self.year(); + let year2 = rhs.year(); + let (year1_div_400, year1_mod_400) = div_mod_floor(year1.try_into().unwrap(), 400); + let (year2_div_400, year2_mod_400) = div_mod_floor(year2.try_into().unwrap(), 400); + let cycle1: i64 = yo_to_cycle(year1_mod_400.try_into().unwrap(), self.ordinal()) + .try_into() + .unwrap(); + let cycle2: i64 = yo_to_cycle(year2_mod_400.try_into().unwrap(), rhs.ordinal()) + .try_into() + .unwrap(); + let days = (year1_div_400.try_into().unwrap() - year2_div_400.try_into().unwrap()) * 146_097 + + (cycle1 - cycle2); + // The range of `TimeDelta` is ca. 585 million years, the range of `NaiveDate` ca. 525.000 + // years. + TimeDeltaTrait::try_days(days).expect('always in range') + } + + /// Returns the number of whole years from the given `base` until `self`. + /// + /// # Errors + /// + /// Returns `None` if `base > self`. + fn years_since(self: @Date, base: Date) -> Option { + let mut years = self.year() - base.year(); + // Comparing tuples is not (yet) possible in const context. Instead we combine month and + // day into one `u32` for easy comparison. + if (u32_shl(self.month(), 5) | self.day()) < (u32_shl(base.month(), 5) | base.day()) { + years -= 1; + } + + match years >= 0 { + true => Some(years), + false => None, + } + } + + /// Returns the [`NaiveWeek`] that the date belongs to, starting with the [`Weekday`] + /// specified. + #[inline] + const fn week(self: @Date, start: Weekday) -> Week { + WeekTrait::new(*self, start) + } + + /// Returns `true` if this is a leap year. + /// + /// ``` + /// # use chrono::NaiveDate; + /// assert_eq!(NaiveDate::from_ymd_opt(2000, 1, 1).unwrap().leap_year(), true); + /// assert_eq!(NaiveDate::from_ymd_opt(2001, 1, 1).unwrap().leap_year(), false); + /// assert_eq!(NaiveDate::from_ymd_opt(2002, 1, 1).unwrap().leap_year(), false); + /// assert_eq!(NaiveDate::from_ymd_opt(2003, 1, 1).unwrap().leap_year(), false); + /// assert_eq!(NaiveDate::from_ymd_opt(2004, 1, 1).unwrap().leap_year(), true); + /// assert_eq!(NaiveDate::from_ymd_opt(2100, 1, 1).unwrap().leap_year(), false); + /// ``` + const fn leap_year(self: @Date) -> bool { + self.yof() & LEAP_YEAR_MASK == 0 + } + + #[inline] + const fn year_flags(self: @Date) -> YearFlags { + let flags = self.yof() & YEAR_FLAGS_MASK; + YearFlags { flags: flags.try_into().unwrap() } + } + + /// Create a new `NaiveDate` from a raw year-ordinal-flags `i32`. + /// + /// In a valid value an ordinal is never `0`, and neither are the year flags. This method + /// doesn't do any validation in release builds. + #[inline] + const fn from_yof(yof: u32) -> Date { + // The following are the invariants our ordinal and flags should uphold for a valid + // `NaiveDate`. + // debug_assert!(((yof & OL_MASK) >> 3) > 1); + // debug_assert!(((yof & OL_MASK) >> 3) <= MAX_OL); + // debug_assert!((yof & 0b111) != 000); + Date { yof } + } + + /// Get the raw year-ordinal-flags `i32`. + #[inline] + const fn yof(self: @Date) -> u32 { + *self.yof + } + + /// The minimum possible `NaiveDate` (January 1, 262144 BCE). + /// (MIN_YEAR << 13) | (1 << 4) | 0o12 /* D */ + const MIN: Date = Self::from_yof((MIN_YEAR * 2_u32.pow(13)) | (1 * 2_u32.pow(4)) | 0o4); + /// The maximum possible `NaiveDate` (December 31, 262142 CE). + /// (MAX_YEAR << 13) | (365 << 4) | 0o16 /* G */ + const MAX: Date = Self::from_yof((MAX_YEAR * 2_u32.pow(13)) | (365 * 2_u32.pow(4)) | 0o16); + + /// One day before the minimum possible `NaiveDate` (December 31, 262145 BCE). + // pub(crate) const BEFORE_MIN: NaiveDate = + // NaiveDate::from_yof(((MIN_YEAR - 1) << 13) | (366 << 4) | 0o07 /* FE */); + /// One day after the maximum possible `NaiveDate` (January 1, 262143 CE). + const AFTER_MAX: Date = Self::from_yof( + ((MAX_YEAR + 1) * 2_u32.pow(13)) | (1 * 2_u32.pow(4)) | 0o17, + ); +} + +impl DateDatelikeImpl of Datelike { + /// Returns the year number in the [calendar date](#calendar-date). + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate}; + /// + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().year(), 2015); + /// assert_eq!(NaiveDate::from_ymd_opt(-308, 3, 14).unwrap().year(), -308); // 309 BCE + /// ``` + #[inline] + const fn year(self: @Date) -> u32 { + u32_shr(self.yof(), 13) + } + + /// Returns the month number starting from 1. + /// + /// The return value ranges from 1 to 12. + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate}; + /// + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().month(), 9); + /// assert_eq!(NaiveDate::from_ymd_opt(-308, 3, 14).unwrap().month(), 3); + /// ``` + #[inline] + fn month(self: @Date) -> u32 { + self.mdf().month() + } + + /// Returns the month number starting from 0. + /// + /// The return value ranges from 0 to 11. + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate}; + /// + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().month0(), 8); + /// assert_eq!(NaiveDate::from_ymd_opt(-308, 3, 14).unwrap().month0(), 2); + /// ``` + #[inline] + fn month0(self: @Date) -> u32 { + Self::month(self) - 1 + } + + /// Returns the day of month starting from 1. + /// + /// The return value ranges from 1 to 31. (The last day of month differs by months.) + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate}; + /// + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().day(), 8); + /// assert_eq!(NaiveDate::from_ymd_opt(-308, 3, 14).unwrap().day(), 14); + /// ``` + /// + /// Combined with [`NaiveDate::pred_opt`](#method.pred_opt), + /// one can determine the number of days in a particular month. + /// (Note that this panics when `year` is out of range.) + /// + /// ``` + /// use chrono::{Datelike, NaiveDate}; + /// + /// fn ndays_in_month(year: i32, month: u32) -> u32 { + /// // the first day of the next month... + /// let (y, m) = if month == 12 { (year + 1, 1) } else { (year, month + 1) }; + /// let d = NaiveDate::from_ymd_opt(y, m, 1).unwrap(); + /// + /// // ...is preceded by the last day of the original month + /// d.pred_opt().unwrap().day() + /// } + /// + /// assert_eq!(ndays_in_month(2015, 8), 31); + /// assert_eq!(ndays_in_month(2015, 9), 30); + /// assert_eq!(ndays_in_month(2015, 12), 31); + /// assert_eq!(ndays_in_month(2016, 2), 29); + /// assert_eq!(ndays_in_month(2017, 2), 28); + /// ``` + #[inline] + fn day(self: @Date) -> u32 { + self.mdf().day() + } + + /// Returns the day of month starting from 0. + /// + /// The return value ranges from 0 to 30. (The last day of month differs by months.) + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate}; + /// + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().day0(), 7); + /// assert_eq!(NaiveDate::from_ymd_opt(-308, 3, 14).unwrap().day0(), 13); + /// ``` + #[inline] + fn day0(self: @Date) -> u32 { + Self::day(self) - 1 + } + + /// Returns the day of year starting from 1. + /// + /// The return value ranges from 1 to 366. (The last day of year differs by years.) + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate}; + /// + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().ordinal(), 251); + /// assert_eq!(NaiveDate::from_ymd_opt(-308, 3, 14).unwrap().ordinal(), 74); + /// ``` + /// + /// Combined with [`NaiveDate::pred_opt`](#method.pred_opt), + /// one can determine the number of days in a particular year. + /// (Note that this panics when `year` is out of range.) + /// + /// ``` + /// use chrono::{Datelike, NaiveDate}; + /// + /// fn ndays_in_year(year: i32) -> u32 { + /// // the first day of the next year... + /// let d = NaiveDate::from_ymd_opt(year + 1, 1, 1).unwrap(); + /// + /// // ...is preceded by the last day of the original year + /// d.pred_opt().unwrap().ordinal() + /// } + /// + /// assert_eq!(ndays_in_year(2015), 365); + /// assert_eq!(ndays_in_year(2016), 366); + /// assert_eq!(ndays_in_year(2017), 365); + /// assert_eq!(ndays_in_year(2000), 366); + /// assert_eq!(ndays_in_year(2100), 365); + /// ``` + #[inline] + const fn ordinal(self: @Date) -> u32 { + u32_shr(self.yof() & ORDINAL_MASK, 4) + } + + /// Returns the day of year starting from 0. + /// + /// The return value ranges from 0 to 365. (The last day of year differs by years.) + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate}; + /// + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().ordinal0(), 250); + /// assert_eq!(NaiveDate::from_ymd_opt(-308, 3, 14).unwrap().ordinal0(), 73); + /// ``` + #[inline] + const fn ordinal0(self: @Date) -> u32 { + Self::ordinal(self) - 1 + } + + /// Returns the day of week. + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate, Weekday}; + /// + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().weekday(), Weekday::Tue); + /// assert_eq!(NaiveDate::from_ymd_opt(-308, 3, 14).unwrap().weekday(), Weekday::Fri); + /// ``` + #[inline] + const fn weekday(self: @Date) -> Weekday { + match ((u32_shr(self.yof() & ORDINAL_MASK, 4)) + (self.yof() & WEEKDAY_FLAGS_MASK)) % 7 { + 0 => Weekday::Mon, + 1 => Weekday::Tue, + 2 => Weekday::Wed, + 3 => Weekday::Thu, + 4 => Weekday::Fri, + 5 => Weekday::Sat, + _ => Weekday::Sun, + } + } + + #[inline] + fn iso_week(self: @Date) -> IsoWeek { + IsoWeekTrait::from_yof(self.year().try_into().unwrap(), self.ordinal(), self.year_flags()) + } + + /// Makes a new `NaiveDate` with the year number changed, while keeping the same month and day. + /// /// This method assumes you want to work on the date as a year-month-day value. Don't use it if /// you want the ordinal to stay the same after changing the year, of if you want the week and /// weekday values to stay the same. @@ -881,6 +1085,7 @@ pub impl DateImpl of DateTrait { /// NaiveDate::from_yo_opt(2023, 100).unwrap() // result is 2023-101 /// ); /// ``` + #[inline] fn with_year(self: @Date, year: u32) -> Option { // we need to operate with `mdf` since we should keep the month and day number as is let mdf = self.mdf(); @@ -889,7 +1094,7 @@ pub impl DateImpl of DateTrait { let flags = YearFlagsTrait::from_year(year.try_into().unwrap()); let mdf = mdf.with_flags(flags); - Self::from_mdf(year, mdf) + DateTrait::from_mdf(year, mdf) } /// Makes a new `NaiveDate` with the month number (starting from 1) changed. @@ -932,10 +1137,37 @@ pub impl DateImpl of DateTrait { /// let d = NaiveDate::from_ymd_opt(2020, 2, 29).unwrap(); /// assert_eq!(with_year_month_fixed(d, 2019, 1), NaiveDate::from_ymd_opt(2019, 1, 29)); /// ``` + #[inline] fn with_month(self: @Date, month: u32) -> Option { self.with_mdf(self.mdf().with_month(month)?) } + /// Makes a new `NaiveDate` with the month number (starting from 0) changed. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The resulting date does not exist (for example `month0(3)` when day of the month is 31). + /// - The value for `month0` is invalid. + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate}; + /// + /// assert_eq!( + /// NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().with_month0(9), + /// Some(NaiveDate::from_ymd_opt(2015, 10, 8).unwrap()) + /// ); + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().with_month0(12), None); // No month + /// 12 assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 30).unwrap().with_month0(1), None); // No Feb + /// 30 ``` + #[inline] + fn with_month0(self: @Date, month0: u32) -> Option { + let month = month0.checked_add(1)?; + self.with_mdf(self.mdf().with_month(month)?) + } + /// Makes a new `NaiveDate` with the day of month (starting from 1) changed. /// /// # Errors @@ -956,10 +1188,37 @@ pub impl DateImpl of DateTrait { /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().with_day(31), None); /// // no September 31 /// ``` + #[inline] fn with_day(self: @Date, day: u32) -> Option { self.with_mdf(self.mdf().with_day(day)?) } + /// Makes a new `NaiveDate` with the day of month (starting from 0) changed. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The resulting date does not exist (for example `day(30)` in April). + /// - The value for `day0` is invalid. + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate}; + /// + /// assert_eq!( + /// NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().with_day0(29), + /// Some(NaiveDate::from_ymd_opt(2015, 9, 30).unwrap()) + /// ); + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().with_day0(30), None); + /// // no September 31 + /// ``` + #[inline] + fn with_day0(self: @Date, day0: u32) -> Option { + let day = day0.checked_add(1)?; + self.with_mdf(self.mdf().with_day(day)?) + } + /// Makes a new `NaiveDate` with the day of year (starting from 1) changed. /// /// # Errors @@ -983,37 +1242,54 @@ pub impl DateImpl of DateTrait { /// assert_eq!(NaiveDate::from_ymd_opt(2016, 1, 1).unwrap().with_ordinal(366), /// Some(NaiveDate::from_ymd_opt(2016, 12, 31).unwrap())); /// ``` + #[inline] fn with_ordinal(self: @Date, ordinal: u32) -> Option { if ordinal == 0 || ordinal > 366 { return None; } - let yof = (self.yof() & NOT_ORDINAL_MASK) | ushl(ordinal, 4); + let yof = (self.yof() & NOT_ORDINAL_MASK) | u32_shl(ordinal, 4); match yof & OL_MASK <= MAX_OL { - true => Some(Self::from_yof(yof)), + true => Some(DateTrait::from_yof(yof)), false => None // Does not exist: Ordinal 366 in a common year. } } - /// The minimum possible `NaiveDate` (January 1, 262144 BCE). - /// (MIN_YEAR << 13) | (1 << 4) | 0o12 /* D */ - const MIN: Date = Date { yof: (MIN_YEAR * 2_u32.pow(13)) | (1 * 2_u32.pow(4)) | 0o4 }; - /// The maximum possible `NaiveDate` (December 31, 262142 CE). - /// (MAX_YEAR << 13) | (365 << 4) | 0o16 /* G */ - const MAX: Date = Date { yof: (MAX_YEAR * 2_u32.pow(13)) | (365 * 2_u32.pow(4)) | 0o16 }; - - /// One day before the minimum possible `NaiveDate` (December 31, 262145 BCE). - // pub(crate) const BEFORE_MIN: NaiveDate = - // NaiveDate::from_yof(((MIN_YEAR - 1) << 13) | (366 << 4) | 0o07 /* FE */); - /// One day after the maximum possible `NaiveDate` (January 1, 262143 CE). - const AFTER_MAX: Date = Date { - yof: ((MAX_YEAR + 1) * 2_u32.pow(13)) | (1 * 2_u32.pow(4)) | 0o17, - }; + /// Makes a new `NaiveDate` with the day of year (starting from 0) changed. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The resulting date does not exist (`with_ordinal0(365)` in a non-leap year). + /// - The value for `ordinal0` is invalid. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveDate, Datelike}; + /// + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 1, 1).unwrap().with_ordinal0(59), + /// Some(NaiveDate::from_ymd_opt(2015, 3, 1).unwrap())); + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 1, 1).unwrap().with_ordinal0(365), + /// None); // 2015 had only 365 days + /// + /// assert_eq!(NaiveDate::from_ymd_opt(2016, 1, 1).unwrap().with_ordinal0(59), + /// Some(NaiveDate::from_ymd_opt(2016, 2, 29).unwrap())); + /// assert_eq!(NaiveDate::from_ymd_opt(2016, 1, 1).unwrap().with_ordinal0(365), + /// Some(NaiveDate::from_ymd_opt(2016, 12, 31).unwrap())); + /// ``` + #[inline] + fn with_ordinal0(self: @Date, ordinal0: u32) -> Option { + let ordinal = ordinal0.checked_add(1)?; + Self::with_ordinal(self, ordinal) + } } impl DatePartialOrd of PartialOrd { + #[inline] fn lt(lhs: Date, rhs: Date) -> bool { lhs.yof < rhs.yof } + #[inline] fn ge(lhs: Date, rhs: Date) -> bool { lhs.yof >= rhs.yof } @@ -1045,7 +1321,7 @@ impl DateDebug of Debug { fn fmt(self: @Date, ref f: Formatter) -> Result<(), Error> { let year = self.year(); let mdf = self.mdf(); - if year >= 0 && year <= 9999 { + if (0..=9999).contains(@year) { write_hundreds(ref f, (year / 100).try_into().unwrap())?; write_hundreds(ref f, (year % 100).try_into().unwrap())?; } else { diff --git a/examples/cairo/scripts/datetime/src/datetime.cairo b/packages/chrono/src/datetime.cairo similarity index 74% rename from examples/cairo/scripts/datetime/src/datetime.cairo rename to packages/chrono/src/datetime.cairo index 5b9dfdf..c15f6a6 100644 --- a/examples/cairo/scripts/datetime/src/datetime.cairo +++ b/packages/chrono/src/datetime.cairo @@ -1,10 +1,14 @@ use core::fmt::{Debug, Display, Error, Formatter}; use core::num::traits::Bounded; -use datetime::date::{Date, DateTrait}; -use datetime::time::{Time, TimeTrait}; -use datetime::time_delta::{TimeDelta, TimeDeltaTrait}; -use datetime::utils::{div_euclid, rem_euclid}; -use datetime::weekday::Weekday; +use super::date::{Date, DateTrait}; +use super::days::Days; +use super::isoweek::IsoWeek; +use super::months::Months; +use super::time::{Time, TimeTrait}; +use super::time_delta::{TimeDelta, TimeDeltaTrait}; +use super::traits::{Datelike, Timelike}; +use super::utils::{div_euclid, rem_euclid}; +use super::weekday::Weekday; /// ISO 8601 combined date and time without timezone. /// @@ -32,7 +36,7 @@ use datetime::weekday::Weekday; /// assert_eq!(dt.weekday(), Weekday::Fri); /// assert_eq!(dt.num_seconds_from_midnight(), 33011); /// ``` -#[derive(Copy, PartialEq, Drop)] +#[derive(Copy, PartialEq, Drop, Serde, starknet::Store)] pub struct DateTime { pub date: Date, pub time: Time, @@ -56,16 +60,11 @@ pub impl DateTimeImpl of DateTimeTrait { /// assert_eq!(dt.date(), d); /// assert_eq!(dt.time(), t); /// ``` - fn new(date: Date, time: Time) -> DateTime { + #[inline] + const fn new(date: Date, time: Time) -> DateTime { DateTime { date, time } } - fn from_ymd_and_hms_opt( - year: u32, month: u32, day: u32, hour: u32, min: u32, sec: u32, - ) -> Option { - DateTrait::from_ymd_opt(year, month, day)?.and_hms_opt(hour, min, sec) - } - /// Makes a new `NaiveDateTime` corresponding to a UTC date and time, /// from the number of non-leap seconds /// since the midnight UTC on January 1, 1970 (aka "UNIX timestamp") @@ -82,6 +81,7 @@ pub impl DateTimeImpl of DateTimeTrait { /// Panics if the number of seconds would be out of range for a `NaiveDateTime` (more than /// ca. 262,000 years away from common era), and panics on an invalid nanosecond (2 seconds or /// more). + #[inline] fn from_timestamp(secs: i64) -> Option { let days = div_euclid(secs, 86_400)? + UNIX_EPOCH_DAY; let secs = rem_euclid(secs, 86_400); @@ -94,6 +94,11 @@ pub impl DateTimeImpl of DateTimeTrait { Some(date.and_time(time)) } + #[inline] + fn from_block_timestamp(block_timestamp: u64) -> Option { + Self::from_timestamp(block_timestamp.try_into().unwrap()) + } + /// Retrieves a date component. /// /// # Example @@ -104,7 +109,8 @@ pub impl DateTimeImpl of DateTimeTrait { /// let dt = NaiveDate::from_ymd_opt(2016, 7, 8).unwrap().and_hms_opt(9, 10, 11).unwrap(); /// assert_eq!(dt.date(), NaiveDate::from_ymd_opt(2016, 7, 8).unwrap()); /// ``` - fn date(self: @DateTime) -> Date { + #[inline] + const fn date(self: @DateTime) -> Date { *self.date } @@ -118,7 +124,8 @@ pub impl DateTimeImpl of DateTimeTrait { /// let dt = NaiveDate::from_ymd_opt(2016, 7, 8).unwrap().and_hms_opt(9, 10, 11).unwrap(); /// assert_eq!(dt.time(), NaiveTime::from_hms_opt(9, 10, 11).unwrap()); /// ``` - fn time(self: @DateTime) -> Time { + #[inline] + const fn time(self: @DateTime) -> Time { *self.time } @@ -137,10 +144,11 @@ pub impl DateTimeImpl of DateTimeTrait { /// assert_eq!(DateTime::from_timestamp(dt.timestamp(), dt.timestamp_subsec_nanos()).unwrap(), /// dt); /// ``` + #[inline] fn timestamp(self: @DateTime) -> i64 { - let gregorian_day: i64 = self.date.num_days_from_ce().try_into().unwrap(); - let seconds_from_midnight = self.time.num_seconds_from_midnight().try_into().unwrap(); - (gregorian_day - UNIX_EPOCH_DAY) * 86_400 + seconds_from_midnight + let gregorian_day = self.date.num_days_from_ce().into(); + let seconds_from_midnight = self.time.num_seconds_from_midnight(); + (gregorian_day - UNIX_EPOCH_DAY.into()) * 86_400 + seconds_from_midnight.into() } /// Adds given `TimeDelta` to the current date and time. @@ -229,6 +237,41 @@ pub impl DateTimeImpl of DateTimeTrait { Some(DateTime { date, time }) } + /// Adds given `Months` to the current date and time. + /// + /// Uses the last day of the month if the day does not exist in the resulting month. + /// + /// # Errors + /// + /// Returns `None` if the resulting date would be out of range. + /// + /// # Example + /// + /// ``` + /// use chrono::{Months, NaiveDate}; + /// + /// assert_eq!( + /// NaiveDate::from_ymd_opt(2014, 1, 1) + /// .unwrap() + /// .and_hms_opt(1, 0, 0) + /// .unwrap() + /// .checked_add_months(Months::new(1)), + /// Some(NaiveDate::from_ymd_opt(2014, 2, 1).unwrap().and_hms_opt(1, 0, 0).unwrap()) + /// ); + /// + /// assert_eq!( + /// NaiveDate::from_ymd_opt(2014, 1, 1) + /// .unwrap() + /// .and_hms_opt(1, 0, 0) + /// .unwrap() + /// .checked_add_months(Months::new(core::i32::MAX as u32 + 1)), + /// None + /// ); + /// ``` + fn checked_add_months(self: @DateTime, rhs: Months) -> Option { + Some(DateTime { date: self.date.checked_add_months(rhs)?, ..*self }) + } + /// Subtracts given `TimeDelta` from the current date and time. /// /// As a part of Chrono's [leap second handling](./struct.NaiveTime.html#leap-second-handling), @@ -311,6 +354,55 @@ pub impl DateTimeImpl of DateTimeTrait { Some(DateTime { date, time }) } + /// Subtracts given `Months` from the current date and time. + /// + /// Uses the last day of the month if the day does not exist in the resulting month. + /// + /// # Errors + /// + /// Returns `None` if the resulting date would be out of range. + /// + /// # Example + /// + /// ``` + /// use chrono::{Months, NaiveDate}; + /// + /// assert_eq!( + /// NaiveDate::from_ymd_opt(2014, 1, 1) + /// .unwrap() + /// .and_hms_opt(1, 0, 0) + /// .unwrap() + /// .checked_sub_months(Months::new(1)), + /// Some(NaiveDate::from_ymd_opt(2013, 12, 1).unwrap().and_hms_opt(1, 0, 0).unwrap()) + /// ); + /// + /// assert_eq!( + /// NaiveDate::from_ymd_opt(2014, 1, 1) + /// .unwrap() + /// .and_hms_opt(1, 0, 0) + /// .unwrap() + /// .checked_sub_months(Months::new(core::i32::MAX as u32 + 1)), + /// None + /// ); + /// ``` + fn checked_sub_months(self: @DateTime, rhs: Months) -> Option { + Some(DateTime { date: self.date.checked_sub_months(rhs)?, ..*self }) + } + + /// Add a duration in [`Days`] to the date part of the `NaiveDateTime` + /// + /// Returns `None` if the resulting date would be out of range. + fn checked_add_days(self: @DateTime, days: Days) -> Option { + Some(DateTime { date: self.date.checked_add_days(days)?, ..*self }) + } + + /// Subtract a duration in [`Days`] from the date part of the `NaiveDateTime` + /// + /// Returns `None` if the resulting date would be out of range. + fn checked_sub_days(self: @DateTime, days: Days) -> Option { + Some(DateTime { date: self.date.checked_sub_days(days)?, ..*self }) + } + /// Subtracts another `NaiveDateTime` from the current date and time. /// This does not overflow or underflow at all. /// @@ -365,9 +457,24 @@ pub impl DateTimeImpl of DateTimeTrait { .date .signed_duration_since(rhs.date) .checked_add(self.time.signed_duration_since(rhs.time)) - .unwrap() + .expect('always in range') } + /// The minimum possible `NaiveDateTime`. + const MIN: DateTime = DateTime { date: DateTrait::MIN, time: TimeTrait::MIN }; + + /// The maximum possible `NaiveDateTime`. + const MAX: DateTime = DateTime { date: DateTrait::MAX, time: TimeTrait::MAX }; + + /// The datetime of the Unix Epoch, 1970-01-01 00:00:00. + /// + /// Note that while this may look like the UNIX epoch, it is missing the + /// time zone. The actual UNIX epoch cannot be expressed by this type, + /// however it is available as [`DateTime::UNIX_EPOCH`]. + const UNIX_EPOCH: DateTime = DateTime { date: Date { yof: 16138266 }, time: Time { secs: 0 } }; +} + +impl DateTimeDatelikeImpl of Datelike { /// Returns the year number in the [calendar date](./struct.NaiveDate.html#calendar-date). /// /// See also the [`NaiveDate::year`](./struct.NaiveDate.html#method.year) method. @@ -381,7 +488,8 @@ pub impl DateTimeImpl of DateTimeTrait { /// NaiveDate::from_ymd_opt(2015, 9, 25).unwrap().and_hms_opt(12, 34, 56).unwrap(); /// assert_eq!(dt.year(), 2015); /// ``` - fn year(self: @DateTime) -> u32 { + #[inline] + const fn year(self: @DateTime) -> u32 { self.date.year() } @@ -400,10 +508,31 @@ pub impl DateTimeImpl of DateTimeTrait { /// NaiveDate::from_ymd_opt(2015, 9, 25).unwrap().and_hms_opt(12, 34, 56).unwrap(); /// assert_eq!(dt.month(), 9); /// ``` + #[inline] fn month(self: @DateTime) -> u32 { self.date.month() } + /// Returns the month number starting from 0. + /// + /// The return value ranges from 0 to 11. + /// + /// See also the [`NaiveDate::month0`] method. + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate, NaiveDateTime}; + /// + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 25).unwrap().and_hms_opt(12, 34, 56).unwrap(); + /// assert_eq!(dt.month0(), 8); + /// ``` + #[inline] + fn month0(self: @DateTime) -> u32 { + self.date.month0() + } + /// Returns the day of month starting from 1. /// /// The return value ranges from 1 to 31. (The last day of month differs by months.) @@ -419,10 +548,31 @@ pub impl DateTimeImpl of DateTimeTrait { /// NaiveDate::from_ymd_opt(2015, 9, 25).unwrap().and_hms_opt(12, 34, 56).unwrap(); /// assert_eq!(dt.day(), 25); /// ``` + #[inline] fn day(self: @DateTime) -> u32 { self.date.day() } + /// Returns the day of month starting from 0. + /// + /// The return value ranges from 0 to 30. (The last day of month differs by months.) + /// + /// See also the [`NaiveDate::day0`] method. + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate, NaiveDateTime}; + /// + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 25).unwrap().and_hms_opt(12, 34, 56).unwrap(); + /// assert_eq!(dt.day0(), 24); + /// ``` + #[inline] + fn day0(self: @DateTime) -> u32 { + self.date.day0() + } + /// Returns the day of year starting from 1. /// /// The return value ranges from 1 to 366. (The last day of year differs by years.) @@ -438,10 +588,31 @@ pub impl DateTimeImpl of DateTimeTrait { /// NaiveDate::from_ymd_opt(2015, 9, 25).unwrap().and_hms_opt(12, 34, 56).unwrap(); /// assert_eq!(dt.ordinal(), 268); /// ``` - fn ordinal(self: @DateTime) -> u32 { + #[inline] + const fn ordinal(self: @DateTime) -> u32 { self.date.ordinal() } + /// Returns the day of year starting from 0. + /// + /// The return value ranges from 0 to 365. (The last day of year differs by years.) + /// + /// See also the [`NaiveDate::ordinal0`] method. + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate, NaiveDateTime}; + /// + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 25).unwrap().and_hms_opt(12, 34, 56).unwrap(); + /// assert_eq!(dt.ordinal0(), 267); + /// ``` + #[inline] + const fn ordinal0(self: @DateTime) -> u32 { + self.date.ordinal0() + } + /// Returns the day of week. /// /// See also the [`NaiveDate::weekday`](./struct.NaiveDate.html#method.weekday) method. @@ -455,8 +626,14 @@ pub impl DateTimeImpl of DateTimeTrait { /// NaiveDate::from_ymd_opt(2015, 9, 25).unwrap().and_hms_opt(12, 34, 56).unwrap(); /// assert_eq!(dt.weekday(), Weekday::Fri); /// ``` - fn weekday(self: @DateTime) -> Weekday { - (*self.date).weekday() + #[inline] + const fn weekday(self: @DateTime) -> Weekday { + self.date.weekday() + } + + #[inline] + fn iso_week(self: @DateTime) -> IsoWeek { + self.date.iso_week() } /// Makes a new `NaiveDateTime` with the year number changed, while keeping the same month and @@ -486,8 +663,9 @@ pub impl DateTimeImpl of DateTimeTrait { /// Some(NaiveDate::from_ymd_opt(-308, 9, 25).unwrap().and_hms_opt(12, 34, 56).unwrap()) /// ); /// ``` + #[inline] fn with_year(self: @DateTime, year: u32) -> Option { - Some(DateTime { date: self.date.with_year(year)?, time: *self.time }) + self.date.with_year(year).map(|d| DateTime { date: d, ..*self }) } /// Makes a new `NaiveDateTime` with the month number (starting from 1) changed. @@ -516,8 +694,38 @@ pub impl DateTimeImpl of DateTimeTrait { /// assert_eq!(dt.with_month(13), None); // No month 13 /// assert_eq!(dt.with_month(2), None); // No February 30 /// ``` + #[inline] fn with_month(self: @DateTime, month: u32) -> Option { - Some(DateTime { date: self.date.with_month(month)?, time: *self.time }) + self.date.with_month(month).map(|d| DateTime { date: d, ..*self }) + } + + /// Makes a new `NaiveDateTime` with the month number (starting from 0) changed. + /// + /// See also the [`NaiveDate::with_month0`] method. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The resulting date does not exist (for example `month0(3)` when day of the month is 31). + /// - The value for `month0` is invalid. + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate, NaiveDateTime}; + /// + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 30).unwrap().and_hms_opt(12, 34, 56).unwrap(); + /// assert_eq!( + /// dt.with_month0(9), + /// Some(NaiveDate::from_ymd_opt(2015, 10, 30).unwrap().and_hms_opt(12, 34, 56).unwrap()) + /// ); + /// assert_eq!(dt.with_month0(12), None); // No month 13 + /// assert_eq!(dt.with_month0(1), None); // No February 30 + /// ``` + #[inline] + fn with_month0(self: @DateTime, month0: u32) -> Option { + self.date.with_month0(month0).map(|d| DateTime { date: d, ..*self }) } /// Makes a new `NaiveDateTime` with the day of month (starting from 1) changed. @@ -543,8 +751,37 @@ pub impl DateTimeImpl of DateTimeTrait { /// ); /// assert_eq!(dt.with_day(31), None); // no September 31 /// ``` + #[inline] fn with_day(self: @DateTime, day: u32) -> Option { - Some(DateTime { date: self.date.with_day(day)?, time: *self.time }) + self.date.with_day(day).map(|d| DateTime { date: d, ..*self }) + } + + /// Makes a new `NaiveDateTime` with the day of month (starting from 0) changed. + /// + /// See also the [`NaiveDate::with_day0`] method. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The resulting date does not exist (for example `day(30)` in April). + /// - The value for `day0` is invalid. + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate, NaiveDateTime}; + /// + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_opt(12, 34, 56).unwrap(); + /// assert_eq!( + /// dt.with_day0(29), + /// Some(NaiveDate::from_ymd_opt(2015, 9, 30).unwrap().and_hms_opt(12, 34, 56).unwrap()) + /// ); + /// assert_eq!(dt.with_day0(30), None); // no September 31 + /// ``` + #[inline] + fn with_day0(self: @DateTime, day0: u32) -> Option { + self.date.with_day0(day0).map(|d| DateTime { date: d, ..*self }) } /// Makes a new `NaiveDateTime` with the day of year (starting from 1) changed. @@ -581,10 +818,52 @@ pub impl DateTimeImpl of DateTimeTrait { /// Some(NaiveDate::from_ymd_opt(2016, 12, 31).unwrap().and_hms_opt(12, 34, 56).unwrap()) /// ); /// ``` + #[inline] fn with_ordinal(self: @DateTime, ordinal: u32) -> Option { - Some(DateTime { date: self.date.with_ordinal(ordinal)?, time: *self.time }) + self.date.with_ordinal(ordinal).map(|d| DateTime { date: d, ..*self }) } + /// Makes a new `NaiveDateTime` with the day of year (starting from 0) changed. + /// + /// See also the [`NaiveDate::with_ordinal0`] method. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The resulting date does not exist (`with_ordinal0(365)` in a non-leap year). + /// - The value for `ordinal0` is invalid. + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate, NaiveDateTime}; + /// + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_opt(12, 34, 56).unwrap(); + /// assert_eq!( + /// dt.with_ordinal0(59), + /// Some(NaiveDate::from_ymd_opt(2015, 3, 1).unwrap().and_hms_opt(12, 34, 56).unwrap()) + /// ); + /// assert_eq!(dt.with_ordinal0(365), None); // 2015 had only 365 days + /// + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2016, 9, 8).unwrap().and_hms_opt(12, 34, 56).unwrap(); + /// assert_eq!( + /// dt.with_ordinal0(59), + /// Some(NaiveDate::from_ymd_opt(2016, 2, 29).unwrap().and_hms_opt(12, 34, 56).unwrap()) + /// ); + /// assert_eq!( + /// dt.with_ordinal0(365), + /// Some(NaiveDate::from_ymd_opt(2016, 12, 31).unwrap().and_hms_opt(12, 34, 56).unwrap()) + /// ); + /// ``` + #[inline] + fn with_ordinal0(self: @DateTime, ordinal0: u32) -> Option { + self.date.with_ordinal0(ordinal0).map(|d| DateTime { date: d, ..*self }) + } +} + +impl DateTimeTimelikeImpl of Timelike { /// Returns the hour number from 0 to 23. /// /// See also the [`NaiveTime::hour`] method. @@ -599,7 +878,8 @@ pub impl DateTimeImpl of DateTimeTrait { /// 789).unwrap(); /// assert_eq!(dt.hour(), 12); /// ``` - fn hour(self: @DateTime) -> u32 { + #[inline] + const fn hour(self: @DateTime) -> u32 { self.time.hour() } @@ -617,7 +897,8 @@ pub impl DateTimeImpl of DateTimeTrait { /// 789).unwrap(); /// assert_eq!(dt.minute(), 34); /// ``` - fn minute(self: @DateTime) -> u32 { + #[inline] + const fn minute(self: @DateTime) -> u32 { self.time.minute() } @@ -635,7 +916,8 @@ pub impl DateTimeImpl of DateTimeTrait { /// 789).unwrap(); /// assert_eq!(dt.second(), 56); /// ``` - fn second(self: @DateTime) -> u32 { + #[inline] + const fn second(self: @DateTime) -> u32 { self.time.second() } @@ -664,8 +946,9 @@ pub impl DateTimeImpl of DateTimeTrait { /// ); /// assert_eq!(dt.with_hour(24), None); /// ``` + #[inline] fn with_hour(self: @DateTime, hour: u32) -> Option { - Some(DateTime { date: *self.date, time: self.time.with_hour(hour)? }) + self.time.with_hour(hour).map(|t| DateTime { time: t, ..*self }) } /// Makes a new `NaiveDateTime` with the minute number changed. @@ -695,8 +978,9 @@ pub impl DateTimeImpl of DateTimeTrait { /// ); /// assert_eq!(dt.with_minute(60), None); /// ``` + #[inline] fn with_minute(self: @DateTime, min: u32) -> Option { - Some(DateTime { date: *self.date, time: self.time.with_minute(min)? }) + self.time.with_minute(min).map(|t| DateTime { time: t, ..*self }) } /// Makes a new `NaiveDateTime` with the second number changed. @@ -729,22 +1013,10 @@ pub impl DateTimeImpl of DateTimeTrait { /// ); /// assert_eq!(dt.with_second(60), None); /// ``` + #[inline] fn with_second(self: @DateTime, sec: u32) -> Option { - Some(DateTime { date: *self.date, time: self.time.with_second(sec)? }) + self.time.with_second(sec).map(|t| DateTime { time: t, ..*self }) } - - /// The minimum possible `NaiveDateTime`. - const MIN: DateTime = DateTime { date: DateTrait::MIN, time: TimeTrait::MIN }; - - /// The maximum possible `NaiveDateTime`. - const MAX: DateTime = DateTime { date: DateTrait::MAX, time: TimeTrait::MAX }; - - /// The datetime of the Unix Epoch, 1970-01-01 00:00:00. - /// - /// Note that while this may look like the UNIX epoch, it is missing the - /// time zone. The actual UNIX epoch cannot be expressed by this type, - /// however it is available as [`DateTime::UNIX_EPOCH`]. - const UNIX_EPOCH: DateTime = DateTime { date: Date { yof: 16138266 }, time: Time { secs: 0 } }; } impl DateTimePartialOrd of PartialOrd { @@ -759,7 +1031,6 @@ impl DateTimePartialOrd of PartialOrd { return lhs.time >= rhs.time; } lhs.date >= rhs.date - // lhs.date >= rhs.date && lhs.time >= rhs.time } } diff --git a/examples/cairo/scripts/datetime/src/lib.cairo b/packages/chrono/src/days.cairo similarity index 72% rename from examples/cairo/scripts/datetime/src/lib.cairo rename to packages/chrono/src/days.cairo index c9d8f87..557c162 100644 --- a/examples/cairo/scripts/datetime/src/lib.cairo +++ b/packages/chrono/src/days.cairo @@ -1,15 +1,3 @@ -pub mod format { - pub mod formatting; -} -pub mod date; -pub mod datetime; -pub mod internals; -pub mod month; -pub mod time_delta; -pub mod time; -pub mod utils; -pub mod weekday; - /// A duration in calendar days. /// /// This is useful because when using `TimeDelta` it is possible that adding `TimeDelta::days(1)` @@ -18,13 +6,13 @@ pub mod weekday; /// `TimeDelta::days(n)` and `Days::new(n)` are equivalent. #[derive(Clone, Copy, PartialEq, Drop, Debug)] pub struct Days { - num: u64, + pub(crate) num: u64, } #[generate_trait] pub impl DaysImpl of DaysTrait { /// Construct a new `Days` from a number of days - fn new(num: u64) -> Days { + const fn new(num: u64) -> Days { Days { num } } } diff --git a/examples/cairo/scripts/datetime/src/format/formatting.cairo b/packages/chrono/src/format/formatting.cairo similarity index 50% rename from examples/cairo/scripts/datetime/src/format/formatting.cairo rename to packages/chrono/src/format/formatting.cairo index 23ccb63..7f9b35a 100644 --- a/examples/cairo/scripts/datetime/src/format/formatting.cairo +++ b/packages/chrono/src/format/formatting.cairo @@ -1,9 +1,11 @@ -use core::fmt::{Formatter, Error}; +use core::fmt::{Error, Formatter}; -pub fn write_hundreds(ref f: Formatter, n: u8) -> Result<(), Error> { +/// Equivalent to `{:02}` formatting for n < 100. +pub(crate) fn write_hundreds(ref f: Formatter, n: u8) -> Result<(), Error> { if n >= 100 { return Result::Err(Error {}); } + f.buffer.append_byte('0' + n / 10); f.buffer.append_byte('0' + n % 10); Result::Ok(()) diff --git a/examples/cairo/scripts/datetime/src/internals.cairo b/packages/chrono/src/internals.cairo similarity index 91% rename from examples/cairo/scripts/datetime/src/internals.cairo rename to packages/chrono/src/internals.cairo index e421821..86f982f 100644 --- a/examples/cairo/scripts/datetime/src/internals.cairo +++ b/packages/chrono/src/internals.cairo @@ -1,8 +1,8 @@ -use core::num::traits::Pow; -use datetime::utils::{rem_euclid, ushl, ushr}; - //! Internal helper types for working with dates. +use core::num::traits::Pow; +use super::utils::{rem_euclid, u32_shl, u32_shr}; + /// Year flags (aka the dominical letter). /// /// `YearFlags` are used as the last four bits of `NaiveDate`, `Mdf` and `IsoWeek`. @@ -15,7 +15,7 @@ use datetime::utils::{rem_euclid, ushl, ushr}; /// `Weekday` of the last day in the preceding year. #[derive(Clone, Copy, PartialEq, Drop, Debug)] pub struct YearFlags { - pub flags: u8, + pub(crate) flags: u8, } // Weekday of the last day in the preceding year. @@ -68,20 +68,23 @@ const YEAR_TO_FLAGS: [YearFlags; 400] = [ #[generate_trait] pub impl YearFlagsImpl of YearFlagsTrait { + #[inline] fn from_year(year: i32) -> YearFlags { - // Self::from_year_mod_400(year % 400) Self::from_year_mod_400(rem_euclid(year, 400).try_into().unwrap()) } + #[inline] fn from_year_mod_400(year: u32) -> YearFlags { *YEAR_TO_FLAGS.span()[year % 400] } + #[inline] fn ndays(self: @YearFlags) -> u32 { - let leap_year_flag = ushr((*self.flags).into(), 3); + let leap_year_flag = u32_shr((*self.flags).into(), 3); 366 - leap_year_flag.try_into().unwrap() } + #[inline] fn isoweek_delta(self: @YearFlags) -> u32 { let mut delta: u32 = (*self.flags).try_into().unwrap() & 0b0111; if delta < 3 { @@ -90,8 +93,9 @@ pub impl YearFlagsImpl of YearFlagsTrait { delta } - fn nisoweeks(self: @YearFlags) -> u32 { - 52 + ushr(0b0000_0100_0000_0110, *self.flags) % 2 + #[inline] + const fn nisoweeks(self: @YearFlags) -> u32 { + 52 + u32_shr(0b0000_0100_0000_0110, *self.flags) % 2 } } @@ -103,7 +107,7 @@ const MAX_MDL: u32 = (12 * 2_u32.pow(6)) | (31 * 2_u32.pow(1)) | 1; // ordinal-leapyear. OL = MDL - adjustment. // Dates that do not exist are encoded as `XX`. const XX: u8 = 0; -pub const MDL_TO_OL: [u8; MAX_MDL + 1] = [ +const MDL_TO_OL: [u8; MAX_MDL + 1] = [ XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, // 0 @@ -216,7 +220,7 @@ const OL_TO_MDL: [u8; MAX_OL + 1] = [ /// table lookup, which is good for performance. #[derive(Clone, Copy, PartialEq, Drop, Debug)] pub struct Mdf { - pub mdf: u32, + mdf: u32, } #[generate_trait] @@ -229,9 +233,10 @@ pub impl MdfImpl of MdfTrait { /// # Errors /// /// Returns `None` if `month > 12` or `day > 31`. + #[inline] fn new(month: u32, day: u32, flags: YearFlags) -> Option { match month <= 12 && day <= 31 { - true => Some(Mdf { mdf: ushl(month, 9) | ushl(day, 4) | flags.flags.into() }), + true => Some(Mdf { mdf: u32_shl(month, 9) | u32_shl(day, 4) | flags.flags.into() }), false => None, } } @@ -240,18 +245,20 @@ pub impl MdfImpl of MdfTrait { /// `flags`. /// /// The `ol` is trusted to be valid, and the `flags` are trusted to match it. + #[inline] fn from_ol(ol: i32, flags: YearFlags) -> Mdf { // debug_assert!(ol > 1 && ol <= MAX_OL as i32); // Array is indexed from `[2..=MAX_OL]`, with a `0` index having a meaningless value. // Mdf(((ol as u32 + OL_TO_MDL[ol as usize] as u32) << 3) | flags as u32) let ol_u32: u32 = ol.try_into().unwrap(); let mdl = *OL_TO_MDL.span()[ol_u32]; - Mdf { mdf: ushl(ol_u32 + mdl.into(), 3) | flags.flags.into() } + Mdf { mdf: u32_shl(ol_u32 + mdl.into(), 3) | flags.flags.into() } } /// Returns the month of this `Mdf`. - fn month(self: @Mdf) -> u32 { - ushr(*self.mdf, 9) + #[inline] + const fn month(self: @Mdf) -> u32 { + u32_shr(*self.mdf, 9) } /// Replaces the month of this `Mdf`, keeping the day and flags. @@ -259,17 +266,19 @@ pub impl MdfImpl of MdfTrait { /// # Errors /// /// Returns `None` if `month > 12`. + #[inline] fn with_month(self: @Mdf, month: u32) -> Option { if month > 12 { return None; } - Some(Mdf { mdf: (*self.mdf & 0b0_0001_1111_1111) | ushl(month, 9) }) + Some(Mdf { mdf: (*self.mdf & 0b0_0001_1111_1111) | u32_shl(month, 9) }) } /// Returns the day of this `Mdf`. - fn day(self: @Mdf) -> u32 { - ushr(*self.mdf, 4) & 0b1_1111 + #[inline] + const fn day(self: @Mdf) -> u32 { + u32_shr(*self.mdf, 4) & 0b1_1111 } /// Replaces the day of this `Mdf`, keeping the month and flags. @@ -277,16 +286,18 @@ pub impl MdfImpl of MdfTrait { /// # Errors /// /// Returns `None` if `day > 31`. + #[inline] fn with_day(self: @Mdf, day: u32) -> Option { if day > 31 { return None; } - Some(Mdf { mdf: (*self.mdf & 0b1_1110_0000_1111) | ushl(day, 4) }) + Some(Mdf { mdf: (*self.mdf & 0b1_1110_0000_1111) | u32_shl(day, 4) }) } /// Replaces the flags of this `Mdf`, keeping the month and day. - fn with_flags(self: @Mdf, flags: YearFlags) -> Mdf { + #[inline] + const fn with_flags(self: @Mdf, flags: YearFlags) -> Mdf { Mdf { mdf: (*self.mdf & 0b1_1111_1111_0000) | flags.flags.into() } } @@ -299,13 +310,14 @@ pub impl MdfImpl of MdfTrait { /// /// Returns `None` if `month == 0` or `day == 0`, or if a the given day does not exist in the /// given month. + #[inline] fn ordinal(self: @Mdf) -> Option { - let mdl = ushr(*self.mdf, 3); + let mdl = u32_shr(*self.mdf, 3); let ol = *MDL_TO_OL.span()[mdl]; if ol == XX { None } else { - Some(ushr(mdl - ol.into(), 1)) + Some(u32_shr(mdl - ol.into(), 1)) } } @@ -318,13 +330,20 @@ pub impl MdfImpl of MdfTrait { /// /// Returns `None` if `month == 0` or `day == 0`, or if a the given day does not exist in the /// given month. + #[inline] fn ordinal_and_flags(self: @Mdf) -> Option { - let mdl = ushr(*self.mdf, 3); + let mdl = u32_shr(*self.mdf, 3); let ol = *MDL_TO_OL.span()[mdl]; if ol == XX { None } else { - Some(*self.mdf - ushl(ol.into(), 3)) + Some(*self.mdf - u32_shl(ol.into(), 3)) } } + + // #[cfg(test)] + fn valid(self: @Mdf) -> bool { + let mdl = u32_shr(*self.mdf, 3); + *MDL_TO_OL.span()[mdl.try_into().unwrap()] > 0 + } } diff --git a/packages/chrono/src/isoweek.cairo b/packages/chrono/src/isoweek.cairo new file mode 100644 index 0000000..f76f800 --- /dev/null +++ b/packages/chrono/src/isoweek.cairo @@ -0,0 +1,142 @@ +//! ISO 8601 week. + +use core::fmt::{Debug, Error, Formatter}; +use core::ops::RangeInclusiveTrait; +use super::format::formatting::write_hundreds; +use super::internals::{YearFlags, YearFlagsTrait}; +use super::utils::{u32_shl, u32_shr}; + +/// ISO 8601 week. +/// +/// This type, combined with [`Weekday`](../enum.Weekday.html), +/// constitutes the ISO 8601 [week date](./struct.NaiveDate.html#week-date). +/// One can retrieve this type from the existing [`Datelike`](../trait.Datelike.html) types +/// via the [`Datelike::iso_week`](../trait.Datelike.html#tymethod.iso_week) method. +#[derive(Clone, Copy, PartialEq, Drop)] +pub struct IsoWeek { + // Note that this allows for larger year range than `NaiveDate`. + // This is crucial because we have an edge case for the first and last week supported, + // which year number might not match the calendar year number. + ywf: u32 // (year << 10) | (week << 4) | flag +} + +#[generate_trait] +pub impl IsoWeekImpl of IsoWeekTrait { + /// Returns the corresponding `IsoWeek` from the year and the `Of` internal value. + // + // Internal use only. We don't expose the public constructor for `IsoWeek` for now + // because the year range for the week date and the calendar date do not match, and + // it is confusing to have a date that is out of range in one and not in another. + // Currently we sidestep this issue by making `IsoWeek` fully dependent of `Datelike`. + fn from_yof(year: i32, ordinal: u32, year_flags: YearFlags) -> IsoWeek { + let rawweek = (ordinal + year_flags.isoweek_delta()) / 7; + let (year, week) = if rawweek < 1 { + // previous year + let prevlastweek = YearFlagsTrait::from_year(year - 1).nisoweeks(); + (year - 1, prevlastweek) + } else { + let lastweek = year_flags.nisoweeks(); + if rawweek > lastweek { + // next year + (year + 1, 1) + } else { + (year, rawweek) + } + }; + let flags = YearFlagsTrait::from_year(year); + IsoWeek { + ywf: u32_shl(year.try_into().unwrap(), 10) | u32_shl(week, 4) | flags.flags.into(), + } + } + + /// Returns the year number for this ISO week. + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate, Weekday}; + /// + /// let d = NaiveDate::from_isoywd_opt(2015, 1, Weekday::Mon).unwrap(); + /// assert_eq!(d.iso_week().year(), 2015); + /// ``` + /// + /// This year number might not match the calendar year number. + /// Continuing the example... + /// + /// ``` + /// # use chrono::{NaiveDate, Datelike, Weekday}; + /// # let d = NaiveDate::from_isoywd_opt(2015, 1, Weekday::Mon).unwrap(); + /// assert_eq!(d.year(), 2014); + /// assert_eq!(d, NaiveDate::from_ymd_opt(2014, 12, 29).unwrap()); + /// ``` + #[inline] + const fn year(self: @IsoWeek) -> u32 { + u32_shr(*self.ywf, 10) + } + + /// Returns the ISO week number starting from 1. + /// + /// The return value ranges from 1 to 53. (The last week of year differs by years.) + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate, Weekday}; + /// + /// let d = NaiveDate::from_isoywd_opt(2015, 15, Weekday::Mon).unwrap(); + /// assert_eq!(d.iso_week().week(), 15); + /// ``` + #[inline] + const fn week(self: @IsoWeek) -> u32 { + (u32_shr(*self.ywf, 4) & 0x3f) + } + + /// Returns the ISO week number starting from 0. + /// + /// The return value ranges from 0 to 52. (The last week of year differs by years.) + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate, Weekday}; + /// + /// let d = NaiveDate::from_isoywd_opt(2015, 15, Weekday::Mon).unwrap(); + /// assert_eq!(d.iso_week().week0(), 14); + /// ``` + #[inline] + const fn week0(self: @IsoWeek) -> u32 { + (u32_shr(*self.ywf, 4) & 0x3f) - 1 + } +} + +impl IsoWeekPartialOrd of PartialOrd { + #[inline] + fn lt(lhs: IsoWeek, rhs: IsoWeek) -> bool { + lhs.ywf < rhs.ywf + } + #[inline] + fn ge(lhs: IsoWeek, rhs: IsoWeek) -> bool { + lhs.ywf >= rhs.ywf + } +} + +impl IsoWeekDebug of Debug { + fn fmt(self: @IsoWeek, ref f: Formatter) -> Result<(), Error> { + let year = self.year(); + let week = self.week(); + if (0..=9999).contains(@year) { + write_hundreds(ref f, (year / 100).try_into().unwrap())?; + write_hundreds(ref f, (year % 100).try_into().unwrap())?; + } else { + let sign = if year > 0 { + '+' + } else { + '-' + }; + f.buffer.append_byte(sign); + write!(f, "{year}")?; + } + f.buffer.append(@"-W"); + write_hundreds(ref f, week.try_into().unwrap()) + } +} diff --git a/packages/chrono/src/lib.cairo b/packages/chrono/src/lib.cairo new file mode 100644 index 0000000..9b96306 --- /dev/null +++ b/packages/chrono/src/lib.cairo @@ -0,0 +1,35 @@ +//! Date and time types unconcerned with timezones. +//! +//! They are primarily building blocks for other types +//! (e.g. [`TimeZone`](../offset/trait.TimeZone.html)), +//! but can be also used for the simpler date and time handling. + +pub mod format { + pub mod formatting; +} +pub mod date; +pub mod datetime; +pub mod days; +pub mod internals; +pub mod isoweek; +pub mod months; +pub mod time; +pub mod time_delta; +pub mod traits; +pub mod utils; +pub mod week; +pub mod weekday; + +/// A convenience module appropriate for glob imports (`use chrono::prelude::*;`). +pub mod prelude { + pub use crate::date::{Date, DateTrait}; + pub use crate::datetime::{DateTime, DateTimeTrait}; + pub use crate::days::{Days, DaysTrait}; + pub use crate::isoweek::{IsoWeek, IsoWeekTrait}; + pub use crate::months::{Month, MonthTrait, Months, MonthsTrait}; + pub use crate::time::{Time, TimeTrait}; + pub use crate::time_delta::{TimeDelta, TimeDeltaTrait}; + pub use crate::traits::{Datelike, Timelike}; + pub use crate::week::{Week, WeekTrait}; + pub use crate::weekday::{Weekday, WeekdayTrait}; +} diff --git a/examples/cairo/scripts/datetime/src/month.cairo b/packages/chrono/src/months.cairo similarity index 86% rename from examples/cairo/scripts/datetime/src/month.cairo rename to packages/chrono/src/months.cairo index 3d45765..64c06c5 100644 --- a/examples/cairo/scripts/datetime/src/month.cairo +++ b/packages/chrono/src/months.cairo @@ -1,5 +1,5 @@ use core::fmt::{Debug, Display, Error, Formatter}; -use datetime::date::DateTrait; +use super::date::DateTrait; /// The month of the year. /// @@ -59,7 +59,8 @@ pub impl MonthImpl of MonthTrait { /// `m`: | `January` | `February` | `...` | `December` /// ----------- | --------- | ---------- | --- | --------- /// `m.succ()`: | `February` | `March` | `...` | `January` - fn succ(self: @Month) -> Month { + #[inline] + const fn succ(self: @Month) -> Month { match self { Month::January => Month::February, Month::February => Month::March, @@ -81,7 +82,8 @@ pub impl MonthImpl of MonthTrait { /// `m`: | `January` | `February` | `...` | `December` /// ----------- | --------- | ---------- | --- | --------- /// `m.pred()`: | `December` | `January` | `...` | `November` - fn pred(self: @Month) -> Month { + #[inline] + const fn pred(self: @Month) -> Month { match self { Month::January => Month::December, Month::February => Month::January, @@ -103,7 +105,8 @@ pub impl MonthImpl of MonthTrait { /// `m`: | `January` | `February` | `...` | `December` /// -------------------------| --------- | ---------- | --- | ----- /// `m.number_from_month()`: | 1 | 2 | `...` | 12 - fn number_from_month(self: @Month) -> u32 { + #[inline] + const fn number_from_month(self: @Month) -> u32 { match self { Month::January => 1, Month::February => 2, @@ -120,6 +123,30 @@ pub impl MonthImpl of MonthTrait { } } + /// Get the name of the month + /// + /// ``` + /// use chrono::Month; + /// + /// assert_eq!(Month::January.name(), "January") + /// ``` + const fn name(self: @Month) -> felt252 { + match *self { + Month::January => 'January', + Month::February => 'February', + Month::March => 'March', + Month::April => 'April', + Month::May => 'May', + Month::June => 'June', + Month::July => 'July', + Month::August => 'August', + Month::September => 'September', + Month::October => 'October', + Month::November => 'November', + Month::December => 'December', + } + } + /// Get the length in days of the month /// /// Yields `None` if `year` is out of range for `NaiveDate`. @@ -145,14 +172,17 @@ pub impl MonthImpl of MonthTrait { ) } + #[inline] fn from_u64(n: u64) -> Option { Self::from_u32(n.try_into().unwrap()) } + #[inline] fn from_i64(n: i64) -> Option { Self::from_u32(n.try_into().unwrap()) } + #[inline] fn from_u32(n: u32) -> Option { match n { 0 => None, @@ -174,9 +204,11 @@ pub impl MonthImpl of MonthTrait { } impl MonthPartialOrd of PartialOrd { + #[inline] fn lt(lhs: Month, rhs: Month) -> bool { lhs.number_from_month() < rhs.number_from_month() } + #[inline] fn ge(lhs: Month, rhs: Month) -> bool { lhs.number_from_month() >= rhs.number_from_month() } @@ -184,22 +216,7 @@ impl MonthPartialOrd of PartialOrd { impl MonthTryInto of TryInto { fn try_into(self: u8) -> Option { - match self { - 0 => None, - 1 => Some(Month::January), - 2 => Some(Month::February), - 3 => Some(Month::March), - 4 => Some(Month::April), - 5 => Some(Month::May), - 6 => Some(Month::June), - 7 => Some(Month::July), - 8 => Some(Month::August), - 9 => Some(Month::September), - 10 => Some(Month::October), - 11 => Some(Month::November), - 12 => Some(Month::December), - _ => None, - } + MonthTrait::from_u32(self.into()) } } @@ -237,20 +254,23 @@ pub struct Months { #[generate_trait] pub impl MonthsImpl of MonthsTrait { /// Construct a new `Months` from a number of months - fn new(months: u32) -> Months { + const fn new(months: u32) -> Months { Months { months } } /// Returns the total number of months in the `Months` instance. - fn as_u32(self: @Months) -> u32 { + #[inline] + const fn as_u32(self: @Months) -> u32 { *self.months } } impl MonthsPartialOrd of PartialOrd { + #[inline] fn lt(lhs: Months, rhs: Months) -> bool { lhs.months < rhs.months } + #[inline] fn ge(lhs: Months, rhs: Months) -> bool { lhs.months >= rhs.months } diff --git a/examples/cairo/scripts/datetime/src/time.cairo b/packages/chrono/src/time.cairo similarity index 92% rename from examples/cairo/scripts/datetime/src/time.cairo rename to packages/chrono/src/time.cairo index fa1b135..b8d4155 100644 --- a/examples/cairo/scripts/datetime/src/time.cairo +++ b/packages/chrono/src/time.cairo @@ -1,11 +1,14 @@ +//! ISO 8601 time without timezone. + use core::fmt::{Debug, Display, Error, Formatter}; -use datetime::format::formatting::write_hundreds; -use datetime::time_delta::{TimeDelta, TimeDeltaTrait}; -use datetime::utils::rem_euclid; +use super::format::formatting::write_hundreds; +use super::time_delta::{TimeDelta, TimeDeltaTrait}; +use super::traits::Timelike; +use super::utils::rem_euclid; -#[derive(Clone, Copy, PartialEq, Drop)] +#[derive(Clone, Copy, PartialEq, Drop, Serde, starknet::Store)] pub struct Time { - pub secs: u32, + pub(crate) secs: u32, } #[generate_trait] @@ -32,6 +35,7 @@ pub impl TimeImpl of TimeTrait { /// assert!(from_hms_opt(23, 60, 0).is_none()); /// assert!(from_hms_opt(23, 59, 60).is_none()); /// ``` + #[inline] fn from_hms_opt(hour: u32, min: u32, sec: u32) -> Option