Skip to content

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

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

Merged
merged 6 commits into from
Apr 8, 2023
Merged
Show file tree
Hide file tree
Changes from all 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 @@ -13,6 +13,7 @@ New features:
Bugfixes:

Other improvements:
- Added module documentation for Data.Formatter.DateTime and Data.Formatter.Number (#73 by @ntwilson)

## [v7.0.0](https://github.com/purescript-contrib/purescript-formatters/releases/tag/v7.0.0) - 2022-04-28

Expand Down
51 changes: 51 additions & 0 deletions src/Data/Formatter/DateTime.purs
Original file line number Diff line number Diff line change
@@ -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(..)
Expand Down Expand Up @@ -48,6 +72,8 @@ import Parsing.Combinators as PC
import Parsing.String as PS
import Parsing.String.Basic as PSB

-- | 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 @@ -79,6 +105,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 @@ -107,9 +137,15 @@ 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"`.
printFormatter :: Formatter -> String
printFormatter = foldMap printFormatterCommand

-- | Attempt to parse a `String` as a `Formatter`.
parseFormatString :: String -> Either String Formatter
parseFormatString = runP formatParser

Expand Down Expand Up @@ -212,16 +248,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 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.
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 @@ -410,6 +456,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 :: forall m. Monad m => Formatter -> P.ParserT String m DT.DateTime
unformatParser f = do
acc <- P.mapParserT unState $ foldMap unformatCommandParser f
Expand All @@ -419,6 +467,9 @@ 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.
unformatDateTime :: String -> String -> Either String DT.DateTime
unformatDateTime pattern str =
parseFormatString pattern >>= (_ `unformat` str)
Expand Down
59 changes: 57 additions & 2 deletions src/Data/Formatter/Number.purs
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -37,6 +67,17 @@ import Parsing.Combinators as PC
import Parsing.String as PS
import Parsing.String.Basic as PSB

-- | 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 @@ -63,6 +104,7 @@ instance showFormatter :: Show Formatter where

derive instance eqFormatter :: Eq Formatter

-- | The format string representation of a `Formatter`.
printFormatter :: Formatter -> String
printFormatter (Formatter f) =
(if f.sign then "+" else "")
Expand All @@ -72,6 +114,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 @@ -100,7 +144,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 @@ -174,9 +218,13 @@ format (Formatter f) num = do
<> shownInt
<> leftovers

-- | 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 @@ -252,9 +300,16 @@ unformatParser (Formatter f) = do
* sign
* (before + after / Number.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