diff --git a/.lock b/.lock new file mode 100644 index 00000000..e69de29b diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 00000000..e69de29b diff --git a/crates.js b/crates.js new file mode 100644 index 00000000..6f5a879b --- /dev/null +++ b/crates.js @@ -0,0 +1 @@ +window.ALL_CRATES = ["strftime"]; \ No newline at end of file diff --git a/help.html b/help.html new file mode 100644 index 00000000..71e406bf --- /dev/null +++ b/help.html @@ -0,0 +1 @@ +
strftime
…","t":"RENNNNNNINLLAAKKKALLLLLLKLAKKKKKLAKKLLLLKKFFFFF","n":["ASCTIME_FORMAT_STRING","Error","FmtError","FormattedStringTooLarge","InvalidFormatString","InvalidTime","IoError","OutOfMemory","Time","WriteZero","borrow","borrow_mut","buffered","bytes","day","day_of_week","day_of_year","fmt","fmt","fmt","from","from","from","from","hour","into","io","is_utc","minute","month","nanoseconds","second","source","string","time_zone","to_int","to_string","try_from","try_into","type_id","utc_offset","year","strftime","strftime","strftime","strftime","strftime"],"q":[[0,"strftime"],[42,"strftime::buffered"],[43,"strftime::bytes"],[44,"strftime::fmt"],[45,"strftime::io"],[46,"strftime::string"],[47,"core::fmt"],[48,"core::fmt"],[49,"alloc::collections"],[50,"core::error"],[51,"core::option"],[52,"alloc::string"],[53,"core::result"],[54,"core::any"],[55,"alloc::vec"],[56,"core::fmt"]],"d":["Format string used by Ruby Time#asctime
method.","Error type returned by the strftime
functions.","Formatting error, corresponding to core::fmt::Error
.","Formatted string is too large and could cause an …","Provided format string is ended by an unterminated format …","Provided time implementation returns invalid values.","An I/O error has occurred in io::strftime
.","An allocation failure has occurred in either …","Common methods needed for formatting time.","Provided buffer for the buffered::strftime
function is too …","","","Provides a strftime
implementation using a format string …","Provides a strftime
implementation using a format string …","Returns the day of the month in 1..=31
for time.","Returns an integer representing the day of the week in …","Returns an integer representing the day of the year in …","Provides a strftime
implementation using a UTF-8 format …","","","Returns the argument unchanged.","","","","Returns the hour of the day in 0..=23
for time.","Calls U::from(self)
.","Provides a strftime
implementation using a format string …","Returns true if the time zone is UTC.","Returns the minute of the hour in 0..=59
for time.","Returns the month of the year in 1..=12
for time.","Returns the number of nanoseconds in 0..=999_999_999
for …","Returns the second of the minute in 0..=60
for time.","","Provides a strftime
implementation using a UTF-8 format …","Returns the name of the time zone as a string.","Returns the number of seconds as a signed integer since …","","","","","Returns the offset in seconds between the timezone of time …","Returns the year for time (including the century).","Format a time implementation with the specified format …","Format a time implementation with the specified format …","Format a time implementation with the specified UTF-8 …","Format a time implementation with the specified format …","Format a time implementation with the specified UTF-8 …"],"i":[0,0,3,3,3,3,3,3,0,3,3,3,0,0,20,20,20,0,3,3,3,3,3,3,20,3,0,20,20,20,20,20,3,0,20,20,3,3,3,3,20,20,0,0,0,0,0],"f":[0,0,0,0,0,0,0,0,0,0,[-1,-2,[],[]],[-1,-2,[],[]],0,0,[-1,1,[]],[-1,1,[]],[-1,2,[]],0,[[3,4],5],[[3,4],5],[-1,-1,[]],[6,3],[7,3],[8,3],[-1,1,[]],[-1,-2,[],[]],0,[-1,9,[]],[-1,1,[]],[-1,1,[]],[-1,10,[]],[-1,1,[]],[3,[[12,[11]]]],0,[-1,13,[]],[-1,14,[]],[-1,15,[]],[-1,[[16,[-2]]],[],[]],[-1,[[16,[-2]]],[],[]],[-1,17,[]],[-1,18,[]],[-1,18,[]],[[-1,[19,[1]],[19,[1]]],[[16,[[19,[1]],3]]],20],[[-1,[19,[1]]],[[16,[[21,[1]],3]]],20],[[-1,13,22],[[16,[23,3]]],20],[[-1,[19,[1]],24],[[16,[23,3]]],20],[[-1,13],[[16,[15,3]]],20]],"c":[],"p":[[15,"u8"],[15,"u16"],[4,"Error",0],[3,"Formatter",47],[6,"Result",47],[3,"Error",47],[3,"Error",48],[3,"TryReserveError",49],[15,"bool"],[15,"u32"],[8,"Error",50],[4,"Option",51],[15,"str"],[15,"i64"],[3,"String",52],[4,"Result",53],[3,"TypeId",54],[15,"i32"],[15,"slice"],[8,"Time",0],[3,"Vec",55],[8,"Write",47],[15,"tuple"],[8,"Write",56]],"b":[[18,"impl-Debug-for-Error"],[19,"impl-Display-for-Error"],[21,"impl-From%3CError%3E-for-Error"],[22,"impl-From%3CError%3E-for-Error"],[23,"impl-From%3CTryReserveError%3E-for-Error"]]}\
+}');
+if (typeof window !== 'undefined' && window.initSearch) {window.initSearch(searchIndex)};
+if (typeof exports !== 'undefined') {exports.searchIndex = searchIndex};
diff --git a/settings.html b/settings.html
new file mode 100644
index 00000000..9d10e550
--- /dev/null
+++ b/settings.html
@@ -0,0 +1 @@
+1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +
//! Compile-time assert functions.
+
+/// Helper macro for implementing asserts.
+macro_rules! assert_sorted_by_key {
+ ($s:expr, $f:expr) => {{
+ let mut i = 0;
+ while i + 1 < $s.len() {
+ assert!(*$f(&$s[i]) < *$f(&$s[i + 1]));
+ i += 1;
+ }
+ $s
+ }};
+}
+
+/// Returns the first element of a tuple.
+const fn elem_0<T>(x: &(u8, T)) -> &u8 {
+ &x.0
+}
+
+/// Asserts that a slice is sorted and has no duplicates.
+pub(crate) const fn assert_sorted(s: &[u8]) -> &[u8] {
+ assert_sorted_by_key!(s, core::convert::identity)
+}
+
+/// Asserts that a slice is sorted by its first element and has no duplicates.
+pub(crate) const fn assert_sorted_elem_0<T>(s: &[(u8, T)]) -> &[(u8, T)] {
+ assert_sorted_by_key!(s, elem_0)
+}
+
+/// Asserts that converting the first input to uppercase yields the second input.
+#[allow(dead_code)]
+pub(crate) const fn assert_to_ascii_uppercase(table: &[&str], upper_table: &[&str]) {
+ assert!(table.len() == upper_table.len());
+
+ let mut index = 0;
+ while index < table.len() {
+ let (s, upper_s) = (table[index].as_bytes(), upper_table[index].as_bytes());
+ assert!(s.len() == upper_s.len());
+
+ let mut i = 0;
+ while i < s.len() {
+ assert!(s[i].is_ascii());
+ assert!(upper_s[i].is_ascii());
+ assert!(upper_s[i] == s[i].to_ascii_uppercase());
+ i += 1;
+ }
+
+ index += 1;
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_assert_sorted() {
+ assert_sorted(&[1, 2, 3]);
+ }
+
+ #[test]
+ #[should_panic]
+ fn test_assert_sorted_invalid() {
+ assert_sorted(&[1, 3, 2]);
+ }
+
+ #[test]
+ fn test_assert_sorted_elem_0() {
+ assert_sorted_elem_0(&[(1, 3), (2, 2), (3, 1)]);
+ }
+
+ #[test]
+ #[should_panic]
+ fn test_assert_sorted_elem_0_invalid() {
+ assert_sorted_elem_0(&[(1, 3), (3, 2), (2, 1)]);
+ }
+
+ #[test]
+ fn test_assert_to_ascii_uppercase() {
+ assert_to_ascii_uppercase(&["aaa"], &["AAA"]);
+ }
+
+ #[test]
+ #[should_panic]
+ fn test_assert_to_ascii_uppercase_invalid() {
+ assert_to_ascii_uppercase(&["aaa"], &["AaA"]);
+ }
+
+ #[test]
+ #[should_panic]
+ fn test_assert_to_ascii_uppercase_invalid_ascii() {
+ assert_to_ascii_uppercase(&["€"], &["€"]);
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +
//! Checks for a time implementation.
+
+use crate::{Error, Time};
+
+/// Wrapper trait for validating a time implementation.
+pub(crate) trait CheckedTime {
+ /// No checks.
+ fn year(&self) -> i32;
+ /// Checks if the month is in `1..=12`.
+ fn month(&self) -> Result<u8, Error>;
+ /// Checks if the day of the month is in `1..=31`.
+ fn day(&self) -> Result<u8, Error>;
+ /// Checks if the hour of the day is in `0..=23`.
+ fn hour(&self) -> Result<u8, Error>;
+ /// Checks if the minute of the hour is in `0..=59`.
+ fn minute(&self) -> Result<u8, Error>;
+ /// Checks if the second of the minute is in `0..=60`.
+ fn second(&self) -> Result<u8, Error>;
+ /// Checks if the number of nanoseconds is in `0..=999_999_999`.
+ fn nanoseconds(&self) -> Result<u32, Error>;
+ /// Checks if the day of the week is in `0..=6`.
+ fn day_of_week(&self) -> Result<u8, Error>;
+ /// Checks if the day of the year is in `1..=366`.
+ fn day_of_year(&self) -> Result<u16, Error>;
+ /// No checks.
+ fn to_int(&self) -> i64;
+ /// No checks.
+ fn is_utc(&self) -> bool;
+ /// No checks.
+ fn utc_offset(&self) -> i32;
+ /// Checks if the name of the time zone is valid ASCII.
+ fn time_zone(&self) -> Result<&str, Error>;
+}
+
+impl<T: Time> CheckedTime for T {
+ fn year(&self) -> i32 {
+ self.year()
+ }
+
+ fn month(&self) -> Result<u8, Error> {
+ match self.month() {
+ month @ 1..=12 => Ok(month),
+ _ => Err(Error::InvalidTime),
+ }
+ }
+
+ fn day(&self) -> Result<u8, Error> {
+ match self.day() {
+ day @ 1..=31 => Ok(day),
+ _ => Err(Error::InvalidTime),
+ }
+ }
+
+ fn hour(&self) -> Result<u8, Error> {
+ match self.hour() {
+ hour @ 0..=23 => Ok(hour),
+ _ => Err(Error::InvalidTime),
+ }
+ }
+
+ fn minute(&self) -> Result<u8, Error> {
+ match self.minute() {
+ minute @ 0..=59 => Ok(minute),
+ _ => Err(Error::InvalidTime),
+ }
+ }
+
+ fn second(&self) -> Result<u8, Error> {
+ match self.second() {
+ second @ 0..=60 => Ok(second),
+ _ => Err(Error::InvalidTime),
+ }
+ }
+
+ fn nanoseconds(&self) -> Result<u32, Error> {
+ match self.nanoseconds() {
+ nanoseconds @ 0..=999_999_999 => Ok(nanoseconds),
+ _ => Err(Error::InvalidTime),
+ }
+ }
+
+ fn day_of_week(&self) -> Result<u8, Error> {
+ match self.day_of_week() {
+ day_of_week @ 0..=6 => Ok(day_of_week),
+ _ => Err(Error::InvalidTime),
+ }
+ }
+
+ fn day_of_year(&self) -> Result<u16, Error> {
+ match self.day_of_year() {
+ day_of_year @ 1..=366 => Ok(day_of_year),
+ _ => Err(Error::InvalidTime),
+ }
+ }
+
+ fn to_int(&self) -> i64 {
+ self.to_int()
+ }
+
+ fn is_utc(&self) -> bool {
+ self.is_utc()
+ }
+
+ fn utc_offset(&self) -> i32 {
+ self.utc_offset()
+ }
+
+ fn time_zone(&self) -> Result<&str, Error> {
+ match self.time_zone() {
+ time_zone if time_zone.is_ascii() => Ok(time_zone),
+ _ => Err(Error::InvalidTime),
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ include!("../mock.rs.in");
+
+ fn check<T>(ok: bool, result: &Result<T, Error>) {
+ if ok {
+ assert!(result.is_ok());
+ } else {
+ assert!(matches!(result, Err(Error::InvalidTime)));
+ }
+ }
+
+ #[test]
+ fn test_checked_time() {
+ #[rustfmt::skip]
+ let times = [
+ MockTime::new(1970, 1, 1, 0, 0, 0, 0, 4, 1, 0, false, 0, ""),
+ MockTime::new(1970, 0, 0, 99, 99, 99, 1_000_000_000, 9, 999, 0, false, 0, "€"),
+ ];
+
+ check(true, &CheckedTime::month(×[0]));
+ check(true, &CheckedTime::day(×[0]));
+ check(true, &CheckedTime::hour(×[0]));
+ check(true, &CheckedTime::minute(×[0]));
+ check(true, &CheckedTime::second(×[0]));
+ check(true, &CheckedTime::nanoseconds(×[0]));
+ check(true, &CheckedTime::day_of_week(×[0]));
+ check(true, &CheckedTime::day_of_year(×[0]));
+ check(true, &CheckedTime::time_zone(×[0]));
+
+ check(false, &CheckedTime::month(×[1]));
+ check(false, &CheckedTime::day(×[1]));
+ check(false, &CheckedTime::hour(×[1]));
+ check(false, &CheckedTime::minute(×[1]));
+ check(false, &CheckedTime::second(×[1]));
+ check(false, &CheckedTime::nanoseconds(×[1]));
+ check(false, &CheckedTime::day_of_week(×[1]));
+ check(false, &CheckedTime::day_of_year(×[1]));
+ check(false, &CheckedTime::time_zone(×[1]));
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352 +353 +354 +355 +356 +357 +358 +359 +360 +361 +362 +363 +364 +365 +366 +367 +368 +369 +370 +371 +372 +373 +374 +375 +376 +377 +378 +379 +380 +381 +382 +383 +384 +385 +386 +387 +388 +389 +390 +391 +392 +393 +394 +395 +396 +397 +398 +399 +400 +401 +402 +403 +404 +405 +406 +407 +408 +409 +410 +411 +412 +413 +414 +415 +416 +417 +418 +419 +420 +421 +422 +423 +424 +425 +426 +427 +428 +429 +430 +431 +432 +433 +434 +435 +436 +437 +438 +439 +440 +441 +442 +443 +444 +445 +446 +447 +448 +449 +450 +451 +452 +453 +454 +455 +456 +457 +458 +459 +460 +461 +462 +463 +464 +465 +466 +467 +468 +469 +470 +471 +472 +473 +474 +475 +476 +477 +478 +479 +480 +481 +482 +483 +484 +485 +486 +487 +488 +489 +490 +491 +492 +493 +494 +495 +496 +497 +498 +499 +500 +501 +502 +503 +504 +505 +506 +507 +508 +509 +510 +511 +512 +513 +514 +515 +516 +517 +518 +519 +520 +521 +522 +523 +524 +525 +526 +527 +528 +529 +530 +531 +532 +533 +534 +535 +536 +537 +538 +539 +540 +541 +542 +543 +544 +545 +546 +547 +548 +549 +550 +551 +552 +553 +554 +555 +556 +557 +558 +559 +560 +561 +562 +563 +564 +565 +566 +567 +568 +569 +570 +571 +572 +573 +574 +575 +576 +577 +578 +579 +580 +581 +582 +583 +584 +585 +586 +587 +588 +589 +590 +591 +592 +593 +594 +595 +596 +597 +598 +599 +600 +601 +602 +603 +604 +605 +606 +607 +608 +609 +610 +611 +612 +613 +614 +615 +616 +617 +618 +619 +620 +621 +622 +623 +624 +625 +626 +627 +628 +629 +630 +631 +632 +633 +634 +635 +636 +637 +638 +639 +640 +641 +642 +643 +644 +645 +646 +647 +648 +649 +650 +651 +652 +653 +654 +655 +656 +657 +658 +659 +660 +661 +662 +663 +664 +665 +666 +667 +668 +669 +670 +671 +672 +673 +674 +675 +676 +677 +678 +679 +680 +681 +682 +683 +684 +685 +686 +687 +688 +689 +690 +691 +692 +693 +694 +695 +696 +697 +698 +699 +700 +701 +702 +703 +704 +705 +706 +707 +708 +709 +710 +711 +712 +713 +714 +715 +716 +717 +718 +719 +720 +721 +722 +723 +724 +725 +726 +727 +728 +729 +730 +731 +732 +733 +734 +735 +736 +737 +738 +739 +740 +741 +742 +743 +744 +745 +746 +747 +748 +749 +750 +751 +752 +753 +754 +755 +756 +757 +758 +759 +760 +761 +762 +763 +764 +765 +766 +767 +768 +769 +770 +771 +772 +773 +774 +775 +776 +777 +778 +779 +780 +781 +782 +783 +784 +785 +786 +787 +788 +789 +790 +791 +792 +793 +794 +795 +796 +797 +798 +799 +800 +801 +802 +803 +804 +805 +806 +807 +808 +809 +810 +811 +812 +813 +814 +815 +816 +817 +818 +819 +820 +821 +822 +823 +824 +825 +826 +827 +828 +829 +830 +831 +832 +833 +834 +835 +836 +837 +838 +839 +840 +841 +842 +843 +844 +845 +846 +847 +848 +849 +850 +851 +852 +853 +854 +855 +856 +857 +858 +859 +860 +861 +862 +863 +864 +865 +866 +867 +868 +869 +870 +871 +872 +873 +874 +875 +876 +877 +878 +879 +880 +881 +882 +883 +884 +885 +886 +887 +888 +889 +890 +891 +892 +893 +894 +895 +896 +897 +898 +899 +900 +901 +902 +903 +904 +905 +906 +907 +908 +909 +910 +911 +912 +913 +914 +915 +916 +917 +918 +919 +920 +921 +922 +923 +924 +925 +926 +927 +928 +929 +930 +931 +932 +933 +934 +935 +936 +937 +938 +939 +940 +941 +942 +943 +944 +945 +946 +947 +948 +949 +950 +951 +952 +953 +954 +955 +956 +957 +958 +959 +960 +961 +962 +963 +964 +965 +966 +967 +968 +969 +970 +971 +972 +973 +974 +975 +976 +977 +978 +979 +980 +981 +982 +983 +984 +985 +986 +987 +988 +989 +990 +991 +992 +993 +994 +995 +996 +997 +998 +999 +1000 +1001 +1002 +1003 +1004 +1005 +1006 +1007 +1008 +1009 +1010 +1011 +1012 +1013 +1014 +1015 +1016 +1017 +1018 +1019 +1020 +1021 +1022 +1023 +1024 +1025 +1026 +1027 +1028 +1029 +1030 +1031 +1032 +1033 +1034 +1035 +1036 +1037 +
//! Module containing the formatting logic.
+
+mod assert;
+mod check;
+mod utils;
+mod week;
+mod write;
+
+use core::fmt;
+use core::num::IntErrorKind;
+use core::str;
+
+use crate::Error;
+use assert::{assert_sorted, assert_sorted_elem_0, assert_to_ascii_uppercase};
+use check::CheckedTime;
+use utils::{Cursor, SizeLimiter};
+use week::{iso_8601_year_and_week_number, week_number, WeekStart};
+use write::Write;
+
+pub(crate) use write::FmtWrite;
+#[cfg(feature = "std")]
+pub(crate) use write::IoWrite;
+
+/// Alias to a `c_int`.
+#[cfg(feature = "std")]
+type Int = std::os::raw::c_int;
+/// Fallback alias to a `c_int`.
+#[cfg(not(feature = "std"))]
+type Int = i32;
+
+/// List of weekday names.
+const DAYS: [&str; 7] = [
+ "Sunday",
+ "Monday",
+ "Tuesday",
+ "Wednesday",
+ "Thursday",
+ "Friday",
+ "Saturday",
+];
+
+/// List of uppercase weekday names.
+const DAYS_UPPER: [&str; 7] = [
+ "SUNDAY",
+ "MONDAY",
+ "TUESDAY",
+ "WEDNESDAY",
+ "THURSDAY",
+ "FRIDAY",
+ "SATURDAY",
+];
+
+/// List of month names.
+const MONTHS: [&str; 12] = [
+ "January",
+ "February",
+ "March",
+ "April",
+ "May",
+ "June",
+ "July",
+ "August",
+ "September",
+ "October",
+ "November",
+ "December",
+];
+
+/// List of uppercase month names.
+const MONTHS_UPPER: [&str; 12] = [
+ "JANUARY",
+ "FEBRUARY",
+ "MARCH",
+ "APRIL",
+ "MAY",
+ "JUNE",
+ "JULY",
+ "AUGUST",
+ "SEPTEMBER",
+ "OCTOBER",
+ "NOVEMBER",
+ "DECEMBER",
+];
+
+// Check day and month tables
+const _: () = {
+ assert_to_ascii_uppercase(&DAYS, &DAYS_UPPER);
+ assert_to_ascii_uppercase(&MONTHS, &MONTHS_UPPER);
+};
+
+/// Formatting flag.
+#[repr(u8)]
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+enum Flag {
+ /// Use left padding, removing all other padding options in most cases.
+ LeftPadding = 1 << 0,
+ /// Change case for a string value.
+ ChangeCase = 1 << 1,
+ /// Convert a string value to uppercase.
+ UpperCase = 1 << 2,
+}
+
+/// Combination of formatting flags.
+#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
+struct Flags(u8);
+
+impl Flags {
+ /// Checks if a flag is set.
+ #[must_use]
+ fn contains(self, flag: Flag) -> bool {
+ let flag = flag as u8;
+ (self.0 & flag) == flag
+ }
+
+ /// Sets a flag.
+ fn set(&mut self, flag: Flag) {
+ self.0 |= flag as u8;
+ }
+
+ /// Checks if one of the case flags is set.
+ #[must_use]
+ fn has_change_or_upper_case(self) -> bool {
+ let flags = Flag::ChangeCase as u8 | Flag::UpperCase as u8;
+ self.0 & flags != 0
+ }
+}
+
+/// Padding method.
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+enum Padding {
+ /// Left padding.
+ Left,
+ /// Padding with spaces.
+ Spaces,
+ /// Padding with zeros.
+ Zeros,
+}
+
+/// Formatting specifier.
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+enum Spec {
+ /// `"%Y"`: Year with century if provided, zero-padded to at least 4 digits
+ /// plus the possible negative sign.
+ Year4Digits,
+ /// `"%C"`: `Year / 100` using Euclidean division, zero-padded to at least 2
+ /// digits.
+ YearDiv100,
+ /// `"%y"`: `Year % 100` in `00..=99`, using Euclidean remainder, zero-padded
+ /// to 2 digits.
+ YearRem100,
+ /// `"%m"`: Month of the year in `01..=12`, zero-padded to 2 digits.
+ Month,
+ /// `"%B"`: Locale independent full month name.
+ MonthName,
+ /// `"%b"` and `"%h"`: Locale independent abbreviated month name, using the
+ /// first 3 letters.
+ MonthNameAbbr,
+ /// `"%d"`: Day of the month in `01..=31`, zero-padded to 2 digits.
+ MonthDayZero,
+ /// `"%e"`: Day of the month in ` 1..=31`, blank-padded to 2 digits.
+ MonthDaySpace,
+ /// `"%j"`: Day of the year in `001..=366`, zero-padded to 3 digits.
+ YearDay,
+ /// `"%H"`: Hour of the day (24-hour clock) in `00..=23`, zero-padded to 2
+ /// digits.
+ Hour24hZero,
+ /// `"%k"`: Hour of the day (24-hour clock) in ` 0..=23`, blank-padded to 2
+ /// digits.
+ Hour24hSpace,
+ /// `"%I"`: Hour of the day (12-hour clock) in `01..=12`, zero-padded to 2
+ /// digits.
+ Hour12hZero,
+ /// `"%l"`: Hour of the day (12-hour clock) in ` 1..=12`, blank-padded to 2
+ /// digits.
+ Hour12hSpace,
+ /// `"%P"`: Lowercase meridian indicator (`"am"` or `"pm"`).
+ MeridianLower,
+ /// `"%p"`: Uppercase meridian indicator (`"AM"` or `"PM"`).
+ MeridianUpper,
+ /// `"%M"`: Minute of the hour in `00..=59`, zero-padded to 2 digits.
+ Minute,
+ /// `"%S"`: Second of the minute in `00..=60`, zero-padded to 2 digits.
+ Second,
+ /// `"%L"`: Truncated fractional seconds digits, with 3 digits by default.
+ /// Number of digits is specified by the width field.
+ MilliSecond,
+ /// `"%N"`: Truncated fractional seconds digits, with 9 digits by default.
+ /// Number of digits is specified by the width field.
+ FractionalSecond,
+ /// `"%z"`: Zero-padded signed time zone UTC hour and minute offsets
+ /// (`+hhmm`).
+ TimeZoneOffsetHourMinute,
+ /// `"%:z"`: Zero-padded signed time zone UTC hour and minute offsets with
+ /// colons (`+hh:mm`).
+ TimeZoneOffsetHourMinuteColon,
+ /// `"%::z"`: Zero-padded signed time zone UTC hour, minute and second
+ /// offsets with colons (`+hh:mm:ss`).
+ TimeZoneOffsetHourMinuteSecondColon,
+ /// `"%:::z"`: Zero-padded signed time zone UTC hour offset, with optional
+ /// minute and second offsets with colons (`+hh[:mm[:ss]]`).
+ TimeZoneOffsetColonMinimal,
+ /// `"%Z"`: Platform-dependent abbreviated time zone name.
+ TimeZoneName,
+ /// `"%A"`: Locale independent full weekday name.
+ WeekDayName,
+ /// `"%a"`: Locale independent abbreviated weekday name, using the first 3
+ /// letters.
+ WeekDayNameAbbr,
+ /// `"%u"`: Day of the week from Monday in `1..=7`, zero-padded to 1 digit.
+ WeekDayFrom1,
+ /// `"%w"`: Day of the week from Sunday in `0..=6`, zero-padded to 1 digit.
+ WeekDayFrom0,
+ /// `"%G"`: Same as `%Y`, but using the ISO 8601 week-based year.
+ YearIso8601,
+ /// `"%g"`: Same as `%y`, but using the ISO 8601 week-based year.
+ YearIso8601Rem100,
+ /// `"%V"`: ISO 8601 week number in `01..=53`, zero-padded to 2 digits.
+ WeekNumberIso8601,
+ /// `"%U"`: Week number from Sunday in `00..=53`, zero-padded to 2 digits.
+ /// The week `1` starts with the first Sunday of the year.
+ WeekNumberFromSunday,
+ /// `"%W"`: Week number from Monday in `00..=53`, zero-padded to 2 digits.
+ /// The week `1` starts with the first Monday of the year.
+ WeekNumberFromMonday,
+ /// `"%s"`: Number of seconds since `1970-01-01 00:00:00 UTC`, zero-padded
+ /// to at least 1 digit.
+ SecondsSinceEpoch,
+ /// `"%n"`: Newline character `'\n'`.
+ Newline,
+ /// `"%t"`: Tab character `'\t'`.
+ Tabulation,
+ /// `"%%"`: Literal `'%'` character.
+ Percent,
+ /// `"%c"`: Date and time, equivalent to `"%a %b %e %H:%M:%S %Y"`.
+ CombinationDateTime,
+ /// `"%D"` and `"%x"`: Date, equivalent to `"%m/%d/%y"`.
+ CombinationDate,
+ /// `"%F"`: ISO 8601 date, equivalent to `"%Y-%m-%d"`.
+ CombinationIso8601,
+ /// `"%v"`: VMS date, equivalent to `"%e-%^b-%4Y"`.
+ CombinationVmsDate,
+ /// `"%r"`: 12-hour time, equivalent to `"%I:%M:%S %p"`.
+ CombinationTime12h,
+ /// `"%R"`: 24-hour time without seconds, equivalent to `"%H:%M"`.
+ CombinationHourMinute24h,
+ /// `"%T"` and `"%X"`: 24-hour time, equivalent to `"%H:%M:%S"`.
+ CombinationTime24h,
+}
+
+/// UTC offset parts.
+#[derive(Debug)]
+struct UtcOffset {
+ /// Signed hour.
+ hour: f64,
+ /// Minute.
+ minute: u32,
+ /// Second.
+ second: u32,
+}
+
+impl UtcOffset {
+ /// Construct a new `UtcOffset`.
+ fn new(hour: f64, minute: u32, second: u32) -> Self {
+ Self {
+ hour,
+ minute,
+ second,
+ }
+ }
+}
+
+/// Formatting directive.
+#[derive(Debug)]
+struct Piece {
+ /// Optional width.
+ width: Option<usize>,
+ /// Padding method.
+ padding: Padding,
+ /// Combination of formatting flags.
+ flags: Flags,
+ /// Formatting specifier.
+ spec: Spec,
+}
+
+impl Piece {
+ /// Construct a new `Piece`.
+ fn new(width: Option<usize>, padding: Padding, flags: Flags, spec: Spec) -> Self {
+ Self {
+ width,
+ padding,
+ flags,
+ spec,
+ }
+ }
+
+ /// Format a numerical value, padding with zeros by default.
+ fn format_num_zeros(
+ &self,
+ f: &mut SizeLimiter<'_>,
+ value: impl fmt::Display,
+ default_width: usize,
+ ) -> Result<(), Error> {
+ if self.flags.contains(Flag::LeftPadding) {
+ write!(f, "{value}")
+ } else if self.padding == Padding::Spaces {
+ let width = self.width.unwrap_or(default_width);
+ write!(f, "{value: >width$}")
+ } else {
+ let width = self.width.unwrap_or(default_width);
+ write!(f, "{value:0width$}")
+ }
+ }
+
+ /// Format a numerical value, padding with spaces by default.
+ fn format_num_spaces(
+ &self,
+ f: &mut SizeLimiter<'_>,
+ value: impl fmt::Display,
+ default_width: usize,
+ ) -> Result<(), Error> {
+ if self.flags.contains(Flag::LeftPadding) {
+ write!(f, "{value}")
+ } else if self.padding == Padding::Zeros {
+ let width = self.width.unwrap_or(default_width);
+ write!(f, "{value:0width$}")
+ } else {
+ let width = self.width.unwrap_or(default_width);
+ write!(f, "{value: >width$}")
+ }
+ }
+
+ /// Format nanoseconds with the specified precision.
+ #[allow(clippy::uninlined_format_args)] // for readability and symmetry between if branches
+ fn format_nanoseconds(
+ &self,
+ f: &mut SizeLimiter<'_>,
+ nanoseconds: u32,
+ default_width: usize,
+ ) -> Result<(), Error> {
+ let width = self.width.unwrap_or(default_width);
+
+ if width <= 9 {
+ let value = nanoseconds / 10u32.pow(9 - width as u32);
+ write!(f, "{value:0n$}", n = width)
+ } else {
+ write!(f, "{nanoseconds:09}{:0n$}", 0, n = width - 9)
+ }
+ }
+
+ /// Format a string value.
+ fn format_string(&self, f: &mut SizeLimiter<'_>, s: &str) -> Result<(), Error> {
+ match self.width {
+ None => write!(f, "{s}"),
+ Some(width) => {
+ if self.flags.contains(Flag::LeftPadding) {
+ write!(f, "{s}")
+ } else if self.padding == Padding::Zeros {
+ write!(f, "{s:0>width$}")
+ } else {
+ write!(f, "{s: >width$}")
+ }
+ }
+ }
+ }
+
+ /// Write padding separately.
+ fn write_padding(&self, f: &mut SizeLimiter<'_>, min_width: usize) -> Result<(), Error> {
+ if let Some(width) = self.width {
+ let n = width.saturating_sub(min_width);
+
+ match self.padding {
+ Padding::Zeros => write!(f, "{:0>n$}", "")?,
+ _ => write!(f, "{: >n$}", "")?,
+ };
+ }
+ Ok(())
+ }
+
+ /// Compute UTC offset parts for the `%z` specifier.
+ fn compute_offset_parts(&self, time: &impl CheckedTime) -> UtcOffset {
+ let utc_offset = time.utc_offset();
+ let utc_offset_abs = utc_offset.unsigned_abs();
+
+ // UTC is represented as "-00:00" if the '-' flag is set
+ let sign = if utc_offset < 0 || time.is_utc() && self.flags.contains(Flag::LeftPadding) {
+ -1.0
+ } else {
+ 1.0
+ };
+
+ // Convert to `f64` to have signed zero
+ let hour = sign * f64::from(utc_offset_abs / 3600);
+ let minute = (utc_offset_abs / 60) % 60;
+ let second = utc_offset_abs % 60;
+
+ UtcOffset::new(hour, minute, second)
+ }
+
+ /// Compute hour padding for the `%z` specifier.
+ fn hour_padding(&self, min_width: usize) -> usize {
+ const MIN_PADDING: usize = "+hh".len();
+
+ match self.width {
+ Some(width) => width.saturating_sub(min_width) + MIN_PADDING,
+ None => MIN_PADDING,
+ }
+ }
+
+ /// Write the time zone UTC offset as `"+hh"`.
+ fn write_offset_hh(
+ &self,
+ f: &mut SizeLimiter<'_>,
+ utc_offset: &UtcOffset,
+ ) -> Result<(), Error> {
+ let hour = utc_offset.hour;
+ let n = self.hour_padding("+hh".len());
+
+ match self.padding {
+ Padding::Spaces => write!(f, "{hour: >+n$.0}"),
+ _ => write!(f, "{hour:+0n$.0}"),
+ }
+ }
+
+ /// Write the time zone UTC offset as `"+hhmm"`.
+ fn write_offset_hhmm(
+ &self,
+ f: &mut SizeLimiter<'_>,
+ utc_offset: &UtcOffset,
+ ) -> Result<(), Error> {
+ let UtcOffset { hour, minute, .. } = utc_offset;
+ let n = self.hour_padding("+hhmm".len());
+
+ match self.padding {
+ Padding::Spaces => write!(f, "{hour: >+n$.0}{minute:02}"),
+ _ => write!(f, "{hour:+0n$.0}{minute:02}"),
+ }
+ }
+
+ /// Write the time zone UTC offset as `"+hh:mm"`.
+ fn write_offset_hh_mm(
+ &self,
+ f: &mut SizeLimiter<'_>,
+ utc_offset: &UtcOffset,
+ ) -> Result<(), Error> {
+ let UtcOffset { hour, minute, .. } = utc_offset;
+ let n = self.hour_padding("+hh:mm".len());
+
+ match self.padding {
+ Padding::Spaces => write!(f, "{hour: >+n$.0}:{minute:02}"),
+ _ => write!(f, "{hour:+0n$.0}:{minute:02}"),
+ }
+ }
+
+ /// Write the time zone UTC offset as `"+hh:mm:ss"`.
+ fn write_offset_hh_mm_ss(
+ &self,
+ f: &mut SizeLimiter<'_>,
+ utc_offset: &UtcOffset,
+ ) -> Result<(), Error> {
+ let UtcOffset {
+ hour,
+ minute,
+ second,
+ } = utc_offset;
+
+ let n = self.hour_padding("+hh:mm:ss".len());
+
+ match self.padding {
+ Padding::Spaces => write!(f, "{hour: >+n$.0}:{minute:02}:{second:02}"),
+ _ => write!(f, "{hour:+0n$.0}:{minute:02}:{second:02}"),
+ }
+ }
+
+ /// Format time using the formatting directive.
+ #[allow(clippy::too_many_lines)]
+ fn fmt(&self, f: &mut SizeLimiter<'_>, time: &impl CheckedTime) -> Result<(), Error> {
+ match self.spec {
+ Spec::Year4Digits => {
+ let year = time.year();
+ let default_width = if year < 0 { 5 } else { 4 };
+ self.format_num_zeros(f, year, default_width)
+ }
+ Spec::YearDiv100 => self.format_num_zeros(f, time.year().div_euclid(100), 2),
+ Spec::YearRem100 => self.format_num_zeros(f, time.year().rem_euclid(100), 2),
+ Spec::Month => self.format_num_zeros(f, time.month()?, 2),
+ Spec::MonthName => {
+ let index = (time.month()? - 1) as usize;
+ if self.flags.has_change_or_upper_case() {
+ self.format_string(f, MONTHS_UPPER[index])
+ } else {
+ self.format_string(f, MONTHS[index])
+ }
+ }
+ Spec::MonthNameAbbr => {
+ let index = (time.month()? - 1) as usize;
+ if self.flags.has_change_or_upper_case() {
+ self.format_string(f, &MONTHS_UPPER[index][..3])
+ } else {
+ self.format_string(f, &MONTHS[index][..3])
+ }
+ }
+ Spec::MonthDayZero => self.format_num_zeros(f, time.day()?, 2),
+ Spec::MonthDaySpace => self.format_num_spaces(f, time.day()?, 2),
+ Spec::YearDay => self.format_num_zeros(f, time.day_of_year()?, 3),
+ Spec::Hour24hZero => self.format_num_zeros(f, time.hour()?, 2),
+ Spec::Hour24hSpace => self.format_num_spaces(f, time.hour()?, 2),
+ Spec::Hour12hZero => {
+ let hour = time.hour()? % 12;
+ let hour = if hour == 0 { 12 } else { hour };
+ self.format_num_zeros(f, hour, 2)
+ }
+ Spec::Hour12hSpace => {
+ let hour = time.hour()? % 12;
+ let hour = if hour == 0 { 12 } else { hour };
+ self.format_num_spaces(f, hour, 2)
+ }
+ Spec::MeridianLower => {
+ let (am, pm) = if self.flags.has_change_or_upper_case() {
+ ("AM", "PM")
+ } else {
+ ("am", "pm")
+ };
+ let meridian = if time.hour()? < 12 { am } else { pm };
+ self.format_string(f, meridian)
+ }
+ Spec::MeridianUpper => {
+ let (am, pm) = if self.flags.contains(Flag::ChangeCase) {
+ ("am", "pm")
+ } else {
+ ("AM", "PM")
+ };
+ let meridian = if time.hour()? < 12 { am } else { pm };
+ self.format_string(f, meridian)
+ }
+ Spec::Minute => self.format_num_zeros(f, time.minute()?, 2),
+ Spec::Second => self.format_num_zeros(f, time.second()?, 2),
+ Spec::MilliSecond => self.format_nanoseconds(f, time.nanoseconds()?, 3),
+ Spec::FractionalSecond => self.format_nanoseconds(f, time.nanoseconds()?, 9),
+ Spec::TimeZoneOffsetHourMinute => {
+ self.write_offset_hhmm(f, &self.compute_offset_parts(time))
+ }
+ Spec::TimeZoneOffsetHourMinuteColon => {
+ self.write_offset_hh_mm(f, &self.compute_offset_parts(time))
+ }
+ Spec::TimeZoneOffsetHourMinuteSecondColon => {
+ self.write_offset_hh_mm_ss(f, &self.compute_offset_parts(time))
+ }
+ Spec::TimeZoneOffsetColonMinimal => {
+ let utc_offset = self.compute_offset_parts(time);
+
+ if utc_offset.second != 0 {
+ self.write_offset_hh_mm_ss(f, &utc_offset)
+ } else if utc_offset.minute != 0 {
+ self.write_offset_hh_mm(f, &utc_offset)
+ } else {
+ self.write_offset_hh(f, &utc_offset)
+ }
+ }
+ Spec::TimeZoneName => {
+ let tz_name = time.time_zone()?;
+ if !tz_name.is_empty() {
+ if !self.flags.contains(Flag::LeftPadding) {
+ self.write_padding(f, tz_name.len())?;
+ }
+
+ // The time zone name is guaranteed to be ASCII at this point.
+ let convert: fn(&u8) -> u8 = if self.flags.contains(Flag::ChangeCase) {
+ u8::to_ascii_lowercase
+ } else if self.flags.contains(Flag::UpperCase) {
+ u8::to_ascii_uppercase
+ } else {
+ |&x| x
+ };
+
+ for x in tz_name.as_bytes() {
+ f.write_all(&[convert(x)])?;
+ }
+ }
+ Ok(())
+ }
+ Spec::WeekDayName => {
+ let index = time.day_of_week()? as usize;
+ if self.flags.has_change_or_upper_case() {
+ self.format_string(f, DAYS_UPPER[index])
+ } else {
+ self.format_string(f, DAYS[index])
+ }
+ }
+ Spec::WeekDayNameAbbr => {
+ let index = time.day_of_week()? as usize;
+ if self.flags.has_change_or_upper_case() {
+ self.format_string(f, &DAYS_UPPER[index][..3])
+ } else {
+ self.format_string(f, &DAYS[index][..3])
+ }
+ }
+ Spec::WeekDayFrom1 => {
+ let day_of_week = time.day_of_week()?;
+ let day_of_week = if day_of_week == 0 { 7 } else { day_of_week };
+ self.format_num_zeros(f, day_of_week, 1)
+ }
+ Spec::WeekDayFrom0 => self.format_num_zeros(f, time.day_of_week()?, 1),
+ Spec::YearIso8601 => {
+ let (iso_year, _) = iso_8601_year_and_week_number(
+ time.year().into(),
+ time.day_of_week()?.into(),
+ time.day_of_year()?.into(),
+ );
+ let default_width = if iso_year < 0 { 5 } else { 4 };
+ self.format_num_zeros(f, iso_year, default_width)
+ }
+ Spec::YearIso8601Rem100 => {
+ let (iso_year, _) = iso_8601_year_and_week_number(
+ time.year().into(),
+ time.day_of_week()?.into(),
+ time.day_of_year()?.into(),
+ );
+ self.format_num_zeros(f, iso_year.rem_euclid(100), 2)
+ }
+ Spec::WeekNumberIso8601 => {
+ let (_, iso_week_number) = iso_8601_year_and_week_number(
+ time.year().into(),
+ time.day_of_week()?.into(),
+ time.day_of_year()?.into(),
+ );
+ self.format_num_zeros(f, iso_week_number, 2)
+ }
+ Spec::WeekNumberFromSunday => {
+ let week_number = week_number(
+ time.day_of_week()?.into(),
+ time.day_of_year()?.into(),
+ WeekStart::Sunday,
+ );
+ self.format_num_zeros(f, week_number, 2)
+ }
+ Spec::WeekNumberFromMonday => {
+ let week_number = week_number(
+ time.day_of_week()?.into(),
+ time.day_of_year()?.into(),
+ WeekStart::Monday,
+ );
+ self.format_num_zeros(f, week_number, 2)
+ }
+ Spec::SecondsSinceEpoch => self.format_num_zeros(f, time.to_int(), 1),
+ Spec::Newline => self.format_string(f, "\n"),
+ Spec::Tabulation => self.format_string(f, "\t"),
+ Spec::Percent => self.format_string(f, "%"),
+ Spec::CombinationDateTime => {
+ const MIN_WIDTH_NO_YEAR: usize = "www mmm dd HH:MM:SS ".len();
+
+ let year = time.year();
+ let default_year_width = if year < 0 { 5 } else { 4 };
+ let min_width = MIN_WIDTH_NO_YEAR + year_width(year).max(default_year_width);
+ self.write_padding(f, min_width)?;
+
+ let (day_names, month_names) = if self.flags.contains(Flag::UpperCase) {
+ (&DAYS_UPPER, &MONTHS_UPPER)
+ } else {
+ (&DAYS, &MONTHS)
+ };
+
+ let week_day_name = &day_names[time.day_of_week()? as usize][..3];
+ let month_name = &month_names[(time.month()? - 1) as usize][..3];
+ let day = time.day()?;
+ let (hour, minute, second) = (time.hour()?, time.minute()?, time.second()?);
+
+ write!(f, "{week_day_name} {month_name} ")?;
+ write!(f, "{day: >2} {hour:02}:{minute:02}:{second:02} ")?;
+ write!(f, "{year:0default_year_width$}")
+ }
+ Spec::CombinationDate => {
+ self.write_padding(f, "mm/dd/yy".len())?;
+
+ let year = time.year().rem_euclid(100);
+ let month = time.month()?;
+ let day = time.day()?;
+
+ write!(f, "{month:02}/{day:02}/{year:02}")
+ }
+ Spec::CombinationIso8601 => {
+ const MIN_WIDTH_NO_YEAR: usize = "-mm-dd".len();
+
+ let year = time.year();
+ let default_year_width = if year < 0 { 5 } else { 4 };
+ let min_width = MIN_WIDTH_NO_YEAR + year_width(year).max(default_year_width);
+ self.write_padding(f, min_width)?;
+
+ let month = time.month()?;
+ let day = time.day()?;
+
+ write!(f, "{year:0default_year_width$}-{month:02}-{day:02}")
+ }
+ Spec::CombinationVmsDate => {
+ let year = time.year();
+ self.write_padding(f, "dd-mmm-".len() + year_width(year).max(4))?;
+
+ let month_name = &MONTHS_UPPER[(time.month()? - 1) as usize][..3];
+ let day = time.day()?;
+
+ write!(f, "{day: >2}-{month_name}-{year:04}")
+ }
+ Spec::CombinationTime12h => {
+ self.write_padding(f, "HH:MM:SS PM".len())?;
+
+ let hour = time.hour()? % 12;
+ let hour = if hour == 0 { 12 } else { hour };
+
+ let (minute, second) = (time.minute()?, time.second()?);
+ let meridian = if time.hour()? < 12 { "AM" } else { "PM" };
+
+ write!(f, "{hour:02}:{minute:02}:{second:02} {meridian}")
+ }
+ Spec::CombinationHourMinute24h => {
+ self.write_padding(f, "HH:MM".len())?;
+ let (hour, minute) = (time.hour()?, time.minute()?);
+ write!(f, "{hour:02}:{minute:02}")
+ }
+ Spec::CombinationTime24h => {
+ self.write_padding(f, "HH:MM:SS".len())?;
+ let (hour, minute, second) = (time.hour()?, time.minute()?, time.second()?);
+ write!(f, "{hour:02}:{minute:02}:{second:02}")
+ }
+ }
+ }
+}
+
+/// Wrapper struct for formatting time with the provided format string.
+pub(crate) struct TimeFormatter<'t, 'f, T> {
+ /// Time implementation
+ time: &'t T,
+ /// Format string
+ format: &'f [u8],
+}
+
+impl<'t, 'f, T: CheckedTime> TimeFormatter<'t, 'f, T> {
+ /// Construct a new `TimeFormatter` wrapper.
+ pub(crate) fn new<F: AsRef<[u8]> + ?Sized>(time: &'t T, format: &'f F) -> Self {
+ Self {
+ time,
+ format: format.as_ref(),
+ }
+ }
+
+ /// Format time using the format string.
+ pub(crate) fn fmt(&self, buf: &mut dyn Write) -> Result<(), Error> {
+ // Do nothing if the format string is empty
+ if self.format.is_empty() {
+ return Ok(());
+ }
+
+ // Use a size limiter to limit the maximum size of the resulting
+ // formatted string
+ let size_limit = self.format.len().saturating_mul(512 * 1024);
+ let mut f = SizeLimiter::new(buf, size_limit);
+
+ let mut cursor = Cursor::new(self.format);
+
+ loop {
+ f.write_all(cursor.read_until(|&x| x == b'%'))?;
+
+ let remaining_before = cursor.remaining();
+
+ // Read the '%' character
+ if cursor.next().is_none() {
+ break;
+ }
+
+ if let Some(piece) = Self::parse_spec(&mut cursor)? {
+ piece.fmt(&mut f, self.time)?;
+ } else {
+ // No valid format specifier was found
+ let remaining_after = cursor.remaining();
+ let text = &remaining_before[..remaining_before.len() - remaining_after.len()];
+ f.write_all(text)?;
+ }
+ }
+
+ Ok(())
+ }
+
+ /// Parse a formatting directive.
+ fn parse_spec(cursor: &mut Cursor<'_>) -> Result<Option<Piece>, Error> {
+ // Parse flags
+ let mut padding = Padding::Left;
+ let mut flags = Flags::default();
+
+ loop {
+ // The left padding overrides the other padding options for most cases.
+ // It is also used for the hour sign in the `%z` specifier.
+ //
+ // Similarly, the change case flag overrides the upper case flag,
+ // except when using combination specifiers (`%c`, `%D`, `%x`, `%F`,
+ // `%v`, `%r`, `%R`, `%T`, `%X`).
+ match cursor.remaining().first() {
+ Some(&b'-') => {
+ padding = Padding::Left;
+ flags.set(Flag::LeftPadding);
+ }
+ Some(&b'_') => padding = Padding::Spaces,
+ Some(&b'0') => padding = Padding::Zeros,
+ Some(&b'^') => flags.set(Flag::UpperCase),
+ Some(&b'#') => flags.set(Flag::ChangeCase),
+ _ => break,
+ }
+ cursor.next();
+ }
+
+ // Parse width
+ let width_digits = str::from_utf8(cursor.read_while(u8::is_ascii_digit))
+ .expect("reading ASCII digits should yield a valid UTF-8 slice");
+
+ let width = match width_digits.parse::<usize>() {
+ Ok(width) if Int::try_from(width).is_ok() => Some(width),
+ Err(err) if *err.kind() == IntErrorKind::Empty => None,
+ _ => return Ok(None),
+ };
+
+ // Ignore POSIX locale extensions per MRI 3.1.2:
+ //
+ // <https://github.com/ruby/ruby/blob/v3_1_2/strftime.c#L713-L722>
+ if let Some(&[ext, spec]) = cursor.remaining().get(..2) {
+ const EXT_E_SPECS: &[u8] = assert_sorted(b"CXYcxy");
+ const EXT_O_SPECS: &[u8] = assert_sorted(b"HIMSUVWdeklmuwy");
+
+ match ext {
+ b'E' if EXT_E_SPECS.binary_search(&spec).is_ok() => cursor.next(),
+ b'O' if EXT_O_SPECS.binary_search(&spec).is_ok() => cursor.next(),
+ _ => None,
+ };
+ }
+
+ // Parse spec
+ let colons = cursor.read_while(|&x| x == b':');
+
+ let spec = if colons.is_empty() {
+ const POSSIBLE_SPECS: &[(u8, Spec)] = assert_sorted_elem_0(&[
+ (b'%', Spec::Percent),
+ (b'A', Spec::WeekDayName),
+ (b'B', Spec::MonthName),
+ (b'C', Spec::YearDiv100),
+ (b'D', Spec::CombinationDate),
+ (b'F', Spec::CombinationIso8601),
+ (b'G', Spec::YearIso8601),
+ (b'H', Spec::Hour24hZero),
+ (b'I', Spec::Hour12hZero),
+ (b'L', Spec::MilliSecond),
+ (b'M', Spec::Minute),
+ (b'N', Spec::FractionalSecond),
+ (b'P', Spec::MeridianLower),
+ (b'R', Spec::CombinationHourMinute24h),
+ (b'S', Spec::Second),
+ (b'T', Spec::CombinationTime24h),
+ (b'U', Spec::WeekNumberFromSunday),
+ (b'V', Spec::WeekNumberIso8601),
+ (b'W', Spec::WeekNumberFromMonday),
+ (b'X', Spec::CombinationTime24h),
+ (b'Y', Spec::Year4Digits),
+ (b'Z', Spec::TimeZoneName),
+ (b'a', Spec::WeekDayNameAbbr),
+ (b'b', Spec::MonthNameAbbr),
+ (b'c', Spec::CombinationDateTime),
+ (b'd', Spec::MonthDayZero),
+ (b'e', Spec::MonthDaySpace),
+ (b'g', Spec::YearIso8601Rem100),
+ (b'h', Spec::MonthNameAbbr),
+ (b'j', Spec::YearDay),
+ (b'k', Spec::Hour24hSpace),
+ (b'l', Spec::Hour12hSpace),
+ (b'm', Spec::Month),
+ (b'n', Spec::Newline),
+ (b'p', Spec::MeridianUpper),
+ (b'r', Spec::CombinationTime12h),
+ (b's', Spec::SecondsSinceEpoch),
+ (b't', Spec::Tabulation),
+ (b'u', Spec::WeekDayFrom1),
+ (b'v', Spec::CombinationVmsDate),
+ (b'w', Spec::WeekDayFrom0),
+ (b'x', Spec::CombinationDate),
+ (b'y', Spec::YearRem100),
+ (b'z', Spec::TimeZoneOffsetHourMinute),
+ ]);
+
+ match cursor.next() {
+ Some(x) => match POSSIBLE_SPECS.binary_search_by_key(&x, |&(c, _)| c) {
+ Ok(index) => Some(POSSIBLE_SPECS[index].1),
+ Err(_) => None,
+ },
+ None => return Err(Error::InvalidFormatString),
+ }
+ } else if cursor.read_optional_tag(b"z") {
+ match colons.len() {
+ 1 => Some(Spec::TimeZoneOffsetHourMinuteColon),
+ 2 => Some(Spec::TimeZoneOffsetHourMinuteSecondColon),
+ 3 => Some(Spec::TimeZoneOffsetColonMinimal),
+ _ => None,
+ }
+ } else {
+ None
+ };
+
+ Ok(spec.map(|spec| Piece::new(width, padding, flags, spec)))
+ }
+}
+
+/// Compute the width of the string representation of a year.
+fn year_width(year: i32) -> usize {
+ const MINUS_SIGN_WIDTH: usize = 1;
+ let mut n = if year <= 0 { MINUS_SIGN_WIDTH } else { 0 };
+ let mut val = year;
+ while val != 0 {
+ val /= 10;
+ n += 1;
+ }
+ n
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_year_width() {
+ assert_eq!(year_width(-100), 4);
+ assert_eq!(year_width(-99), 3);
+ assert_eq!(year_width(-10), 3);
+ assert_eq!(year_width(-9), 2);
+ assert_eq!(year_width(-1), 2);
+ assert_eq!(year_width(0), 1);
+ assert_eq!(year_width(1), 1);
+ assert_eq!(year_width(9), 1);
+ assert_eq!(year_width(10), 2);
+ assert_eq!(year_width(99), 2);
+ assert_eq!(year_width(100), 3);
+ }
+
+ #[cfg(feature = "alloc")]
+ #[test]
+ fn test_flag_debug_is_non_empty() {
+ use alloc::format;
+
+ assert!(!format!("{:?}", Flag::LeftPadding).is_empty());
+ assert!(!format!("{:?}", Flag::ChangeCase).is_empty());
+ assert!(!format!("{:?}", Flag::UpperCase).is_empty());
+ }
+
+ #[cfg(feature = "alloc")]
+ #[test]
+ fn test_flags_debug_is_non_empty() {
+ use alloc::format;
+
+ assert!(!format!("{:?}", Flags::default()).is_empty());
+ }
+
+ #[cfg(feature = "alloc")]
+ #[test]
+ fn test_padding_debug_is_non_empty() {
+ use alloc::format;
+
+ assert!(!format!("{:?}", Padding::Left).is_empty());
+ assert!(!format!("{:?}", Padding::Spaces).is_empty());
+ assert!(!format!("{:?}", Padding::Zeros).is_empty());
+ }
+
+ #[cfg(feature = "alloc")]
+ #[test]
+ fn test_spec_debug_is_non_empty() {
+ use alloc::format;
+
+ assert!(!format!("{:?}", Spec::Year4Digits).is_empty());
+ assert!(!format!("{:?}", Spec::YearDiv100).is_empty());
+ assert!(!format!("{:?}", Spec::YearRem100).is_empty());
+ assert!(!format!("{:?}", Spec::Month).is_empty());
+ assert!(!format!("{:?}", Spec::MonthName).is_empty());
+ assert!(!format!("{:?}", Spec::MonthNameAbbr).is_empty());
+ assert!(!format!("{:?}", Spec::MonthDayZero).is_empty());
+ assert!(!format!("{:?}", Spec::MonthDaySpace).is_empty());
+ assert!(!format!("{:?}", Spec::YearDay).is_empty());
+ assert!(!format!("{:?}", Spec::Hour24hZero).is_empty());
+ assert!(!format!("{:?}", Spec::Hour24hSpace).is_empty());
+ assert!(!format!("{:?}", Spec::Hour12hZero).is_empty());
+ assert!(!format!("{:?}", Spec::Hour12hSpace).is_empty());
+ assert!(!format!("{:?}", Spec::MeridianLower).is_empty());
+ assert!(!format!("{:?}", Spec::MeridianUpper).is_empty());
+ assert!(!format!("{:?}", Spec::Minute).is_empty());
+ assert!(!format!("{:?}", Spec::Second).is_empty());
+ assert!(!format!("{:?}", Spec::MilliSecond).is_empty());
+ assert!(!format!("{:?}", Spec::FractionalSecond).is_empty());
+ assert!(!format!("{:?}", Spec::TimeZoneOffsetHourMinute).is_empty());
+ assert!(!format!("{:?}", Spec::TimeZoneOffsetHourMinuteColon).is_empty());
+ assert!(!format!("{:?}", Spec::TimeZoneOffsetHourMinuteSecondColon).is_empty());
+ assert!(!format!("{:?}", Spec::TimeZoneOffsetColonMinimal).is_empty());
+ assert!(!format!("{:?}", Spec::TimeZoneName).is_empty());
+ assert!(!format!("{:?}", Spec::WeekDayName).is_empty());
+ assert!(!format!("{:?}", Spec::WeekDayNameAbbr).is_empty());
+ assert!(!format!("{:?}", Spec::WeekDayFrom1).is_empty());
+ assert!(!format!("{:?}", Spec::WeekDayFrom0).is_empty());
+ assert!(!format!("{:?}", Spec::YearIso8601).is_empty());
+ assert!(!format!("{:?}", Spec::YearIso8601Rem100).is_empty());
+ assert!(!format!("{:?}", Spec::WeekNumberIso8601).is_empty());
+ assert!(!format!("{:?}", Spec::WeekNumberFromSunday).is_empty());
+ assert!(!format!("{:?}", Spec::WeekNumberFromMonday).is_empty());
+ assert!(!format!("{:?}", Spec::SecondsSinceEpoch).is_empty());
+ assert!(!format!("{:?}", Spec::Newline).is_empty());
+ assert!(!format!("{:?}", Spec::Tabulation).is_empty());
+ assert!(!format!("{:?}", Spec::Percent).is_empty());
+ assert!(!format!("{:?}", Spec::CombinationDateTime).is_empty());
+ assert!(!format!("{:?}", Spec::CombinationDate).is_empty());
+ assert!(!format!("{:?}", Spec::CombinationIso8601).is_empty());
+ assert!(!format!("{:?}", Spec::CombinationVmsDate).is_empty());
+ assert!(!format!("{:?}", Spec::CombinationTime12h).is_empty());
+ assert!(!format!("{:?}", Spec::CombinationHourMinute24h).is_empty());
+ assert!(!format!("{:?}", Spec::CombinationTime24h).is_empty());
+ }
+
+ #[cfg(feature = "alloc")]
+ #[test]
+ fn test_utc_offset_debug_is_non_empty() {
+ use alloc::format;
+
+ assert!(!format!("{:?}", UtcOffset::new(0.0, 0, 0)).is_empty());
+ }
+
+ #[cfg(feature = "alloc")]
+ #[test]
+ fn test_piece_debug_is_non_empty() {
+ use alloc::format;
+
+ let piece = Piece::new(
+ None,
+ Padding::Spaces,
+ Flags::default(),
+ Spec::CombinationTime24h,
+ );
+
+ assert!(!format!("{piece:?}").is_empty());
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +
//! Some useful types.
+
+use super::write::Write;
+use crate::Error;
+
+/// A `Cursor` contains a slice of a buffer.
+#[derive(Debug, Clone)]
+pub(crate) struct Cursor<'a> {
+ /// Slice representing the remaining data to be read.
+ remaining: &'a [u8],
+}
+
+impl<'a> Cursor<'a> {
+ /// Construct a new `Cursor` from remaining data.
+ pub(crate) fn new(remaining: &'a [u8]) -> Self {
+ Self { remaining }
+ }
+
+ /// Returns remaining data.
+ pub(crate) fn remaining(&self) -> &'a [u8] {
+ self.remaining
+ }
+
+ /// Returns the next byte.
+ pub(crate) fn next(&mut self) -> Option<u8> {
+ let (&first, tail) = self.remaining.split_first()?;
+ self.remaining = tail;
+ Some(first)
+ }
+
+ /// Read bytes if the remaining data is prefixed by the provided tag.
+ pub(crate) fn read_optional_tag(&mut self, tag: &[u8]) -> bool {
+ if self.remaining.starts_with(tag) {
+ self.read_exact(tag.len());
+ true
+ } else {
+ false
+ }
+ }
+
+ /// Read bytes as long as the provided predicate is true.
+ pub(crate) fn read_while<F: Fn(&u8) -> bool>(&mut self, f: F) -> &'a [u8] {
+ match self.remaining.iter().position(|x| !f(x)) {
+ None => self.read_exact(self.remaining.len()),
+ Some(position) => self.read_exact(position),
+ }
+ }
+
+ /// Read bytes until the provided predicate is true.
+ pub(crate) fn read_until<F: Fn(&u8) -> bool>(&mut self, f: F) -> &'a [u8] {
+ match self.remaining.iter().position(f) {
+ None => self.read_exact(self.remaining.len()),
+ Some(position) => self.read_exact(position),
+ }
+ }
+
+ /// Read exactly `count` bytes.
+ fn read_exact(&mut self, count: usize) -> &'a [u8] {
+ let (result, remaining) = self.remaining.split_at(count);
+ self.remaining = remaining;
+ result
+ }
+}
+
+/// A `SizeLimiter` limits the maximum amount a writer can write.
+pub(crate) struct SizeLimiter<'a> {
+ /// Inner writer.
+ inner: &'a mut dyn Write,
+ /// Size limit.
+ size_limit: usize,
+ /// Current write count.
+ count: usize,
+}
+
+impl<'a> SizeLimiter<'a> {
+ /// Construct a new `SizeLimiter`.
+ pub(crate) fn new(inner: &'a mut dyn Write, size_limit: usize) -> Self {
+ Self {
+ inner,
+ size_limit,
+ count: 0,
+ }
+ }
+}
+
+impl<'a> Write for SizeLimiter<'a> {
+ fn write(&mut self, buf: &[u8]) -> Result<usize, Error> {
+ if self.count + buf.len() > self.size_limit {
+ return Err(Error::FormattedStringTooLarge);
+ }
+
+ let written = self.inner.write(buf)?;
+ self.count += written;
+ Ok(written)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ #[cfg(feature = "alloc")]
+ #[test]
+ fn test_cursor_debug_is_non_empty() {
+ use alloc::format;
+
+ use super::Cursor;
+
+ assert!(!format!("{:?}", Cursor::new(&[])).is_empty());
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +
//! Module containing week-related items.
+
+/// Start day of the week.
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+pub(crate) enum WeekStart {
+ /// Sunday.
+ Sunday = 0,
+ /// Monday.
+ Monday = 1,
+}
+
+/// Compute the week number, beginning at the provided start day of the week.
+///
+/// ## Inputs
+///
+/// * `week_day`: Day of the week from Sunday in `0..=6`.
+/// * `year_day_1`: Day of the year in `1..=366`.
+/// * `week_start`: Start day of the week.
+///
+pub(crate) fn week_number(week_day: i64, year_day_1: i64, week_start: WeekStart) -> i64 {
+ let year_day = year_day_1 - 1;
+ let start_of_first_week = (year_day - week_day + week_start as i64).rem_euclid(7);
+ (year_day + 7 - start_of_first_week) / 7
+}
+
+/// Compute the ISO 8601 week-based year and week number.
+///
+/// The first week of `YYYY` starts with a Monday and includes `YYYY-01-04`.
+/// The days in the year before the first week are in the last week of the
+/// previous year.
+///
+/// ## Inputs
+///
+/// * `year`: Year.
+/// * `week_day`: Day of the week from Sunday in `0..=6`.
+/// * `year_day_1`: Day of the year in `1..=366`.
+///
+pub(crate) fn iso_8601_year_and_week_number(
+ year: i64,
+ week_day: i64,
+ year_day_1: i64,
+) -> (i64, i64) {
+ let year_day = year_day_1 - 1;
+
+ let mut start_of_first_week = (year_day - week_day + 1).rem_euclid(7);
+
+ if start_of_first_week > 3 {
+ start_of_first_week -= 7;
+ }
+
+ if year_day < start_of_first_week {
+ // Use previous year
+ let previous_year = year - 1;
+
+ let previous_year_day = if is_leap_year(previous_year) {
+ 366 + year_day
+ } else {
+ 365 + year_day
+ };
+
+ return iso_8601_year_and_week_number(previous_year, week_day, previous_year_day + 1);
+ }
+
+ let week_number = (year_day + 7 - start_of_first_week) / 7;
+
+ if week_number >= 52 {
+ let last_year_day = if is_leap_year(year) { 365 } else { 364 };
+
+ let week_day_of_last_year_day = (week_day + last_year_day - year_day) % 7;
+
+ if (1..=3).contains(&week_day_of_last_year_day) {
+ let last_monday = last_year_day - (week_day_of_last_year_day - 1);
+ if year_day >= last_monday {
+ // Use next year
+ return (year + 1, 1);
+ }
+ }
+ }
+
+ // Use current year
+ (year, week_number)
+}
+
+/// Check if a year is a leap year.
+fn is_leap_year(year: i64) -> bool {
+ year % 400 == 0 || (year % 4 == 0 && year % 100 != 0)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_week_number() {
+ assert_eq!(week_number(1, 0, WeekStart::Sunday), 0);
+ assert_eq!(week_number(2, 1, WeekStart::Sunday), 0);
+ assert_eq!(week_number(3, 2, WeekStart::Sunday), 0);
+ assert_eq!(week_number(4, 3, WeekStart::Sunday), 0);
+ assert_eq!(week_number(5, 4, WeekStart::Sunday), 0);
+ assert_eq!(week_number(6, 5, WeekStart::Sunday), 0);
+ assert_eq!(week_number(0, 6, WeekStart::Sunday), 1);
+ assert_eq!(week_number(1, 7, WeekStart::Sunday), 1);
+ assert_eq!(week_number(2, 8, WeekStart::Sunday), 1);
+
+ assert_eq!(week_number(0, 0, WeekStart::Monday), 0);
+ assert_eq!(week_number(1, 1, WeekStart::Monday), 1);
+ assert_eq!(week_number(2, 2, WeekStart::Monday), 1);
+ assert_eq!(week_number(3, 3, WeekStart::Monday), 1);
+ assert_eq!(week_number(4, 4, WeekStart::Monday), 1);
+ assert_eq!(week_number(5, 5, WeekStart::Monday), 1);
+ assert_eq!(week_number(6, 6, WeekStart::Monday), 1);
+ assert_eq!(week_number(7, 7, WeekStart::Monday), 1);
+ assert_eq!(week_number(8, 8, WeekStart::Monday), 2);
+
+ assert_eq!(week_number(0, 365, WeekStart::Sunday), 53);
+ }
+
+ #[test]
+ fn test_iso_8601_year_and_week() {
+ assert_eq!(iso_8601_year_and_week_number(2025, 0, 362), (2025, 52));
+ assert_eq!(iso_8601_year_and_week_number(2025, 1, 363), (2026, 1));
+ assert_eq!(iso_8601_year_and_week_number(2025, 2, 364), (2026, 1));
+ assert_eq!(iso_8601_year_and_week_number(2025, 3, 365), (2026, 1));
+ assert_eq!(iso_8601_year_and_week_number(2026, 4, 1), (2026, 1));
+ assert_eq!(iso_8601_year_and_week_number(2026, 5, 2), (2026, 1));
+ assert_eq!(iso_8601_year_and_week_number(2026, 6, 3), (2026, 1));
+ assert_eq!(iso_8601_year_and_week_number(2026, 0, 4), (2026, 1));
+ assert_eq!(iso_8601_year_and_week_number(2026, 1, 5), (2026, 2));
+
+ assert_eq!(iso_8601_year_and_week_number(2026, 0, 361), (2026, 52));
+ assert_eq!(iso_8601_year_and_week_number(2026, 1, 362), (2026, 53));
+ assert_eq!(iso_8601_year_and_week_number(2026, 2, 363), (2026, 53));
+ assert_eq!(iso_8601_year_and_week_number(2026, 3, 364), (2026, 53));
+ assert_eq!(iso_8601_year_and_week_number(2026, 4, 365), (2026, 53));
+ assert_eq!(iso_8601_year_and_week_number(2027, 5, 1), (2026, 53));
+ assert_eq!(iso_8601_year_and_week_number(2027, 6, 2), (2026, 53));
+ assert_eq!(iso_8601_year_and_week_number(2027, 0, 3), (2026, 53));
+ assert_eq!(iso_8601_year_and_week_number(2027, 1, 4), (2027, 1));
+
+ assert_eq!(iso_8601_year_and_week_number(2020, 0, 362), (2020, 52));
+ assert_eq!(iso_8601_year_and_week_number(2020, 1, 363), (2020, 53));
+ assert_eq!(iso_8601_year_and_week_number(2020, 2, 364), (2020, 53));
+ assert_eq!(iso_8601_year_and_week_number(2020, 3, 365), (2020, 53));
+ assert_eq!(iso_8601_year_and_week_number(2020, 4, 366), (2020, 53));
+ assert_eq!(iso_8601_year_and_week_number(2021, 5, 1), (2020, 53));
+ assert_eq!(iso_8601_year_and_week_number(2021, 6, 2), (2020, 53));
+ assert_eq!(iso_8601_year_and_week_number(2021, 0, 3), (2020, 53));
+ assert_eq!(iso_8601_year_and_week_number(2021, 1, 4), (2021, 1));
+ }
+
+ #[test]
+ fn test_is_leap_year() {
+ assert!(is_leap_year(2000));
+ assert!(!is_leap_year(2001));
+ assert!(is_leap_year(2004));
+ assert!(!is_leap_year(2100));
+ assert!(!is_leap_year(2200));
+ assert!(!is_leap_year(2300));
+ assert!(is_leap_year(2400));
+ }
+
+ #[cfg(feature = "alloc")]
+ #[test]
+ fn test_week_start_debug_is_non_empty() {
+ use alloc::format;
+
+ assert!(!format!("{:?}", WeekStart::Sunday).is_empty());
+ assert!(!format!("{:?}", WeekStart::Monday).is_empty());
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +
//! This module is a copy of the [`std::io::Write`] implementation, in order to
+//! use it in a no-std context.
+//!
+//! [`std::io::Write`]: <https://doc.rust-lang.org/std/io/trait.Write.html>
+
+#[cfg(feature = "alloc")]
+use alloc::vec::Vec;
+use core::fmt;
+use core::str;
+
+use crate::Error;
+
+/// An `Adapter` implements [`core::fmt::Write`] from a [`Write`] object,
+/// storing write errors instead of discarding them.
+struct Adapter<'a, T: ?Sized> {
+ /// Inner writer.
+ inner: &'a mut T,
+ /// Write result.
+ error: Result<(), Error>,
+}
+
+impl<T: Write + ?Sized> fmt::Write for Adapter<'_, T> {
+ fn write_str(&mut self, s: &str) -> fmt::Result {
+ match self.inner.write_all(s.as_bytes()) {
+ Ok(()) => Ok(()),
+ Err(e) => {
+ self.error = Err(e);
+ Err(fmt::Error)
+ }
+ }
+ }
+}
+
+/// Simplified copy of the [`std::io::Write`] trait.
+///
+/// [`std::io::Write`]: <https://doc.rust-lang.org/std/io/trait.Write.html>
+pub(crate) trait Write {
+ /// Write a buffer into this writer, returning how many bytes were written.
+ fn write(&mut self, data: &[u8]) -> Result<usize, Error>;
+
+ /// Attempts to write an entire buffer into this writer.
+ fn write_all(&mut self, mut data: &[u8]) -> Result<(), Error> {
+ while !data.is_empty() {
+ match self.write(data)? {
+ 0 => return Err(Error::WriteZero),
+ n => data = &data[n..],
+ }
+ }
+ Ok(())
+ }
+
+ /// Writes a formatted string into this writer, returning any error
+ /// encountered.
+ fn write_fmt(&mut self, fmt_args: fmt::Arguments<'_>) -> Result<(), Error> {
+ let mut output = Adapter {
+ inner: self,
+ error: Ok(()),
+ };
+
+ match fmt::write(&mut output, fmt_args) {
+ Ok(()) => Ok(()),
+ Err(_) if output.error.is_err() => output.error,
+ Err(err) => Err(err.into()),
+ }
+ }
+}
+
+/// Write is implemented for `&mut [u8]` by copying into the slice, overwriting
+/// its data.
+impl Write for &mut [u8] {
+ fn write(&mut self, data: &[u8]) -> Result<usize, Error> {
+ let size = data.len().min(self.len());
+ let (a, b) = core::mem::take(self).split_at_mut(size);
+ a.copy_from_slice(&data[..size]);
+ *self = b;
+ Ok(size)
+ }
+}
+
+/// Wrapper for a [`core::fmt::Write`] writer.
+pub(crate) struct FmtWrite<'a> {
+ /// Inner writer.
+ inner: &'a mut dyn fmt::Write,
+}
+
+impl<'a> FmtWrite<'a> {
+ /// Construct a new `FmtWrite`.
+ pub(crate) fn new(inner: &'a mut dyn fmt::Write) -> Self {
+ Self { inner }
+ }
+}
+
+/// Write is implemented for `FmtWrite` by writing to its inner writer.
+impl Write for FmtWrite<'_> {
+ fn write(&mut self, data: &[u8]) -> Result<usize, Error> {
+ let data = str::from_utf8(data).expect("FmtWrite should only receive UTF-8 data");
+ self.inner.write_str(data)?;
+ Ok(data.len())
+ }
+
+ fn write_fmt(&mut self, fmt_args: fmt::Arguments<'_>) -> Result<(), Error> {
+ Ok(self.inner.write_fmt(fmt_args)?)
+ }
+}
+
+/// Write is implemented for `Vec<u8>` by appending to the vector, growing as
+/// needed.
+#[cfg(feature = "alloc")]
+#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
+impl Write for Vec<u8> {
+ fn write(&mut self, data: &[u8]) -> Result<usize, Error> {
+ self.try_reserve(data.len())?;
+ self.extend_from_slice(data);
+ Ok(data.len())
+ }
+}
+
+/// Wrapper for a [`std::io::Write`] writer.
+#[cfg(feature = "std")]
+#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
+pub(crate) struct IoWrite<'a> {
+ /// Inner writer.
+ inner: &'a mut dyn std::io::Write,
+}
+
+#[cfg(feature = "std")]
+impl<'a> IoWrite<'a> {
+ /// Construct a new `IoWrite`.
+ pub(crate) fn new(inner: &'a mut dyn std::io::Write) -> Self {
+ Self { inner }
+ }
+}
+
+/// Write is implemented for `IoWrite` by writing to its inner writer.
+#[cfg(feature = "std")]
+impl Write for IoWrite<'_> {
+ fn write(&mut self, data: &[u8]) -> Result<usize, Error> {
+ Ok(self.inner.write(data)?)
+ }
+
+ fn write_all(&mut self, data: &[u8]) -> Result<(), Error> {
+ Ok(self.inner.write_all(data)?)
+ }
+
+ fn write_fmt(&mut self, fmt_args: fmt::Arguments<'_>) -> Result<(), Error> {
+ Ok(self.inner.write_fmt(fmt_args)?)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_fmt_error() {
+ use core::fmt;
+
+ struct S;
+
+ impl fmt::Display for S {
+ fn fmt(&self, _: &mut fmt::Formatter<'_>) -> fmt::Result {
+ Err(fmt::Error)
+ }
+ }
+
+ let result = write!(&mut &mut [0u8; 1][..], "{S}");
+ assert!(matches!(result, Err(Error::FmtError(_))));
+ }
+
+ #[cfg(feature = "std")]
+ #[test]
+ fn test_io_write() {
+ let mut buf = Vec::new();
+
+ let mut writer = IoWrite::new(&mut buf);
+ writer.write_all(b"ok").unwrap();
+ write!(writer, "{}", 1).unwrap();
+
+ assert_eq!(buf, *b"ok1");
+ }
+
+ #[cfg(feature = "alloc")]
+ #[test]
+ fn test_fmt_write() {
+ use alloc::string::String;
+
+ let mut buf = String::new();
+ write!(FmtWrite::new(&mut buf), "{}", 1).unwrap();
+ assert_eq!(buf, "1");
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352 +353 +354 +355 +356 +357 +358 +359 +360 +361 +362 +363 +364 +365 +366 +367 +368 +369 +370 +371 +372 +373 +374 +375 +376 +377 +378 +379 +380 +381 +382 +383 +384 +385 +386 +387 +388 +389 +390 +391 +392 +393 +394 +395 +396 +397 +398 +399 +400 +401 +402 +403 +404 +405 +406 +407 +408 +409 +410 +411 +412 +413 +414 +415 +416 +417 +418 +419 +420 +421 +422 +423 +424 +425 +426 +427 +428 +429 +430 +431 +432 +433 +434 +435 +436 +437 +438 +439 +440 +441 +442 +443 +444 +445 +446 +447 +448 +449 +450 +451 +452 +453 +454 +455 +456 +457 +458 +459 +460 +461 +462 +463 +464 +465 +466 +467 +468 +469 +470 +471 +472 +473 +474 +475 +476 +477 +478 +479 +480 +481 +482 +483 +484 +485 +486 +487 +488 +489 +490 +491 +492 +493 +494 +495 +496 +497 +498 +499 +500 +501 +502 +503 +504 +505 +506 +507 +508 +509 +510 +511 +512 +513 +514 +515 +516 +517 +518 +519 +520 +521 +522 +523 +524 +
#![forbid(unsafe_code)]
+#![warn(clippy::all)]
+#![warn(clippy::pedantic)]
+#![warn(clippy::cargo)]
+#![allow(clippy::cast_possible_truncation)]
+#![allow(unknown_lints)]
+#![warn(missing_copy_implementations)]
+#![warn(missing_debug_implementations)]
+#![warn(missing_docs)]
+#![warn(rust_2018_idioms)]
+#![warn(trivial_casts, trivial_numeric_casts)]
+#![warn(unsafe_op_in_unsafe_fn)]
+#![warn(unused_qualifications)]
+#![warn(variant_size_differences)]
+// Enable feature callouts in generated documentation:
+// https://doc.rust-lang.org/beta/unstable-book/language-features/doc-cfg.html
+//
+// This approach is borrowed from tokio.
+#![cfg_attr(docsrs, feature(doc_cfg))]
+#![cfg_attr(docsrs, feature(doc_alias))]
+
+//! This crate provides a Ruby 3.1.2 compatible `strftime` function, which
+//! formats time according to the directives in the given format string.
+//!
+//! The directives begin with a percent `%` character. Any text not listed as a
+//! directive will be passed through to the output string.
+//!
+//! Each directive consists of a percent `%` character, zero or more flags,
+//! optional minimum field width, optional modifier and a conversion specifier
+//! as follows:
+//!
+//! ```text
+//! %<flags><width><modifier><conversion>
+//! ```
+//!
+//! # Usage
+//!
+//! The various `strftime` functions in this crate take a generic _time_
+//! parameter that implements the [`Time`] trait.
+//!
+//! # Format Specifiers
+//!
+//! ## Flags
+//!
+//! | Flag | Description |
+//! |------|----------------------------------------------------------------------------------------|
+//! | `-` | Use left padding, ignoring width and removing all other padding options in most cases. |
+//! | `_` | Use spaces for padding. |
+//! | `0` | Use zeros for padding. |
+//! | `^` | Convert the resulting string to uppercase. |
+//! | `#` | Change case of the resulting string. |
+//!
+//!
+//! ## Width
+//!
+//! The minimum field width specifies the minimum width.
+//!
+//! ## Modifiers
+//!
+//! The modifiers are `E` and `O`. They are ignored.
+//!
+//! ## Specifiers
+//!
+//! | Specifier | Example | Description |
+//! |------------|---------------|-----------------------------------------------------------------------------------------------------------------------|
+//! | `%Y` | `-2001` | Year with century if provided, zero-padded to at least 4 digits plus the possible negative sign. |
+//! | `%C` | `-21` | `Year / 100` using Euclidean division, zero-padded to at least 2 digits. |
+//! | `%y` | `99` | `Year % 100` in `00..=99`, using Euclidean remainder, zero-padded to 2 digits. |
+//! | `%m` | `01` | Month of the year in `01..=12`, zero-padded to 2 digits. |
+//! | `%B` | `July` | Locale independent full month name. |
+//! | `%b`, `%h` | `Jul` | Locale independent abbreviated month name, using the first 3 letters. |
+//! | `%d` | `01` | Day of the month in `01..=31`, zero-padded to 2 digits. |
+//! | `%e` | ` 1` | Day of the month in ` 1..=31`, blank-padded to 2 digits. |
+//! | `%j` | `001` | Day of the year in `001..=366`, zero-padded to 3 digits. |
+//! | `%H` | `00` | Hour of the day (24-hour clock) in `00..=23`, zero-padded to 2 digits. |
+//! | `%k` | ` 0` | Hour of the day (24-hour clock) in ` 0..=23`, blank-padded to 2 digits. |
+//! | `%I` | `01` | Hour of the day (12-hour clock) in `01..=12`, zero-padded to 2 digits. |
+//! | `%l` | ` 1` | Hour of the day (12-hour clock) in ` 1..=12`, blank-padded to 2 digits. |
+//! | `%P` | `am` | Lowercase meridian indicator (`"am"` or `"pm"`). |
+//! | `%p` | `AM` | Uppercase meridian indicator (`"AM"` or `"PM"`). |
+//! | `%M` | `00` | Minute of the hour in `00..=59`, zero-padded to 2 digits. |
+//! | `%S` | `00` | Second of the minute in `00..=60`, zero-padded to 2 digits. |
+//! | `%L` | `123` | Truncated fractional seconds digits, with 3 digits by default. Number of digits is specified by the width field. |
+//! | `%N` | `123456789` | Truncated fractional seconds digits, with 9 digits by default. Number of digits is specified by the width field. |
+//! | `%z` | `+0200` | Zero-padded signed time zone UTC hour and minute offsets (`+hhmm`). |
+//! | `%:z` | `+02:00` | Zero-padded signed time zone UTC hour and minute offsets with colons (`+hh:mm`). |
+//! | `%::z` | `+02:00:00` | Zero-padded signed time zone UTC hour, minute and second offsets with colons (`+hh:mm:ss`). |
+//! | `%:::z` | `+02` | Zero-padded signed time zone UTC hour offset, with optional minute and second offsets with colons (`+hh[:mm[:ss]]`). |
+//! | `%Z` | `CEST` | Platform-dependent abbreviated time zone name. |
+//! | `%A` | `Sunday` | Locale independent full weekday name. |
+//! | `%a` | `Sun` | Locale independent abbreviated weekday name, using the first 3 letters. |
+//! | `%u` | `1` | Day of the week from Monday in `1..=7`, zero-padded to 1 digit. |
+//! | `%w` | `0` | Day of the week from Sunday in `0..=6`, zero-padded to 1 digit. |
+//! | `%G` | `-2001` | Same as `%Y`, but using the ISO 8601 week-based year. [^1] |
+//! | `%g` | `99` | Same as `%y`, but using the ISO 8601 week-based year. [^1] |
+//! | `%V` | `01` | ISO 8601 week number in `01..=53`, zero-padded to 2 digits. [^1] |
+//! | `%U` | `00` | Week number from Sunday in `00..=53`, zero-padded to 2 digits. The week `1` starts with the first Sunday of the year. |
+//! | `%W` | `00` | Week number from Monday in `00..=53`, zero-padded to 2 digits. The week `1` starts with the first Monday of the year. |
+//! | `%s` | `86400` | Number of seconds since `1970-01-01 00:00:00 UTC`, zero-padded to at least 1 digit. |
+//! | `%n` | `\n` | Newline character `'\n'`. |
+//! | `%t` | `\t` | Tab character `'\t'`. |
+//! | `%%` | `%` | Literal `'%'` character. |
+//! | `%c` | `Sun Jul 8 00:23:45 2001` | Date and time, equivalent to `"%a %b %e %H:%M:%S %Y"`. |
+//! | `%D`, `%x` | `07/08/01` | Date, equivalent to `"%m/%d/%y"`. |
+//! | `%F` | `2001-07-08` | ISO 8601 date, equivalent to `"%Y-%m-%d"`. |
+//! | `%v` | ` 8-JUL-2001` | VMS date, equivalent to `"%e-%^b-%4Y"`. |
+//! | `%r` | `12:23:45 AM` | 12-hour time, equivalent to `"%I:%M:%S %p"`. |
+//! | `%R` | `00:23` | 24-hour time without seconds, equivalent to `"%H:%M"`. |
+//! | `%T`, `%X` | `00:23:45` | 24-hour time, equivalent to `"%H:%M:%S"`. |
+//!
+//! [^1]: `%G`, `%g`, `%V`: Week 1 of ISO 8601 is the first week with at least 4
+//! days in that year. The days before the first week are in the last week of
+//! the previous year.
+
+#![doc(html_root_url = "https://docs.rs/strftime-ruby/1.0.1")]
+#![no_std]
+
+#[cfg(feature = "alloc")]
+extern crate alloc;
+
+#[cfg(feature = "std")]
+extern crate std;
+
+#[cfg(feature = "alloc")]
+use alloc::collections::TryReserveError;
+
+mod format;
+
+#[cfg(test)]
+mod tests;
+
+/// Error type returned by the `strftime` functions.
+#[derive(Debug)]
+// To ensure the API is the same for all feature combinations, do not derive
+// `Copy`. The `OutOfMemory` variant (when it is enabled by `alloc`) contains a
+// member that is not `Copy`.
+#[non_exhaustive]
+#[allow(missing_copy_implementations)]
+#[allow(variant_size_differences)]
+pub enum Error {
+ /// Provided time implementation returns invalid values.
+ InvalidTime,
+ /// Provided format string is ended by an unterminated format specifier.
+ InvalidFormatString,
+ /// Formatted string is too large and could cause an out-of-memory error.
+ FormattedStringTooLarge,
+ /// Provided buffer for the [`buffered::strftime`] function is too small for
+ /// the formatted string.
+ ///
+ /// This corresponds to the [`std::io::ErrorKind::WriteZero`] variant.
+ ///
+ /// [`std::io::ErrorKind::WriteZero`]: <https://doc.rust-lang.org/std/io/enum.ErrorKind.html#variant.WriteZero>
+ WriteZero,
+ /// Formatting error, corresponding to [`core::fmt::Error`].
+ FmtError(core::fmt::Error),
+ /// An allocation failure has occurred in either [`bytes::strftime`] or
+ /// [`string::strftime`].
+ #[cfg(feature = "alloc")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
+ OutOfMemory(TryReserveError),
+ /// An I/O error has occurred in [`io::strftime`].
+ #[cfg(feature = "std")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
+ IoError(std::io::Error),
+}
+
+impl core::fmt::Display for Error {
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ match self {
+ Error::InvalidTime => f.write_str("invalid time"),
+ Error::InvalidFormatString => f.write_str("invalid format string"),
+ Error::FormattedStringTooLarge => f.write_str("formatted string too large"),
+ Error::WriteZero => f.write_str("failed to write the whole buffer"),
+ Error::FmtError(_) => f.write_str("formatter error"),
+ #[cfg(feature = "alloc")]
+ Error::OutOfMemory(_) => f.write_str("allocation failure"),
+ #[cfg(feature = "std")]
+ Error::IoError(_) => f.write_str("I/O error"),
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
+impl std::error::Error for Error {
+ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
+ match self {
+ Self::FmtError(inner) => Some(inner),
+ Self::OutOfMemory(inner) => Some(inner),
+ Self::IoError(inner) => Some(inner),
+ _ => None,
+ }
+ }
+}
+
+impl From<core::fmt::Error> for Error {
+ fn from(err: core::fmt::Error) -> Self {
+ Self::FmtError(err)
+ }
+}
+
+#[cfg(feature = "alloc")]
+#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
+impl From<TryReserveError> for Error {
+ fn from(err: TryReserveError) -> Self {
+ Self::OutOfMemory(err)
+ }
+}
+
+#[cfg(feature = "std")]
+#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
+impl From<std::io::Error> for Error {
+ fn from(err: std::io::Error) -> Self {
+ Self::IoError(err)
+ }
+}
+
+/// Common methods needed for formatting _time_.
+///
+/// This should be implemented for structs representing a _time_.
+///
+/// All the `strftime` functions take as input an implementation of this trait.
+pub trait Time {
+ /// Returns the year for _time_ (including the century).
+ fn year(&self) -> i32;
+ /// Returns the month of the year in `1..=12` for _time_.
+ fn month(&self) -> u8;
+ /// Returns the day of the month in `1..=31` for _time_.
+ fn day(&self) -> u8;
+ /// Returns the hour of the day in `0..=23` for _time_.
+ fn hour(&self) -> u8;
+ /// Returns the minute of the hour in `0..=59` for _time_.
+ fn minute(&self) -> u8;
+ /// Returns the second of the minute in `0..=60` for _time_.
+ fn second(&self) -> u8;
+ /// Returns the number of nanoseconds in `0..=999_999_999` for _time_.
+ fn nanoseconds(&self) -> u32;
+ /// Returns an integer representing the day of the week in `0..=6`, with
+ /// `Sunday == 0`.
+ fn day_of_week(&self) -> u8;
+ /// Returns an integer representing the day of the year in `1..=366`.
+ fn day_of_year(&self) -> u16;
+ /// Returns the number of seconds as a signed integer since the Epoch.
+ fn to_int(&self) -> i64;
+ /// Returns true if the time zone is UTC.
+ fn is_utc(&self) -> bool;
+ /// Returns the offset in seconds between the timezone of _time_ and UTC.
+ fn utc_offset(&self) -> i32;
+ /// Returns the name of the time zone as a string.
+ fn time_zone(&self) -> &str;
+}
+
+// Check that the Time trait is object-safe
+const _: Option<&dyn Time> = None;
+
+/// Format string used by Ruby [`Time#asctime`] method.
+///
+/// [`Time#asctime`]: <https://ruby-doc.org/core-3.1.2/Time.html#method-i-asctime>
+pub const ASCTIME_FORMAT_STRING: &str = "%c";
+
+/// Provides a `strftime` implementation using a format string with arbitrary
+/// bytes, writing to a provided byte slice.
+pub mod buffered {
+ use super::{Error, Time};
+ use crate::format::TimeFormatter;
+
+ /// Format a _time_ implementation with the specified format byte string,
+ /// writing in the provided buffer and returning the written subslice.
+ ///
+ /// See the [crate-level documentation](crate) for a complete description of
+ /// possible format specifiers.
+ ///
+ /// # Allocations
+ ///
+ /// This `strftime` implementation makes no heap allocations and is usable
+ /// in a `no_std` context.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use strftime::buffered::strftime;
+ /// use strftime::Time;
+ ///
+ /// // Not shown: create a time implementation with the year 1970
+ /// // let time = ...;
+ /// # include!("mock.rs.in");
+ /// # fn main() -> Result<(), strftime::Error> {
+ /// # let time = MockTime { year: 1970, ..Default::default() };
+ /// assert_eq!(time.year(), 1970);
+ ///
+ /// let mut buf = [0u8; 8];
+ /// assert_eq!(strftime(&time, b"%Y", &mut buf)?, b"1970");
+ /// assert_eq!(buf, *b"1970\0\0\0\0");
+ /// # Ok(())
+ /// # }
+ /// ```
+ ///
+ /// # Errors
+ ///
+ /// Can produce an [`Error`] when the formatting fails.
+ pub fn strftime<'a>(
+ time: &impl Time,
+ format: &[u8],
+ buf: &'a mut [u8],
+ ) -> Result<&'a mut [u8], Error> {
+ let len = buf.len();
+
+ let mut cursor = &mut buf[..];
+ TimeFormatter::new(time, format).fmt(&mut cursor)?;
+ let remaining_len = cursor.len();
+
+ Ok(&mut buf[..len - remaining_len])
+ }
+}
+
+/// Provides a `strftime` implementation using a UTF-8 format string, writing to
+/// a [`core::fmt::Write`] object.
+pub mod fmt {
+ use core::fmt::Write;
+
+ use super::{Error, Time};
+ use crate::format::{FmtWrite, TimeFormatter};
+
+ /// Format a _time_ implementation with the specified UTF-8 format string,
+ /// writing to the provided [`core::fmt::Write`] object.
+ ///
+ /// See the [crate-level documentation](crate) for a complete description of
+ /// possible format specifiers.
+ ///
+ /// # Allocations
+ ///
+ /// This `strftime` implementation makes no heap allocations on its own, but
+ /// the provided writer may allocate.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use strftime::fmt::strftime;
+ /// use strftime::Time;
+ ///
+ /// // Not shown: create a time implementation with the year 1970
+ /// // let time = ...;
+ /// # include!("mock.rs.in");
+ /// # fn main() -> Result<(), strftime::Error> {
+ /// # let time = MockTime { year: 1970, ..Default::default() };
+ /// assert_eq!(time.year(), 1970);
+ ///
+ /// let mut buf = String::new();
+ /// strftime(&time, "%Y", &mut buf)?;
+ /// assert_eq!(buf, "1970");
+ /// # Ok(())
+ /// # }
+ /// ```
+ ///
+ /// # Errors
+ ///
+ /// Can produce an [`Error`] when the formatting fails.
+ pub fn strftime(time: &impl Time, format: &str, buf: &mut dyn Write) -> Result<(), Error> {
+ TimeFormatter::new(time, format).fmt(&mut FmtWrite::new(buf))
+ }
+}
+
+/// Provides a `strftime` implementation using a format string with arbitrary
+/// bytes, writing to a newly allocated [`Vec`].
+///
+/// [`Vec`]: alloc::vec::Vec
+#[cfg(feature = "alloc")]
+#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
+pub mod bytes {
+ use alloc::vec::Vec;
+
+ use super::{Error, Time};
+ use crate::format::TimeFormatter;
+
+ /// Format a _time_ implementation with the specified format byte string.
+ ///
+ /// See the [crate-level documentation](crate) for a complete description of
+ /// possible format specifiers.
+ ///
+ /// # Allocations
+ ///
+ /// This `strftime` implementation writes its output to a heap-allocated
+ /// [`Vec`]. The implementation exclusively uses fallible allocation APIs
+ /// like [`Vec::try_reserve`]. This function will return [`Error::OutOfMemory`]
+ /// if there is an allocation failure.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use strftime::bytes::strftime;
+ /// use strftime::Time;
+ ///
+ /// // Not shown: create a time implementation with the year 1970
+ /// // let time = ...;
+ /// # include!("mock.rs.in");
+ /// # fn main() -> Result<(), strftime::Error> {
+ /// # let time = MockTime { year: 1970, ..Default::default() };
+ /// assert_eq!(time.year(), 1970);
+ ///
+ /// assert_eq!(strftime(&time, b"%Y")?, b"1970");
+ /// # Ok(())
+ /// # }
+ /// ```
+ ///
+ /// # Errors
+ ///
+ /// Can produce an [`Error`] when the formatting fails.
+ pub fn strftime(time: &impl Time, format: &[u8]) -> Result<Vec<u8>, Error> {
+ let mut buf = Vec::new();
+ TimeFormatter::new(time, format).fmt(&mut buf)?;
+ Ok(buf)
+ }
+}
+
+/// Provides a `strftime` implementation using a UTF-8 format string, writing to
+/// a newly allocated [`String`].
+///
+/// [`String`]: alloc::string::String
+#[cfg(feature = "alloc")]
+#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
+pub mod string {
+ use alloc::string::String;
+ use alloc::vec::Vec;
+
+ use super::{Error, Time};
+ use crate::format::TimeFormatter;
+
+ /// Format a _time_ implementation with the specified UTF-8 format string.
+ ///
+ /// See the [crate-level documentation](crate) for a complete description of
+ /// possible format specifiers.
+ ///
+ /// # Allocations
+ ///
+ /// This `strftime` implementation writes its output to a heap-allocated
+ /// [`Vec`]. The implementation exclusively uses fallible allocation APIs
+ /// like [`Vec::try_reserve`]. This function will return [`Error::OutOfMemory`]
+ /// if there is an allocation failure.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use strftime::string::strftime;
+ /// use strftime::Time;
+ ///
+ /// // Not shown: create a time implementation with the year 1970
+ /// // let time = ...;
+ /// # include!("mock.rs.in");
+ /// # fn main() -> Result<(), strftime::Error> {
+ /// # let time = MockTime { year: 1970, ..Default::default() };
+ /// assert_eq!(time.year(), 1970);
+ ///
+ /// assert_eq!(strftime(&time, "%Y")?, "1970");
+ /// # Ok(())
+ /// # }
+ /// ```
+ ///
+ /// # Errors
+ ///
+ /// Can produce an [`Error`] when the formatting fails.
+ #[allow(clippy::missing_panics_doc)]
+ pub fn strftime(time: &impl Time, format: &str) -> Result<String, Error> {
+ let mut buf = Vec::new();
+ TimeFormatter::new(time, format).fmt(&mut buf)?;
+ Ok(String::from_utf8(buf).expect("formatted string should be valid UTF-8"))
+ }
+}
+
+/// Provides a `strftime` implementation using a format string with arbitrary
+/// bytes, writing to a [`std::io::Write`] object.
+#[cfg(feature = "std")]
+#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
+pub mod io {
+ use std::io::Write;
+
+ use super::{Error, Time};
+ use crate::format::{IoWrite, TimeFormatter};
+
+ /// Format a _time_ implementation with the specified format byte string,
+ /// writing to the provided [`std::io::Write`] object.
+ ///
+ /// See the [crate-level documentation](crate) for a complete description of
+ /// possible format specifiers.
+ ///
+ /// # Allocations
+ ///
+ /// This `strftime` implementation makes no heap allocations on its own, but
+ /// the provided writer may allocate.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use strftime::io::strftime;
+ /// use strftime::Time;
+ ///
+ /// // Not shown: create a time implementation with the year 1970
+ /// // let time = ...;
+ /// # include!("mock.rs.in");
+ /// # fn main() -> Result<(), strftime::Error> {
+ /// # let time = MockTime { year: 1970, ..Default::default() };
+ /// assert_eq!(time.year(), 1970);
+ ///
+ /// let mut buf = Vec::new();
+ /// strftime(&time, b"%Y", &mut buf)?;
+ /// assert_eq!(buf, *b"1970");
+ /// # Ok(())
+ /// # }
+ /// ```
+ ///
+ /// # Errors
+ ///
+ /// Can produce an [`Error`] when the formatting fails.
+ pub fn strftime(time: &impl Time, format: &[u8], buf: &mut dyn Write) -> Result<(), Error> {
+ TimeFormatter::new(time, format).fmt(&mut IoWrite::new(buf))
+ }
+}
+
+// Ensure code blocks in `README.md` compile.
+//
+// This module declaration should be kept at the end of the file, in order to
+// not interfere with code coverage.
+#[cfg(all(doctest, feature = "std"))]
+#[doc = include_str!("../README.md")]
+mod readme {}
+
fn:
) to \
+ restrict the search to a given item kind.","Accepted kinds are: fn
, mod
, struct
, \
+ enum
, trait
, type
, macro
, \
+ and const
.","Search functions by type signature (e.g., vec -> usize
or \
+ -> vec
or String, enum:Cow -> bool
)","You can look for items with an exact name by putting double quotes around \
+ your request: \"string\"
","Look for functions that accept or return \
+ slices and \
+ arrays by writing \
+ square brackets (e.g., -> [u8]
or [] -> Option
)","Look for items inside another one by searching for a path: vec::Vec
",].map(x=>""+x+"
").join("");const div_infos=document.createElement("div");addClass(div_infos,"infos");div_infos.innerHTML="${value.replaceAll(" ", " ")}
`}else{error[index]=value}});output+=`pub fn strftime<'a>(
+ time: &impl Time,
+ format: &[u8],
+ buf: &'a mut [u8]
+) -> Result<&'a mut [u8], Error>
Format a time implementation with the specified format byte string, +writing in the provided buffer and returning the written subslice.
+See the crate-level documentation for a complete description of +possible format specifiers.
+This strftime
implementation makes no heap allocations and is usable
+in a no_std
context.
use strftime::buffered::strftime;
+use strftime::Time;
+
+// Not shown: create a time implementation with the year 1970
+// let time = ...;
+assert_eq!(time.year(), 1970);
+
+let mut buf = [0u8; 8];
+assert_eq!(strftime(&time, b"%Y", &mut buf)?, b"1970");
+assert_eq!(buf, *b"1970\0\0\0\0");
Can produce an Error
when the formatting fails.
Provides a strftime
implementation using a format string with arbitrary
+bytes, writing to a provided byte slice.
pub fn strftime(time: &impl Time, format: &[u8]) -> Result<Vec<u8>, Error>
alloc
only.Format a time implementation with the specified format byte string.
+See the crate-level documentation for a complete description of +possible format specifiers.
+This strftime
implementation writes its output to a heap-allocated
+Vec
. The implementation exclusively uses fallible allocation APIs
+like Vec::try_reserve
. This function will return Error::OutOfMemory
+if there is an allocation failure.
use strftime::bytes::strftime;
+use strftime::Time;
+
+// Not shown: create a time implementation with the year 1970
+// let time = ...;
+assert_eq!(time.year(), 1970);
+
+assert_eq!(strftime(&time, b"%Y")?, b"1970");
Can produce an Error
when the formatting fails.
pub const ASCTIME_FORMAT_STRING: &str = "%c";
Format string used by Ruby Time#asctime
method.
#[non_exhaustive]pub enum Error {
+ InvalidTime,
+ InvalidFormatString,
+ FormattedStringTooLarge,
+ WriteZero,
+ FmtError(Error),
+ OutOfMemory(TryReserveError),
+ IoError(Error),
+}
Error type returned by the strftime
functions.
Provided time implementation returns invalid values.
+Provided format string is ended by an unterminated format specifier.
+Formatted string is too large and could cause an out-of-memory error.
+Provided buffer for the buffered::strftime
function is too small for
+the formatted string.
This corresponds to the std::io::ErrorKind::WriteZero
variant.
Formatting error, corresponding to core::fmt::Error
.
alloc
only.An allocation failure has occurred in either bytes::strftime
or
+string::strftime
.
std
only.An I/O error has occurred in io::strftime
.
std
only.alloc
only.pub fn strftime(
+ time: &impl Time,
+ format: &str,
+ buf: &mut dyn Write
+) -> Result<(), Error>
Format a time implementation with the specified UTF-8 format string,
+writing to the provided core::fmt::Write
object.
See the crate-level documentation for a complete description of +possible format specifiers.
+This strftime
implementation makes no heap allocations on its own, but
+the provided writer may allocate.
use strftime::fmt::strftime;
+use strftime::Time;
+
+// Not shown: create a time implementation with the year 1970
+// let time = ...;
+assert_eq!(time.year(), 1970);
+
+let mut buf = String::new();
+strftime(&time, "%Y", &mut buf)?;
+assert_eq!(buf, "1970");
Can produce an Error
when the formatting fails.
Provides a strftime
implementation using a UTF-8 format string, writing to
+a core::fmt::Write
object.
core::fmt::Write
object.This crate provides a Ruby 3.1.2 compatible strftime
function, which
+formats time according to the directives in the given format string.
The directives begin with a percent %
character. Any text not listed as a
+directive will be passed through to the output string.
Each directive consists of a percent %
character, zero or more flags,
+optional minimum field width, optional modifier and a conversion specifier
+as follows:
%<flags><width><modifier><conversion>
+
The various strftime
functions in this crate take a generic time
+parameter that implements the Time
trait.
Flag | Description |
---|---|
- | Use left padding, ignoring width and removing all other padding options in most cases. |
_ | Use spaces for padding. |
0 | Use zeros for padding. |
^ | Convert the resulting string to uppercase. |
# | Change case of the resulting string. |
The minimum field width specifies the minimum width.
+The modifiers are E
and O
. They are ignored.
Specifier | Example | Description |
---|---|---|
%Y | -2001 | Year with century if provided, zero-padded to at least 4 digits plus the possible negative sign. |
%C | -21 | Year / 100 using Euclidean division, zero-padded to at least 2 digits. |
%y | 99 | Year % 100 in 00..=99 , using Euclidean remainder, zero-padded to 2 digits. |
%m | 01 | Month of the year in 01..=12 , zero-padded to 2 digits. |
%B | July | Locale independent full month name. |
%b , %h | Jul | Locale independent abbreviated month name, using the first 3 letters. |
%d | 01 | Day of the month in 01..=31 , zero-padded to 2 digits. |
%e | 1 | Day of the month in 1..=31 , blank-padded to 2 digits. |
%j | 001 | Day of the year in 001..=366 , zero-padded to 3 digits. |
%H | 00 | Hour of the day (24-hour clock) in 00..=23 , zero-padded to 2 digits. |
%k | 0 | Hour of the day (24-hour clock) in 0..=23 , blank-padded to 2 digits. |
%I | 01 | Hour of the day (12-hour clock) in 01..=12 , zero-padded to 2 digits. |
%l | 1 | Hour of the day (12-hour clock) in 1..=12 , blank-padded to 2 digits. |
%P | am | Lowercase meridian indicator ("am" or "pm" ). |
%p | AM | Uppercase meridian indicator ("AM" or "PM" ). |
%M | 00 | Minute of the hour in 00..=59 , zero-padded to 2 digits. |
%S | 00 | Second of the minute in 00..=60 , zero-padded to 2 digits. |
%L | 123 | Truncated fractional seconds digits, with 3 digits by default. Number of digits is specified by the width field. |
%N | 123456789 | Truncated fractional seconds digits, with 9 digits by default. Number of digits is specified by the width field. |
%z | +0200 | Zero-padded signed time zone UTC hour and minute offsets (+hhmm ). |
%:z | +02:00 | Zero-padded signed time zone UTC hour and minute offsets with colons (+hh:mm ). |
%::z | +02:00:00 | Zero-padded signed time zone UTC hour, minute and second offsets with colons (+hh:mm:ss ). |
%:::z | +02 | Zero-padded signed time zone UTC hour offset, with optional minute and second offsets with colons (+hh[:mm[:ss]] ). |
%Z | CEST | Platform-dependent abbreviated time zone name. |
%A | Sunday | Locale independent full weekday name. |
%a | Sun | Locale independent abbreviated weekday name, using the first 3 letters. |
%u | 1 | Day of the week from Monday in 1..=7 , zero-padded to 1 digit. |
%w | 0 | Day of the week from Sunday in 0..=6 , zero-padded to 1 digit. |
%G | -2001 | Same as %Y , but using the ISO 8601 week-based year. 1 |
%g | 99 | Same as %y , but using the ISO 8601 week-based year. 1 |
%V | 01 | ISO 8601 week number in 01..=53 , zero-padded to 2 digits. 1 |
%U | 00 | Week number from Sunday in 00..=53 , zero-padded to 2 digits. The week 1 starts with the first Sunday of the year. |
%W | 00 | Week number from Monday in 00..=53 , zero-padded to 2 digits. The week 1 starts with the first Monday of the year. |
%s | 86400 | Number of seconds since 1970-01-01 00:00:00 UTC , zero-padded to at least 1 digit. |
%n | \n | Newline character '\n' . |
%t | \t | Tab character '\t' . |
%% | % | Literal '%' character. |
%c | Sun Jul 8 00:23:45 2001 | Date and time, equivalent to "%a %b %e %H:%M:%S %Y" . |
%D , %x | 07/08/01 | Date, equivalent to "%m/%d/%y" . |
%F | 2001-07-08 | ISO 8601 date, equivalent to "%Y-%m-%d" . |
%v | 8-JUL-2001 | VMS date, equivalent to "%e-%^b-%4Y" . |
%r | 12:23:45 AM | 12-hour time, equivalent to "%I:%M:%S %p" . |
%R | 00:23 | 24-hour time without seconds, equivalent to "%H:%M" . |
%T , %X | 00:23:45 | 24-hour time, equivalent to "%H:%M:%S" . |
%G
, %g
, %V
: Week 1 of ISO 8601 is the first week with at least 4
+days in that year. The days before the first week are in the last week of
+the previous year. ↩
strftime
implementation using a format string with arbitrary
+bytes, writing to a provided byte slice.alloc
strftime
implementation using a format string with arbitrary
+bytes, writing to a newly allocated Vec
.strftime
implementation using a UTF-8 format string, writing to
+a core::fmt::Write
object.std
strftime
implementation using a format string with arbitrary
+bytes, writing to a std::io::Write
object.alloc
strftime
implementation using a UTF-8 format string, writing to
+a newly allocated String
.strftime
functions.Time#asctime
method.pub fn strftime(
+ time: &impl Time,
+ format: &[u8],
+ buf: &mut dyn Write
+) -> Result<(), Error>
std
only.Format a time implementation with the specified format byte string,
+writing to the provided std::io::Write
object.
See the crate-level documentation for a complete description of +possible format specifiers.
+This strftime
implementation makes no heap allocations on its own, but
+the provided writer may allocate.
use strftime::io::strftime;
+use strftime::Time;
+
+// Not shown: create a time implementation with the year 1970
+// let time = ...;
+assert_eq!(time.year(), 1970);
+
+let mut buf = Vec::new();
+strftime(&time, b"%Y", &mut buf)?;
+assert_eq!(buf, *b"1970");
Can produce an Error
when the formatting fails.
std
only.Provides a strftime
implementation using a format string with arbitrary
+bytes, writing to a std::io::Write
object.
std::io::Write
object.pub fn strftime(time: &impl Time, format: &str) -> Result<String, Error>
alloc
only.Format a time implementation with the specified UTF-8 format string.
+See the crate-level documentation for a complete description of +possible format specifiers.
+This strftime
implementation writes its output to a heap-allocated
+Vec
. The implementation exclusively uses fallible allocation APIs
+like Vec::try_reserve
. This function will return Error::OutOfMemory
+if there is an allocation failure.
use strftime::string::strftime;
+use strftime::Time;
+
+// Not shown: create a time implementation with the year 1970
+// let time = ...;
+assert_eq!(time.year(), 1970);
+
+assert_eq!(strftime(&time, "%Y")?, "1970");
Can produce an Error
when the formatting fails.
pub trait Time {
+Show 13 methods
// Required methods
+ fn year(&self) -> i32;
+ fn month(&self) -> u8;
+ fn day(&self) -> u8;
+ fn hour(&self) -> u8;
+ fn minute(&self) -> u8;
+ fn second(&self) -> u8;
+ fn nanoseconds(&self) -> u32;
+ fn day_of_week(&self) -> u8;
+ fn day_of_year(&self) -> u16;
+ fn to_int(&self) -> i64;
+ fn is_utc(&self) -> bool;
+ fn utc_offset(&self) -> i32;
+ fn time_zone(&self) -> &str;
+}
Common methods needed for formatting time.
+This should be implemented for structs representing a time.
+All the strftime
functions take as input an implementation of this trait.
Returns the number of nanoseconds in 0..=999_999_999
for time.
Returns an integer representing the day of the week in 0..=6
, with
+Sunday == 0
.
Returns an integer representing the day of the year in 1..=366
.
Returns the offset in seconds between the timezone of time and UTC.
+