Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Docs for most public functions for formatting DateTimes and Numbers #73

Merged
merged 6 commits into from
Apr 8, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
31 changes: 31 additions & 0 deletions src/Data/Formatter/DateTime.purs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could use a spot check on that statement. From what I could tell, the only situation that would return a Left value was if the string is empty (from List.some on 164), but literally anything else would parse as a Formatter and just have Placeholder for any unrecognized portions of the input string.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmmm, I ran this:

quickCheck' 100_000 \str -> 
  (if str == "" then true else isRight $ parseFormatString str)
  <?> "Test failed for input '" <> str <> "'"

and got

Test failed for input 'ᆌH졶簮'

So any advice what this message should say?

-- | 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
Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand Down
28 changes: 27 additions & 1 deletion src/Data/Formatter/Number.purs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same thing applies here as to the comment about moment.js. Also this information is news to me, I didn't know where it came from 😄 (I didn't implement this module).

printFormatter ∷ Formatter → String
printFormatter (Formatter f) =
(if f.sign then "+" else "")
Expand All @@ -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

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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 "-"
Expand Down Expand Up @@ -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
Expand Down