From c4e118350105f69d0eb48ef2f0ae425a3585ea3c Mon Sep 17 00:00:00 2001 From: nathan Date: Sat, 3 Jul 2021 22:06:27 -0500 Subject: [PATCH 1/5] docs for most public functions for formatting/printing DateTimes and Numbers --- src/Data/Formatter/DateTime.purs | 31 +++++++++++++++++++++++++++++++ src/Data/Formatter/Number.purs | 28 +++++++++++++++++++++++++++- 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/src/Data/Formatter/DateTime.purs b/src/Data/Formatter/DateTime.purs index a096a0b..420dffb 100644 --- a/src/Data/Formatter/DateTime.purs +++ b/src/Data/Formatter/DateTime.purs @@ -47,6 +47,8 @@ import Text.Parsing.Parser as P import Text.Parsing.Parser.Combinators as PC import Text.Parsing.Parser.String as PS +-- | One part of a DateTime `Formatter`. Use `Placeholder` for +-- | any static portion of the format, such as whitespace or separators `-` or `:`. data FormatterCommand = YearFull | YearTwoDigits @@ -78,6 +80,10 @@ derive instance genericFormatter ∷ Generic FormatterCommand _ instance showFormatter ∷ Show FormatterCommand where show = genericShow +-- | A description of a string format for dates and times. +-- | Functions such as `format` and `unformat` use a `Formatter` +-- | such as `(YearFull : MonthTwoDigits : DayOfMonthTwoDigits : Nil)` +-- | in place of a format string such as `"YYYYMMDD"`. type Formatter = List.List FormatterCommand printFormatterCommand ∷ FormatterCommand → String @@ -106,9 +112,18 @@ printFormatterCommand = case _ of Milliseconds → "SSS" Placeholder s → s +-- | The format string representation of a `Formatter`. +-- | +-- | `show (Hours24 : MinutesTwoDigits : Nil) = "(Hours24 : MinutesTwoDigits : Nil)"` +-- | +-- | while `printFormatter (Hours24 : MinutesTwoDigits : Nil) = "HHmm"`. +-- | +-- | The interpretation of the format string is inspired by [momentjs](https://momentjs.com/). printFormatter ∷ Formatter → String printFormatter = foldMap printFormatterCommand +-- | Attempt to parse a `String` as a `Formatter`, +-- | using an interpretation inspired by [momentjs](https://momentjs.com/). parseFormatString ∷ String → Either String Formatter parseFormatString = runP formatParser @@ -210,16 +225,26 @@ padQuadrupleDigit i | i < 1000 = "0" <> (show i) | otherwise = show i +-- | Format a DateTime according to the format defined in the given `Formatter` format ∷ Formatter → DT.DateTime → String format f d = foldMap (formatCommand d) f +-- | Format a DateTime according to the format defined in the given format string. +-- | If the format string is empty, will return a `Left` value. Note that any +-- | unrecognized character is treated as a placeholder, so while "yyyy-MM-dd" might +-- | not produce the format you want (since "y" and "d" aren't recognized format +-- | characters), it will still return a `Right` value. +-- | The interpretation of the format string is inspired by [momentjs](https://momentjs.com/). formatDateTime ∷ String → DT.DateTime → Either String String formatDateTime pattern datetime = parseFormatString pattern <#> (_ `format` datetime) +-- | Attempt to parse a String as a DateTime according to the format defined in the +-- | given `Formatter`. unformat ∷ Formatter → String → Either String DT.DateTime unformat = runP <<< unformatParser +-- | Before or after noon (AM/PM) data Meridiem = AM | PM derive instance eqMeridiem ∷ Eq Meridiem @@ -403,6 +428,8 @@ unformatCommandParser = case _ of v ← p lift $ modify_ (flip f (Just v)) +-- | A `ParserT` for `String`s that parses a `DateTime` +-- | according to the format defined in the given `Formatter`. unformatParser ∷ ∀ m. Monad m ⇒ Formatter → P.ParserT String m DT.DateTime unformatParser f = do acc ← P.mapParserT unState $ foldMap unformatCommandParser f @@ -412,6 +439,10 @@ unformatParser f = do unState s = case runState s initialAccum of Tuple (Tuple e state) res → pure (Tuple (e $> res) state) +-- | Attempt to parse a `String` as a `DateTime` according to the format defined in the +-- | given format string. Returns a `Left` value if the given format string was empty, or +-- | if the date string fails to parse according to the format. +-- | The interpretation of the format string is inspired by [momentjs](https://momentjs.com/). unformatDateTime ∷ String → String → Either String DT.DateTime unformatDateTime pattern str = parseFormatString pattern >>= (_ `unformat` str) diff --git a/src/Data/Formatter/Number.purs b/src/Data/Formatter/Number.purs index adffdda..e2b62d6 100644 --- a/src/Data/Formatter/Number.purs +++ b/src/Data/Formatter/Number.purs @@ -35,6 +35,17 @@ import Text.Parsing.Parser.Combinators as PC import Text.Parsing.Parser.String as PS +-- | Defines a format for printing/parsing numbers. +-- | +-- | `comma`: use a ',' for a thousands separator +-- | +-- | `before`: the minimum number of characters to print before the decimal point +-- | +-- | `after`: the total number of characters to print after the decimal point +-- | +-- | `abbreviations`: "31600.0" → "32K"; "31600000.0" → "32M" +-- | +-- | `sign`: always print a sign, including a `+` for positive numbers newtype Formatter = Formatter { comma ∷ Boolean , before ∷ Int @@ -51,6 +62,8 @@ instance showFormatter ∷ Show Formatter where derive instance eqFormatter ∷ Eq Formatter +-- | The format string representation of a `Formatter`. +-- | The interpretation of the format string inspired by [numeral.js](http://numeraljs.com/#format) printFormatter ∷ Formatter → String printFormatter (Formatter f) = (if f.sign then "+" else "") @@ -60,6 +73,8 @@ printFormatter (Formatter f) = <> (repeat "0" f.after) <> (if f.abbreviations then "a" else "") +-- | Attempt to parse a `String` as a `Formatter`, +-- | using an interpretation inspired by [numeral.js](http://numeraljs.com/#format) parseFormatString ∷ String → Either String Formatter parseFormatString = runP formatParser @@ -87,7 +102,7 @@ formatParser = do -- means of showing an integer potentially larger than +/- 2 billion. foreign import showNumberAsInt :: Number -> String --- | Formats a number according to the format object provided. +-- | Format a `Number` according to the `Formatter` provided. -- | Due to the nature of floating point numbers, may yield unpredictable results for extremely -- | large or extremely small numbers, such as numbers whose absolute values are ≥ 1e21 or ≤ 1e-21, -- | or when formatting with > 20 digits after the decimal place. @@ -155,9 +170,13 @@ format (Formatter f) num = <> (if leftover > 0.0 then leftoverWithZeros else "")) +-- | Attempt to parse a `String` as a `Number` according to the format defined in the +-- | given `Formatter`. unformat ∷ Formatter → String → Either String Number unformat = runP <<< unformatParser +-- | A `ParserT` for `String`s that parses a `Number` +-- | according to the format defined in the given `Formatter`. unformatParser ∷ Formatter → P.Parser String Number unformatParser (Formatter f) = do minus ← PC.optionMaybe $ PC.try $ PS.string "-" @@ -232,10 +251,17 @@ unformatParser (Formatter f) = do * sign * (before + after / Math.pow 10.0 (Int.toNumber f.after)) +-- | Format a Number according to the format defined in the given format string. +-- | If the format string fails to parse, will return a `Left` value. +-- | The interpretation of the format string is inspired by [numeral.js](http://numeraljs.com/#format) formatNumber ∷ String → Number → Either String String formatNumber pattern number = parseFormatString pattern <#> flip format number +-- | Attempt to parse a `String` as a `Number` according to the format defined in the +-- | given format string. Returns a `Left` value if the given format string fails to parse, or +-- | if the number string fails to parse according to the format. +-- | The interpretation of the format string is inspired by [numeral.js](http://numeraljs.com/#format) unformatNumber ∷ String → String → Either String Number unformatNumber pattern str = parseFormatString pattern >>= flip unformat str From 12afbaa29e675847a3db5cf6132afedb614c6231 Mon Sep 17 00:00:00 2001 From: nathan Date: Sat, 3 Jul 2021 22:12:57 -0500 Subject: [PATCH 2/5] update the changelog with a link to this PR --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b25c6c..e6aae38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ New features: Bugfixes: Other improvements: +- Added module documentation for Data.Formatter.DateTime and Data.Formatter.Number (#73 by @ntwilson) ## [v5.0.1](https://github.com/purescript-contrib/purescript-formatters/releases/tag/v5.0.1) - 2021-05-06 From 8adaf113ae283a5ae9ff79422aca15a02328882b Mon Sep 17 00:00:00 2001 From: nathan Date: Sun, 4 Jul 2021 12:10:50 -0500 Subject: [PATCH 3/5] update the momentjs link to their detailed formatting docs, and correct an inaccuracy in one of the doc comments --- src/Data/Formatter/DateTime.purs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Data/Formatter/DateTime.purs b/src/Data/Formatter/DateTime.purs index 420dffb..e3d91f2 100644 --- a/src/Data/Formatter/DateTime.purs +++ b/src/Data/Formatter/DateTime.purs @@ -118,12 +118,12 @@ printFormatterCommand = case _ of -- | -- | while `printFormatter (Hours24 : MinutesTwoDigits : Nil) = "HHmm"`. -- | --- | The interpretation of the format string is inspired by [momentjs](https://momentjs.com/). +-- | The interpretation of the format string is inspired by [momentjs](https://momentjs.com/docs/#/displaying/format/). printFormatter ∷ Formatter → String printFormatter = foldMap printFormatterCommand -- | Attempt to parse a `String` as a `Formatter`, --- | using an interpretation inspired by [momentjs](https://momentjs.com/). +-- | using an interpretation inspired by [momentjs](https://momentjs.com/docs/#/displaying/format/). parseFormatString ∷ String → Either String Formatter parseFormatString = runP formatParser @@ -232,9 +232,9 @@ format f d = foldMap (formatCommand d) f -- | Format a DateTime according to the format defined in the given format string. -- | If the format string is empty, will return a `Left` value. Note that any -- | unrecognized character is treated as a placeholder, so while "yyyy-MM-dd" might --- | not produce the format you want (since "y" and "d" aren't recognized format --- | characters), it will still return a `Right` value. --- | The interpretation of the format string is inspired by [momentjs](https://momentjs.com/). +-- | not produce the format you want (since "yyyy" and "dd" aren't recognized formats), +-- | it will still return a `Right` value. +-- | The interpretation of the format string is inspired by [momentjs](https://momentjs.com/docs/#/displaying/format/). formatDateTime ∷ String → DT.DateTime → Either String String formatDateTime pattern datetime = parseFormatString pattern <#> (_ `format` datetime) @@ -442,7 +442,7 @@ unformatParser f = do -- | Attempt to parse a `String` as a `DateTime` according to the format defined in the -- | given format string. Returns a `Left` value if the given format string was empty, or -- | if the date string fails to parse according to the format. --- | The interpretation of the format string is inspired by [momentjs](https://momentjs.com/). +-- | The interpretation of the format string is inspired by [momentjs](https://momentjs.com/docs/#/displaying/format/). unformatDateTime ∷ String → String → Either String DT.DateTime unformatDateTime pattern str = parseFormatString pattern >>= (_ `unformat` str) From d9dd31b451abd726a865bfae4a15e1b7f3650031 Mon Sep 17 00:00:00 2001 From: nathan wilson Date: Fri, 24 Feb 2023 21:52:40 -0500 Subject: [PATCH 4/5] update the docs for astral plane characters --- src/Data/Formatter/DateTime.purs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Data/Formatter/DateTime.purs b/src/Data/Formatter/DateTime.purs index 6cd29e9..22b3517 100644 --- a/src/Data/Formatter/DateTime.purs +++ b/src/Data/Formatter/DateTime.purs @@ -232,8 +232,9 @@ format :: Formatter -> DT.DateTime -> String format f d = foldMap (formatCommand d) f -- | Format a DateTime according to the format defined in the given format string. --- | If the format string is empty, will return a `Left` value. Note that any --- | unrecognized character is treated as a placeholder, so while "yyyy-MM-dd" might +-- | If the format string is empty, or contains astral plane characters (i.e., unicode +-- | code points that aren't representable in a single code unit), will return a `Left` value. +-- | Note that any unrecognized `Char` is treated as a placeholder, so while "yyyy-MM-dd" might -- | not produce the format you want (since "yyyy" and "dd" aren't recognized formats), -- | it will still return a `Right` value. -- | The interpretation of the format string is inspired by [momentjs](https://momentjs.com/docs/#/displaying/format/). From ae6558d646c8c41755edc0bec31ab2bf32eb1f11 Mon Sep 17 00:00:00 2001 From: nathan wilson Date: Sat, 25 Mar 2023 15:38:54 -0400 Subject: [PATCH 5/5] address review comments --- src/Data/Formatter/DateTime.purs | 41 +++++++++++++++++++++++--------- src/Data/Formatter/Number.purs | 33 +++++++++++++++++++++++-- 2 files changed, 61 insertions(+), 13 deletions(-) diff --git a/src/Data/Formatter/DateTime.purs b/src/Data/Formatter/DateTime.purs index 22b3517..0983e4d 100644 --- a/src/Data/Formatter/DateTime.purs +++ b/src/Data/Formatter/DateTime.purs @@ -1,3 +1,27 @@ +-- | This is a subset of common format/parse strings currently supported. +-- | +-- | + `YYYY` - Full Year (1999) +-- | + `YY` - 2 digit year (99) +-- | + `MMMM` - Full Month (January) +-- | + `MMM` - Short Month (Jan) +-- | + `DD` - Padded Day (02) +-- | + `D` - Day of month (2) +-- | + `X` - Unix Timestamp (1506875681) +-- | + `E` - Day of Week (2) +-- | + `dddd` - DOW Name (Monday) +-- | + `ddd` - DOW Name Short (Mon) +-- | + `HH` - 24 Hour (13) +-- | + `hh` - 12 Hour (1) +-- | + `a` - Meridiem (am/pm) +-- | + `mm` - Minutes Padded (02) +-- | + `m` - Minutes (2) +-- | + `ss` - Seconds Padded (02) +-- | + `s` - Seconds (2) +-- | + `S` - MilliSeconds (4) +-- | + `SS` - MilliSeconds (04) +-- | + `SSS` - MilliSeconds (004) +-- | +-- | Full list is defined [here](https://github.com/slamdata/purescript-formatters/blob/master/src/Data/Formatter/DateTime.purs) module Data.Formatter.DateTime ( Formatter , FormatterCommand(..) @@ -118,13 +142,10 @@ printFormatterCommand = case _ of -- | `show (Hours24 : MinutesTwoDigits : Nil) = "(Hours24 : MinutesTwoDigits : Nil)"` -- | -- | while `printFormatter (Hours24 : MinutesTwoDigits : Nil) = "HHmm"`. --- | --- | The interpretation of the format string is inspired by [momentjs](https://momentjs.com/docs/#/displaying/format/). printFormatter :: Formatter -> String printFormatter = foldMap printFormatterCommand --- | Attempt to parse a `String` as a `Formatter`, --- | using an interpretation inspired by [momentjs](https://momentjs.com/docs/#/displaying/format/). +-- | Attempt to parse a `String` as a `Formatter`. parseFormatString :: String -> Either String Formatter parseFormatString = runP formatParser @@ -227,17 +248,16 @@ padQuadrupleDigit i | i < 1000 = "0" <> (show i) | otherwise = show i --- | Format a DateTime according to the format defined in the given `Formatter` +-- | Format a DateTime according to the format defined in the given `Formatter`. format :: Formatter -> DT.DateTime -> String format f d = foldMap (formatCommand d) f -- | Format a DateTime according to the format defined in the given format string. --- | If the format string is empty, or contains astral plane characters (i.e., unicode --- | code points that aren't representable in a single code unit), will return a `Left` value. --- | Note that any unrecognized `Char` is treated as a placeholder, so while "yyyy-MM-dd" might --- | not produce the format you want (since "yyyy" and "dd" aren't recognized formats), +-- | If the format string is empty or contains a reserved character such as a single "M" or "H", +-- | will return a `Left` value. +-- | Note that any non-reserved `Char` is treated as a placeholder, so while "yyyy-MM-dd" might +-- | not produce the format you want (since "yyyy" isn't a recognized format), -- | it will still return a `Right` value. --- | The interpretation of the format string is inspired by [momentjs](https://momentjs.com/docs/#/displaying/format/). formatDateTime :: String -> DT.DateTime -> Either String String formatDateTime pattern datetime = parseFormatString pattern <#> (_ `format` datetime) @@ -450,7 +470,6 @@ unformatParser f = do -- | Attempt to parse a `String` as a `DateTime` according to the format defined in the -- | given format string. Returns a `Left` value if the given format string was empty, or -- | if the date string fails to parse according to the format. --- | The interpretation of the format string is inspired by [momentjs](https://momentjs.com/docs/#/displaying/format/). unformatDateTime :: String -> String -> Either String DT.DateTime unformatDateTime pattern str = parseFormatString pattern >>= (_ `unformat` str) diff --git a/src/Data/Formatter/Number.purs b/src/Data/Formatter/Number.purs index 8d46628..30665e3 100644 --- a/src/Data/Formatter/Number.purs +++ b/src/Data/Formatter/Number.purs @@ -1,7 +1,37 @@ +-- | Formatter has following properties +-- | + Number of digits before dot +-- | + Number of digits after dot +-- | + Should sign be printed for positive numbers +-- | + Should thousands be separated by comma +-- | + Should output string have abbreviations (like `K` or `M`) +-- | + What decimal-separator character should be used (default '.') +-- | + What thousand-group-separator character should be used (default '+') +-- | +-- | **Note:** The parser will return a formatter with the default separator-characters - use `withSeparators` to override this after parsing. +-- | +-- | Number will be padded with zeros to have at least this number of leading zeros. This doesn't restrict number to have more digits then leading zeros in format string. +-- | + `0000.0` will show 4 digits: `12 → "0012.0"`, `1234 → "1234.0"` +-- | + `00.0` will show only 2 digits : `12 → "12.0"`, `1234 → "1234.0"` +-- | +-- | Number of digits after dot is set by number of trailing zeros (note the rounding) +-- | + `0.000` will show 3 digits: `0.12345 → "0.123"`, `12.98765 → "12.988"` +-- | + `0.0` will show only 1 digit: `0.12345 → "0.1"`, `12.98765 → "13.0"` +-- | +-- | If number is lesser then zero `-` is always printed. Otherwise you could specify `+` in format string +-- | + `+0`: `12.0 → "+12"`, `-34.8 → "-35"` +-- | + `0`: `12.0 → "12"`, `-34.8 → "-35"` +-- | +-- | Thousands separator is specified as `,0` please note that this `0` isn't counted as leading. +-- | + `00,0`: `1234567890 → "1,234,567,890.0", `1 → "1.0"` +-- | +-- | For abbreviation one could use `a` flag. In general it tries to find the closest power of thousand and +-- | then use formatter to result of division of input number and that power. +-- | + `0a`: `1234567 → "1M"`, `1 → "1"` +-- | -- | This module has no support of percents and currencies. -- | Please, note that using simple formatter that tabulates number with -- | zeros and put commas between thousands should be enough for everything --- | because one could just compose it with `flip append "%"` or whatever +-- | because one could just compose it with `flip append "%"` or whatever. module Data.Formatter.Number ( Formatter(..) , withSeparators @@ -75,7 +105,6 @@ instance showFormatter :: Show Formatter where derive instance eqFormatter :: Eq Formatter -- | The format string representation of a `Formatter`. --- | The interpretation of the format string inspired by [numeral.js](http://numeraljs.com/#format) printFormatter :: Formatter -> String printFormatter (Formatter f) = (if f.sign then "+" else "")