From 5350f8c08f70883193f5b784e2bfb6a8c0288019 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Wed, 5 Apr 2017 22:44:13 +0400 Subject: [PATCH 01/88] WIP Interval formatters --- src/Data/Formatter/Interval.purs | 60 ++++++++++++++ src/Data/Formatter/Number.purs | 9 ++- src/Data/Interval.purs | 134 +++++++++++++++++++++++++++++++ test/src/Main.purs | 20 +++++ 4 files changed, 222 insertions(+), 1 deletion(-) create mode 100644 src/Data/Formatter/Interval.purs create mode 100644 src/Data/Interval.purs diff --git a/src/Data/Formatter/Interval.purs b/src/Data/Formatter/Interval.purs new file mode 100644 index 0000000..99f319f --- /dev/null +++ b/src/Data/Formatter/Interval.purs @@ -0,0 +1,60 @@ +module Data.Formatter.Interval + -- TODO parser should't be exposed + ( parseDuration + ) where + +import Prelude +import Data.Interval as I +import Text.Parsing.Parser as P +import Text.Parsing.Parser.Combinators as PC +import Text.Parsing.Parser.String as PS +import Control.Alt ((<|>)) +import Data.Array (length, some) +import Data.Formatter.Internal (digit, foldDigits) +import Data.Int (toNumber) +import Data.Monoid (mempty) + + +nums ∷ P.Parser String Int +nums = foldDigits <$> some digit + +-- TODO try to use unformatNumberParser here +number ∷ P.Parser String Number +number = do + whole ← nums + _ ← (PC.try $ PS.string ".") <|> (PC.try $ PS.string ",") <|> pure "" + restdigits ← PC.try (some digit) <|> pure [0] + let rest = foldDigits restdigits + pure $ if rest == 0 then toNumber whole else toNumber whole + ((toNumber rest) / (toNumber $ length restdigits)) + + + +component ∷ String → P.Parser String Number +component designator = number <* PS.string designator + +tryOr :: ∀ a. a → P.Parser String a → P.Parser String a +tryOr a p = PC.option a $ PC.try p + +parseDuration :: P.Parser String (I.Duration) +-- parseDuration = PS.string "P" *> weekDuration +parseDuration = PS.string "P" *> (weekDuration <|> fullDuration)-- <* PS.eof + where + weekDuration :: P.Parser String I.Duration + weekDuration = PC.try $ I.week <$> component "W" + + fullDuration ∷ P.Parser String I.Duration + fullDuration = append <$> durationDatePart <*> durationTimePart + + durationDatePart ∷ P.Parser String I.Duration + durationDatePart = (\y m d → I.year y <> I.month m <> I.day d) + <$> (tryOr 0.0 $ component "Y") + <*> (tryOr 0.0 $ component "M") + <*> (tryOr 0.0 $ component "D") + + durationTimePart ∷ P.Parser String I.Duration + durationTimePart = tryOr mempty $ + PS.string "T" *> + pure (\h m s → I.hours h <> I.minutes m <> I.seconds s) + <*> (tryOr 0.0 $ component "H") + <*> (tryOr 0.0 $ component "M") + <*> (tryOr 0.0 $ component "S") diff --git a/src/Data/Formatter/Number.purs b/src/Data/Formatter/Number.purs index 4b4c701..117885b 100644 --- a/src/Data/Formatter/Number.purs +++ b/src/Data/Formatter/Number.purs @@ -11,6 +11,8 @@ module Data.Formatter.Number , formatNumber , formatOrShowNumber , unformatNumber + -- TODO move to internal or add doc + , unformatNumberParser ) where import Prelude @@ -20,7 +22,7 @@ import Data.Array as Arr import Data.Array (many, some) import Data.Maybe (Maybe(..), fromMaybe, isJust) import Data.Traversable (for) -import Data.Either (Either, either) +import Data.Either (Either(..), either) import Data.Int as Int import Data.String as Str @@ -215,6 +217,11 @@ unformatNumber ∷ String → String → Either String Number unformatNumber pattern str = parseFormatString pattern >>= flip unformat str +unformatNumberParser ∷ String → P.Parser String Number +unformatNumberParser pattern = case P.runParser pattern formatParser of + Left e → P.fail $ P.parseErrorMessage e + Right p → unformatParser p + -- Supposed to be used in chaining, because after calling format number there is no -- good way to extract number back to show. formatOrShowNumber ∷ String → Number → String diff --git a/src/Data/Interval.purs b/src/Data/Interval.purs new file mode 100644 index 0000000..f0bb011 --- /dev/null +++ b/src/Data/Interval.purs @@ -0,0 +1,134 @@ +-- TODO commiting this temporarly as depending on my fork of datetime is +-- not possibel as this module is not updated to ps@0.11 +module Data.Interval + ( Duration + , Interval(..) + , RecurringInterval(..) + , year + , month + , week + , day + , hours + , minutes + , seconds + , milliseconds + ) where + +import Prelude + +import Data.Foldable (class Foldable, foldrDefault, foldMapDefaultL) +import Data.Traversable (class Traversable, sequenceDefault) +import Data.Monoid (class Monoid, mempty) +import Control.Extend (class Extend) + +import Data.Maybe (Maybe) +import Data.List (List(..), (:)) +import Data.Tuple (Tuple(..)) + + +data RecurringInterval a = RecurringInterval (Maybe Int) (Interval a) + +data Interval a + = StartEnd a a + | DurationEnd Duration a + | StartDuration a Duration + | JustDuration Duration + +instance showInterval ∷ (Show a) => Show (Interval a) where + show (StartEnd x y) = "(StartEnd " <> show x <> " " <> show y <> ")" + show (DurationEnd d x) = "(DurationEnd " <> show d <> " " <> show x <> ")" + show (StartDuration x d) = "(StartDuration " <> show x <> " " <> show d <> ")" + show (JustDuration d) = "(JustDuration " <> show d <> ")" + +instance functorInterval ∷ Functor Interval where + map f (StartEnd x y) = StartEnd (f x) (f y ) + map f (DurationEnd d x) = DurationEnd d (f x ) + map f (StartDuration x d) = StartDuration (f x) d + map _ (JustDuration d) = JustDuration d + +instance foldableInterval ∷ Foldable Interval where + foldl f z (StartEnd x y) = (z `f` x) `f` y + foldl f z (DurationEnd d x) = z `f` x + foldl f z (StartDuration x d) = z `f` x + foldl _ z _ = z + foldr x = foldrDefault x + foldMap = foldMapDefaultL + +instance traversableInterval ∷ Traversable Interval where + traverse f (StartEnd x y) = StartEnd <$> f x <*> f y + traverse f (DurationEnd d x) = f x <#> DurationEnd d + traverse f (StartDuration x d) = f x <#> (_ `StartDuration` d) + traverse _ (JustDuration d) = pure (JustDuration d) + sequence = sequenceDefault + +instance extendInterval ∷ Extend Interval where + extend f a@(StartEnd x y) = StartEnd (f a) (f a ) + extend f a@(DurationEnd d x) = DurationEnd d (f a ) + extend f a@(StartDuration x d) = StartDuration (f a) d + extend f (JustDuration d) = JustDuration d + + +data Duration = Duration DurationIn +type DurationIn = List (Tuple DurationComponent Number) + +-- TODO `day 1 == hours 24` +derive instance eqDuration ∷ Eq Duration +instance showDuration ∷ Show Duration where + show (Duration d)= "(Duration " <> show d <> ")" + +instance semigroupDuration ∷ Semigroup Duration where + append (Duration a) (Duration b) = Duration (appendComponents a b) + +instance monoidDuration ∷ Monoid Duration where + mempty = Duration mempty + +appendComponents ∷ DurationIn → DurationIn → DurationIn +appendComponents Nil x = x +appendComponents x Nil = x +appendComponents ass@(a:as) bss@(b:bs) = case a, b of + Tuple aC aV, Tuple bC bV + | aC > bC → a : appendComponents as bss + | aC < bC → b : appendComponents ass bs + | otherwise → Tuple aC (aV + bV) : appendComponents as bs + +data DurationComponent = Seconds | Minutes | Hours | Day | Month | Year + +instance showDurationComponent ∷ Show DurationComponent where + show Year = "Year" + show Month = "Month" + show Day = "Day" + show Hours = "Hours" + show Minutes = "Minutes" + show Seconds = "Seconds" + +derive instance eqDurationComponent ∷ Eq DurationComponent +derive instance ordDurationComponent ∷ Ord DurationComponent + + +week ∷ Number → Duration +week = durationFromComponent Day <<< (_ * 7.0) + +year ∷ Number → Duration +year = durationFromComponent Year + +month ∷ Number → Duration +month = durationFromComponent Month + +day ∷ Number → Duration +day = durationFromComponent Day + +hours ∷ Number → Duration +hours = durationFromComponent Hours + +minutes ∷ Number → Duration +minutes = durationFromComponent Minutes + +seconds ∷ Number → Duration +seconds = durationFromComponent Seconds + +milliseconds ∷ Number → Duration +milliseconds = durationFromComponent Seconds <<< (_ / 1000.0) + +durationFromComponent ∷ DurationComponent → Number → Duration +durationFromComponent c 0.0 = mempty +durationFromComponent c n = Duration $ pure $ Tuple c n diff --git a/test/src/Main.purs b/test/src/Main.purs index e03aea7..418808e 100644 --- a/test/src/Main.purs +++ b/test/src/Main.purs @@ -4,6 +4,12 @@ import Prelude import Control.Monad.Aff.Console as AffC import Data.Date as D import Data.DateTime as DTi + +-- TODO parser should't be exposed so this should be removed +import Text.Parsing.Parser as P +import Data.Interval as I +import Data.Formatter.Interval as FI + import Data.Formatter.DateTime as FDT import Data.Formatter.Number as FN import Data.Time as T @@ -155,7 +161,20 @@ assertFormatting target' format dateTime = do ((show result) <> " equals " <> (show target)) (result == target) +assertParserRes :: forall a e. (Show a, Eq a) => a -> a -> Tests e Unit +assertParserRes result target = + assert + ((show result) <> " does not equal " <> (show target)) + ((show result) <> " equals " <> (show target)) + (result == target) +timeInterval :: forall e. Tests e Unit +timeInterval = do + log "- Data.Formatter.Interval.parseDuration" + assertParserRes (P.runParser "P1W" FI.parseDuration) (Right $ I.day 7.0) + assertParserRes (P.runParser "P1.0W" FI.parseDuration) (Right $ I.day 7.0) + assertParserRes (P.runParser "P1.0D" FI.parseDuration) (Right $ I.day 1.0) + assertParserRes (P.runParser "P1DT1H1M1S" FI.parseDuration) (Right $ I.day 1.0 <> I.hours 1.0 <> I.minutes 1.0 <> I.seconds 1.0) timeTest :: forall e. Tests e Unit timeTest = do log "- Data.Formatter.DateTime.formatDateTime" @@ -217,6 +236,7 @@ main = execTests tests true tests = do log "Testing time functions..." timeTest + timeInterval passed <- get when (passed /= true) (throwError (error "Tests did not pass.")) --numeralTests From 8e275e44bbc49251933553f7fe3d925aa58a5905 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Thu, 6 Apr 2017 16:12:44 +0400 Subject: [PATCH 02/88] fix number parser in duration --- src/Data/Formatter/Interval.purs | 40 ++++++++++++++++++++------------ src/Data/Formatter/Number.purs | 9 +------ test/src/Main.purs | 2 +- 3 files changed, 27 insertions(+), 24 deletions(-) diff --git a/src/Data/Formatter/Interval.purs b/src/Data/Formatter/Interval.purs index 99f319f..8cdbbe7 100644 --- a/src/Data/Formatter/Interval.purs +++ b/src/Data/Formatter/Interval.purs @@ -5,29 +5,40 @@ module Data.Formatter.Interval import Prelude import Data.Interval as I +import Math as Math import Text.Parsing.Parser as P import Text.Parsing.Parser.Combinators as PC import Text.Parsing.Parser.String as PS import Control.Alt ((<|>)) -import Data.Array (length, some) +import Data.Array (some) +import Data.Function (on) import Data.Formatter.Internal (digit, foldDigits) -import Data.Int (toNumber) +import Data.Int (toNumber, floor) import Data.Monoid (mempty) -nums ∷ P.Parser String Int -nums = foldDigits <$> some digit +numOfDigits ∷ Int → Int +numOfDigits 0 = 0 +numOfDigits n = 1 + (floor $ log10 $ toNumber n) --- TODO try to use unformatNumberParser here -number ∷ P.Parser String Number -number = do - whole ← nums - _ ← (PC.try $ PS.string ".") <|> (PC.try $ PS.string ",") <|> pure "" - restdigits ← PC.try (some digit) <|> pure [0] - let rest = foldDigits restdigits - pure $ if rest == 0 then toNumber whole else toNumber whole + ((toNumber rest) / (toNumber $ length restdigits)) +log10 ∷ Number → Number +log10 n = Math.log10e * Math.log n + +integer ∷ P.Parser String Int +integer = some digit <#> foldDigits +pow :: Int -> Int -> Number +pow = Math.pow `on` toNumber +fractional ∷ P.Parser String Number +fractional = integer <#> case _ of + 0 -> 0.0 + n -> (toNumber n) / (pow 10 $ numOfDigits n) + +number ∷ P.Parser String Number +number = (+) + <$> (integer <#> toNumber) + <*> (PC.option 0.0 $ PC.try $ PS.oneOf ['.', ','] *> fractional) component ∷ String → P.Parser String Number component designator = number <* PS.string designator @@ -35,9 +46,8 @@ component designator = number <* PS.string designator tryOr :: ∀ a. a → P.Parser String a → P.Parser String a tryOr a p = PC.option a $ PC.try p -parseDuration :: P.Parser String (I.Duration) --- parseDuration = PS.string "P" *> weekDuration -parseDuration = PS.string "P" *> (weekDuration <|> fullDuration)-- <* PS.eof +parseDuration :: P.Parser String I.Duration +parseDuration = PS.string "P" *> (weekDuration <|> fullDuration) <* PS.eof where weekDuration :: P.Parser String I.Duration weekDuration = PC.try $ I.week <$> component "W" diff --git a/src/Data/Formatter/Number.purs b/src/Data/Formatter/Number.purs index 117885b..4b4c701 100644 --- a/src/Data/Formatter/Number.purs +++ b/src/Data/Formatter/Number.purs @@ -11,8 +11,6 @@ module Data.Formatter.Number , formatNumber , formatOrShowNumber , unformatNumber - -- TODO move to internal or add doc - , unformatNumberParser ) where import Prelude @@ -22,7 +20,7 @@ import Data.Array as Arr import Data.Array (many, some) import Data.Maybe (Maybe(..), fromMaybe, isJust) import Data.Traversable (for) -import Data.Either (Either(..), either) +import Data.Either (Either, either) import Data.Int as Int import Data.String as Str @@ -217,11 +215,6 @@ unformatNumber ∷ String → String → Either String Number unformatNumber pattern str = parseFormatString pattern >>= flip unformat str -unformatNumberParser ∷ String → P.Parser String Number -unformatNumberParser pattern = case P.runParser pattern formatParser of - Left e → P.fail $ P.parseErrorMessage e - Right p → unformatParser p - -- Supposed to be used in chaining, because after calling format number there is no -- good way to extract number back to show. formatOrShowNumber ∷ String → Number → String diff --git a/test/src/Main.purs b/test/src/Main.purs index 418808e..18955ea 100644 --- a/test/src/Main.purs +++ b/test/src/Main.purs @@ -173,7 +173,7 @@ timeInterval = do log "- Data.Formatter.Interval.parseDuration" assertParserRes (P.runParser "P1W" FI.parseDuration) (Right $ I.day 7.0) assertParserRes (P.runParser "P1.0W" FI.parseDuration) (Right $ I.day 7.0) - assertParserRes (P.runParser "P1.0D" FI.parseDuration) (Right $ I.day 1.0) + assertParserRes (P.runParser "P1.9748600D" FI.parseDuration) (Right $ I.day 1.97486) assertParserRes (P.runParser "P1DT1H1M1S" FI.parseDuration) (Right $ I.day 1.0 <> I.hours 1.0 <> I.minutes 1.0 <> I.seconds 1.0) timeTest :: forall e. Tests e Unit timeTest = do From f57476863ad33cd85d2d22cd344d3c3daecfc64c Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Thu, 6 Apr 2017 20:30:49 +0400 Subject: [PATCH 03/88] update Data.Interval --- src/Data/Interval.purs | 121 +++++++++++++++++++++++++++-------------- 1 file changed, 79 insertions(+), 42 deletions(-) diff --git a/src/Data/Interval.purs b/src/Data/Interval.purs index f0bb011..27614a3 100644 --- a/src/Data/Interval.purs +++ b/src/Data/Interval.purs @@ -1,9 +1,13 @@ -- TODO commiting this temporarly as depending on my fork of datetime is -- not possibel as this module is not updated to ps@0.11 module Data.Interval - ( Duration - , Interval(..) + ( Interval(..) , RecurringInterval(..) + , IsoDuration + , unIsoDuration + , mkIsoDuration + , isValidIsoDuration + , Duration , year , month , week @@ -15,38 +19,45 @@ module Data.Interval ) where import Prelude - -import Data.Foldable (class Foldable, foldrDefault, foldMapDefaultL) -import Data.Traversable (class Traversable, sequenceDefault) +import Control.Extend (class Extend, (=>>)) +import Data.Foldable (class Foldable, fold, foldMap, foldrDefault, foldMapDefaultL) +import Data.Bifoldable (class Bifoldable, bifoldrDefault, bifoldMapDefaultL) +import Data.Bifunctor (class Bifunctor, bimap) +import Data.List ((:), reverse) +import Data.Maybe (Maybe(..)) +import Data.Map as Map import Data.Monoid (class Monoid, mempty) -import Control.Extend (class Extend) - -import Data.Maybe (Maybe) -import Data.List (List(..), (:)) -import Data.Tuple (Tuple(..)) +import Data.Monoid.Conj (Conj(..)) +import Data.Monoid.Additive (Additive(..)) +import Data.Traversable (class Traversable, sequenceDefault) +import Data.Tuple (Tuple(..), snd) +import Math as Math -data RecurringInterval a = RecurringInterval (Maybe Int) (Interval a) +data RecurringInterval d a = RecurringInterval (Maybe Int) (Interval d a) -data Interval a +data Interval d a = StartEnd a a - | DurationEnd Duration a - | StartDuration a Duration - | JustDuration Duration + | DurationEnd d a + | StartDuration a d + | JustDuration d -instance showInterval ∷ (Show a) => Show (Interval a) where +instance showInterval ∷ (Show d, Show a) => Show (Interval d a) where show (StartEnd x y) = "(StartEnd " <> show x <> " " <> show y <> ")" show (DurationEnd d x) = "(DurationEnd " <> show d <> " " <> show x <> ")" show (StartDuration x d) = "(StartDuration " <> show x <> " " <> show d <> ")" show (JustDuration d) = "(JustDuration " <> show d <> ")" -instance functorInterval ∷ Functor Interval where - map f (StartEnd x y) = StartEnd (f x) (f y ) - map f (DurationEnd d x) = DurationEnd d (f x ) - map f (StartDuration x d) = StartDuration (f x) d - map _ (JustDuration d) = JustDuration d +instance functorInterval ∷ Functor (Interval d) where + map = bimap id + +instance bifunctorInterval ∷ Bifunctor Interval where + bimap _ f (StartEnd x y) = StartEnd (f x) (f y ) + bimap g f (DurationEnd d x) = DurationEnd (g d) (f x ) + bimap g f (StartDuration x d) = StartDuration (f x) (g d) + bimap g _ (JustDuration d) = JustDuration (g d) -instance foldableInterval ∷ Foldable Interval where +instance foldableInterval ∷ Foldable (Interval d) where foldl f z (StartEnd x y) = (z `f` x) `f` y foldl f z (DurationEnd d x) = z `f` x foldl f z (StartDuration x d) = z `f` x @@ -54,44 +65,73 @@ instance foldableInterval ∷ Foldable Interval where foldr x = foldrDefault x foldMap = foldMapDefaultL -instance traversableInterval ∷ Traversable Interval where +instance bifoldableInterval ∷ Bifoldable Interval where + bifoldl _ f z (StartEnd x y) = (z `f` x) `f` y + bifoldl g f z (DurationEnd d x) = (z `g` d) `f` x + bifoldl g f z (StartDuration x d) = (z `g` d) `f` x + bifoldl g _ z (JustDuration d) = z `g` d + bifoldr x = bifoldrDefault x + bifoldMap = bifoldMapDefaultL + +instance traversableInterval ∷ Traversable (Interval d) where traverse f (StartEnd x y) = StartEnd <$> f x <*> f y traverse f (DurationEnd d x) = f x <#> DurationEnd d traverse f (StartDuration x d) = f x <#> (_ `StartDuration` d) traverse _ (JustDuration d) = pure (JustDuration d) sequence = sequenceDefault -instance extendInterval ∷ Extend Interval where +instance extendInterval ∷ Extend (Interval d) where extend f a@(StartEnd x y) = StartEnd (f a) (f a ) extend f a@(DurationEnd d x) = DurationEnd d (f a ) extend f a@(StartDuration x d) = StartDuration (f a) d extend f (JustDuration d) = JustDuration d -data Duration = Duration DurationIn -type DurationIn = List (Tuple DurationComponent Number) +mkIsoDuration ∷ Duration → Maybe IsoDuration +mkIsoDuration d | isValidIsoDuration d = Just $ IsoDuration d +mkIsoDuration _ = Nothing + +isFractional ∷ Number → Boolean +isFractional a = Math.floor a /= a + +-- allow only last number to be fractional +isValidIsoDuration ∷ Duration → Boolean +isValidIsoDuration (Duration m) = Map.toAscUnfoldable m + # reverse + =>> (validateFractionalUse >>> Conj) + # fold + # unConj + where + unConj (Conj a) = a + validateFractionalUse = case _ of + (Tuple _ n):as | isFractional n → foldMap (snd >>> Additive) as == mempty + _ → true + +unIsoDuration ∷ IsoDuration → Duration +unIsoDuration (IsoDuration a) = a +data IsoDuration = IsoDuration Duration +derive instance eqIsoDuration ∷ Eq IsoDuration +instance showIsoDuration ∷ Show IsoDuration where + show (IsoDuration d)= "(IsoDuration " <> show d <> ")" + + +data Duration = Duration (Map.Map DurationComponent Number) -- TODO `day 1 == hours 24` derive instance eqDuration ∷ Eq Duration + instance showDuration ∷ Show Duration where show (Duration d)= "(Duration " <> show d <> ")" instance semigroupDuration ∷ Semigroup Duration where - append (Duration a) (Duration b) = Duration (appendComponents a b) + append (Duration a) (Duration b) = Duration $ Map.unionWith (+) a b instance monoidDuration ∷ Monoid Duration where mempty = Duration mempty -appendComponents ∷ DurationIn → DurationIn → DurationIn -appendComponents Nil x = x -appendComponents x Nil = x -appendComponents ass@(a:as) bss@(b:bs) = case a, b of - Tuple aC aV, Tuple bC bV - | aC > bC → a : appendComponents as bss - | aC < bC → b : appendComponents ass bs - | otherwise → Tuple aC (aV + bV) : appendComponents as bs - data DurationComponent = Seconds | Minutes | Hours | Day | Month | Year +derive instance eqDurationComponent ∷ Eq DurationComponent +derive instance ordDurationComponent ∷ Ord DurationComponent instance showDurationComponent ∷ Show DurationComponent where show Year = "Year" @@ -101,9 +141,6 @@ instance showDurationComponent ∷ Show DurationComponent where show Minutes = "Minutes" show Seconds = "Seconds" -derive instance eqDurationComponent ∷ Eq DurationComponent -derive instance ordDurationComponent ∷ Ord DurationComponent - week ∷ Number → Duration week = durationFromComponent Day <<< (_ * 7.0) @@ -129,6 +166,6 @@ seconds = durationFromComponent Seconds milliseconds ∷ Number → Duration milliseconds = durationFromComponent Seconds <<< (_ / 1000.0) -durationFromComponent ∷ DurationComponent → Number → Duration -durationFromComponent c 0.0 = mempty -durationFromComponent c n = Duration $ pure $ Tuple c n +durationFromComponent ∷ DurationComponent → Number → Duration +-- durationFromComponent _ 0.0 = mempty +durationFromComponent k v= Duration $ Map.singleton k v From 620cc40f8857e5f986eb7e3984a25d6989b5eeda Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Thu, 6 Apr 2017 20:49:04 +0400 Subject: [PATCH 04/88] refactor Duration parser at this point we need to upgrade to ps@0.11 --- src/Data/Formatter/Internal.purs | 14 ++++++++ src/Data/Formatter/Interval.purs | 60 +++++++++++++++++++------------- 2 files changed, 50 insertions(+), 24 deletions(-) diff --git a/src/Data/Formatter/Internal.purs b/src/Data/Formatter/Internal.purs index 9106c43..7877708 100644 --- a/src/Data/Formatter/Internal.purs +++ b/src/Data/Formatter/Internal.purs @@ -27,6 +27,20 @@ digit = do '9' → pure 9 _ → P.fail "Incorrect digit, impossible situation" +-- https://github.com/purescript-contrib/purescript-parsing/issues/50 +-- digit ∷ ∀ m. Monad m ⇒ P.ParserT String m Int +-- digit = PS.oneOfAs $ +-- [ Tuple '0' 0 +-- , Tuple '1' 1 +-- , Tuple '2' 2 +-- , Tuple '3' 3 +-- , Tuple '4' 4 +-- , Tuple '5' 5 +-- , Tuple '6' 6 +-- , Tuple '7' 7 +-- , Tuple '8' 8 +-- , Tuple '9' 9] + repeat ∷ ∀ a. Monoid a ⇒ a → Int → a repeat = repeat' mempty where diff --git a/src/Data/Formatter/Interval.purs b/src/Data/Formatter/Interval.purs index 8cdbbe7..e6720bf 100644 --- a/src/Data/Formatter/Interval.purs +++ b/src/Data/Formatter/Interval.purs @@ -1,5 +1,4 @@ module Data.Formatter.Interval - -- TODO parser should't be exposed ( parseDuration ) where @@ -11,10 +10,14 @@ import Text.Parsing.Parser.Combinators as PC import Text.Parsing.Parser.String as PS import Control.Alt ((<|>)) import Data.Array (some) -import Data.Function (on) +import Data.Foldable (class Foldable, fold) import Data.Formatter.Internal (digit, foldDigits) +import Data.Function (on) import Data.Int (toNumber, floor) -import Data.Monoid (mempty) +import Data.Maybe (Maybe, maybe) +import Data.Monoid (class Monoid, mempty) +import Data.Traversable (sequence) +import Data.Tuple (Tuple(..)) numOfDigits ∷ Int → Int @@ -40,31 +43,40 @@ number = (+) <$> (integer <#> toNumber) <*> (PC.option 0.0 $ PC.try $ PS.oneOf ['.', ','] *> fractional) +durationParser :: Array (Tuple (Number -> I.Duration) String) -> P.Parser String I.Duration +durationParser arr = arr + <#> applyDurations + # sequence + <#> foldFoldableMaybe + +applyDurations :: Tuple (Number -> I.Duration) String -> P.Parser String (Maybe I.Duration) +applyDurations (Tuple f c) = PC.optionMaybe $ PC.try (f <$> component c) + +foldFoldableMaybe :: ∀ f a. (Foldable f, Monoid a) => f (Maybe a) -> a +foldFoldableMaybe = fold >>> unMaybe + +unMaybe :: ∀ a. (Monoid a) => Maybe a -> a +unMaybe = maybe mempty id + component ∷ String → P.Parser String Number component designator = number <* PS.string designator -tryOr :: ∀ a. a → P.Parser String a → P.Parser String a -tryOr a p = PC.option a $ PC.try p +tryM :: ∀ a. (Monoid a) => P.Parser String a → P.Parser String a +tryM p = PC.option mempty $ PC.try p + +parseIsoDuration :: P.Parser String I.IsoDuration +parseIsoDuration = do + dur ← parseDuration + case I.mkIsoDuration dur of + Nothing -> PC.fail "extracted Duration is not valid ISO duration" + Just a -> pure a parseDuration :: P.Parser String I.Duration -parseDuration = PS.string "P" *> (weekDuration <|> fullDuration) <* PS.eof +parseDuration = + PS.string "P" *> (weekDuration <|> fullDuration) <* PS.eof where - weekDuration :: P.Parser String I.Duration - weekDuration = PC.try $ I.week <$> component "W" - - fullDuration ∷ P.Parser String I.Duration + weekDuration = durationParser [ Tuple I.week "W" ] fullDuration = append <$> durationDatePart <*> durationTimePart - - durationDatePart ∷ P.Parser String I.Duration - durationDatePart = (\y m d → I.year y <> I.month m <> I.day d) - <$> (tryOr 0.0 $ component "Y") - <*> (tryOr 0.0 $ component "M") - <*> (tryOr 0.0 $ component "D") - - durationTimePart ∷ P.Parser String I.Duration - durationTimePart = tryOr mempty $ - PS.string "T" *> - pure (\h m s → I.hours h <> I.minutes m <> I.seconds s) - <*> (tryOr 0.0 $ component "H") - <*> (tryOr 0.0 $ component "M") - <*> (tryOr 0.0 $ component "S") + durationDatePart = durationParser [ Tuple I.year "Y" , Tuple I.month "M" , Tuple I.day "D" ] + durationTimePart = tryM $ PS.string "T" *> + (durationParser [ Tuple I.hours "H" , Tuple I.minutes "M" , Tuple I.seconds "S" ]) From b017f505d812bf8878ff52f420bd1fb0a5da8ab7 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Fri, 7 Apr 2017 18:47:23 +0400 Subject: [PATCH 05/88] add is valid iso duration test --- test/src/Main.purs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/src/Main.purs b/test/src/Main.purs index 18955ea..62b1936 100644 --- a/test/src/Main.purs +++ b/test/src/Main.purs @@ -175,6 +175,12 @@ timeInterval = do assertParserRes (P.runParser "P1.0W" FI.parseDuration) (Right $ I.day 7.0) assertParserRes (P.runParser "P1.9748600D" FI.parseDuration) (Right $ I.day 1.97486) assertParserRes (P.runParser "P1DT1H1M1S" FI.parseDuration) (Right $ I.day 1.0 <> I.hours 1.0 <> I.minutes 1.0 <> I.seconds 1.0) + assertParserRes (P.runParser "P1DT1H1M1S" FI.parseDuration <#> I.isValidIsoDuration) (Right True) + assertParserRes (P.runParser "P1DT1H1M1.5S" FI.parseDuration <#> I.isValidIsoDuration) (Right True) + assertParserRes (P.runParser "P1DT1H1.5M0S" FI.parseDuration <#> I.isValidIsoDuration) (Right True) + assertParserRes (P.runParser "P1DT1.5H0M0S" FI.parseDuration <#> I.isValidIsoDuration) (Right True) + assertParserRes (P.runParser "P1DT1.5H0M1S" FI.parseDuration <#> I.isValidIsoDuration) (Right False) + timeTest :: forall e. Tests e Unit timeTest = do log "- Data.Formatter.DateTime.formatDateTime" From a03b56de0a99d478b4ebc8475de92e1ecefa2a93 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Fri, 7 Apr 2017 20:31:46 +0400 Subject: [PATCH 06/88] add interval parsers --- bower.json | 2 +- src/Data/Formatter/Interval.purs | 27 +++-- src/Data/Interval.purs | 171 ------------------------------- test/src/Main.purs | 12 +-- 4 files changed, 28 insertions(+), 184 deletions(-) delete mode 100644 src/Data/Interval.purs diff --git a/bower.json b/bower.json index 8206534..766a9ee 100644 --- a/bower.json +++ b/bower.json @@ -19,7 +19,7 @@ "purescript-prelude": "^3.0.0", "purescript-parsing": "^4.0.0", "purescript-fixed-points": "^4.0.0", - "purescript-datetime": "^3.0.0" + "purescript-datetime": "git://github.com/safareli/purescript-datetime.git#interval" }, "devDependencies": { "purescript-aff": "^3.0.0", diff --git a/src/Data/Formatter/Interval.purs b/src/Data/Formatter/Interval.purs index e6720bf..ac1fa73 100644 --- a/src/Data/Formatter/Interval.purs +++ b/src/Data/Formatter/Interval.purs @@ -9,12 +9,12 @@ import Text.Parsing.Parser as P import Text.Parsing.Parser.Combinators as PC import Text.Parsing.Parser.String as PS import Control.Alt ((<|>)) -import Data.Array (some) +import Data.Array (some, many, length) import Data.Foldable (class Foldable, fold) import Data.Formatter.Internal (digit, foldDigits) import Data.Function (on) import Data.Int (toNumber, floor) -import Data.Maybe (Maybe, maybe) +import Data.Maybe (Maybe(..), maybe) import Data.Monoid (class Monoid, mempty) import Data.Traversable (sequence) import Data.Tuple (Tuple(..)) @@ -30,6 +30,9 @@ log10 n = Math.log10e * Math.log n integer ∷ P.Parser String Int integer = some digit <#> foldDigits +integerMaybe ∷ P.Parser String (Maybe Int) +integerMaybe = many digit <#> (\l -> if length l == 0 then Nothing else Just $ foldDigits l) + pow :: Int -> Int -> Number pow = Math.pow `on` toNumber @@ -52,23 +55,35 @@ durationParser arr = arr applyDurations :: Tuple (Number -> I.Duration) String -> P.Parser String (Maybe I.Duration) applyDurations (Tuple f c) = PC.optionMaybe $ PC.try (f <$> component c) -foldFoldableMaybe :: ∀ f a. (Foldable f, Monoid a) => f (Maybe a) -> a +foldFoldableMaybe :: ∀ f a. Foldable f => Monoid a => f (Maybe a) -> a foldFoldableMaybe = fold >>> unMaybe -unMaybe :: ∀ a. (Monoid a) => Maybe a -> a +unMaybe :: ∀ a. Monoid a => Maybe a -> a unMaybe = maybe mempty id component ∷ String → P.Parser String Number component designator = number <* PS.string designator -tryM :: ∀ a. (Monoid a) => P.Parser String a → P.Parser String a +tryM :: ∀ a. Monoid a => P.Parser String a → P.Parser String a tryM p = PC.option mempty $ PC.try p +parseRecurringInterval :: ∀ a b. P.Parser String a -> P.Parser String b -> P.Parser String (I.RecurringInterval a b) +parseRecurringInterval duration date = + I.RecurringInterval <$> (PS.string "R" *> integerMaybe) <*> (PS.string "/" *> parseInterval duration date) + +parseInterval :: ∀ a b. P.Parser String a -> P.Parser String b -> P.Parser String (I.Interval a b) +parseInterval duration date = startEnd <|> durationEnd <|> startDuration <|> justDuration + where + startEnd = I.StartEnd <$> date <* PS.string "/" <*> date + durationEnd = I.DurationEnd <$> duration <* PS.string "/" <*> date + startDuration = I.StartDuration <$> date <* PS.string "/" <*> duration + justDuration = I.JustDuration <$> duration + parseIsoDuration :: P.Parser String I.IsoDuration parseIsoDuration = do dur ← parseDuration case I.mkIsoDuration dur of - Nothing -> PC.fail "extracted Duration is not valid ISO duration" + Nothing -> P.fail "extracted Duration is not valid ISO duration" Just a -> pure a parseDuration :: P.Parser String I.Duration diff --git a/src/Data/Interval.purs b/src/Data/Interval.purs deleted file mode 100644 index 27614a3..0000000 --- a/src/Data/Interval.purs +++ /dev/null @@ -1,171 +0,0 @@ --- TODO commiting this temporarly as depending on my fork of datetime is --- not possibel as this module is not updated to ps@0.11 -module Data.Interval - ( Interval(..) - , RecurringInterval(..) - , IsoDuration - , unIsoDuration - , mkIsoDuration - , isValidIsoDuration - , Duration - , year - , month - , week - , day - , hours - , minutes - , seconds - , milliseconds - ) where - -import Prelude -import Control.Extend (class Extend, (=>>)) -import Data.Foldable (class Foldable, fold, foldMap, foldrDefault, foldMapDefaultL) -import Data.Bifoldable (class Bifoldable, bifoldrDefault, bifoldMapDefaultL) -import Data.Bifunctor (class Bifunctor, bimap) -import Data.List ((:), reverse) -import Data.Maybe (Maybe(..)) -import Data.Map as Map -import Data.Monoid (class Monoid, mempty) -import Data.Monoid.Conj (Conj(..)) -import Data.Monoid.Additive (Additive(..)) -import Data.Traversable (class Traversable, sequenceDefault) -import Data.Tuple (Tuple(..), snd) -import Math as Math - - -data RecurringInterval d a = RecurringInterval (Maybe Int) (Interval d a) - -data Interval d a - = StartEnd a a - | DurationEnd d a - | StartDuration a d - | JustDuration d - -instance showInterval ∷ (Show d, Show a) => Show (Interval d a) where - show (StartEnd x y) = "(StartEnd " <> show x <> " " <> show y <> ")" - show (DurationEnd d x) = "(DurationEnd " <> show d <> " " <> show x <> ")" - show (StartDuration x d) = "(StartDuration " <> show x <> " " <> show d <> ")" - show (JustDuration d) = "(JustDuration " <> show d <> ")" - -instance functorInterval ∷ Functor (Interval d) where - map = bimap id - -instance bifunctorInterval ∷ Bifunctor Interval where - bimap _ f (StartEnd x y) = StartEnd (f x) (f y ) - bimap g f (DurationEnd d x) = DurationEnd (g d) (f x ) - bimap g f (StartDuration x d) = StartDuration (f x) (g d) - bimap g _ (JustDuration d) = JustDuration (g d) - -instance foldableInterval ∷ Foldable (Interval d) where - foldl f z (StartEnd x y) = (z `f` x) `f` y - foldl f z (DurationEnd d x) = z `f` x - foldl f z (StartDuration x d) = z `f` x - foldl _ z _ = z - foldr x = foldrDefault x - foldMap = foldMapDefaultL - -instance bifoldableInterval ∷ Bifoldable Interval where - bifoldl _ f z (StartEnd x y) = (z `f` x) `f` y - bifoldl g f z (DurationEnd d x) = (z `g` d) `f` x - bifoldl g f z (StartDuration x d) = (z `g` d) `f` x - bifoldl g _ z (JustDuration d) = z `g` d - bifoldr x = bifoldrDefault x - bifoldMap = bifoldMapDefaultL - -instance traversableInterval ∷ Traversable (Interval d) where - traverse f (StartEnd x y) = StartEnd <$> f x <*> f y - traverse f (DurationEnd d x) = f x <#> DurationEnd d - traverse f (StartDuration x d) = f x <#> (_ `StartDuration` d) - traverse _ (JustDuration d) = pure (JustDuration d) - sequence = sequenceDefault - -instance extendInterval ∷ Extend (Interval d) where - extend f a@(StartEnd x y) = StartEnd (f a) (f a ) - extend f a@(DurationEnd d x) = DurationEnd d (f a ) - extend f a@(StartDuration x d) = StartDuration (f a) d - extend f (JustDuration d) = JustDuration d - - -mkIsoDuration ∷ Duration → Maybe IsoDuration -mkIsoDuration d | isValidIsoDuration d = Just $ IsoDuration d -mkIsoDuration _ = Nothing - -isFractional ∷ Number → Boolean -isFractional a = Math.floor a /= a - --- allow only last number to be fractional -isValidIsoDuration ∷ Duration → Boolean -isValidIsoDuration (Duration m) = Map.toAscUnfoldable m - # reverse - =>> (validateFractionalUse >>> Conj) - # fold - # unConj - where - unConj (Conj a) = a - validateFractionalUse = case _ of - (Tuple _ n):as | isFractional n → foldMap (snd >>> Additive) as == mempty - _ → true - -unIsoDuration ∷ IsoDuration → Duration -unIsoDuration (IsoDuration a) = a - -data IsoDuration = IsoDuration Duration -derive instance eqIsoDuration ∷ Eq IsoDuration -instance showIsoDuration ∷ Show IsoDuration where - show (IsoDuration d)= "(IsoDuration " <> show d <> ")" - - -data Duration = Duration (Map.Map DurationComponent Number) --- TODO `day 1 == hours 24` -derive instance eqDuration ∷ Eq Duration - -instance showDuration ∷ Show Duration where - show (Duration d)= "(Duration " <> show d <> ")" - -instance semigroupDuration ∷ Semigroup Duration where - append (Duration a) (Duration b) = Duration $ Map.unionWith (+) a b - -instance monoidDuration ∷ Monoid Duration where - mempty = Duration mempty - -data DurationComponent = Seconds | Minutes | Hours | Day | Month | Year -derive instance eqDurationComponent ∷ Eq DurationComponent -derive instance ordDurationComponent ∷ Ord DurationComponent - -instance showDurationComponent ∷ Show DurationComponent where - show Year = "Year" - show Month = "Month" - show Day = "Day" - show Hours = "Hours" - show Minutes = "Minutes" - show Seconds = "Seconds" - - -week ∷ Number → Duration -week = durationFromComponent Day <<< (_ * 7.0) - -year ∷ Number → Duration -year = durationFromComponent Year - -month ∷ Number → Duration -month = durationFromComponent Month - -day ∷ Number → Duration -day = durationFromComponent Day - -hours ∷ Number → Duration -hours = durationFromComponent Hours - -minutes ∷ Number → Duration -minutes = durationFromComponent Minutes - -seconds ∷ Number → Duration -seconds = durationFromComponent Seconds - -milliseconds ∷ Number → Duration -milliseconds = durationFromComponent Seconds <<< (_ / 1000.0) - -durationFromComponent ∷ DurationComponent → Number → Duration --- durationFromComponent _ 0.0 = mempty -durationFromComponent k v= Duration $ Map.singleton k v diff --git a/test/src/Main.purs b/test/src/Main.purs index 62b1936..c69a536 100644 --- a/test/src/Main.purs +++ b/test/src/Main.purs @@ -161,7 +161,7 @@ assertFormatting target' format dateTime = do ((show result) <> " equals " <> (show target)) (result == target) -assertParserRes :: forall a e. (Show a, Eq a) => a -> a -> Tests e Unit +assertParserRes :: forall a e. Show a => Eq a => a -> a -> Tests e Unit assertParserRes result target = assert ((show result) <> " does not equal " <> (show target)) @@ -175,11 +175,11 @@ timeInterval = do assertParserRes (P.runParser "P1.0W" FI.parseDuration) (Right $ I.day 7.0) assertParserRes (P.runParser "P1.9748600D" FI.parseDuration) (Right $ I.day 1.97486) assertParserRes (P.runParser "P1DT1H1M1S" FI.parseDuration) (Right $ I.day 1.0 <> I.hours 1.0 <> I.minutes 1.0 <> I.seconds 1.0) - assertParserRes (P.runParser "P1DT1H1M1S" FI.parseDuration <#> I.isValidIsoDuration) (Right True) - assertParserRes (P.runParser "P1DT1H1M1.5S" FI.parseDuration <#> I.isValidIsoDuration) (Right True) - assertParserRes (P.runParser "P1DT1H1.5M0S" FI.parseDuration <#> I.isValidIsoDuration) (Right True) - assertParserRes (P.runParser "P1DT1.5H0M0S" FI.parseDuration <#> I.isValidIsoDuration) (Right True) - assertParserRes (P.runParser "P1DT1.5H0M1S" FI.parseDuration <#> I.isValidIsoDuration) (Right False) + assertParserRes (P.runParser "P1DT1H1M1S" FI.parseDuration <#> I.isValidIsoDuration) (Right true) + assertParserRes (P.runParser "P1DT1H1M1.5S" FI.parseDuration <#> I.isValidIsoDuration) (Right true) + assertParserRes (P.runParser "P1DT1H1.5M0S" FI.parseDuration <#> I.isValidIsoDuration) (Right true) + assertParserRes (P.runParser "P1DT1.5H0M0S" FI.parseDuration <#> I.isValidIsoDuration) (Right true) + assertParserRes (P.runParser "P1DT1.5H0M1S" FI.parseDuration <#> I.isValidIsoDuration) (Right false) timeTest :: forall e. Tests e Unit timeTest = do From de07f5ace2d9ea67a775a226c753db67527067ca Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Thu, 13 Apr 2017 18:42:17 +0100 Subject: [PATCH 07/88] extract parsers in seperate module --- src/Data/Formatter/Internal.purs | 1 + src/Data/Formatter/Interval.purs | 152 ++++++++++-------------- src/Data/Formatter/Parser/Interval.purs | 68 +++++++++++ src/Data/Formatter/Parser/Number.purs | 44 +++++++ test/src/Main.purs | 22 ++-- 5 files changed, 184 insertions(+), 103 deletions(-) create mode 100644 src/Data/Formatter/Parser/Interval.purs create mode 100644 src/Data/Formatter/Parser/Number.purs diff --git a/src/Data/Formatter/Internal.purs b/src/Data/Formatter/Internal.purs index 7877708..e80af6a 100644 --- a/src/Data/Formatter/Internal.purs +++ b/src/Data/Formatter/Internal.purs @@ -11,6 +11,7 @@ import Text.Parsing.Parser.String as PS foldDigits ∷ ∀ f. Foldable f ⇒ f Int → Int foldDigits = foldl (\acc d → acc * 10 + d) zero +-- TODO move to Parser.Number digit ∷ ∀ m. Monad m ⇒ P.ParserT String m Int digit = do char ← PS.oneOf ['0','1','2','3','4','5','6','7','8','9'] diff --git a/src/Data/Formatter/Interval.purs b/src/Data/Formatter/Interval.purs index ac1fa73..e8cd60a 100644 --- a/src/Data/Formatter/Interval.purs +++ b/src/Data/Formatter/Interval.purs @@ -1,97 +1,65 @@ module Data.Formatter.Interval - ( parseDuration + ( unformatRecurringInterval + , unformatInterval + , unformatDuration ) where import Prelude import Data.Interval as I -import Math as Math -import Text.Parsing.Parser as P -import Text.Parsing.Parser.Combinators as PC -import Text.Parsing.Parser.String as PS -import Control.Alt ((<|>)) -import Data.Array (some, many, length) -import Data.Foldable (class Foldable, fold) -import Data.Formatter.Internal (digit, foldDigits) -import Data.Function (on) -import Data.Int (toNumber, floor) -import Data.Maybe (Maybe(..), maybe) -import Data.Monoid (class Monoid, mempty) -import Data.Traversable (sequence) -import Data.Tuple (Tuple(..)) - - -numOfDigits ∷ Int → Int -numOfDigits 0 = 0 -numOfDigits n = 1 + (floor $ log10 $ toNumber n) - -log10 ∷ Number → Number -log10 n = Math.log10e * Math.log n - -integer ∷ P.Parser String Int -integer = some digit <#> foldDigits - -integerMaybe ∷ P.Parser String (Maybe Int) -integerMaybe = many digit <#> (\l -> if length l == 0 then Nothing else Just $ foldDigits l) - -pow :: Int -> Int -> Number -pow = Math.pow `on` toNumber - -fractional ∷ P.Parser String Number -fractional = integer <#> case _ of - 0 -> 0.0 - n -> (toNumber n) / (pow 10 $ numOfDigits n) - -number ∷ P.Parser String Number -number = (+) - <$> (integer <#> toNumber) - <*> (PC.option 0.0 $ PC.try $ PS.oneOf ['.', ','] *> fractional) - -durationParser :: Array (Tuple (Number -> I.Duration) String) -> P.Parser String I.Duration -durationParser arr = arr - <#> applyDurations - # sequence - <#> foldFoldableMaybe - -applyDurations :: Tuple (Number -> I.Duration) String -> P.Parser String (Maybe I.Duration) -applyDurations (Tuple f c) = PC.optionMaybe $ PC.try (f <$> component c) - -foldFoldableMaybe :: ∀ f a. Foldable f => Monoid a => f (Maybe a) -> a -foldFoldableMaybe = fold >>> unMaybe - -unMaybe :: ∀ a. Monoid a => Maybe a -> a -unMaybe = maybe mempty id - -component ∷ String → P.Parser String Number -component designator = number <* PS.string designator - -tryM :: ∀ a. Monoid a => P.Parser String a → P.Parser String a -tryM p = PC.option mempty $ PC.try p - -parseRecurringInterval :: ∀ a b. P.Parser String a -> P.Parser String b -> P.Parser String (I.RecurringInterval a b) -parseRecurringInterval duration date = - I.RecurringInterval <$> (PS.string "R" *> integerMaybe) <*> (PS.string "/" *> parseInterval duration date) - -parseInterval :: ∀ a b. P.Parser String a -> P.Parser String b -> P.Parser String (I.Interval a b) -parseInterval duration date = startEnd <|> durationEnd <|> startDuration <|> justDuration - where - startEnd = I.StartEnd <$> date <* PS.string "/" <*> date - durationEnd = I.DurationEnd <$> duration <* PS.string "/" <*> date - startDuration = I.StartDuration <$> date <* PS.string "/" <*> duration - justDuration = I.JustDuration <$> duration - -parseIsoDuration :: P.Parser String I.IsoDuration -parseIsoDuration = do - dur ← parseDuration - case I.mkIsoDuration dur of - Nothing -> P.fail "extracted Duration is not valid ISO duration" - Just a -> pure a - -parseDuration :: P.Parser String I.Duration -parseDuration = - PS.string "P" *> (weekDuration <|> fullDuration) <* PS.eof - where - weekDuration = durationParser [ Tuple I.week "W" ] - fullDuration = append <$> durationDatePart <*> durationTimePart - durationDatePart = durationParser [ Tuple I.year "Y" , Tuple I.month "M" , Tuple I.day "D" ] - durationTimePart = tryM $ PS.string "T" *> - (durationParser [ Tuple I.hours "H" , Tuple I.minutes "M" , Tuple I.seconds "S" ]) +import Data.Formatter.Parser.Interval (parseRecurringInterval, parseInterval, parseIsoDuration, parseDuration) + +unformatRecurringInterval :: + ∀ a b + . HasDuration a + ⇒ HasDate b + ⇒ String + → Either String (RecurringInterval a b) +unformatRecurringInterval = run $ parseRecurringInterval getDuration getDate + +unformatInterval :: + ∀ a b + . HasDuration a + ⇒ HasDate b + ⇒ String + → Either String (Interval a b) +unformatInterval = run $ parseInterval getDuration getDate + +unformatDuration :: + ∀ a + . HasDuration a + ⇒ String + → Either String a +unformatDuration = run getDuration + + +run :: Parser String a → String → Either String a +run p s = lmap P.parseErrorMessage $ P.runParser s p + + +class HasDuration a where + getDuration :: Parser String a + +instance hasDurationDuration :: HasDuration Duration where + getDuration = parseDuration + +instance hasDurationIsoDuration :: HasDuration IsoDuration where + getDuration = parseIsoDuration + + +class HasDate a where + getDate :: Parser String a + +-- instance hasDateDate :: HasDate DateTime where +-- getDate = parseFormatString "YYYY-MM-DD`T`HH:MM:SS`Z`" >>= (_ `unformat` str) +-- TODO +-- 2017-04-13T15:36:07+00:00 +-- 2017-04-13T15:36:07Z + + +-- TODO implement date parsers + +-- TODO instance for local versions +-- * LocalDate +-- * LocalDateTime + +-- TODO Q? should we define for LocalTime and Time diff --git a/src/Data/Formatter/Parser/Interval.purs b/src/Data/Formatter/Parser/Interval.purs new file mode 100644 index 0000000..7f14980 --- /dev/null +++ b/src/Data/Formatter/Parser/Interval.purs @@ -0,0 +1,68 @@ +module Data.Formatter.Parser.Interval + ( parseRecurringInterval + , parseInterval + , parseIsoDuration + , parseDuration + ) where + +import Prelude +import Data.Interval as I +import Text.Parsing.Parser as P +import Text.Parsing.Parser.Combinators as PC +import Text.Parsing.Parser.String as PS +import Control.Alt ((<|>)) +import Data.Foldable (class Foldable, fold) +import Data.Maybe (Maybe(..), maybe) +import Data.Monoid (class Monoid, mempty) +import Data.Traversable (sequence) +import Data.Tuple (Tuple(..)) + +import Data.Formatter.Parser.Number (parseNumber, parseMaybeInteger) + +parseRecurringInterval :: ∀ a b. P.Parser String a -> P.Parser String b -> P.Parser String (I.RecurringInterval a b) +parseRecurringInterval duration date = + I.RecurringInterval <$> (PS.string "R" *> parseMaybeInteger) <*> (PS.string "/" *> parseInterval duration date) + +parseInterval :: ∀ a b. P.Parser String a -> P.Parser String b -> P.Parser String (I.Interval a b) +parseInterval duration date = startEnd <|> durationEnd <|> startDuration <|> justDuration + where + startEnd = I.StartEnd <$> date <* PS.string "/" <*> date + durationEnd = I.DurationEnd <$> duration <* PS.string "/" <*> date + startDuration = I.StartDuration <$> date <* PS.string "/" <*> duration + justDuration = I.JustDuration <$> duration + +parseIsoDuration :: P.Parser String I.IsoDuration +parseIsoDuration = do + dur ← parseDuration + case I.mkIsoDuration dur of + Nothing -> P.fail "extracted Duration is not valid ISO duration" + Just a -> pure a + +parseDuration :: P.Parser String I.Duration +parseDuration = + PS.string "P" *> (weekDuration <|> fullDuration) <* PS.eof + where + weekDuration = mkComponentsParser [ Tuple I.week "W" ] + fullDuration = append <$> durationDatePart <*> durationTimePart + durationDatePart = mkComponentsParser [ Tuple I.year "Y" , Tuple I.month "M" , Tuple I.day "D" ] + durationTimePart = tryM $ PS.string "T" *> + (mkComponentsParser [ Tuple I.hours "H" , Tuple I.minutes "M" , Tuple I.seconds "S" ]) + + +mkComponentsParser :: Array (Tuple (Number -> I.Duration) String) -> P.Parser String I.Duration +mkComponentsParser arr = arr <#> applyDurations # sequence <#> foldFoldableMaybe + where + applyDurations :: Tuple (Number -> I.Duration) String -> P.Parser String (Maybe I.Duration) + applyDurations (Tuple f c) = PC.optionMaybe $ PC.try (f <$> component c) + + foldFoldableMaybe :: ∀ f a. Foldable f => Monoid a => f (Maybe a) -> a + foldFoldableMaybe = fold >>> unMaybe + + unMaybe :: ∀ a. Monoid a => Maybe a -> a + unMaybe = maybe mempty id + + component ∷ String → P.Parser String Number + component designator = parseNumber <* PS.string designator + +tryM :: ∀ a. Monoid a => P.Parser String a → P.Parser String a +tryM p = PC.option mempty $ PC.try p diff --git a/src/Data/Formatter/Parser/Number.purs b/src/Data/Formatter/Parser/Number.purs new file mode 100644 index 0000000..07ef8ca --- /dev/null +++ b/src/Data/Formatter/Parser/Number.purs @@ -0,0 +1,44 @@ +module Data.Formatter.Parser.Number + ( parseInteger + , parseMaybeInteger + , parseNumber + ) where + +import Prelude + +import Data.Int (toNumber, floor) +import Data.Array (some, many, length) +import Data.Formatter.Internal (digit, foldDigits) +import Data.Function (on) +import Text.Parsing.Parser as P +import Text.Parsing.Parser.Combinators as PC +import Text.Parsing.Parser.String as PS +import Data.Maybe (Maybe(..), maybe) +import Math as Math + + +parseInteger ∷ P.Parser String Int +parseInteger = some digit <#> foldDigits + +parseMaybeInteger ∷ P.Parser String (Maybe Int) +parseMaybeInteger = many digit <#> (\l -> if length l == 0 then Nothing else Just $ foldDigits l) + +parseFractional ∷ P.Parser String Number +parseFractional = parseInteger <#> case _ of + 0 -> 0.0 + n -> (toNumber n) / (pow 10 $ numOfDigits n) + +parseNumber ∷ P.Parser String Number +parseNumber = (+) + <$> (parseInteger <#> toNumber) + <*> (PC.option 0.0 $ PC.try $ PS.oneOf ['.', ','] *> parseFractional) + +pow :: Int -> Int -> Number +pow = Math.pow `on` toNumber + +numOfDigits ∷ Int → Int +numOfDigits 0 = 0 +numOfDigits n = 1 + (floor $ log10 $ toNumber n) + +log10 ∷ Number → Number +log10 n = Math.log10e * Math.log n diff --git a/test/src/Main.purs b/test/src/Main.purs index c69a536..707b712 100644 --- a/test/src/Main.purs +++ b/test/src/Main.purs @@ -8,7 +8,7 @@ import Data.DateTime as DTi -- TODO parser should't be exposed so this should be removed import Text.Parsing.Parser as P import Data.Interval as I -import Data.Formatter.Interval as FI +import Data.Formatter.Interval as FPI import Data.Formatter.DateTime as FDT import Data.Formatter.Number as FN @@ -170,16 +170,16 @@ assertParserRes result target = timeInterval :: forall e. Tests e Unit timeInterval = do - log "- Data.Formatter.Interval.parseDuration" - assertParserRes (P.runParser "P1W" FI.parseDuration) (Right $ I.day 7.0) - assertParserRes (P.runParser "P1.0W" FI.parseDuration) (Right $ I.day 7.0) - assertParserRes (P.runParser "P1.9748600D" FI.parseDuration) (Right $ I.day 1.97486) - assertParserRes (P.runParser "P1DT1H1M1S" FI.parseDuration) (Right $ I.day 1.0 <> I.hours 1.0 <> I.minutes 1.0 <> I.seconds 1.0) - assertParserRes (P.runParser "P1DT1H1M1S" FI.parseDuration <#> I.isValidIsoDuration) (Right true) - assertParserRes (P.runParser "P1DT1H1M1.5S" FI.parseDuration <#> I.isValidIsoDuration) (Right true) - assertParserRes (P.runParser "P1DT1H1.5M0S" FI.parseDuration <#> I.isValidIsoDuration) (Right true) - assertParserRes (P.runParser "P1DT1.5H0M0S" FI.parseDuration <#> I.isValidIsoDuration) (Right true) - assertParserRes (P.runParser "P1DT1.5H0M1S" FI.parseDuration <#> I.isValidIsoDuration) (Right false) + log "- Data.Formatter.Parser.Interval.parseDuration" + assertParserRes (P.runParser "P1W" FPI.parseDuration) (Right $ I.day 7.0) + assertParserRes (P.runParser "P1.0W" FPI.parseDuration) (Right $ I.day 7.0) + assertParserRes (P.runParser "P1.9748600D" FPI.parseDuration) (Right $ I.day 1.97486) + assertParserRes (P.runParser "P1DT1H1M1S" FPI.parseDuration) (Right $ I.day 1.0 <> I.hours 1.0 <> I.minutes 1.0 <> I.seconds 1.0) + assertParserRes (P.runParser "P1DT1H1M1S" FPI.parseDuration <#> I.isValidIsoDuration) (Right true) + assertParserRes (P.runParser "P1DT1H1M1.5S" FPI.parseDuration <#> I.isValidIsoDuration) (Right true) + assertParserRes (P.runParser "P1DT1H1.5M0S" FPI.parseDuration <#> I.isValidIsoDuration) (Right true) + assertParserRes (P.runParser "P1DT1.5H0M0S" FPI.parseDuration <#> I.isValidIsoDuration) (Right true) + assertParserRes (P.runParser "P1DT1.5H0M1S" FPI.parseDuration <#> I.isValidIsoDuration) (Right false) timeTest :: forall e. Tests e Unit timeTest = do From f7aaa529cc82d62280eae06ea055b0bffdcf0e1c Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Fri, 14 Apr 2017 15:16:50 +0100 Subject: [PATCH 08/88] move 'digit' to parser.number --- src/Data/Formatter/DateTime.purs | 29 +++++++++++++------------ src/Data/Formatter/Internal.purs | 31 --------------------------- src/Data/Formatter/Number.purs | 11 +++++----- src/Data/Formatter/Parser/Number.purs | 22 ++++++++++++++++--- 4 files changed, 40 insertions(+), 53 deletions(-) diff --git a/src/Data/Formatter/DateTime.purs b/src/Data/Formatter/DateTime.purs index 2085cb1..df26637 100644 --- a/src/Data/Formatter/DateTime.purs +++ b/src/Data/Formatter/DateTime.purs @@ -32,7 +32,8 @@ import Data.String as Str import Data.Time as T import Data.Time.Duration as Dur import Data.Tuple (Tuple(..)) -import Data.Formatter.Internal (digit, foldDigits) +import Data.Formatter.Internal (foldDigits) +import Data.Formatter.Parser.Number (parseDigit) import Text.Parsing.Parser as P import Text.Parsing.Parser.Combinators as PC @@ -292,19 +293,19 @@ unformatFParser → P.ParserT String (State UnformatAccum) Unit unformatFParser cb = case _ of YearFull a → do - ds ← some digit + ds ← some parseDigit when (Arr.length ds /= 4) $ P.fail "Incorrect full year" lift $ modify _{year = Just $ foldDigits ds} cb a YearTwoDigits a → do - ds ← some digit + ds ← some parseDigit when (Arr.length ds /= 2) $ P.fail "Incorrect 2-digit year" let y = foldDigits ds lift $ modify _{year = Just $ if y > 69 then y + 1900 else y + 2000} cb a YearAbsolute a → do sign ← PC.optionMaybe $ PC.try $ PS.string "-" - year ← map foldDigits $ some digit + year ← map foldDigits $ some parseDigit lift $ modify _{year = Just $ (if isJust sign then -1 else 1) * year} cb a MonthFull a → do @@ -316,25 +317,25 @@ unformatFParser cb = case _ of lift $ modify _{month = Just $ fromEnum month} cb a MonthTwoDigits a → do - ds ← some digit + ds ← some parseDigit let month = foldDigits ds when (Arr.length ds /= 2 || month > 12 || month < 1) $ P.fail "Incorrect 2-digit month" lift $ modify _{month = Just month} cb a DayOfMonthTwoDigits a → do - ds ← some digit + ds ← some parseDigit let dom = foldDigits ds when (Arr.length ds /= 2 || dom > 31 || dom < 1) $ P.fail "Incorrect day of month" lift $ modify _{day = Just dom} cb a DayOfMonth a → do - ds ← some digit + ds ← some parseDigit let dom = foldDigits ds when (Arr.length ds > 2 || dom > 31 || dom < 1) $ P.fail "Incorrect day of month" lift $ modify _{day = Just dom} cb a UnixTimestamp a → do - s ← map foldDigits $ some digit + s ← map foldDigits $ some parseDigit case map toDateTime $ instant $ Dur.Milliseconds $ 1000.0 * Int.toNumber s of Nothing → P.fail "Incorrect timestamp" Just (DT.DateTime d t) → do @@ -349,17 +350,17 @@ unformatFParser cb = case _ of } cb a DayOfWeek a → do - dow ← digit + dow ← parseDigit when (dow > 7 || dow < 1) $ P.fail "Incorrect day of week" cb a Hours24 a → do - ds ← some digit + ds ← some parseDigit let hh = foldDigits ds when (Arr.length ds /= 2 || hh < 0 || hh > 23) $ P.fail "Incorrect 24 hour" lift $ modify _{hour = Just hh} cb a Hours12 a → do - ds ← some digit + ds ← some parseDigit let hh = foldDigits ds when (Arr.length ds /= 2 || hh < 0 || hh > 11) $ P.fail "Incorrect 24 hour" lift $ modify _{hour = Just hh} @@ -377,19 +378,19 @@ unformatFParser cb = case _ of lift $ modify f cb a Minutes a → do - ds ← some digit + ds ← some parseDigit let mm = foldDigits ds when (Arr.length ds /= 2 || mm < 0 || mm > 59) $ P.fail "Incorrect minute" lift $ modify _{minute = Just mm} cb a Seconds a → do - ds ← some digit + ds ← some parseDigit let ss = foldDigits ds when (Arr.length ds /= 2 || ss < 0 || ss > 59) $ P.fail "Incorrect second" lift $ modify _{second = Just ss} cb a Milliseconds a → do - ds ← some digit + ds ← some parseDigit let sss = foldDigits ds when (Arr.length ds /= 3 || sss < 0 || sss > 999) $ P.fail "Incorrect millisecond" lift $ modify _{millisecond = Just sss} diff --git a/src/Data/Formatter/Internal.purs b/src/Data/Formatter/Internal.purs index e80af6a..6ee47f7 100644 --- a/src/Data/Formatter/Internal.purs +++ b/src/Data/Formatter/Internal.purs @@ -11,37 +11,6 @@ import Text.Parsing.Parser.String as PS foldDigits ∷ ∀ f. Foldable f ⇒ f Int → Int foldDigits = foldl (\acc d → acc * 10 + d) zero --- TODO move to Parser.Number -digit ∷ ∀ m. Monad m ⇒ P.ParserT String m Int -digit = do - char ← PS.oneOf ['0','1','2','3','4','5','6','7','8','9'] - case char of - '0' → pure 0 - '1' → pure 1 - '2' → pure 2 - '3' → pure 3 - '4' → pure 4 - '5' → pure 5 - '6' → pure 6 - '7' → pure 7 - '8' → pure 8 - '9' → pure 9 - _ → P.fail "Incorrect digit, impossible situation" - --- https://github.com/purescript-contrib/purescript-parsing/issues/50 --- digit ∷ ∀ m. Monad m ⇒ P.ParserT String m Int --- digit = PS.oneOfAs $ --- [ Tuple '0' 0 --- , Tuple '1' 1 --- , Tuple '2' 2 --- , Tuple '3' 3 --- , Tuple '4' 4 --- , Tuple '5' 5 --- , Tuple '6' 6 --- , Tuple '7' 7 --- , Tuple '8' 8 --- , Tuple '9' 9] - repeat ∷ ∀ a. Monoid a ⇒ a → Int → a repeat = repeat' mempty where diff --git a/src/Data/Formatter/Number.purs b/src/Data/Formatter/Number.purs index 4b4c701..7cd1b3b 100644 --- a/src/Data/Formatter/Number.purs +++ b/src/Data/Formatter/Number.purs @@ -24,7 +24,8 @@ import Data.Either (Either, either) import Data.Int as Int import Data.String as Str -import Data.Formatter.Internal (foldDigits, digit, repeat) +import Data.Formatter.Internal (foldDigits, repeat) +import Data.Formatter.Parser.Number (parseDigit) import Math as Math @@ -149,13 +150,13 @@ unformatParser f = do digitsWithCommas = if not f.comma then do - some digit <* PS.string "." + some parseDigit <* PS.string "." else digitsWithCommas' [ ] digitsWithCommas' ∷ Array Int → P.Parser String (Array Int) digitsWithCommas' accum = do - ds ← some digit + ds ← some parseDigit when (Arr.null accum && Arr.length ds > 3) $ P.fail "Wrong number of digits between thousand separators" @@ -174,7 +175,7 @@ unformatParser f = do then P.fail "Error: too few digits before dot" else pure $ Int.toNumber $ foldDigits beforeDigits - afterDigits ← some digit + afterDigits ← some parseDigit after ← if Arr.length afterDigits < f.after then P.fail "Error: too few digits after dot" @@ -191,7 +192,7 @@ unformatParser f = do Nothing → pure 0 Just _ → - map foldDigits $ many digit + map foldDigits $ many parseDigit Just 'K' → pure 3 Just 'M' → pure 6 Just 'G' → pure 9 diff --git a/src/Data/Formatter/Parser/Number.purs b/src/Data/Formatter/Parser/Number.purs index 07ef8ca..999c73e 100644 --- a/src/Data/Formatter/Parser/Number.purs +++ b/src/Data/Formatter/Parser/Number.purs @@ -2,13 +2,14 @@ module Data.Formatter.Parser.Number ( parseInteger , parseMaybeInteger , parseNumber + , parseDigit ) where import Prelude import Data.Int (toNumber, floor) import Data.Array (some, many, length) -import Data.Formatter.Internal (digit, foldDigits) +import Data.Formatter.Internal (parseDigit, foldDigits) import Data.Function (on) import Text.Parsing.Parser as P import Text.Parsing.Parser.Combinators as PC @@ -18,10 +19,10 @@ import Math as Math parseInteger ∷ P.Parser String Int -parseInteger = some digit <#> foldDigits +parseInteger = some parseDigit <#> foldDigits parseMaybeInteger ∷ P.Parser String (Maybe Int) -parseMaybeInteger = many digit <#> (\l -> if length l == 0 then Nothing else Just $ foldDigits l) +parseMaybeInteger = many parseDigit <#> (\l -> if length l == 0 then Nothing else Just $ foldDigits l) parseFractional ∷ P.Parser String Number parseFractional = parseInteger <#> case _ of @@ -42,3 +43,18 @@ numOfDigits n = 1 + (floor $ log10 $ toNumber n) log10 ∷ Number → Number log10 n = Math.log10e * Math.log n + +parseDigit = PS.char `oneOfAs` + [ Tuple '0' 0 + , Tuple '1' 1 + , Tuple '2' 2 + , Tuple '3' 3 + , Tuple '4' 4 + , Tuple '5' 5 + , Tuple '6' 6 + , Tuple '7' 7 + , Tuple '8' 8 + , Tuple '9' 9] + where + -- TODO remove after https://github.com/purescript-contrib/purescript-parsing/pull/51 + oneOfAs p xs = choice $ (\(Tuple s r) -> try $ p s $> r) <$> xs From c628d7c6e951f256a63cec759d9cb50bc49ffddd Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Tue, 18 Apr 2017 18:58:12 +0400 Subject: [PATCH 09/88] remove State from unformatParser --- src/Data/Formatter/DateTime.purs | 90 ++++++++++--------------- src/Data/Formatter/Internal.purs | 3 - src/Data/Formatter/Interval.purs | 22 ++++-- src/Data/Formatter/Parser/DateTime.purs | 47 +++++++++++++ src/Data/Formatter/Parser/Number.purs | 6 +- test/src/Main.purs | 2 +- 6 files changed, 101 insertions(+), 69 deletions(-) create mode 100644 src/Data/Formatter/Parser/DateTime.purs diff --git a/src/Data/Formatter/DateTime.purs b/src/Data/Formatter/DateTime.purs index df26637..9f7f062 100644 --- a/src/Data/Formatter/DateTime.purs +++ b/src/Data/Formatter/DateTime.purs @@ -12,11 +12,13 @@ module Data.Formatter.DateTime import Prelude import Control.Lazy as Lazy -import Control.Monad.State (State, runState, put, modify) +import Control.Monad.State (State, StateT, runState, mapStateT, put, modify) import Control.Monad.Trans.Class (lift) +import Control.Monad.Except.Trans (mapExceptT) import Data.Ord (abs) import Data.Array (some) +import Data.Tuple (Tuple(..)) import Data.Array as Arr import Data.Bifunctor (lmap) import Data.Date as D @@ -31,9 +33,9 @@ import Data.Newtype (unwrap) import Data.String as Str import Data.Time as T import Data.Time.Duration as Dur -import Data.Tuple (Tuple(..)) import Data.Formatter.Internal (foldDigits) import Data.Formatter.Parser.Number (parseDigit) +import Data.Formatter.Parser.DateTime (parseMonth, parseShortMonth) import Text.Parsing.Parser as P import Text.Parsing.Parser.Combinators as PC @@ -224,15 +226,10 @@ formatDateTime pattern datetime = unformat ∷ Formatter → String → Either String DT.DateTime unformat f s = - let - run = - runState - (P.runParserT s $ unformatParser f) - initialAccum - in - case run of - Tuple (Left err) _ → Left $ P.parseErrorMessage err - Tuple _ accum → unformatAccumToDateTime accum + unformatParser f + # P.runParser s + # lmap P.parseErrorMessage + >>= unformatAccumToDateTime data Meridiem = AM | PM @@ -367,15 +364,12 @@ unformatFParser cb = case _ of cb a Meridiem a → do m ← - PC.choice [ PC.try $ PS.string "am" - , PC.try $ PS.string "AM" - , PC.try $ PS.string "pm" - , PC.try $ PS.string "PM" + PC.choice [ PC.try $ PS.string "am" $> AM + , PC.try $ PS.string "AM" $> AM + , PC.try $ PS.string "pm" $> PM + , PC.try $ PS.string "PM" $> PM ] - let f | m == "am" || m == "AM" = _{meridiem = Just AM} - | m == "pm" || m == "PM" = _{meridiem = Just PM} - | otherwise = id - lift $ modify f + lift $ modify _{meridiem = Just m} cb a Minutes a → do ds ← some parseDigit @@ -401,48 +395,32 @@ unformatFParser cb = case _ of pure unit -unformatParser ∷ Formatter → P.ParserT String (State UnformatAccum) Unit -unformatParser f = - unformatFParser unformatParser $ unroll f +-- unformatParser ∷ ∀ m. Formatter → P.ParserT String m UnformatAccum +unformatParser ∷ Formatter → P.Parser String UnformatAccum +unformatParser f' = unState $ rec f' + where + rec ∷ Formatter → P.ParserT String (State UnformatAccum) Unit + rec f = unformatFParser rec $ unroll f + +unState ∷ ∀ m. Monad m => P.ParserT String (State UnformatAccum) Unit -> P.ParserT String m UnformatAccum +unState (P.ParserT m) = P.ParserT $ mapExceptT mapOutState m + +mapOutState :: ∀ s m a e + . Monad m + => StateT s (State UnformatAccum) (Either e a) + -> StateT s m (Either e UnformatAccum) +mapOutState s = mapStateT unStateIn s + +unStateIn ∷ ∀ m a s e . Monad m => State UnformatAccum (Tuple (Either e a) s) -> m (Tuple (Either e UnformatAccum) s) +unStateIn s = case runState s initialAccum of + Tuple (Tuple e state) res -> pure (Tuple (e $> res) state) + + unformatDateTime ∷ String → String → Either String DT.DateTime unformatDateTime pattern str = parseFormatString pattern >>= flip unformat str -parseMonth ∷ ∀ m. Monad m ⇒ P.ParserT String m D.Month -parseMonth = - PC.choice - [ (PC.try $ PS.string "January") $> D.January - , (PC.try $ PS.string "February") $> D.February - , (PC.try $ PS.string "March") $> D.March - , (PC.try $ PS.string "April") $> D.April - , (PC.try $ PS.string "May") $> D.May - , (PC.try $ PS.string "June") $> D.June - , (PC.try $ PS.string "July") $> D.July - , (PC.try $ PS.string "August") $> D.August - , (PC.try $ PS.string "September") $> D.September - , (PC.try $ PS.string "October") $> D.October - , (PC.try $ PS.string "November") $> D.November - , (PC.try $ PS.string "December") $> D.December - ] - -parseShortMonth ∷ ∀ m. Monad m ⇒ P.ParserT String m D.Month -parseShortMonth = - PC.choice - [ (PC.try $ PS.string "Jan") $> D.January - , (PC.try $ PS.string "Feb") $> D.February - , (PC.try $ PS.string "Mar") $> D.March - , (PC.try $ PS.string "Apr") $> D.April - , (PC.try $ PS.string "May") $> D.May - , (PC.try $ PS.string "Jun") $> D.June - , (PC.try $ PS.string "Jul") $> D.July - , (PC.try $ PS.string "Aug") $> D.August - , (PC.try $ PS.string "Sep") $> D.September - , (PC.try $ PS.string "Oct") $> D.October - , (PC.try $ PS.string "Nov") $> D.November - , (PC.try $ PS.string "Dec") $> D.December - ] - printShortMonth ∷ D.Month → String printShortMonth = case _ of D.January → "Jan" diff --git a/src/Data/Formatter/Internal.purs b/src/Data/Formatter/Internal.purs index 6ee47f7..2a91ab7 100644 --- a/src/Data/Formatter/Internal.purs +++ b/src/Data/Formatter/Internal.purs @@ -5,9 +5,6 @@ import Prelude import Data.Foldable (class Foldable, foldl) import Data.Monoid (class Monoid, mempty) -import Text.Parsing.Parser as P -import Text.Parsing.Parser.String as PS - foldDigits ∷ ∀ f. Foldable f ⇒ f Int → Int foldDigits = foldl (\acc d → acc * 10 + d) zero diff --git a/src/Data/Formatter/Interval.purs b/src/Data/Formatter/Interval.purs index e8cd60a..3e50b0b 100644 --- a/src/Data/Formatter/Interval.purs +++ b/src/Data/Formatter/Interval.purs @@ -2,10 +2,18 @@ module Data.Formatter.Interval ( unformatRecurringInterval , unformatInterval , unformatDuration + , class HasDuration + , class HasDate + , getDuration + , getDate ) where import Prelude + +import Text.Parsing.Parser as P import Data.Interval as I +import Data.Either (Either) +import Data.Bifunctor (lmap) import Data.Formatter.Parser.Interval (parseRecurringInterval, parseInterval, parseIsoDuration, parseDuration) unformatRecurringInterval :: @@ -13,7 +21,7 @@ unformatRecurringInterval :: . HasDuration a ⇒ HasDate b ⇒ String - → Either String (RecurringInterval a b) + → Either String (I.RecurringInterval a b) unformatRecurringInterval = run $ parseRecurringInterval getDuration getDate unformatInterval :: @@ -21,7 +29,7 @@ unformatInterval :: . HasDuration a ⇒ HasDate b ⇒ String - → Either String (Interval a b) + → Either String (I.Interval a b) unformatInterval = run $ parseInterval getDuration getDate unformatDuration :: @@ -32,22 +40,22 @@ unformatDuration :: unformatDuration = run getDuration -run :: Parser String a → String → Either String a +run :: ∀ a. P.Parser String a → String → Either String a run p s = lmap P.parseErrorMessage $ P.runParser s p class HasDuration a where - getDuration :: Parser String a + getDuration :: P.Parser String a -instance hasDurationDuration :: HasDuration Duration where +instance hasDurationDuration :: HasDuration I.Duration where getDuration = parseDuration -instance hasDurationIsoDuration :: HasDuration IsoDuration where +instance hasDurationIsoDuration :: HasDuration I.IsoDuration where getDuration = parseIsoDuration class HasDate a where - getDate :: Parser String a + getDate :: P.Parser String a -- instance hasDateDate :: HasDate DateTime where -- getDate = parseFormatString "YYYY-MM-DD`T`HH:MM:SS`Z`" >>= (_ `unformat` str) diff --git a/src/Data/Formatter/Parser/DateTime.purs b/src/Data/Formatter/Parser/DateTime.purs new file mode 100644 index 0000000..26f5320 --- /dev/null +++ b/src/Data/Formatter/Parser/DateTime.purs @@ -0,0 +1,47 @@ +module Data.Formatter.Parser.DateTime + ( parseMonth + , parseShortMonth + ) where + +import Prelude + +import Text.Parsing.Parser as P +import Text.Parsing.Parser.Combinators as PC +import Text.Parsing.Parser.String as PS +import Data.Date as D + +-- TODO use `oneOfAs` +parseMonth ∷ ∀ m. Monad m ⇒ P.ParserT String m D.Month +parseMonth = + PC.choice + [ (PC.try $ PS.string "January") $> D.January + , (PC.try $ PS.string "February") $> D.February + , (PC.try $ PS.string "March") $> D.March + , (PC.try $ PS.string "April") $> D.April + , (PC.try $ PS.string "May") $> D.May + , (PC.try $ PS.string "June") $> D.June + , (PC.try $ PS.string "July") $> D.July + , (PC.try $ PS.string "August") $> D.August + , (PC.try $ PS.string "September") $> D.September + , (PC.try $ PS.string "October") $> D.October + , (PC.try $ PS.string "November") $> D.November + , (PC.try $ PS.string "December") $> D.December + ] + +-- TODO use `oneOfAs` +parseShortMonth ∷ ∀ m. Monad m ⇒ P.ParserT String m D.Month +parseShortMonth = + PC.choice + [ (PC.try $ PS.string "Jan") $> D.January + , (PC.try $ PS.string "Feb") $> D.February + , (PC.try $ PS.string "Mar") $> D.March + , (PC.try $ PS.string "Apr") $> D.April + , (PC.try $ PS.string "May") $> D.May + , (PC.try $ PS.string "Jun") $> D.June + , (PC.try $ PS.string "Jul") $> D.July + , (PC.try $ PS.string "Aug") $> D.August + , (PC.try $ PS.string "Sep") $> D.September + , (PC.try $ PS.string "Oct") $> D.October + , (PC.try $ PS.string "Nov") $> D.November + , (PC.try $ PS.string "Dec") $> D.December + ] diff --git a/src/Data/Formatter/Parser/Number.purs b/src/Data/Formatter/Parser/Number.purs index 999c73e..2489a16 100644 --- a/src/Data/Formatter/Parser/Number.purs +++ b/src/Data/Formatter/Parser/Number.purs @@ -9,8 +9,10 @@ import Prelude import Data.Int (toNumber, floor) import Data.Array (some, many, length) -import Data.Formatter.Internal (parseDigit, foldDigits) +import Data.Formatter.Parser.Number (parseDigit) +import Data.Formatter.Internal (foldDigits) import Data.Function (on) +import Data.Tuple (Tuple(..)) import Text.Parsing.Parser as P import Text.Parsing.Parser.Combinators as PC import Text.Parsing.Parser.String as PS @@ -57,4 +59,4 @@ parseDigit = PS.char `oneOfAs` , Tuple '9' 9] where -- TODO remove after https://github.com/purescript-contrib/purescript-parsing/pull/51 - oneOfAs p xs = choice $ (\(Tuple s r) -> try $ p s $> r) <$> xs + oneOfAs p xs = PC.choice $ (\(Tuple s r) -> PC.try $ p s $> r) <$> xs diff --git a/test/src/Main.purs b/test/src/Main.purs index 707b712..299a266 100644 --- a/test/src/Main.purs +++ b/test/src/Main.purs @@ -8,7 +8,7 @@ import Data.DateTime as DTi -- TODO parser should't be exposed so this should be removed import Text.Parsing.Parser as P import Data.Interval as I -import Data.Formatter.Interval as FPI +import Data.Formatter.Parser.Interval as FPI import Data.Formatter.DateTime as FDT import Data.Formatter.Number as FN From f61ec8c9b4e8a94bef203c3f54a148a629e6f4b5 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Tue, 18 Apr 2017 20:13:09 +0400 Subject: [PATCH 10/88] use weaker hoistParserT --- src/Data/Formatter/DateTime.purs | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/src/Data/Formatter/DateTime.purs b/src/Data/Formatter/DateTime.purs index 9f7f062..c665d13 100644 --- a/src/Data/Formatter/DateTime.purs +++ b/src/Data/Formatter/DateTime.purs @@ -12,7 +12,7 @@ module Data.Formatter.DateTime import Prelude import Control.Lazy as Lazy -import Control.Monad.State (State, StateT, runState, mapStateT, put, modify) +import Control.Monad.State (State, mapStateT, modify, put, runState) import Control.Monad.Trans.Class (lift) import Control.Monad.Except.Trans (mapExceptT) @@ -395,25 +395,18 @@ unformatFParser cb = case _ of pure unit +hoistParserT' :: ∀ b n s a m. (m (Tuple (Either P.ParseError a) (P.ParseState s)) -> n (Tuple (Either P.ParseError b) (P.ParseState s))) -> P.ParserT s m a -> P.ParserT s n b +hoistParserT' f (P.ParserT m) = P.ParserT (mapExceptT (mapStateT f) m) + -- unformatParser ∷ ∀ m. Formatter → P.ParserT String m UnformatAccum unformatParser ∷ Formatter → P.Parser String UnformatAccum -unformatParser f' = unState $ rec f' +unformatParser f' = hoistParserT' unState $ rec f' where rec ∷ Formatter → P.ParserT String (State UnformatAccum) Unit rec f = unformatFParser rec $ unroll f - -unState ∷ ∀ m. Monad m => P.ParserT String (State UnformatAccum) Unit -> P.ParserT String m UnformatAccum -unState (P.ParserT m) = P.ParserT $ mapExceptT mapOutState m - -mapOutState :: ∀ s m a e - . Monad m - => StateT s (State UnformatAccum) (Either e a) - -> StateT s m (Either e UnformatAccum) -mapOutState s = mapStateT unStateIn s - -unStateIn ∷ ∀ m a s e . Monad m => State UnformatAccum (Tuple (Either e a) s) -> m (Tuple (Either e UnformatAccum) s) -unStateIn s = case runState s initialAccum of - Tuple (Tuple e state) res -> pure (Tuple (e $> res) state) + unState :: ∀ x y m. Monad m => State UnformatAccum (Tuple (Either y Unit) x) -> m (Tuple (Either y UnformatAccum) x) + unState s = case runState s initialAccum of + Tuple (Tuple e state) res -> pure (Tuple (e $> res) state) From b7a8b10c839261e0b9d2fd247f048b6858f006a0 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Tue, 18 Apr 2017 20:24:43 +0400 Subject: [PATCH 11/88] use ParserT instead of Parser --- src/Data/Formatter/DateTime.purs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Data/Formatter/DateTime.purs b/src/Data/Formatter/DateTime.purs index c665d13..ae3b2e7 100644 --- a/src/Data/Formatter/DateTime.purs +++ b/src/Data/Formatter/DateTime.purs @@ -398,13 +398,12 @@ unformatFParser cb = case _ of hoistParserT' :: ∀ b n s a m. (m (Tuple (Either P.ParseError a) (P.ParseState s)) -> n (Tuple (Either P.ParseError b) (P.ParseState s))) -> P.ParserT s m a -> P.ParserT s n b hoistParserT' f (P.ParserT m) = P.ParserT (mapExceptT (mapStateT f) m) --- unformatParser ∷ ∀ m. Formatter → P.ParserT String m UnformatAccum -unformatParser ∷ Formatter → P.Parser String UnformatAccum +unformatParser ∷ ∀ m. Monad m => Formatter → P.ParserT String m UnformatAccum unformatParser f' = hoistParserT' unState $ rec f' where rec ∷ Formatter → P.ParserT String (State UnformatAccum) Unit rec f = unformatFParser rec $ unroll f - unState :: ∀ x y m. Monad m => State UnformatAccum (Tuple (Either y Unit) x) -> m (Tuple (Either y UnformatAccum) x) + unState :: ∀ x y n. Monad n => State UnformatAccum (Tuple (Either y Unit) x) -> n (Tuple (Either y UnformatAccum) x) unState s = case runState s initialAccum of Tuple (Tuple e state) res -> pure (Tuple (e $> res) state) From 0f8e48deceffd20e91546d4ff938797a3bf24876 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Tue, 18 Apr 2017 21:50:31 +0400 Subject: [PATCH 12/88] use more oneOfAs --- src/Data/Formatter/Parser/DateTime.purs | 57 ++++++++++++------------- src/Data/Formatter/Parser/Number.purs | 6 +-- src/Data/Formatter/Parser/Utils.purs | 8 ++++ 3 files changed, 38 insertions(+), 33 deletions(-) create mode 100644 src/Data/Formatter/Parser/Utils.purs diff --git a/src/Data/Formatter/Parser/DateTime.purs b/src/Data/Formatter/Parser/DateTime.purs index 26f5320..32451b1 100644 --- a/src/Data/Formatter/Parser/DateTime.purs +++ b/src/Data/Formatter/Parser/DateTime.purs @@ -5,6 +5,7 @@ module Data.Formatter.Parser.DateTime import Prelude +import Text.Formatter.Parser.Utils (oneOfAs) import Text.Parsing.Parser as P import Text.Parsing.Parser.Combinators as PC import Text.Parsing.Parser.String as PS @@ -12,36 +13,34 @@ import Data.Date as D -- TODO use `oneOfAs` parseMonth ∷ ∀ m. Monad m ⇒ P.ParserT String m D.Month -parseMonth = - PC.choice - [ (PC.try $ PS.string "January") $> D.January - , (PC.try $ PS.string "February") $> D.February - , (PC.try $ PS.string "March") $> D.March - , (PC.try $ PS.string "April") $> D.April - , (PC.try $ PS.string "May") $> D.May - , (PC.try $ PS.string "June") $> D.June - , (PC.try $ PS.string "July") $> D.July - , (PC.try $ PS.string "August") $> D.August - , (PC.try $ PS.string "September") $> D.September - , (PC.try $ PS.string "October") $> D.October - , (PC.try $ PS.string "November") $> D.November - , (PC.try $ PS.string "December") $> D.December - ] +parseMonth = (PC.try <<< PS.string) `oneOfAs` + [ Tuple "January" D.January + , Tuple "February" D.February + , Tuple "March" D.March + , Tuple "April" D.April + , Tuple "May" D.May + , Tuple "June" D.June + , Tuple "July" D.July + , Tuple "August" D.August + , Tuple "September" D.September + , Tuple "October" D.October + , Tuple "November" D.November + , Tuple "December" D.December + ] -- TODO use `oneOfAs` parseShortMonth ∷ ∀ m. Monad m ⇒ P.ParserT String m D.Month -parseShortMonth = - PC.choice - [ (PC.try $ PS.string "Jan") $> D.January - , (PC.try $ PS.string "Feb") $> D.February - , (PC.try $ PS.string "Mar") $> D.March - , (PC.try $ PS.string "Apr") $> D.April - , (PC.try $ PS.string "May") $> D.May - , (PC.try $ PS.string "Jun") $> D.June - , (PC.try $ PS.string "Jul") $> D.July - , (PC.try $ PS.string "Aug") $> D.August - , (PC.try $ PS.string "Sep") $> D.September - , (PC.try $ PS.string "Oct") $> D.October - , (PC.try $ PS.string "Nov") $> D.November - , (PC.try $ PS.string "Dec") $> D.December +parseShortMonth = (PC.try <<< PS.string) `oneOfAs` + [ Tuple "Jan" D.January + , Tuple "Feb" D.February + , Tuple "Mar" D.March + , Tuple "Apr" D.April + , Tuple "May" D.May + , Tuple "Jun" D.June + , Tuple "Jul" D.July + , Tuple "Aug" D.August + , Tuple "Sep" D.September + , Tuple "Oct" D.October + , Tuple "Nov" D.November + , Tuple "Dec" D.December ] diff --git a/src/Data/Formatter/Parser/Number.purs b/src/Data/Formatter/Parser/Number.purs index 2489a16..5d663e1 100644 --- a/src/Data/Formatter/Parser/Number.purs +++ b/src/Data/Formatter/Parser/Number.purs @@ -15,6 +15,7 @@ import Data.Function (on) import Data.Tuple (Tuple(..)) import Text.Parsing.Parser as P import Text.Parsing.Parser.Combinators as PC +import Text.Formatter.Parser.Utils (oneOfAs) import Text.Parsing.Parser.String as PS import Data.Maybe (Maybe(..), maybe) import Math as Math @@ -46,7 +47,7 @@ numOfDigits n = 1 + (floor $ log10 $ toNumber n) log10 ∷ Number → Number log10 n = Math.log10e * Math.log n -parseDigit = PS.char `oneOfAs` +parseDigit = PC.try $ PS.char `oneOfAs` [ Tuple '0' 0 , Tuple '1' 1 , Tuple '2' 2 @@ -57,6 +58,3 @@ parseDigit = PS.char `oneOfAs` , Tuple '7' 7 , Tuple '8' 8 , Tuple '9' 9] - where - -- TODO remove after https://github.com/purescript-contrib/purescript-parsing/pull/51 - oneOfAs p xs = PC.choice $ (\(Tuple s r) -> PC.try $ p s $> r) <$> xs diff --git a/src/Data/Formatter/Parser/Utils.purs b/src/Data/Formatter/Parser/Utils.purs new file mode 100644 index 0000000..d4e3f85 --- /dev/null +++ b/src/Data/Formatter/Parser/Utils.purs @@ -0,0 +1,8 @@ +module Data.Formatter.Parser.Utils where + +import Prelude + +import Data.Tuple (Tuple(..)) +import Text.Parsing.Parser.Combinators as PC + +oneOfAs p xs = PC.choice $ (\(Tuple s r) -> p s $> r) <$> xs From 579660d01277ee95128808c54ee8a8701df6698d Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Wed, 19 Apr 2017 22:39:07 +0400 Subject: [PATCH 13/88] export unformatParser from DateTime; fix interval related parser tests --- src/Data/Formatter/DateTime.purs | 22 ++++++------ src/Data/Formatter/Interval.purs | 25 +++++++++----- src/Data/Formatter/Parser/DateTime.purs | 5 ++- src/Data/Formatter/Parser/Interval.purs | 14 +++++--- src/Data/Formatter/Parser/Number.purs | 2 +- src/Data/Formatter/Parser/Utils.purs | 8 +++++ test/src/Main.purs | 46 +++++++++++++------------ 7 files changed, 72 insertions(+), 50 deletions(-) diff --git a/src/Data/Formatter/DateTime.purs b/src/Data/Formatter/DateTime.purs index ae3b2e7..b7f7e0b 100644 --- a/src/Data/Formatter/DateTime.purs +++ b/src/Data/Formatter/DateTime.purs @@ -1,12 +1,14 @@ module Data.Formatter.DateTime ( Formatter , FormatterF(..) + , Meridiem , printFormatter , parseFormatString , format , formatDateTime , unformat , unformatDateTime + , unformatParser ) where import Prelude @@ -24,7 +26,7 @@ import Data.Bifunctor (lmap) import Data.Date as D import Data.DateTime as DT import Data.DateTime.Instant (instant, toDateTime, fromDateTime, unInstant) -import Data.Either (Either(..)) +import Data.Either (Either(..), either) import Data.Enum (fromEnum, toEnum) import Data.Functor.Mu (Mu, unroll, roll) import Data.Int as Int @@ -34,6 +36,7 @@ import Data.String as Str import Data.Time as T import Data.Time.Duration as Dur import Data.Formatter.Internal (foldDigits) +import Data.Formatter.Parser.Utils (runP) import Data.Formatter.Parser.Number (parseDigit) import Data.Formatter.Parser.DateTime (parseMonth, parseShortMonth) @@ -113,8 +116,7 @@ printFormatter ∷ Formatter → String printFormatter f = printFormatterF printFormatter $ unroll f parseFormatString ∷ String → Either String Formatter -parseFormatString s = - lmap P.parseErrorMessage $ P.runParser s formatParser +parseFormatString = runP formatParser -- | Formatting function that accepts a number that is a year, -- | and strips away the non-significant digits, leaving only the @@ -225,11 +227,7 @@ formatDateTime pattern datetime = parseFormatString pattern <#> flip format datetime unformat ∷ Formatter → String → Either String DT.DateTime -unformat f s = - unformatParser f - # P.runParser s - # lmap P.parseErrorMessage - >>= unformatAccumToDateTime +unformat = runP <<< unformatParser data Meridiem = AM | PM @@ -395,11 +393,13 @@ unformatFParser cb = case _ of pure unit -hoistParserT' :: ∀ b n s a m. (m (Tuple (Either P.ParseError a) (P.ParseState s)) -> n (Tuple (Either P.ParseError b) (P.ParseState s))) -> P.ParserT s m a -> P.ParserT s n b +hoistParserT' :: ∀ b n s a m. (∀ e s'. m (Tuple (Either e a) s') -> n (Tuple (Either e b) s')) -> P.ParserT s m a -> P.ParserT s n b hoistParserT' f (P.ParserT m) = P.ParserT (mapExceptT (mapStateT f) m) -unformatParser ∷ ∀ m. Monad m => Formatter → P.ParserT String m UnformatAccum -unformatParser f' = hoistParserT' unState $ rec f' +unformatParser ∷ ∀ m. Monad m => Formatter → P.ParserT String m DT.DateTime +unformatParser f' = do + acc <- hoistParserT' unState $ rec f' + either P.fail pure $ unformatAccumToDateTime acc where rec ∷ Formatter → P.ParserT String (State UnformatAccum) Unit rec f = unformatFParser rec $ unroll f diff --git a/src/Data/Formatter/Interval.purs b/src/Data/Formatter/Interval.purs index 3e50b0b..597bee9 100644 --- a/src/Data/Formatter/Interval.purs +++ b/src/Data/Formatter/Interval.purs @@ -11,9 +11,13 @@ module Data.Formatter.Interval import Prelude import Text.Parsing.Parser as P +import Text.Parsing.Parser.String as PS +import Data.Formatter.Parser.Utils (runP) import Data.Interval as I -import Data.Either (Either) +import Data.DateTime as D +import Data.Either (Either(..)) import Data.Bifunctor (lmap) +import Data.Formatter.DateTime (Formatter, unformatParser, parseFormatString) import Data.Formatter.Parser.Interval (parseRecurringInterval, parseInterval, parseIsoDuration, parseDuration) unformatRecurringInterval :: @@ -22,7 +26,7 @@ unformatRecurringInterval :: ⇒ HasDate b ⇒ String → Either String (I.RecurringInterval a b) -unformatRecurringInterval = run $ parseRecurringInterval getDuration getDate +unformatRecurringInterval = runP $ parseRecurringInterval getDuration getDate <* PS.eof unformatInterval :: ∀ a b @@ -30,18 +34,16 @@ unformatInterval :: ⇒ HasDate b ⇒ String → Either String (I.Interval a b) -unformatInterval = run $ parseInterval getDuration getDate +unformatInterval = runP $ parseInterval getDuration getDate <* PS.eof unformatDuration :: ∀ a . HasDuration a ⇒ String → Either String a -unformatDuration = run getDuration +unformatDuration = runP $ getDuration <* PS.eof -run :: ∀ a. P.Parser String a → String → Either String a -run p s = lmap P.parseErrorMessage $ P.runParser s p class HasDuration a where @@ -57,8 +59,15 @@ instance hasDurationIsoDuration :: HasDuration I.IsoDuration where class HasDate a where getDate :: P.Parser String a --- instance hasDateDate :: HasDate DateTime where --- getDate = parseFormatString "YYYY-MM-DD`T`HH:MM:SS`Z`" >>= (_ `unformat` str) +isoDateTimeFormatter ∷ Either String Formatter +isoDateTimeFormatter = parseFormatString "YYYY-MM-DDTHH:MM:SSZ" + +instance hasDateDate :: HasDate D.DateTime where + getDate = do + case isoDateTimeFormatter of + Right f -> unformatParser f + Left e -> P.fail e + -- TODO -- 2017-04-13T15:36:07+00:00 -- 2017-04-13T15:36:07Z diff --git a/src/Data/Formatter/Parser/DateTime.purs b/src/Data/Formatter/Parser/DateTime.purs index 32451b1..dd1779f 100644 --- a/src/Data/Formatter/Parser/DateTime.purs +++ b/src/Data/Formatter/Parser/DateTime.purs @@ -5,13 +5,13 @@ module Data.Formatter.Parser.DateTime import Prelude -import Text.Formatter.Parser.Utils (oneOfAs) +import Data.Formatter.Parser.Utils (oneOfAs) import Text.Parsing.Parser as P +import Data.Tuple (Tuple(..)) import Text.Parsing.Parser.Combinators as PC import Text.Parsing.Parser.String as PS import Data.Date as D --- TODO use `oneOfAs` parseMonth ∷ ∀ m. Monad m ⇒ P.ParserT String m D.Month parseMonth = (PC.try <<< PS.string) `oneOfAs` [ Tuple "January" D.January @@ -28,7 +28,6 @@ parseMonth = (PC.try <<< PS.string) `oneOfAs` , Tuple "December" D.December ] --- TODO use `oneOfAs` parseShortMonth ∷ ∀ m. Monad m ⇒ P.ParserT String m D.Month parseShortMonth = (PC.try <<< PS.string) `oneOfAs` [ Tuple "Jan" D.January diff --git a/src/Data/Formatter/Parser/Interval.purs b/src/Data/Formatter/Parser/Interval.purs index 7f14980..89ab043 100644 --- a/src/Data/Formatter/Parser/Interval.purs +++ b/src/Data/Formatter/Parser/Interval.purs @@ -10,12 +10,13 @@ import Data.Interval as I import Text.Parsing.Parser as P import Text.Parsing.Parser.Combinators as PC import Text.Parsing.Parser.String as PS +import Control.Monad.State (get) import Control.Alt ((<|>)) import Data.Foldable (class Foldable, fold) import Data.Maybe (Maybe(..), maybe) import Data.Monoid (class Monoid, mempty) import Data.Traversable (sequence) -import Data.Tuple (Tuple(..)) +import Data.Tuple (Tuple(..), snd) import Data.Formatter.Parser.Number (parseNumber, parseMaybeInteger) @@ -24,7 +25,7 @@ parseRecurringInterval duration date = I.RecurringInterval <$> (PS.string "R" *> parseMaybeInteger) <*> (PS.string "/" *> parseInterval duration date) parseInterval :: ∀ a b. P.Parser String a -> P.Parser String b -> P.Parser String (I.Interval a b) -parseInterval duration date = startEnd <|> durationEnd <|> startDuration <|> justDuration +parseInterval duration date = [startEnd, durationEnd, startDuration, justDuration] <#> PC.try # PC.choice where startEnd = I.StartEnd <$> date <* PS.string "/" <*> date durationEnd = I.DurationEnd <$> duration <* PS.string "/" <*> date @@ -39,8 +40,7 @@ parseIsoDuration = do Just a -> pure a parseDuration :: P.Parser String I.Duration -parseDuration = - PS.string "P" *> (weekDuration <|> fullDuration) <* PS.eof +parseDuration = PS.string "P" *> (weekDuration <|> fullDuration) where weekDuration = mkComponentsParser [ Tuple I.week "W" ] fullDuration = append <$> durationDatePart <*> durationTimePart @@ -50,7 +50,11 @@ parseDuration = mkComponentsParser :: Array (Tuple (Number -> I.Duration) String) -> P.Parser String I.Duration -mkComponentsParser arr = arr <#> applyDurations # sequence <#> foldFoldableMaybe +mkComponentsParser arr = do + dur <- arr <#> applyDurations # sequence <#> foldFoldableMaybe + if dur == mempty + then P.fail $ "none of valid duration components (" <> (show $ snd <$> arr) <> ") were present" + else pure dur where applyDurations :: Tuple (Number -> I.Duration) String -> P.Parser String (Maybe I.Duration) applyDurations (Tuple f c) = PC.optionMaybe $ PC.try (f <$> component c) diff --git a/src/Data/Formatter/Parser/Number.purs b/src/Data/Formatter/Parser/Number.purs index 5d663e1..998372c 100644 --- a/src/Data/Formatter/Parser/Number.purs +++ b/src/Data/Formatter/Parser/Number.purs @@ -15,7 +15,7 @@ import Data.Function (on) import Data.Tuple (Tuple(..)) import Text.Parsing.Parser as P import Text.Parsing.Parser.Combinators as PC -import Text.Formatter.Parser.Utils (oneOfAs) +import Data.Formatter.Parser.Utils (oneOfAs) import Text.Parsing.Parser.String as PS import Data.Maybe (Maybe(..), maybe) import Math as Math diff --git a/src/Data/Formatter/Parser/Utils.purs b/src/Data/Formatter/Parser/Utils.purs index d4e3f85..c45032d 100644 --- a/src/Data/Formatter/Parser/Utils.purs +++ b/src/Data/Formatter/Parser/Utils.purs @@ -3,6 +3,14 @@ module Data.Formatter.Parser.Utils where import Prelude import Data.Tuple (Tuple(..)) +import Text.Parsing.Parser as P import Text.Parsing.Parser.Combinators as PC +import Data.Bifunctor (lmap) +import Data.Foldable (class Foldable) +import Data.Either (Either) +oneOfAs :: ∀ c s m f a b. Functor f => Foldable f => Monad m => (a -> P.ParserT s m b) -> f (Tuple a c) -> P.ParserT s m c oneOfAs p xs = PC.choice $ (\(Tuple s r) -> p s $> r) <$> xs + +runP :: ∀ a. P.Parser String a → String → Either String a +runP p s = lmap P.parseErrorMessage $ P.runParser s p diff --git a/test/src/Main.purs b/test/src/Main.purs index 299a266..02235f9 100644 --- a/test/src/Main.purs +++ b/test/src/Main.purs @@ -4,13 +4,10 @@ import Prelude import Control.Monad.Aff.Console as AffC import Data.Date as D import Data.DateTime as DTi - --- TODO parser should't be exposed so this should be removed -import Text.Parsing.Parser as P import Data.Interval as I -import Data.Formatter.Parser.Interval as FPI import Data.Formatter.DateTime as FDT +import Data.Formatter.Interval as FI import Data.Formatter.Number as FN import Data.Time as T import Debug.Trace as DT @@ -25,7 +22,7 @@ import Data.DateTime (DateTime) import Data.Either (Either(..), either) import Data.Enum (toEnum) import Data.Functor.Mu (roll) -import Data.Maybe (fromMaybe) +import Data.Maybe (fromMaybe, Maybe(..), maybe) type Tests e a = StateT Boolean (Aff (exception :: EXCEPTION, console :: CONSOLE | e)) a @@ -156,30 +153,35 @@ assertFormatting :: forall e. String -> String -> DateTime -> Tests e Unit assertFormatting target' format dateTime = do let result = FDT.formatDateTime format dateTime let target = Right target' - assert - ((show result) <> " does not equal " <> (show target)) - ((show result) <> " equals " <> (show target)) - (result == target) + assertEq result target -assertParserRes :: forall a e. Show a => Eq a => a -> a -> Tests e Unit -assertParserRes result target = +assertEq :: forall a e. Show a => Eq a => a -> a -> Tests e Unit +assertEq result target = assert - ((show result) <> " does not equal " <> (show target)) - ((show result) <> " equals " <> (show target)) + ((show result) <> " ≠ " <> (show target)) + ((show result) <> " ≡ " <> (show target)) (result == target) +dur :: Either String (I.RecurringInterval I.IsoDuration DTi.DateTime) +dur = I.mkIsoDuration (I.day 1.0 <> I.hours 1.0 <> I.minutes 0.0 <> I.seconds 1.5) + <#> I.JustDuration + <#> I.RecurringInterval (Just 10) + # maybe (Left "boom") Right + timeInterval :: forall e. Tests e Unit timeInterval = do log "- Data.Formatter.Parser.Interval.parseDuration" - assertParserRes (P.runParser "P1W" FPI.parseDuration) (Right $ I.day 7.0) - assertParserRes (P.runParser "P1.0W" FPI.parseDuration) (Right $ I.day 7.0) - assertParserRes (P.runParser "P1.9748600D" FPI.parseDuration) (Right $ I.day 1.97486) - assertParserRes (P.runParser "P1DT1H1M1S" FPI.parseDuration) (Right $ I.day 1.0 <> I.hours 1.0 <> I.minutes 1.0 <> I.seconds 1.0) - assertParserRes (P.runParser "P1DT1H1M1S" FPI.parseDuration <#> I.isValidIsoDuration) (Right true) - assertParserRes (P.runParser "P1DT1H1M1.5S" FPI.parseDuration <#> I.isValidIsoDuration) (Right true) - assertParserRes (P.runParser "P1DT1H1.5M0S" FPI.parseDuration <#> I.isValidIsoDuration) (Right true) - assertParserRes (P.runParser "P1DT1.5H0M0S" FPI.parseDuration <#> I.isValidIsoDuration) (Right true) - assertParserRes (P.runParser "P1DT1.5H0M1S" FPI.parseDuration <#> I.isValidIsoDuration) (Right false) + assertEq (FI.unformatDuration "P1W") (Right $ I.day 7.0) + assertEq (FI.unformatDuration "P1.0W") (Right $ I.day 7.0) + assertEq (FI.unformatDuration "P1.9748600D") (Right $ I.day 1.97486) + assertEq (FI.unformatDuration "P1DT1H1M1S") (Right $ I.day 1.0 <> I.hours 1.0 <> I.minutes 1.0 <> I.seconds 1.0) + assertEq (FI.unformatDuration "P1DT1H1M0S") (Right $ I.day 1.0 <> I.hours 1.0 <> I.minutes 1.0 <> I.seconds 0.0) + assertEq (FI.unformatDuration "P1DT1H1M1S" <#> I.isValidIsoDuration) (Right true) + assertEq (FI.unformatDuration "P1DT1H1M1.5S" <#> I.isValidIsoDuration) (Right true) + assertEq (FI.unformatDuration "P1DT1H1.5M0S" <#> I.isValidIsoDuration) (Right true) + assertEq (FI.unformatDuration "P1DT1.5H0M0S" <#> I.isValidIsoDuration) (Right true) + assertEq (FI.unformatDuration "P1DT1.5H0M1S" <#> I.isValidIsoDuration) (Right false) + assertEq (FI.unformatRecurringInterval "R10/P1DT1H0M1.5S") dur timeTest :: forall e. Tests e Unit timeTest = do From 018f2defae6416b02e0e5287aaf6f5f48f746b38 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Thu, 20 Apr 2017 18:46:02 +0400 Subject: [PATCH 14/88] un-export HasDuration for Duration (live one for IsoDuration only) --- src/Data/Formatter/DateTime.purs | 1 - src/Data/Formatter/Interval.purs | 6 +----- src/Data/Formatter/Parser/Interval.purs | 2 -- src/Data/Formatter/Parser/Number.purs | 11 ++++++----- 4 files changed, 7 insertions(+), 13 deletions(-) diff --git a/src/Data/Formatter/DateTime.purs b/src/Data/Formatter/DateTime.purs index b7f7e0b..4d9972c 100644 --- a/src/Data/Formatter/DateTime.purs +++ b/src/Data/Formatter/DateTime.purs @@ -22,7 +22,6 @@ import Data.Ord (abs) import Data.Array (some) import Data.Tuple (Tuple(..)) import Data.Array as Arr -import Data.Bifunctor (lmap) import Data.Date as D import Data.DateTime as DT import Data.DateTime.Instant (instant, toDateTime, fromDateTime, unInstant) diff --git a/src/Data/Formatter/Interval.purs b/src/Data/Formatter/Interval.purs index 597bee9..60e2cc0 100644 --- a/src/Data/Formatter/Interval.purs +++ b/src/Data/Formatter/Interval.purs @@ -16,9 +16,8 @@ import Data.Formatter.Parser.Utils (runP) import Data.Interval as I import Data.DateTime as D import Data.Either (Either(..)) -import Data.Bifunctor (lmap) import Data.Formatter.DateTime (Formatter, unformatParser, parseFormatString) -import Data.Formatter.Parser.Interval (parseRecurringInterval, parseInterval, parseIsoDuration, parseDuration) +import Data.Formatter.Parser.Interval (parseRecurringInterval, parseInterval, parseIsoDuration) unformatRecurringInterval :: ∀ a b @@ -49,9 +48,6 @@ unformatDuration = runP $ getDuration <* PS.eof class HasDuration a where getDuration :: P.Parser String a -instance hasDurationDuration :: HasDuration I.Duration where - getDuration = parseDuration - instance hasDurationIsoDuration :: HasDuration I.IsoDuration where getDuration = parseIsoDuration diff --git a/src/Data/Formatter/Parser/Interval.purs b/src/Data/Formatter/Parser/Interval.purs index 89ab043..07d8a52 100644 --- a/src/Data/Formatter/Parser/Interval.purs +++ b/src/Data/Formatter/Parser/Interval.purs @@ -2,7 +2,6 @@ module Data.Formatter.Parser.Interval ( parseRecurringInterval , parseInterval , parseIsoDuration - , parseDuration ) where import Prelude @@ -10,7 +9,6 @@ import Data.Interval as I import Text.Parsing.Parser as P import Text.Parsing.Parser.Combinators as PC import Text.Parsing.Parser.String as PS -import Control.Monad.State (get) import Control.Alt ((<|>)) import Data.Foldable (class Foldable, fold) import Data.Maybe (Maybe(..), maybe) diff --git a/src/Data/Formatter/Parser/Number.purs b/src/Data/Formatter/Parser/Number.purs index 998372c..dc25c75 100644 --- a/src/Data/Formatter/Parser/Number.purs +++ b/src/Data/Formatter/Parser/Number.purs @@ -17,22 +17,22 @@ import Text.Parsing.Parser as P import Text.Parsing.Parser.Combinators as PC import Data.Formatter.Parser.Utils (oneOfAs) import Text.Parsing.Parser.String as PS -import Data.Maybe (Maybe(..), maybe) +import Data.Maybe (Maybe(..)) import Math as Math -parseInteger ∷ P.Parser String Int +parseInteger ∷ ∀ s m. Monad m => PS.StringLike s => P.ParserT s m Int parseInteger = some parseDigit <#> foldDigits -parseMaybeInteger ∷ P.Parser String (Maybe Int) +parseMaybeInteger ∷ ∀ s m. Monad m => PS.StringLike s => P.ParserT s m (Maybe Int) parseMaybeInteger = many parseDigit <#> (\l -> if length l == 0 then Nothing else Just $ foldDigits l) -parseFractional ∷ P.Parser String Number +parseFractional ∷ ∀ s m. Monad m => PS.StringLike s => P.ParserT s m Number parseFractional = parseInteger <#> case _ of 0 -> 0.0 n -> (toNumber n) / (pow 10 $ numOfDigits n) -parseNumber ∷ P.Parser String Number +parseNumber ∷ ∀ s m. Monad m => PS.StringLike s => P.ParserT s m Number parseNumber = (+) <$> (parseInteger <#> toNumber) <*> (PC.option 0.0 $ PC.try $ PS.oneOf ['.', ','] *> parseFractional) @@ -47,6 +47,7 @@ numOfDigits n = 1 + (floor $ log10 $ toNumber n) log10 ∷ Number → Number log10 n = Math.log10e * Math.log n +parseDigit ∷ ∀ s m. Monad m => PS.StringLike s => P.ParserT s m Int parseDigit = PC.try $ PS.char `oneOfAs` [ Tuple '0' 0 , Tuple '1' 1 From 69895b1910322741270522b1ecbf8904109e7b6e Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Thu, 20 Apr 2017 20:51:54 +0400 Subject: [PATCH 15/88] add tests for all variation of valid Recurringinterval --- src/Data/Formatter/Interval.purs | 13 +-- test/src/Interval.purs | 154 +++++++++++++++++++++++++++++++ test/src/Main.purs | 91 +++--------------- test/src/{Main.js => Test.js} | 0 test/src/Test.purs | 57 ++++++++++++ 5 files changed, 229 insertions(+), 86 deletions(-) create mode 100644 test/src/Interval.purs rename test/src/{Main.js => Test.js} (100%) create mode 100644 test/src/Test.purs diff --git a/src/Data/Formatter/Interval.purs b/src/Data/Formatter/Interval.purs index 60e2cc0..06d58d6 100644 --- a/src/Data/Formatter/Interval.purs +++ b/src/Data/Formatter/Interval.purs @@ -56,23 +56,18 @@ class HasDate a where getDate :: P.Parser String a isoDateTimeFormatter ∷ Either String Formatter -isoDateTimeFormatter = parseFormatString "YYYY-MM-DDTHH:MM:SSZ" +isoDateTimeFormatter = parseFormatString "YYYY-MM-DDTHH:mm:ssZ" instance hasDateDate :: HasDate D.DateTime where getDate = do case isoDateTimeFormatter of Right f -> unformatParser f - Left e -> P.fail e + Left e -> P.fail $ "(this must be unrechable) error in parsing ISO date format: " <> e --- TODO +-- TODO read iso spec and check if local datetimes or datetimes with offset are supported -- 2017-04-13T15:36:07+00:00 -- 2017-04-13T15:36:07Z - - --- TODO implement date parsers - +-- TODO instance for Date? -- TODO instance for local versions -- * LocalDate -- * LocalDateTime - --- TODO Q? should we define for LocalTime and Time diff --git a/test/src/Interval.purs b/test/src/Interval.purs new file mode 100644 index 0000000..0e74058 --- /dev/null +++ b/test/src/Interval.purs @@ -0,0 +1,154 @@ +module Test.Interval + ( intervalTest + ) where + + +import Prelude + +import Data.DateTime (DateTime(..)) +import Data.Interval as I +import Control.MonadZero (guard) +import Data.Foldable (for_) +import Data.Time (Time(..)) +import Data.Date (canonicalDate) +import Data.Formatter.Interval (unformatDuration, unformatRecurringInterval, getDate, class HasDuration) +import Data.Either (Either(..)) +import Data.Maybe (Maybe(..), fromJust, fromMaybe) +import Data.Enum (toEnum) +import Partial.Unsafe (unsafePartialBecause) +import Test.Test (Tests, assertEq, log) +import Unsafe.Coerce (unsafeCoerce) +import Data.Formatter.Parser.Utils (runP) + +unsafeMkToIsoDuration :: I.Duration -> I.IsoDuration +unsafeMkToIsoDuration d = unsafePartialBecause "the duration must be valid ISO duration" fromJust $ I.mkIsoDuration d + +makeDateTime ∷ Int -> Int -> Int -> Int -> Int -> Int -> Int -> DateTime +makeDateTime year month day h m s ms= + DateTime + (canonicalDate (fromMaybe bottom $ toEnum year) (fromMaybe bottom $ toEnum month) (fromMaybe bottom $ toEnum day)) + (Time (fromMaybe bottom $ toEnum h) (fromMaybe bottom $ toEnum m) (fromMaybe bottom $ toEnum s) (fromMaybe bottom $ toEnum ms)) + +durations :: Array { str:: String, dur :: I.IsoDuration } +durations = + [ { str: "P1W", dur: I.day 7.0 } + , { str: "P1.0W", dur: I.day 7.0 } + , { str: "P1DT1H1M1S", dur: I.day 1.0 <> I.hours 1.0 <> I.minutes 1.0 <> I.seconds 1.0 } + , { str: "P1.9748600D", dur: I.day 1.97486 } + , { str: "P1DT1H1M0S", dur: I.day 1.0 <> I.hours 1.0 <> I.minutes 1.0 <> I.seconds 0.0 } + , { str: "P1DT1H1M1.5S", dur: I.day 1.0 <> I.hours 1.0 <> I.minutes 1.0 <> I.seconds 1.5 } + , { str: "P1DT1H1.5M", dur: I.day 1.0 <> I.hours 1.0 <> I.minutes 1.5 } + , { str: "P1DT1.5H", dur: I.day 1.0 <> I.hours 1.5 } + ] <#> (\a -> a { dur = unsafeMkToIsoDuration a.dur }) + +invalidDurations :: Array String +invalidDurations = + [ "P1DT1.5H0M1S" -- TODO add some more from https://github.com/arnau/ISO8601/blob/master/spec/iso8601/duration_spec.rb + ] + +invalidIntervals :: Array String +invalidIntervals = + [ "P1DT1.5H0M1S" -- TODO add some more from https://github.com/arnau/ISO8601/blob/master/spec/iso8601/time_interval_spec.rb + ] + +recurrences ∷ Array { str :: String, rec :: Maybe Int } +recurrences = + [ {str: "1", rec: Just 1} + , {str: "", rec: Nothing} + , {str: "99", rec: Just 99} + , {str: "7", rec: Just 7} + ] + +dates :: Array { str:: String, date :: DateTime } +dates = + [ { str: "2015-07-22T00:00:00Z", date: makeDateTime 2015 7 22 0 0 0 0 } + , { str: "2015-07-23T11:12:13Z", date: makeDateTime 2015 7 23 11 12 13 0 } + , { str: "2015-07-29T13:00:00Z", date: makeDateTime 2015 7 29 13 0 0 0 } + ] + +forceIsoDuration :: ∀ a. I.Interval a DateTime -> I.Interval I.IsoDuration DateTime +forceIsoDuration = unsafeCoerce + +intervalStartEndTest ∷ ∀ e. Tests e Unit +intervalStartEndTest = for_ items test + where + test ({ start, end, rec }) = + assertEq + (unformatRecurringInterval $ "R" <> rec.str <> "/" <> start.str <> "/" <> end.str) + (Right $ I.RecurringInterval rec.rec $ forceIsoDuration $ I.StartEnd start.date end.date) + + items = do + start <- dates + end <- dates + rec <- recurrences + guard $ start.str /= end.str -- investigatge if this is needed + pure { start, end, rec} + +intervalDurationEndTest ∷ ∀ e. Tests e Unit +intervalDurationEndTest = for_ items test + where + test ({ dur, end, rec }) = + assertEq + (unformatRecurringInterval $ "R" <> rec.str <> "/" <> dur.str <> "/" <> end.str) + (Right $ I.RecurringInterval rec.rec $ forceIsoDuration $ I.DurationEnd dur.dur end.date) + + items = do + dur <- durations + end <- dates + rec <- recurrences + pure { dur, end, rec} + +intervalStartDurationTest ∷ ∀ e. Tests e Unit +intervalStartDurationTest = for_ items test + where + test ({ dur, start, rec }) = + assertEq + (unformatRecurringInterval $ "R" <> rec.str <> "/" <> start.str <> "/" <> dur.str) + (Right $ I.RecurringInterval rec.rec $ forceIsoDuration $ I.StartDuration start.date dur.dur) + + items = do + dur <- durations + start <- dates + rec <- recurrences + pure { dur, start, rec} + +-- +intervalJustDurationTest ∷ ∀ e. Tests e Unit +intervalJustDurationTest = for_ items test + where + test ({ dur, rec }) = + assertEq + (unformatRecurringInterval $ "R" <> rec.str <> "/" <> dur.str) + (Right $ I.RecurringInterval rec.rec $ forceIsoDuration $ I.JustDuration dur.dur) + + items = do + dur <- durations + rec <- recurrences + pure { dur, rec} + + +intervalTest :: ∀ e. Tests e Unit +intervalTest = do + log "- Data.Formatter.Interval" + + for_ durations \d -> do + assertEq (unformatDuration d.str) (Right d.dur) + + for_ dates \d -> do + assertEq (runP getDate d.str) (Right d.date) + + for_ invalidDurations \d -> do + let dur = (unformatDuration d) :: Either String I.IsoDuration + assertEq dur (Left "extracted Duration is not valid ISO duration") + + log "- Data.Formatter.Interval.StartEnd" + intervalStartEndTest + + log "- Data.Formatter.Interval.DurationEnd" + intervalDurationEndTest + + log "- Data.Formatter.Interval.StartDuration" + intervalStartDurationTest + + log "- Data.Formatter.Interval.JustDuration" + intervalJustDurationTest diff --git a/test/src/Main.purs b/test/src/Main.purs index 02235f9..6bb3c8e 100644 --- a/test/src/Main.purs +++ b/test/src/Main.purs @@ -1,46 +1,27 @@ module Test.Main where import Prelude -import Control.Monad.Aff.Console as AffC + import Data.Date as D import Data.DateTime as DTi -import Data.Interval as I import Data.Formatter.DateTime as FDT -import Data.Formatter.Interval as FI import Data.Formatter.Number as FN import Data.Time as T import Debug.Trace as DT -import Control.Monad.Aff (Aff, Canceler, runAff) -import Control.Monad.Aff.Class (liftAff) +import Control.Monad.Aff (Canceler) import Control.Monad.Eff (Eff, kind Effect) import Control.Monad.Eff.Console (CONSOLE) -import Control.Monad.Eff.Exception (EXCEPTION, error) -import Control.Monad.Error.Class (throwError) -import Control.Monad.State (StateT, put, get, execStateT) +import Control.Monad.Eff.Exception (EXCEPTION) import Data.DateTime (DateTime) import Data.Either (Either(..), either) import Data.Enum (toEnum) import Data.Functor.Mu (roll) -import Data.Maybe (fromMaybe, Maybe(..), maybe) - -type Tests e a = StateT Boolean (Aff (exception :: EXCEPTION, console :: CONSOLE | e)) a - - -execTests :: forall a e c. - StateT a (Aff ( process :: PROCESS | e)) c -> - a -> - Eff (process :: PROCESS | e) (Canceler ( process :: PROCESS | e )) -execTests fn state = runAff (\s -> exit 1) (\s -> exit 0) (execStateT fn state) - - -log :: forall e. String -> Tests e Unit -log message = liftAff $ AffC.log message +import Data.Maybe (fromMaybe) +import Test.Test (PROCESS, Tests, assertEq, exec, failTest, log) +import Test.Interval (intervalTest) -foreign import data PROCESS :: Effect -foreign import exit :: Int -> forall e. Eff (process :: PROCESS | e) Unit - fnOne ∷ FN.Formatter fnOne = @@ -74,11 +55,11 @@ fnThree = fdtOne ∷ FDT.Formatter fdtOne = - roll $ FDT.Placeholder "format string is " - $ roll $ FDT.YearFull - $ roll $ FDT.Placeholder "-" - $ roll $ FDT.MonthShort - $ roll FDT.End + roll $ FDT.Placeholder "format string is " $ + roll $ FDT.YearFull $ + roll $ FDT.Placeholder "-" $ + roll $ FDT.MonthShort $ + roll FDT.End numeralTests :: forall e. Tests e Unit @@ -136,52 +117,12 @@ testDateTime :: DTi.DateTime testDateTime = makeDateTime 2017 4 12 -assert :: forall e. String -> String -> Boolean -> Tests e Unit -assert _ success true = log $ " ✓ - Passed - " <> success -assert fail _ false = do - log $ " ☠ - Failed because " <> fail - put false - - -failTest :: forall e. String -> Tests e Unit -failTest message = do - log message - put false - - assertFormatting :: forall e. String -> String -> DateTime -> Tests e Unit assertFormatting target' format dateTime = do let result = FDT.formatDateTime format dateTime let target = Right target' assertEq result target -assertEq :: forall a e. Show a => Eq a => a -> a -> Tests e Unit -assertEq result target = - assert - ((show result) <> " ≠ " <> (show target)) - ((show result) <> " ≡ " <> (show target)) - (result == target) - -dur :: Either String (I.RecurringInterval I.IsoDuration DTi.DateTime) -dur = I.mkIsoDuration (I.day 1.0 <> I.hours 1.0 <> I.minutes 0.0 <> I.seconds 1.5) - <#> I.JustDuration - <#> I.RecurringInterval (Just 10) - # maybe (Left "boom") Right - -timeInterval :: forall e. Tests e Unit -timeInterval = do - log "- Data.Formatter.Parser.Interval.parseDuration" - assertEq (FI.unformatDuration "P1W") (Right $ I.day 7.0) - assertEq (FI.unformatDuration "P1.0W") (Right $ I.day 7.0) - assertEq (FI.unformatDuration "P1.9748600D") (Right $ I.day 1.97486) - assertEq (FI.unformatDuration "P1DT1H1M1S") (Right $ I.day 1.0 <> I.hours 1.0 <> I.minutes 1.0 <> I.seconds 1.0) - assertEq (FI.unformatDuration "P1DT1H1M0S") (Right $ I.day 1.0 <> I.hours 1.0 <> I.minutes 1.0 <> I.seconds 0.0) - assertEq (FI.unformatDuration "P1DT1H1M1S" <#> I.isValidIsoDuration) (Right true) - assertEq (FI.unformatDuration "P1DT1H1M1.5S" <#> I.isValidIsoDuration) (Right true) - assertEq (FI.unformatDuration "P1DT1H1.5M0S" <#> I.isValidIsoDuration) (Right true) - assertEq (FI.unformatDuration "P1DT1.5H0M0S" <#> I.isValidIsoDuration) (Right true) - assertEq (FI.unformatDuration "P1DT1.5H0M1S" <#> I.isValidIsoDuration) (Right false) - assertEq (FI.unformatRecurringInterval "R10/P1DT1H0M1.5S") dur timeTest :: forall e. Tests e Unit timeTest = do @@ -239,13 +180,9 @@ formattingTests = do main :: forall e. Eff ( process :: PROCESS, exception :: EXCEPTION, console :: CONSOLE | e) (Canceler ( process :: PROCESS, exception :: EXCEPTION, console :: CONSOLE | e)) -main = execTests tests true - where - tests = do +main = exec do log "Testing time functions..." - timeTest - timeInterval - passed <- get - when (passed /= true) (throwError (error "Tests did not pass.")) + -- timeTest + intervalTest --numeralTests --formattingTests diff --git a/test/src/Main.js b/test/src/Test.js similarity index 100% rename from test/src/Main.js rename to test/src/Test.js diff --git a/test/src/Test.purs b/test/src/Test.purs new file mode 100644 index 0000000..fe45210 --- /dev/null +++ b/test/src/Test.purs @@ -0,0 +1,57 @@ +module Test.Test where + +import Prelude +import Control.Monad.Aff.Console as AffC +import Control.Monad.Aff (Aff, Canceler, runAff) +import Control.Monad.Aff.Class (liftAff) +import Control.Monad.Eff (Eff, kind Effect) +import Control.Monad.Eff.Console (CONSOLE) +import Control.Monad.Eff.Exception (EXCEPTION, error) +import Control.Monad.Error.Class (throwError) +import Control.Monad.State (StateT, get, put, execStateT) + + + +-- TODO switch to https://github.com/owickstrom/purescript-spec + +foreign import data PROCESS :: Effect +foreign import exit :: Int -> forall e. Eff (process :: PROCESS | e) Unit + + +type Tests e a = StateT Boolean (Aff (exception :: EXCEPTION, console :: CONSOLE | e)) a + + +exec :: forall e c. + StateT Boolean (Aff ( process :: PROCESS | e)) c -> + Eff (process :: PROCESS | e) (Canceler ( process :: PROCESS | e )) +exec t = flip execTests true $ do + void t + passed <- get + when (passed /= true) (throwError (error "Tests did not pass.")) + + +execTests :: forall a e c. + StateT a (Aff ( process :: PROCESS | e)) c -> + a -> + Eff (process :: PROCESS | e) (Canceler ( process :: PROCESS | e )) +execTests fn state = runAff (\s -> exit 1) (\s -> exit 0) (execStateT fn state) + + +log :: forall e. String -> Tests e Unit +log message = liftAff $ AffC.log message + + +assertEq :: forall a e. Show a => Eq a => a -> a -> Tests e Unit +assertEq result target = assert + (show result <> " ≠ " <> show target) + (show result <> " ≡ target") + (result == target) + +assert :: forall e. String -> String -> Boolean -> Tests e Unit +assert _ success true = log $ " ✓ - Passed - " <> success +assert fail _ false = failTest $ " ☠ - Failed because " <> fail + +failTest :: forall e. String -> Tests e Unit +failTest message = do + log message + put false From 9dcae78eb7cb70762b4ba501722da7bc0769cc45 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Fri, 21 Apr 2017 19:45:35 +0400 Subject: [PATCH 16/88] add Eq and Show instance for datetime.FormatterF --- src/Data/Formatter/DateTime.purs | 46 ++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/src/Data/Formatter/DateTime.purs b/src/Data/Formatter/DateTime.purs index 4d9972c..45007ce 100644 --- a/src/Data/Formatter/DateTime.purs +++ b/src/Data/Formatter/DateTime.purs @@ -33,6 +33,7 @@ import Data.Maybe (Maybe(..), maybe, isJust, fromMaybe) import Data.Newtype (unwrap) import Data.String as Str import Data.Time as T +import Data.Eq (class Eq1) import Data.Time.Duration as Dur import Data.Formatter.Internal (foldDigits) import Data.Formatter.Parser.Utils (runP) @@ -84,6 +85,51 @@ instance formatterFFunctor ∷ Functor FormatterF where map f (Placeholder str a) = Placeholder str $ f a map f End = End +instance formatterFShow ∷ Show a => Show (FormatterF a) where + show (YearFull a) = "(YearFull" <> (show a) <> "c" + show (YearTwoDigits a) = "(YearTwoDigits" <> (show a) <> ")" + show (YearAbsolute a) = "(YearAbsolute" <> (show a) <> ")" + show (MonthFull a) = "(MonthFull" <> (show a) <> ")" + show (MonthShort a) = "(MonthShort" <> (show a) <> ")" + show (MonthTwoDigits a) = "(MonthTwoDigits" <> (show a) <> ")" + show (DayOfMonthTwoDigits a) = "(DayOfMonthTwoDigits" <> (show a) <> ")" + show (DayOfMonth a) = "(DayOfMonth" <> (show a) <> ")" + show (UnixTimestamp a) = "(UnixTimestamp" <> (show a) <> ")" + show (DayOfWeek a) = "(DayOfWeek" <> (show a) <> ")" + show (Hours24 a) = "(Hours24" <> (show a) <> ")" + show (Hours12 a) = "(Hours12" <> (show a) <> ")" + show (Meridiem a) = "(Meridiem" <> (show a) <> ")" + show (Minutes a) = "(Minutes" <> (show a) <> ")" + show (Seconds a) = "(Seconds" <> (show a) <> ")" + show (Milliseconds a) = "(Milliseconds" <> (show a) <> ")" + show (Placeholder str a) = "(Placeholder" <> (show str) <> " "<> (show a) <> ")" + show End = "End" + +instance formatterFEq ∷ Eq a => Eq (FormatterF a) where + eq (YearFull a) (YearFull b) = eq a b + eq (YearTwoDigits a) (YearTwoDigits b) = eq a b + eq (YearAbsolute a) (YearAbsolute b) = eq a b + eq (MonthFull a) (MonthFull b) = eq a b + eq (MonthShort a) (MonthShort b) = eq a b + eq (MonthTwoDigits a) (MonthTwoDigits b) = eq a b + eq (DayOfMonthTwoDigits a) (DayOfMonthTwoDigits b) = eq a b + eq (DayOfMonth a) (DayOfMonth b) = eq a b + eq (UnixTimestamp a) (UnixTimestamp b) = eq a b + eq (DayOfWeek a) (DayOfWeek b) = eq a b + eq (Hours24 a) (Hours24 b) = eq a b + eq (Hours12 a) (Hours12 b) = eq a b + eq (Meridiem a) (Meridiem b) = eq a b + eq (Minutes a) (Minutes b) = eq a b + eq (Seconds a) (Seconds b) = eq a b + eq (Milliseconds a) (Milliseconds b) = eq a b + eq (Placeholder stra a) (Placeholder strb b) = eq stra strb && eq a b + eq End End = true + eq _ _ = false + +instance formatterFEq1 :: Eq1 FormatterF where + eq1 = eq + + type Formatter = Mu FormatterF printFormatterF From dcb68153b78b39a29a326e647914c924b27e265d Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Fri, 21 Apr 2017 19:46:13 +0400 Subject: [PATCH 17/88] make formatterF parser error a bit more usable --- src/Data/Formatter/DateTime.purs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Data/Formatter/DateTime.purs b/src/Data/Formatter/DateTime.purs index 45007ce..647085d 100644 --- a/src/Data/Formatter/DateTime.purs +++ b/src/Data/Formatter/DateTime.purs @@ -33,6 +33,7 @@ import Data.Maybe (Maybe(..), maybe, isJust, fromMaybe) import Data.Newtype (unwrap) import Data.String as Str import Data.Time as T +import Control.Alt ((<|>)) import Data.Eq (class Eq1) import Data.Time.Duration as Dur import Data.Formatter.Internal (foldDigits) @@ -182,6 +183,7 @@ placeholderContent = $ PC.try $ Arr.some $ PS.noneOf + -- TODO why this chars: 'Q', 'X', 'W' are included here? $ Str.toCharArray "YQMDXWEHhamsS" formatterFParser @@ -207,7 +209,7 @@ formatterFParser cb = , (PC.try $ PS.string "SSS") *> map Milliseconds cb , (Placeholder <$> placeholderContent <*> cb) , (PS.eof $> End) - ] + ] <|> (P.fail "Format contains invalid string") formatParser ∷ P.Parser String Formatter formatParser = @@ -394,6 +396,9 @@ unformatFParser cb = case _ of when (dow > 7 || dow < 1) $ P.fail "Incorrect day of week" cb a Hours24 a → do + -- TODO because `some` is parsing digits it will consume more then 2 + -- even when input is properly formatted in case of `HHmmss` + -- which results in need to add some seperators to format `HH:mm:ss` ds ← some parseDigit let hh = foldDigits ds when (Arr.length ds /= 2 || hh < 0 || hh > 23) $ P.fail "Incorrect 24 hour" From 59fc87d38ae5ba5b18b42245725434f95c533019 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Fri, 21 Apr 2017 19:46:58 +0400 Subject: [PATCH 18/88] include error position in error string of parser result --- src/Data/Formatter/Parser/Utils.purs | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/Data/Formatter/Parser/Utils.purs b/src/Data/Formatter/Parser/Utils.purs index c45032d..78c9f29 100644 --- a/src/Data/Formatter/Parser/Utils.purs +++ b/src/Data/Formatter/Parser/Utils.purs @@ -1,16 +1,26 @@ -module Data.Formatter.Parser.Utils where +module Data.Formatter.Parser.Utils + ( oneOfAs + , runP + ) where import Prelude import Data.Tuple (Tuple(..)) -import Text.Parsing.Parser as P +import Text.Parsing.Parser (ParserT, Parser, runParser, ParseError, parseErrorMessage, parseErrorPosition) +import Text.Parsing.Parser.Pos (Position(..)) import Text.Parsing.Parser.Combinators as PC import Data.Bifunctor (lmap) import Data.Foldable (class Foldable) import Data.Either (Either) -oneOfAs :: ∀ c s m f a b. Functor f => Foldable f => Monad m => (a -> P.ParserT s m b) -> f (Tuple a c) -> P.ParserT s m c +oneOfAs :: ∀ c s m f a b. Functor f => Foldable f => Monad m => (a -> ParserT s m b) -> f (Tuple a c) -> ParserT s m c oneOfAs p xs = PC.choice $ (\(Tuple s r) -> p s $> r) <$> xs -runP :: ∀ a. P.Parser String a → String → Either String a -runP p s = lmap P.parseErrorMessage $ P.runParser s p +runP :: ∀ s a. Parser s a → s → Either String a +runP p s = lmap printError $ runParser s p + +printError :: ParseError -> String +printError err = parseErrorMessage err <> "@" <> (printPosition $ parseErrorPosition err) + +printPosition :: Position -> String +printPosition (Position {line, column}) = show line <> ":" <> show column From d8260977e61026d10a7ea8c75083cd18d66847e9 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Fri, 21 Apr 2017 19:47:26 +0400 Subject: [PATCH 19/88] use ps-spec and refactor tests to more generative style --- bower.json | 3 +- test/src/Interval.purs | 75 +++++----- test/src/Main.purs | 332 +++++++++++++++++++++++++---------------- test/src/Test.js | 5 - test/src/Test.purs | 57 ------- 5 files changed, 241 insertions(+), 231 deletions(-) delete mode 100644 test/src/Test.js delete mode 100644 test/src/Test.purs diff --git a/bower.json b/bower.json index 766a9ee..ec6c7b2 100644 --- a/bower.json +++ b/bower.json @@ -25,6 +25,7 @@ "purescript-aff": "^3.0.0", "purescript-console": "^3.0.0", "purescript-psci-support": "^3.0.0", - "purescript-debug": "^3.0.0" + "purescript-debug": "^3.0.0", + "purescript-spec": "^0.13.0" } } diff --git a/test/src/Interval.purs b/test/src/Interval.purs index 0e74058..2afdb14 100644 --- a/test/src/Interval.purs +++ b/test/src/Interval.purs @@ -11,14 +11,16 @@ import Control.MonadZero (guard) import Data.Foldable (for_) import Data.Time (Time(..)) import Data.Date (canonicalDate) -import Data.Formatter.Interval (unformatDuration, unformatRecurringInterval, getDate, class HasDuration) +import Data.Formatter.Interval (unformatDuration, unformatRecurringInterval, getDate) import Data.Either (Either(..)) import Data.Maybe (Maybe(..), fromJust, fromMaybe) import Data.Enum (toEnum) import Partial.Unsafe (unsafePartialBecause) -import Test.Test (Tests, assertEq, log) import Unsafe.Coerce (unsafeCoerce) import Data.Formatter.Parser.Utils (runP) +import Control.Monad.Aff (Aff) +import Test.Spec (describe, it, Spec) +import Test.Spec.Assertions (shouldEqual) unsafeMkToIsoDuration :: I.Duration -> I.IsoDuration unsafeMkToIsoDuration d = unsafePartialBecause "the duration must be valid ISO duration" fromJust $ I.mkIsoDuration d @@ -41,14 +43,14 @@ durations = , { str: "P1DT1.5H", dur: I.day 1.0 <> I.hours 1.5 } ] <#> (\a -> a { dur = unsafeMkToIsoDuration a.dur }) -invalidDurations :: Array String +invalidDurations :: Array { str :: String, pos :: String} invalidDurations = - [ "P1DT1.5H0M1S" -- TODO add some more from https://github.com/arnau/ISO8601/blob/master/spec/iso8601/duration_spec.rb + [ { str: "P1DT1.5H0M1S", pos:"1:13" } -- TODO add some more from https://github.com/arnau/ISO8601/blob/master/spec/iso8601/duration_spec.rb ] invalidIntervals :: Array String invalidIntervals = - [ "P1DT1.5H0M1S" -- TODO add some more from https://github.com/arnau/ISO8601/blob/master/spec/iso8601/time_interval_spec.rb + [ -- TODO add some more from https://github.com/arnau/ISO8601/blob/master/spec/iso8601/time_interval_spec.rb ] recurrences ∷ Array { str :: String, rec :: Maybe Int } @@ -69,11 +71,11 @@ dates = forceIsoDuration :: ∀ a. I.Interval a DateTime -> I.Interval I.IsoDuration DateTime forceIsoDuration = unsafeCoerce -intervalStartEndTest ∷ ∀ e. Tests e Unit +intervalStartEndTest ∷ ∀ e. Aff e Unit intervalStartEndTest = for_ items test where test ({ start, end, rec }) = - assertEq + shouldEqual (unformatRecurringInterval $ "R" <> rec.str <> "/" <> start.str <> "/" <> end.str) (Right $ I.RecurringInterval rec.rec $ forceIsoDuration $ I.StartEnd start.date end.date) @@ -84,11 +86,11 @@ intervalStartEndTest = for_ items test guard $ start.str /= end.str -- investigatge if this is needed pure { start, end, rec} -intervalDurationEndTest ∷ ∀ e. Tests e Unit +intervalDurationEndTest ∷ ∀ e. Aff e Unit intervalDurationEndTest = for_ items test where test ({ dur, end, rec }) = - assertEq + shouldEqual (unformatRecurringInterval $ "R" <> rec.str <> "/" <> dur.str <> "/" <> end.str) (Right $ I.RecurringInterval rec.rec $ forceIsoDuration $ I.DurationEnd dur.dur end.date) @@ -98,11 +100,11 @@ intervalDurationEndTest = for_ items test rec <- recurrences pure { dur, end, rec} -intervalStartDurationTest ∷ ∀ e. Tests e Unit +intervalStartDurationTest ∷ ∀ e. Aff e Unit intervalStartDurationTest = for_ items test where test ({ dur, start, rec }) = - assertEq + shouldEqual (unformatRecurringInterval $ "R" <> rec.str <> "/" <> start.str <> "/" <> dur.str) (Right $ I.RecurringInterval rec.rec $ forceIsoDuration $ I.StartDuration start.date dur.dur) @@ -112,12 +114,11 @@ intervalStartDurationTest = for_ items test rec <- recurrences pure { dur, start, rec} --- -intervalJustDurationTest ∷ ∀ e. Tests e Unit +intervalJustDurationTest ∷ ∀ e. Aff e Unit intervalJustDurationTest = for_ items test where test ({ dur, rec }) = - assertEq + shouldEqual (unformatRecurringInterval $ "R" <> rec.str <> "/" <> dur.str) (Right $ I.RecurringInterval rec.rec $ forceIsoDuration $ I.JustDuration dur.dur) @@ -126,29 +127,23 @@ intervalJustDurationTest = for_ items test rec <- recurrences pure { dur, rec} - -intervalTest :: ∀ e. Tests e Unit -intervalTest = do - log "- Data.Formatter.Interval" - - for_ durations \d -> do - assertEq (unformatDuration d.str) (Right d.dur) - - for_ dates \d -> do - assertEq (runP getDate d.str) (Right d.date) - - for_ invalidDurations \d -> do - let dur = (unformatDuration d) :: Either String I.IsoDuration - assertEq dur (Left "extracted Duration is not valid ISO duration") - - log "- Data.Formatter.Interval.StartEnd" - intervalStartEndTest - - log "- Data.Formatter.Interval.DurationEnd" - intervalDurationEndTest - - log "- Data.Formatter.Interval.StartDuration" - intervalStartDurationTest - - log "- Data.Formatter.Interval.JustDuration" - intervalJustDurationTest +intervalTest ∷ ∀ e. Spec e Unit +intervalTest = describe "Data.Formatter.Interval" do + it "should unformat valid durations" do + for_ durations \d -> do + (unformatDuration d.str) `shouldEqual` (Right d.dur) + + it "should unformat valid ISO DateTime" do + for_ dates \d -> do + (runP getDate d.str) `shouldEqual` (Right d.date) + + it "shouldn't unformat invalid ISO DateTime" do + for_ invalidDurations \d -> do + let dur = (unformatDuration d.str) :: Either String I.IsoDuration + dur `shouldEqual` (Left $ "extracted Duration is not valid ISO duration@" <> d.pos) + + describe "Interval variations" do + it "should unformat Interval.StartEnd" intervalStartEndTest + it "should unformat Interval.DurationEnd" intervalDurationEndTest + it "should unformat Interval.StartDuration" intervalStartDurationTest + it "should unformat Interval.JustDuration" intervalJustDurationTest diff --git a/test/src/Main.purs b/test/src/Main.purs index 6bb3c8e..29b9188 100644 --- a/test/src/Main.purs +++ b/test/src/Main.purs @@ -5,23 +5,26 @@ import Prelude import Data.Date as D import Data.DateTime as DTi +import Data.Foldable (for_) import Data.Formatter.DateTime as FDT import Data.Formatter.Number as FN import Data.Time as T -import Debug.Trace as DT -import Control.Monad.Aff (Canceler) +import Control.Monad.Aff (Aff) import Control.Monad.Eff (Eff, kind Effect) -import Control.Monad.Eff.Console (CONSOLE) -import Control.Monad.Eff.Exception (EXCEPTION) import Data.DateTime (DateTime) -import Data.Either (Either(..), either) +import Data.Either (Either(..)) import Data.Enum (toEnum) import Data.Functor.Mu (roll) import Data.Maybe (fromMaybe) -import Test.Test (PROCESS, Tests, assertEq, exec, failTest, log) import Test.Interval (intervalTest) +import Control.Alternative (class Alternative, empty) +import Test.Spec.Reporter.Console (consoleReporter) +import Test.Spec.Runner (RunnerEffects, run) +import Test.Spec (describe, it, Spec) +import Test.Spec.Assertions (shouldEqual) + fnOne ∷ FN.Formatter fnOne = @@ -53,136 +56,209 @@ fnThree = } -fdtOne ∷ FDT.Formatter -fdtOne = - roll $ FDT.Placeholder "format string is " $ - roll $ FDT.YearFull $ - roll $ FDT.Placeholder "-" $ - roll $ FDT.MonthShort $ - roll FDT.End - - -numeralTests :: forall e. Tests e Unit -numeralTests = do - log $ "\nNUMERAL TESTS\n" - - log $ "\nPRINT FORMATTER" - log $ FN.printFormatter fnOne - log $ FN.printFormatter fnTwo - log $ FN.printFormatter fnThree - - log $ "\nPARSE FORMAT STRING" - DT.traceAnyA $ FN.parseFormatString "000,0.00" - DT.traceAnyA $ FN.parseFormatString "000" - DT.traceAnyA $ FN.parseFormatString "0a" - DT.traceAnyA $ FN.parseFormatString "-0,0.000" - DT.traceAnyA $ FN.parseFormatString "+000.0" - - log $ "\n FORMAT" - log $ FN.format fnOne 100.2 - log $ FN.format fnTwo 100.1 - log $ FN.format fnThree 100.3 - log $ FN.format fnThree 10004000.0 - - log $ "\n UNFORMAT" - DT.traceAnyA $ FN.unformat fnOne "001.12" - DT.traceAnyA $ FN.unformat fnOne "-123.12" - DT.traceAnyA $ FN.unformat fnOne "12.12" - DT.traceAnyA $ FN.unformat fnThree "+123" - DT.traceAnyA $ FN.unformat fnTwo "-100,000.1234" - - log $ "\n FORMAT NUMBER" - DT.traceAnyA $ FN.formatNumber "00.00" 12.0 - DT.traceAnyA $ FN.formatNumber "00000,0.000" 123345.1235 - DT.traceAnyA $ FN.formatNumber "0.0" 123345.1235 - DT.traceAnyA $ FN.formatNumber "0.0" (-123345.1235) - - log $ "\n UNFORMAT NUMBER" - DT.traceAnyA $ FN.unformatNumber "0.00" "12.00" - - --- April 12th 2017 at 11:34:34:234 --- 4/12/2017 +-- TODO refactor this +-- numeralTests :: forall e. Spec e Unit +-- numeralTests = do +-- log $ "\nNUMERAL TESTS\n" +-- +-- log $ "\nPRINT FORMATTER" +-- log $ FN.printFormatter fnOne +-- log $ FN.printFormatter fnTwo +-- log $ FN.printFormatter fnThree +-- +-- log $ "\nPARSE FORMAT STRING" +-- DT.traceAnyA $ FN.parseFormatString "000,0.00" +-- DT.traceAnyA $ FN.parseFormatString "000" +-- DT.traceAnyA $ FN.parseFormatString "0a" +-- DT.traceAnyA $ FN.parseFormatString "-0,0.000" +-- DT.traceAnyA $ FN.parseFormatString "+000.0" +-- +-- log $ "\n FORMAT" +-- log $ FN.format fnOne 100.2 +-- log $ FN.format fnTwo 100.1 +-- log $ FN.format fnThree 100.3 +-- log $ FN.format fnThree 10004000.0 +-- +-- log $ "\n UNFORMAT" +-- DT.traceAnyA $ FN.unformat fnOne "001.12" +-- DT.traceAnyA $ FN.unformat fnOne "-123.12" +-- DT.traceAnyA $ FN.unformat fnOne "12.12" +-- DT.traceAnyA $ FN.unformat fnThree "+123" +-- DT.traceAnyA $ FN.unformat fnTwo "-100,000.1234" +-- +-- log $ "\n FORMAT NUMBER" +-- DT.traceAnyA $ FN.formatNumber "00.00" 12.0 +-- DT.traceAnyA $ FN.formatNumber "00000,0.000" 123345.1235 +-- DT.traceAnyA $ FN.formatNumber "0.0" 123345.1235 +-- DT.traceAnyA $ FN.formatNumber "0.0" (-123345.1235) +-- +-- log $ "\n UNFORMAT NUMBER" +-- DT.traceAnyA $ FN.unformatNumber "0.00" "12.00" +-- + makeDateTime ∷ Int -> Int -> Int -> DTi.DateTime makeDateTime year month day = DTi.DateTime (D.canonicalDate (fromMaybe bottom $ toEnum year) (fromMaybe bottom $ toEnum month) (fromMaybe bottom $ toEnum day)) + -- XXX at 11:34:34:234 (T.Time (fromMaybe bottom $ toEnum 11) (fromMaybe bottom $ toEnum 34) (fromMaybe bottom $ toEnum 34) (fromMaybe bottom $ toEnum 234)) + + testDateTime :: DTi.DateTime -testDateTime = makeDateTime 2017 4 12 - - -assertFormatting :: forall e. String -> String -> DateTime -> Tests e Unit -assertFormatting target' format dateTime = do - let result = FDT.formatDateTime format dateTime - let target = Right target' - assertEq result target - - -timeTest :: forall e. Tests e Unit -timeTest = do - log "- Data.Formatter.DateTime.formatDateTime" - - -- var a = moment( - -- 'April 12th 2017 at 11:34:34:234', - -- 'MMMM Do YYYY [at] HH:mm:ss:SSS' - -- ); - -- a.format('MMMM Do YYYY [at] HH:mm:ss:SSS') - -- testDateTime = April 12th 2017 at 11:34:34:234 - assertFormatting "04/12/2017" "MM/DD/YYYY" testDateTime - assertFormatting "April" "MMMM" testDateTime - assertFormatting "2017-12-04" "YYYY-DD-MM" testDateTime - assertFormatting "2017-Apr" "YYYY-MMM" testDateTime - assertFormatting "Apr 1" "MMM D" (makeDateTime 2017 4 1) - - -- This should probably be am (lowercase), if the desired - -- functionality of the library is to mirror momentjs - assertFormatting "11:34:34:234 AM" "hh:mm:ss:SSS a" testDateTime - assertFormatting "17" "YY" testDateTime - log " --- Format 20017 with YY" - assertFormatting "17" "YY" (makeDateTime 20017 4 12) - log " --- Format 0 with YY" - assertFormatting "00" "YY" (makeDateTime 0 4 12) - log " --- Format -1 with YY" - assertFormatting "01" "YY" (makeDateTime (-1) 4 12) - - log "- Data.Formatter.DateTime.unformatDateTime " - - let dt = FDT.unformatDateTime "YYYY-DD-MM SSS" "2017-12-04 234" - either - (const $ failTest "Could not parse 017-12-04 234") - (assertFormatting "2017-12-04 234" "YYYY-DD-MM SSS") - dt - -formattingTests :: forall e. Tests e Unit -formattingTests = do - log $ "\nPARSE FORMAT STRING" - - DT.traceAnyA $ FDT.parseFormatString "YYYY-MM-DD" - DT.traceAnyA $ FDT.parseFormatString "YY-Q-dddd HH:mm Z" - - log $ "\nFORMAT" - DT.traceAnyA $ FDT.parseFormatString "YYYY-MM-DD trololo Q" <#> flip FDT.format testDateTime - - log $ "\nUNFORMAT" - case FDT.parseFormatString "DD-MM-YYYY HH-:-mm" of - Left _ → DT.traceAnyA "?" - Right f → DT.traceAnyA $ FDT.unformat f "12-10-1345 04-:-32" - - log $ "\nUNFORMATDATETIME" - DT.traceAnyA $ FDT.unformatDateTime "YYYY-DD-MM SSS" "3456-09-10 333" - -main :: forall e. - Eff ( process :: PROCESS, exception :: EXCEPTION, console :: CONSOLE | e) - (Canceler ( process :: PROCESS, exception :: EXCEPTION, console :: CONSOLE | e)) -main = exec do - log "Testing time functions..." - -- timeTest - intervalTest - --numeralTests - --formattingTests +testDateTime = makeDateTime 2017 4 12 -- April 12th 2017 + + +assertFormatting :: forall e. String -> String -> DateTime -> Aff e Unit +assertFormatting target' format dateTime = result `shouldEqual` target + where + result = FDT.formatDateTime format dateTime + target = Right target' + +dates :: Array DateTime +dates = + [ testDateTime + , makeDateTime 2017 4 1 + , makeDateTime 20017 4 12 + , makeDateTime 0 4 12 + , makeDateTime (-1) 4 12 + ] + +timeTest :: forall e. Spec e Unit +timeTest = describe "Data.Formatter.DateTime" do + describe "formatDateTime" do + it "should formatt dateTime" do + let items = + -- TODO check if this comments are still valid: + -- var a = moment( + -- 'April 12th 2017 at 11:34:34:234', + -- 'MMMM Do YYYY [at] HH:mm:ss:SSS' + -- ); + -- a.format('MMMM Do YYYY [at] HH:mm:ss:SSS') + -- testDateTime = April 12th 2017 at 11:34:34:234 + [ { format: "MM/DD/YYYY", dateStr: "04/12/2017" , date: testDateTime} + , { format: "MMMM", dateStr: "April" , date: testDateTime} + , { format: "YYYY-DD-MM", dateStr: "2017-12-04" , date: testDateTime} + , { format: "YYYY-MMM", dateStr: "2017-Apr" , date: testDateTime} + , { format: "MMM D", dateStr: "Apr 1" , date: makeDateTime 2017 4 1} + -- TODO check if this comments are still valid: + -- This should probably be am (lowercase), if the desired + -- functionality of the library is to mirror momentjs + , { format: "hh:mm:ss:SSS a", dateStr: "11:34:34:234 AM" , date: testDateTime} + , { format: "YY", dateStr: "17" , date: testDateTime} + + , { format: "YY", dateStr: "17" , date: makeDateTime 20017 4 12} -- Format 20017 with YY + , { format: "YY", dateStr: "00" , date: makeDateTime 0 4 12} -- Format 0 with YY + , { format: "YY", dateStr: "01" , date: makeDateTime (-1) 4 12} -- Format -1 with YY + ] + for_ items \({ format, dateStr, date }) -> do + (format `FDT.formatDateTime` date) `shouldEqual` (Right dateStr) + + describe "parseFormatString" do + it "should parse" do + for_ dateformats \f -> (FDT.parseFormatString f.str) `shouldEqual` (Right f.format) + + it "shouldn't parse" do + for_ invalidDateformats \f -> (FDT.parseFormatString f.str) `shouldEqual` (Left $ "Format contains invalid string@" <> f.pos) + + it "s ≡ format (unformat s)" do + let items = [ {date: "2017-12-04 234", format: "YYYY-DD-MM SSS" } , {date: "3456-09-10 333", format: "YYYY-DD-MM SSS" } ] + for_ items \({date, format}) -> do + (FDT.unformatDateTime format date >>= FDT.formatDateTime format) `shouldEqual` (Right date) + + it "s ≡ unformat (format s)" do + -- TODO check if it's compiler bug that we can't use do notation here + let items = ({ date: _, format: _ }) <$> dates <*> (dateformats # filter _.lossless <#> _.format) + for_ items \({ date, format }) -> do + FDT.unformat format (FDT.format format date) `shouldEqual` (Right date) + + +invalidDateformats ∷ Array { str :: String , pos :: String } +invalidDateformats = + [ { str: "YY-Q-dddd HH:mm Z", pos: "1:4" } + , { str: "YYYY-MM-DD Q", pos: "1:12" } + ] + +dateformats ∷ Array { str :: String , lossless :: Boolean, format :: FDT.Formatter } +dateformats = + [ { str: "YYYY-MM-DD" + , lossless: false + , format: + roll $ FDT.YearFull $ + roll $ FDT.Placeholder "-" $ + roll $ FDT.MonthTwoDigits $ + roll $ FDT.Placeholder "-" $ + roll $ FDT.DayOfMonthTwoDigits $ + roll FDT.End + } + , { str: "Y-MM-DD HH:mm:ss:SSS" + , lossless: true + , format: + roll $ FDT.YearAbsolute $ + roll $ FDT.Placeholder "-" $ + roll $ FDT.MonthTwoDigits $ + roll $ FDT.Placeholder "-" $ + roll $ FDT.DayOfMonthTwoDigits $ + roll $ FDT.Placeholder " " $ + roll $ FDT.Hours24 $ + roll $ FDT.Placeholder ":" $ + roll $ FDT.Minutes $ + roll $ FDT.Placeholder ":" $ + roll $ FDT.Seconds $ + roll $ FDT.Placeholder ":" $ + roll $ FDT.Milliseconds $ + roll FDT.End + } + , { str: "YY-Z-DD HH:mm Z" + , lossless: false + , format: + roll $ FDT.YearTwoDigits $ + roll $ FDT.Placeholder "-Z-" $ + roll $ FDT.DayOfMonthTwoDigits $ + roll $ FDT.Placeholder " " $ + roll $ FDT.Hours24 $ + roll $ FDT.Placeholder ":" $ + roll $ FDT.Minutes $ + roll $ FDT.Placeholder " Z" $ + roll FDT.End + } + , { str: "DD-MM-YYYY trololo HH-:-mm" + , lossless: false + , format: + roll $ FDT.DayOfMonthTwoDigits $ + roll $ FDT.Placeholder "-" $ + roll $ FDT.MonthTwoDigits $ + roll $ FDT.Placeholder "-" $ + roll $ FDT.YearFull $ + roll $ FDT.Placeholder " trololo " $ + roll $ FDT.Hours24 $ + roll $ FDT.Placeholder "-:-" $ + roll $ FDT.Minutes $ + roll FDT.End + } + , { str: "YYYY-DD-MM SSS" + , lossless: false + , format: + roll $ FDT.YearFull $ + roll $ FDT.Placeholder "-" $ + roll $ FDT.DayOfMonthTwoDigits $ + roll $ FDT.Placeholder "-" $ + roll $ FDT.MonthTwoDigits $ + roll $ FDT.Placeholder " " $ + roll $ FDT.Milliseconds $ + roll FDT.End + } + ] + +filter :: ∀ m a. Alternative m => Monad m => (a -> Boolean) -> m a -> m a +filter f m = m >>= \x -> if f x then pure x else empty + +main :: Eff (RunnerEffects ()) Unit +main = run [consoleReporter] do + intervalTest + timeTest +-- --numeralTests diff --git a/test/src/Test.js b/test/src/Test.js deleted file mode 100644 index b089e4e..0000000 --- a/test/src/Test.js +++ /dev/null @@ -1,5 +0,0 @@ -'use strict'; - -exports.exit = function(int) { - process.exit(int); -} diff --git a/test/src/Test.purs b/test/src/Test.purs deleted file mode 100644 index fe45210..0000000 --- a/test/src/Test.purs +++ /dev/null @@ -1,57 +0,0 @@ -module Test.Test where - -import Prelude -import Control.Monad.Aff.Console as AffC -import Control.Monad.Aff (Aff, Canceler, runAff) -import Control.Monad.Aff.Class (liftAff) -import Control.Monad.Eff (Eff, kind Effect) -import Control.Monad.Eff.Console (CONSOLE) -import Control.Monad.Eff.Exception (EXCEPTION, error) -import Control.Monad.Error.Class (throwError) -import Control.Monad.State (StateT, get, put, execStateT) - - - --- TODO switch to https://github.com/owickstrom/purescript-spec - -foreign import data PROCESS :: Effect -foreign import exit :: Int -> forall e. Eff (process :: PROCESS | e) Unit - - -type Tests e a = StateT Boolean (Aff (exception :: EXCEPTION, console :: CONSOLE | e)) a - - -exec :: forall e c. - StateT Boolean (Aff ( process :: PROCESS | e)) c -> - Eff (process :: PROCESS | e) (Canceler ( process :: PROCESS | e )) -exec t = flip execTests true $ do - void t - passed <- get - when (passed /= true) (throwError (error "Tests did not pass.")) - - -execTests :: forall a e c. - StateT a (Aff ( process :: PROCESS | e)) c -> - a -> - Eff (process :: PROCESS | e) (Canceler ( process :: PROCESS | e )) -execTests fn state = runAff (\s -> exit 1) (\s -> exit 0) (execStateT fn state) - - -log :: forall e. String -> Tests e Unit -log message = liftAff $ AffC.log message - - -assertEq :: forall a e. Show a => Eq a => a -> a -> Tests e Unit -assertEq result target = assert - (show result <> " ≠ " <> show target) - (show result <> " ≡ target") - (result == target) - -assert :: forall e. String -> String -> Boolean -> Tests e Unit -assert _ success true = log $ " ✓ - Passed - " <> success -assert fail _ false = failTest $ " ☠ - Failed because " <> fail - -failTest :: forall e. String -> Tests e Unit -failTest message = do - log message - put false From e468816092fddfc59393202c133369ee09a31fb3 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Fri, 21 Apr 2017 23:56:08 +0400 Subject: [PATCH 20/88] remove some outdated commets --- src/Data/Formatter/DateTime.purs | 11 +++++++---- test/src/Main.purs | 23 +++++++++-------------- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/src/Data/Formatter/DateTime.purs b/src/Data/Formatter/DateTime.purs index 647085d..aab8396 100644 --- a/src/Data/Formatter/DateTime.purs +++ b/src/Data/Formatter/DateTime.purs @@ -442,13 +442,16 @@ unformatFParser cb = case _ of End → pure unit - -hoistParserT' :: ∀ b n s a m. (∀ e s'. m (Tuple (Either e a) s') -> n (Tuple (Either e b) s')) -> P.ParserT s m a -> P.ParserT s n b -hoistParserT' f (P.ParserT m) = P.ParserT (mapExceptT (mapStateT f) m) +-- TODO remove after merge https://github.com/purescript-contrib/purescript-parsing/pull/54 +mapParserT :: forall b n s a m. + ( m (Tuple (Either P.ParseError a) (P.ParseState s)) + -> n (Tuple (Either P.ParseError b) (P.ParseState s)) + ) -> P.ParserT s m a -> P.ParserT s n b +mapParserT f (P.ParserT m) = P.ParserT (mapExceptT (mapStateT f) m) unformatParser ∷ ∀ m. Monad m => Formatter → P.ParserT String m DT.DateTime unformatParser f' = do - acc <- hoistParserT' unState $ rec f' + acc <- mapParserT unState $ rec f' either P.fail pure $ unformatAccumToDateTime acc where rec ∷ Formatter → P.ParserT String (State UnformatAccum) Unit diff --git a/test/src/Main.purs b/test/src/Main.purs index 29b9188..6f6097a 100644 --- a/test/src/Main.purs +++ b/test/src/Main.purs @@ -132,25 +132,15 @@ timeTest :: forall e. Spec e Unit timeTest = describe "Data.Formatter.DateTime" do describe "formatDateTime" do it "should formatt dateTime" do - let items = - -- TODO check if this comments are still valid: - -- var a = moment( - -- 'April 12th 2017 at 11:34:34:234', - -- 'MMMM Do YYYY [at] HH:mm:ss:SSS' - -- ); - -- a.format('MMMM Do YYYY [at] HH:mm:ss:SSS') - -- testDateTime = April 12th 2017 at 11:34:34:234 - [ { format: "MM/DD/YYYY", dateStr: "04/12/2017" , date: testDateTime} + -- TODO make sure it's not compiler bug as `[` can't be moved to new line + let items = [ + { format: "MM/DD/YYYY", dateStr: "04/12/2017" , date: testDateTime} , { format: "MMMM", dateStr: "April" , date: testDateTime} , { format: "YYYY-DD-MM", dateStr: "2017-12-04" , date: testDateTime} , { format: "YYYY-MMM", dateStr: "2017-Apr" , date: testDateTime} , { format: "MMM D", dateStr: "Apr 1" , date: makeDateTime 2017 4 1} - -- TODO check if this comments are still valid: - -- This should probably be am (lowercase), if the desired - -- functionality of the library is to mirror momentjs , { format: "hh:mm:ss:SSS a", dateStr: "11:34:34:234 AM" , date: testDateTime} , { format: "YY", dateStr: "17" , date: testDateTime} - , { format: "YY", dateStr: "17" , date: makeDateTime 20017 4 12} -- Format 20017 with YY , { format: "YY", dateStr: "00" , date: makeDateTime 0 4 12} -- Format 0 with YY , { format: "YY", dateStr: "01" , date: makeDateTime (-1) 4 12} -- Format -1 with YY @@ -171,8 +161,13 @@ timeTest = describe "Data.Formatter.DateTime" do (FDT.unformatDateTime format date >>= FDT.formatDateTime format) `shouldEqual` (Right date) it "s ≡ unformat (format s)" do - -- TODO check if it's compiler bug that we can't use do notation here let items = ({ date: _, format: _ }) <$> dates <*> (dateformats # filter _.lossless <#> _.format) + -- TODO check if it's compiler bug that we can't use do notation here + -- let items = do + -- format <-dateformats + -- date <-dates + -- guard format.lossless + -- pure {date, format: format.format} for_ items \({ date, format }) -> do FDT.unformat format (FDT.format format date) `shouldEqual` (Right date) From a000e1756b8e0abab6b610a8b820c47396cebd6e Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Mon, 24 Apr 2017 12:46:21 +0400 Subject: [PATCH 21/88] fix let usage --- test/src/Main.purs | 39 +++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/test/src/Main.purs b/test/src/Main.purs index 6f6097a..f69b461 100644 --- a/test/src/Main.purs +++ b/test/src/Main.purs @@ -132,19 +132,19 @@ timeTest :: forall e. Spec e Unit timeTest = describe "Data.Formatter.DateTime" do describe "formatDateTime" do it "should formatt dateTime" do - -- TODO make sure it's not compiler bug as `[` can't be moved to new line - let items = [ - { format: "MM/DD/YYYY", dateStr: "04/12/2017" , date: testDateTime} - , { format: "MMMM", dateStr: "April" , date: testDateTime} - , { format: "YYYY-DD-MM", dateStr: "2017-12-04" , date: testDateTime} - , { format: "YYYY-MMM", dateStr: "2017-Apr" , date: testDateTime} - , { format: "MMM D", dateStr: "Apr 1" , date: makeDateTime 2017 4 1} - , { format: "hh:mm:ss:SSS a", dateStr: "11:34:34:234 AM" , date: testDateTime} - , { format: "YY", dateStr: "17" , date: testDateTime} - , { format: "YY", dateStr: "17" , date: makeDateTime 20017 4 12} -- Format 20017 with YY - , { format: "YY", dateStr: "00" , date: makeDateTime 0 4 12} -- Format 0 with YY - , { format: "YY", dateStr: "01" , date: makeDateTime (-1) 4 12} -- Format -1 with YY - ] + let + items = + [ { format: "MM/DD/YYYY", dateStr: "04/12/2017" , date: testDateTime} + , { format: "MMMM", dateStr: "April" , date: testDateTime} + , { format: "YYYY-DD-MM", dateStr: "2017-12-04" , date: testDateTime} + , { format: "YYYY-MMM", dateStr: "2017-Apr" , date: testDateTime} + , { format: "MMM D", dateStr: "Apr 1" , date: makeDateTime 2017 4 1} + , { format: "hh:mm:ss:SSS a", dateStr: "11:34:34:234 AM" , date: testDateTime} + , { format: "YY", dateStr: "17" , date: testDateTime} + , { format: "YY", dateStr: "17" , date: makeDateTime 20017 4 12} -- Format 20017 with YY + , { format: "YY", dateStr: "00" , date: makeDateTime 0 4 12} -- Format 0 with YY + , { format: "YY", dateStr: "01" , date: makeDateTime (-1) 4 12} -- Format -1 with YY + ] for_ items \({ format, dateStr, date }) -> do (format `FDT.formatDateTime` date) `shouldEqual` (Right dateStr) @@ -161,13 +161,12 @@ timeTest = describe "Data.Formatter.DateTime" do (FDT.unformatDateTime format date >>= FDT.formatDateTime format) `shouldEqual` (Right date) it "s ≡ unformat (format s)" do - let items = ({ date: _, format: _ }) <$> dates <*> (dateformats # filter _.lossless <#> _.format) - -- TODO check if it's compiler bug that we can't use do notation here - -- let items = do - -- format <-dateformats - -- date <-dates - -- guard format.lossless - -- pure {date, format: format.format} + let + items = do + format <- dateformats + date <- dates + guard format.lossless + pure { date, format: format.format } for_ items \({ date, format }) -> do FDT.unformat format (FDT.format format date) `shouldEqual` (Right date) From 0b30548e5e9ca84ceed6a3c0b685095ce24a0fcc Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Mon, 24 Apr 2017 12:49:38 +0400 Subject: [PATCH 22/88] remove 'Q', 'X', 'W' from restricted chars of placeholder --- src/Data/Formatter/DateTime.purs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Data/Formatter/DateTime.purs b/src/Data/Formatter/DateTime.purs index aab8396..56153c1 100644 --- a/src/Data/Formatter/DateTime.purs +++ b/src/Data/Formatter/DateTime.purs @@ -183,8 +183,7 @@ placeholderContent = $ PC.try $ Arr.some $ PS.noneOf - -- TODO why this chars: 'Q', 'X', 'W' are included here? - $ Str.toCharArray "YQMDXWEHhamsS" + $ Str.toCharArray "YMDEHhamsS" formatterFParser ∷ ∀ a From 7fe10ed621e321379bfa1213d7287eaca53e66d1 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Mon, 24 Apr 2017 12:53:16 +0400 Subject: [PATCH 23/88] fix failing test --- test/src/Main.purs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/src/Main.purs b/test/src/Main.purs index f69b461..ad40bf3 100644 --- a/test/src/Main.purs +++ b/test/src/Main.purs @@ -13,6 +13,7 @@ import Control.Monad.Aff (Aff) import Control.Monad.Eff (Eff, kind Effect) import Data.DateTime (DateTime) import Data.Either (Either(..)) +import Control.MonadZero (guard) import Data.Enum (toEnum) import Data.Functor.Mu (roll) import Data.Maybe (fromMaybe) @@ -173,8 +174,8 @@ timeTest = describe "Data.Formatter.DateTime" do invalidDateformats ∷ Array { str :: String , pos :: String } invalidDateformats = - [ { str: "YY-Q-dddd HH:mm Z", pos: "1:4" } - , { str: "YYYY-MM-DD Q", pos: "1:12" } + [ { str: "YY-SS-dddd HH:mm Z", pos: "1:4" } + , { str: "YYYY-MM-DD M", pos: "1:12" } ] dateformats ∷ Array { str :: String , lossless :: Boolean, format :: FDT.Formatter } From 366717732bed6b52b97905cb90b39cdfe9a315e2 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Mon, 24 Apr 2017 15:21:11 +0400 Subject: [PATCH 24/88] make Number.Formatter newtype reason is that we need to use it in tests where values need to implement Eq and Show --- bower.json | 3 ++- src/Data/Formatter/Number.purs | 45 +++++++++++++++++++++------------- 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/bower.json b/bower.json index ec6c7b2..0f56fac 100644 --- a/bower.json +++ b/bower.json @@ -19,7 +19,8 @@ "purescript-prelude": "^3.0.0", "purescript-parsing": "^4.0.0", "purescript-fixed-points": "^4.0.0", - "purescript-datetime": "git://github.com/safareli/purescript-datetime.git#interval" + "purescript-datetime": "git://github.com/safareli/purescript-datetime.git#interval", + "purescript-generics-rep": "^5.0.0" }, "devDependencies": { "purescript-aff": "^3.0.0", diff --git a/src/Data/Formatter/Number.purs b/src/Data/Formatter/Number.purs index 7cd1b3b..375199c 100644 --- a/src/Data/Formatter/Number.purs +++ b/src/Data/Formatter/Number.purs @@ -3,7 +3,7 @@ -- | zeros and put commas between thousands should be enough for everything -- | because one could just compose it with `flip append "%"` or whatever module Data.Formatter.Number - ( Formatter + ( Formatter(..) , printFormatter , parseFormatString , format @@ -15,7 +15,6 @@ module Data.Formatter.Number import Prelude -import Data.Bifunctor (lmap) import Data.Array as Arr import Data.Array (many, some) import Data.Maybe (Maybe(..), fromMaybe, isJust) @@ -24,6 +23,7 @@ import Data.Either (Either, either) import Data.Int as Int import Data.String as Str +import Data.Formatter.Parser.Utils (runP) import Data.Formatter.Internal (foldDigits, repeat) import Data.Formatter.Parser.Number (parseDigit) @@ -33,7 +33,12 @@ import Text.Parsing.Parser as P import Text.Parsing.Parser.Combinators as PC import Text.Parsing.Parser.String as PS -type Formatter = +import Data.Generic.Rep (class Generic) +import Data.Generic.Rep.Show (genericShow) +import Data.Generic.Rep.Eq (genericEq) + + +newtype Formatter = Formatter { comma ∷ Boolean , before ∷ Int , after ∷ Int @@ -41,9 +46,16 @@ type Formatter = , sign ∷ Boolean } +derive instance genericFormatter :: Generic Formatter _ + +instance showFormatter :: Show Formatter where + show = genericShow + +instance eqFormatter :: Eq Formatter where + eq = genericEq printFormatter ∷ Formatter → String -printFormatter f = +printFormatter (Formatter f) = (if f.sign then "+" else "") <> repeat "0" (f.before - one) <> (if f.comma then "0,0" else "0") @@ -52,8 +64,7 @@ printFormatter f = <> (if f.abbreviations then "a" else "") parseFormatString ∷ String → Either String Formatter -parseFormatString s = - lmap P.parseErrorMessage $ P.runParser s formatParser +parseFormatString = runP formatParser formatParser ∷ P.Parser String Formatter @@ -66,16 +77,17 @@ formatParser = do PC.try $ many $ PS.string "0" abbreviations ← PC.optionMaybe $ PC.try $ PS.string "a" - pure { sign: isJust sign - , before: Arr.length before - , comma: isJust comma - , after: fromMaybe zero $ Arr.length <$> after - , abbreviations: isJust abbreviations - } + pure $ Formatter + { sign: isJust sign + , before: Arr.length before + , comma: isJust comma + , after: fromMaybe zero $ Arr.length <$> after + , abbreviations: isJust abbreviations + } format ∷ Formatter → Number → String -format f num = +format (Formatter f) num = let absed = Math.abs num tens = if absed > 0.0 then Int.floor $ Math.log absed / Math.ln10 else 0 @@ -95,7 +107,7 @@ format f num = | otherwise = "10e+" <> show (thousands * 3) newNum = if thousands < 1 then num else num / Math.pow 1000.0 (Int.toNumber thousands) in - format f{abbreviations = false} newNum <> abbr + format (Formatter f{abbreviations = false}) newNum <> abbr else let zeros = f.before - tens - one @@ -131,11 +143,10 @@ format f num = unformat ∷ Formatter → String → Either String Number -unformat f s = - lmap P.parseErrorMessage $ P.runParser s $ unformatParser f +unformat = runP <<< unformatParser unformatParser ∷ Formatter → P.Parser String Number -unformatParser f = do +unformatParser (Formatter f) = do minus ← PC.optionMaybe $ PC.try $ PS.string "-" sign ← case minus of Nothing | f.sign → From 93d1ae42b4d4c224a262998bda074b67b7b0531d Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Mon, 24 Apr 2017 15:22:15 +0400 Subject: [PATCH 25/88] refactor numeralTests --- test/src/Main.purs | 107 ++++++++++++++++++++++----------------------- 1 file changed, 53 insertions(+), 54 deletions(-) diff --git a/test/src/Main.purs b/test/src/Main.purs index ad40bf3..a966d04 100644 --- a/test/src/Main.purs +++ b/test/src/Main.purs @@ -23,12 +23,17 @@ import Control.Alternative (class Alternative, empty) import Test.Spec.Reporter.Console (consoleReporter) import Test.Spec.Runner (RunnerEffects, run) -import Test.Spec (describe, it, Spec) +import Test.Spec (describe, it, pending, Spec) import Test.Spec.Assertions (shouldEqual) +-- TODO remove after https://github.com/owickstrom/purescript-spec/pull/48 +pending' :: forall r. String + -> Aff r Unit + -> Spec r Unit +pending' name _ = pending name -fnOne ∷ FN.Formatter -fnOne = +fmt1 :: FN.Formatter +fmt1 = FN.Formatter { comma: false , before: 3 , after: 2 @@ -36,9 +41,8 @@ fnOne = , sign: false } - -fnTwo ∷ FN.Formatter -fnTwo = +fmt2 :: FN.Formatter +fmt2 = FN.Formatter { comma: true , before: one , after: 4 @@ -46,56 +50,51 @@ fnTwo = , sign: true } +fmt3 :: FN.Formatter +fmt3 = FN.Formatter + { comma: false + , before: 2 + , after: 2 + , abbreviations: true + , sign: true + } + +numberformatts :: Array { fmt :: FN.Formatter, str :: String } +numberformatts = + [ { str: "000.00" + , fmt: fmt1 + } + , { str: "+0,0.0000" + , fmt: fmt2 + } + , { str: "+00.00a" + , fmt: fmt3 + } + ] + +numeralTests :: forall e. Spec e Unit +numeralTests = describe "Data.Formatter.Number" do + it "should print formatter" do + for_ numberformatts \({fmt, str}) -> do + FN.printFormatter fmt `shouldEqual` str -fnThree ∷ FN.Formatter -fnThree = - { comma: false - , before: 2 - , after: 2 - , abbreviations: true - , sign: true - } + it "parse format string" do + for_ numberformatts \({fmt, str}) -> do + FN.parseFormatString str `shouldEqual` (Right fmt) + it "unformat (format n) = n" do + let ns = [100.2, 100.1, 100.3, 10004000.0] + for_ ns \n -> do + FN.unformat fmt1 (FN.format fmt1 n) `shouldEqual` (Right n) --- TODO refactor this --- numeralTests :: forall e. Spec e Unit --- numeralTests = do --- log $ "\nNUMERAL TESTS\n" --- --- log $ "\nPRINT FORMATTER" --- log $ FN.printFormatter fnOne --- log $ FN.printFormatter fnTwo --- log $ FN.printFormatter fnThree --- --- log $ "\nPARSE FORMAT STRING" --- DT.traceAnyA $ FN.parseFormatString "000,0.00" --- DT.traceAnyA $ FN.parseFormatString "000" --- DT.traceAnyA $ FN.parseFormatString "0a" --- DT.traceAnyA $ FN.parseFormatString "-0,0.000" --- DT.traceAnyA $ FN.parseFormatString "+000.0" --- --- log $ "\n FORMAT" --- log $ FN.format fnOne 100.2 --- log $ FN.format fnTwo 100.1 --- log $ FN.format fnThree 100.3 --- log $ FN.format fnThree 10004000.0 --- --- log $ "\n UNFORMAT" --- DT.traceAnyA $ FN.unformat fnOne "001.12" --- DT.traceAnyA $ FN.unformat fnOne "-123.12" --- DT.traceAnyA $ FN.unformat fnOne "12.12" --- DT.traceAnyA $ FN.unformat fnThree "+123" --- DT.traceAnyA $ FN.unformat fnTwo "-100,000.1234" --- --- log $ "\n FORMAT NUMBER" --- DT.traceAnyA $ FN.formatNumber "00.00" 12.0 --- DT.traceAnyA $ FN.formatNumber "00000,0.000" 123345.1235 --- DT.traceAnyA $ FN.formatNumber "0.0" 123345.1235 --- DT.traceAnyA $ FN.formatNumber "0.0" (-123345.1235) --- --- log $ "\n UNFORMAT NUMBER" --- DT.traceAnyA $ FN.unformatNumber "0.00" "12.00" --- + -- TODO fails on negative numbers + pending' "format (unformat n) = n" do + let ns = ["001.12", "-012.12", "-123.12"] + for_ ns \n -> do + (FN.format fmt1 <$> (FN.unformat fmt1 n)) `shouldEqual` (Right n) + -- TODO check for different formatters + -- DT.traceAnyA $ FN.unformat fnThree "+123" + -- DT.traceAnyA $ FN.unformat fnTwo "-100,000.1234" makeDateTime ∷ Int -> Int -> Int -> DTi.DateTime makeDateTime year month day = @@ -256,4 +255,4 @@ main :: Eff (RunnerEffects ()) Unit main = run [consoleReporter] do intervalTest timeTest --- --numeralTests + numeralTests From 979399922d75de3b7776fc3f38587a0f9069b62c Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Mon, 24 Apr 2017 15:35:18 +0400 Subject: [PATCH 26/88] refactor tests --- test/src/DateTime.purs | 176 ++++++++++++++++++++++++++++ test/src/Interval.purs | 48 ++++---- test/src/Main.purs | 252 +---------------------------------------- test/src/Number.purs | 83 ++++++++++++++ 4 files changed, 287 insertions(+), 272 deletions(-) create mode 100644 test/src/DateTime.purs create mode 100644 test/src/Number.purs diff --git a/test/src/DateTime.purs b/test/src/DateTime.purs new file mode 100644 index 0000000..11d053e --- /dev/null +++ b/test/src/DateTime.purs @@ -0,0 +1,176 @@ +module Test.DateTime (datetimeTest) where + +import Prelude + + +import Data.Date as D +import Data.Time as T +import Data.DateTime as DT +import Data.Foldable (for_) +import Data.Formatter.DateTime as FDT +import Control.Monad.Aff (Aff) +import Data.DateTime (DateTime) +import Data.Either (Either(..)) +import Control.MonadZero (guard) +import Data.Enum (toEnum) +import Data.Functor.Mu (roll) +import Data.Maybe (fromMaybe) +import Control.Alternative (class Alternative, empty) + +import Test.Spec (describe, it, Spec) +import Test.Spec.Assertions (shouldEqual) + +datetimeTest :: forall e. Spec e Unit +datetimeTest = describe "Data.Formatter.DateTime" do + describe "formatDateTime" do + it "should formatt dateTime" do + let + items = + [ { format: "MM/DD/YYYY", dateStr: "04/12/2017" , date: testDateTime} + , { format: "MMMM", dateStr: "April" , date: testDateTime} + , { format: "YYYY-DD-MM", dateStr: "2017-12-04" , date: testDateTime} + , { format: "YYYY-MMM", dateStr: "2017-Apr" , date: testDateTime} + , { format: "MMM D", dateStr: "Apr 1" , date: makeDateTime 2017 4 1} + , { format: "hh:mm:ss:SSS a", dateStr: "11:34:34:234 AM" , date: testDateTime} + , { format: "YY", dateStr: "17" , date: testDateTime} + , { format: "YY", dateStr: "17" , date: makeDateTime 20017 4 12} -- Format 20017 with YY + , { format: "YY", dateStr: "00" , date: makeDateTime 0 4 12} -- Format 0 with YY + , { format: "YY", dateStr: "01" , date: makeDateTime (-1) 4 12} -- Format -1 with YY + ] + for_ items \({ format, dateStr, date }) -> do + (format `FDT.formatDateTime` date) `shouldEqual` (Right dateStr) + + describe "parseFormatString" do + it "should parse" do + for_ dateformats \f -> (FDT.parseFormatString f.str) `shouldEqual` (Right f.format) + + it "shouldn't parse" do + for_ invalidDateformats \f -> (FDT.parseFormatString f.str) `shouldEqual` (Left $ "Format contains invalid string@" <> f.pos) + + it "s ≡ format (unformat s)" do + let items = [ {date: "2017-12-04 234", format: "YYYY-DD-MM SSS" } , {date: "3456-09-10 333", format: "YYYY-DD-MM SSS" } ] + for_ items \({date, format}) -> do + (FDT.unformatDateTime format date >>= FDT.formatDateTime format) `shouldEqual` (Right date) + + it "s ≡ unformat (format s)" do + let + items = do + format <- dateformats + date <- dates + guard format.lossless + pure { date, format: format.format } + for_ items \({ date, format }) -> do + FDT.unformat format (FDT.format format date) `shouldEqual` (Right date) + + +makeDateTime ∷ Int -> Int -> Int -> DT.DateTime +makeDateTime year month day = + DT.DateTime + (D.canonicalDate (fromMaybe bottom $ toEnum year) (fromMaybe bottom $ toEnum month) (fromMaybe bottom $ toEnum day)) + -- XXX at 11:34:34:234 + (T.Time + (fromMaybe bottom $ toEnum 11) + (fromMaybe bottom $ toEnum 34) + (fromMaybe bottom $ toEnum 34) + (fromMaybe bottom $ toEnum 234)) + + + +testDateTime :: DT.DateTime +testDateTime = makeDateTime 2017 4 12 -- April 12th 2017 + + +assertFormatting :: forall e. String -> String -> DateTime -> Aff e Unit +assertFormatting target' format dateTime = result `shouldEqual` target + where + result = FDT.formatDateTime format dateTime + target = Right target' + +dates :: Array DateTime +dates = + [ testDateTime + , makeDateTime 2017 4 1 + , makeDateTime 20017 4 12 + , makeDateTime 0 4 12 + , makeDateTime (-1) 4 12 + ] + +invalidDateformats ∷ Array { str :: String , pos :: String } +invalidDateformats = + [ { str: "YY-SS-dddd HH:mm Z", pos: "1:4" } + , { str: "YYYY-MM-DD M", pos: "1:12" } + ] + +dateformats ∷ Array { str :: String , lossless :: Boolean, format :: FDT.Formatter } +dateformats = + [ { str: "YYYY-MM-DD" + , lossless: false + , format: + roll $ FDT.YearFull $ + roll $ FDT.Placeholder "-" $ + roll $ FDT.MonthTwoDigits $ + roll $ FDT.Placeholder "-" $ + roll $ FDT.DayOfMonthTwoDigits $ + roll FDT.End + } + , { str: "Y-MM-DD HH:mm:ss:SSS" + , lossless: true + , format: + roll $ FDT.YearAbsolute $ + roll $ FDT.Placeholder "-" $ + roll $ FDT.MonthTwoDigits $ + roll $ FDT.Placeholder "-" $ + roll $ FDT.DayOfMonthTwoDigits $ + roll $ FDT.Placeholder " " $ + roll $ FDT.Hours24 $ + roll $ FDT.Placeholder ":" $ + roll $ FDT.Minutes $ + roll $ FDT.Placeholder ":" $ + roll $ FDT.Seconds $ + roll $ FDT.Placeholder ":" $ + roll $ FDT.Milliseconds $ + roll FDT.End + } + , { str: "YY-Z-DD HH:mm Z" + , lossless: false + , format: + roll $ FDT.YearTwoDigits $ + roll $ FDT.Placeholder "-Z-" $ + roll $ FDT.DayOfMonthTwoDigits $ + roll $ FDT.Placeholder " " $ + roll $ FDT.Hours24 $ + roll $ FDT.Placeholder ":" $ + roll $ FDT.Minutes $ + roll $ FDT.Placeholder " Z" $ + roll FDT.End + } + , { str: "DD-MM-YYYY trololo HH-:-mm" + , lossless: false + , format: + roll $ FDT.DayOfMonthTwoDigits $ + roll $ FDT.Placeholder "-" $ + roll $ FDT.MonthTwoDigits $ + roll $ FDT.Placeholder "-" $ + roll $ FDT.YearFull $ + roll $ FDT.Placeholder " trololo " $ + roll $ FDT.Hours24 $ + roll $ FDT.Placeholder "-:-" $ + roll $ FDT.Minutes $ + roll FDT.End + } + , { str: "YYYY-DD-MM SSS" + , lossless: false + , format: + roll $ FDT.YearFull $ + roll $ FDT.Placeholder "-" $ + roll $ FDT.DayOfMonthTwoDigits $ + roll $ FDT.Placeholder "-" $ + roll $ FDT.MonthTwoDigits $ + roll $ FDT.Placeholder " " $ + roll $ FDT.Milliseconds $ + roll FDT.End + } + ] + +filter :: ∀ m a. Alternative m => Monad m => (a -> Boolean) -> m a -> m a +filter f m = m >>= \x -> if f x then pure x else empty diff --git a/test/src/Interval.purs b/test/src/Interval.purs index 2afdb14..d7bc05c 100644 --- a/test/src/Interval.purs +++ b/test/src/Interval.purs @@ -1,7 +1,4 @@ -module Test.Interval - ( intervalTest - ) where - +module Test.Interval (intervalTest) where import Prelude @@ -22,6 +19,28 @@ import Control.Monad.Aff (Aff) import Test.Spec (describe, it, Spec) import Test.Spec.Assertions (shouldEqual) +intervalTest ∷ ∀ e. Spec e Unit +intervalTest = describe "Data.Formatter.Interval" do + it "should unformat valid durations" do + for_ durations \d -> do + (unformatDuration d.str) `shouldEqual` (Right d.dur) + + it "should unformat valid ISO DateTime" do + for_ dates \d -> do + (runP getDate d.str) `shouldEqual` (Right d.date) + + it "shouldn't unformat invalid ISO DateTime" do + for_ invalidDurations \d -> do + let dur = (unformatDuration d.str) :: Either String I.IsoDuration + dur `shouldEqual` (Left $ "extracted Duration is not valid ISO duration@" <> d.pos) + + describe "Interval variations" do + it "should unformat Interval.StartEnd" intervalStartEndTest + it "should unformat Interval.DurationEnd" intervalDurationEndTest + it "should unformat Interval.StartDuration" intervalStartDurationTest + it "should unformat Interval.JustDuration" intervalJustDurationTest + + unsafeMkToIsoDuration :: I.Duration -> I.IsoDuration unsafeMkToIsoDuration d = unsafePartialBecause "the duration must be valid ISO duration" fromJust $ I.mkIsoDuration d @@ -126,24 +145,3 @@ intervalJustDurationTest = for_ items test dur <- durations rec <- recurrences pure { dur, rec} - -intervalTest ∷ ∀ e. Spec e Unit -intervalTest = describe "Data.Formatter.Interval" do - it "should unformat valid durations" do - for_ durations \d -> do - (unformatDuration d.str) `shouldEqual` (Right d.dur) - - it "should unformat valid ISO DateTime" do - for_ dates \d -> do - (runP getDate d.str) `shouldEqual` (Right d.date) - - it "shouldn't unformat invalid ISO DateTime" do - for_ invalidDurations \d -> do - let dur = (unformatDuration d.str) :: Either String I.IsoDuration - dur `shouldEqual` (Left $ "extracted Duration is not valid ISO duration@" <> d.pos) - - describe "Interval variations" do - it "should unformat Interval.StartEnd" intervalStartEndTest - it "should unformat Interval.DurationEnd" intervalDurationEndTest - it "should unformat Interval.StartDuration" intervalStartDurationTest - it "should unformat Interval.JustDuration" intervalJustDurationTest diff --git a/test/src/Main.purs b/test/src/Main.purs index a966d04..54cc9e4 100644 --- a/test/src/Main.purs +++ b/test/src/Main.purs @@ -2,257 +2,15 @@ module Test.Main where import Prelude -import Data.Date as D -import Data.DateTime as DTi - -import Data.Foldable (for_) -import Data.Formatter.DateTime as FDT -import Data.Formatter.Number as FN -import Data.Time as T -import Control.Monad.Aff (Aff) -import Control.Monad.Eff (Eff, kind Effect) -import Data.DateTime (DateTime) -import Data.Either (Either(..)) -import Control.MonadZero (guard) -import Data.Enum (toEnum) -import Data.Functor.Mu (roll) -import Data.Maybe (fromMaybe) import Test.Interval (intervalTest) -import Control.Alternative (class Alternative, empty) - - +import Test.DateTime (datetimeTest) +import Test.Number (numberTest) import Test.Spec.Reporter.Console (consoleReporter) +import Control.Monad.Eff (Eff) import Test.Spec.Runner (RunnerEffects, run) -import Test.Spec (describe, it, pending, Spec) -import Test.Spec.Assertions (shouldEqual) - --- TODO remove after https://github.com/owickstrom/purescript-spec/pull/48 -pending' :: forall r. String - -> Aff r Unit - -> Spec r Unit -pending' name _ = pending name - -fmt1 :: FN.Formatter -fmt1 = FN.Formatter - { comma: false - , before: 3 - , after: 2 - , abbreviations: false - , sign: false - } - -fmt2 :: FN.Formatter -fmt2 = FN.Formatter - { comma: true - , before: one - , after: 4 - , abbreviations: false - , sign: true - } - -fmt3 :: FN.Formatter -fmt3 = FN.Formatter - { comma: false - , before: 2 - , after: 2 - , abbreviations: true - , sign: true - } - -numberformatts :: Array { fmt :: FN.Formatter, str :: String } -numberformatts = - [ { str: "000.00" - , fmt: fmt1 - } - , { str: "+0,0.0000" - , fmt: fmt2 - } - , { str: "+00.00a" - , fmt: fmt3 - } - ] - -numeralTests :: forall e. Spec e Unit -numeralTests = describe "Data.Formatter.Number" do - it "should print formatter" do - for_ numberformatts \({fmt, str}) -> do - FN.printFormatter fmt `shouldEqual` str - - it "parse format string" do - for_ numberformatts \({fmt, str}) -> do - FN.parseFormatString str `shouldEqual` (Right fmt) - - it "unformat (format n) = n" do - let ns = [100.2, 100.1, 100.3, 10004000.0] - for_ ns \n -> do - FN.unformat fmt1 (FN.format fmt1 n) `shouldEqual` (Right n) - - -- TODO fails on negative numbers - pending' "format (unformat n) = n" do - let ns = ["001.12", "-012.12", "-123.12"] - for_ ns \n -> do - (FN.format fmt1 <$> (FN.unformat fmt1 n)) `shouldEqual` (Right n) - -- TODO check for different formatters - -- DT.traceAnyA $ FN.unformat fnThree "+123" - -- DT.traceAnyA $ FN.unformat fnTwo "-100,000.1234" - -makeDateTime ∷ Int -> Int -> Int -> DTi.DateTime -makeDateTime year month day = - DTi.DateTime - (D.canonicalDate (fromMaybe bottom $ toEnum year) (fromMaybe bottom $ toEnum month) (fromMaybe bottom $ toEnum day)) - -- XXX at 11:34:34:234 - (T.Time - (fromMaybe bottom $ toEnum 11) - (fromMaybe bottom $ toEnum 34) - (fromMaybe bottom $ toEnum 34) - (fromMaybe bottom $ toEnum 234)) - - - -testDateTime :: DTi.DateTime -testDateTime = makeDateTime 2017 4 12 -- April 12th 2017 - - -assertFormatting :: forall e. String -> String -> DateTime -> Aff e Unit -assertFormatting target' format dateTime = result `shouldEqual` target - where - result = FDT.formatDateTime format dateTime - target = Right target' - -dates :: Array DateTime -dates = - [ testDateTime - , makeDateTime 2017 4 1 - , makeDateTime 20017 4 12 - , makeDateTime 0 4 12 - , makeDateTime (-1) 4 12 - ] - -timeTest :: forall e. Spec e Unit -timeTest = describe "Data.Formatter.DateTime" do - describe "formatDateTime" do - it "should formatt dateTime" do - let - items = - [ { format: "MM/DD/YYYY", dateStr: "04/12/2017" , date: testDateTime} - , { format: "MMMM", dateStr: "April" , date: testDateTime} - , { format: "YYYY-DD-MM", dateStr: "2017-12-04" , date: testDateTime} - , { format: "YYYY-MMM", dateStr: "2017-Apr" , date: testDateTime} - , { format: "MMM D", dateStr: "Apr 1" , date: makeDateTime 2017 4 1} - , { format: "hh:mm:ss:SSS a", dateStr: "11:34:34:234 AM" , date: testDateTime} - , { format: "YY", dateStr: "17" , date: testDateTime} - , { format: "YY", dateStr: "17" , date: makeDateTime 20017 4 12} -- Format 20017 with YY - , { format: "YY", dateStr: "00" , date: makeDateTime 0 4 12} -- Format 0 with YY - , { format: "YY", dateStr: "01" , date: makeDateTime (-1) 4 12} -- Format -1 with YY - ] - for_ items \({ format, dateStr, date }) -> do - (format `FDT.formatDateTime` date) `shouldEqual` (Right dateStr) - - describe "parseFormatString" do - it "should parse" do - for_ dateformats \f -> (FDT.parseFormatString f.str) `shouldEqual` (Right f.format) - - it "shouldn't parse" do - for_ invalidDateformats \f -> (FDT.parseFormatString f.str) `shouldEqual` (Left $ "Format contains invalid string@" <> f.pos) - - it "s ≡ format (unformat s)" do - let items = [ {date: "2017-12-04 234", format: "YYYY-DD-MM SSS" } , {date: "3456-09-10 333", format: "YYYY-DD-MM SSS" } ] - for_ items \({date, format}) -> do - (FDT.unformatDateTime format date >>= FDT.formatDateTime format) `shouldEqual` (Right date) - - it "s ≡ unformat (format s)" do - let - items = do - format <- dateformats - date <- dates - guard format.lossless - pure { date, format: format.format } - for_ items \({ date, format }) -> do - FDT.unformat format (FDT.format format date) `shouldEqual` (Right date) - - -invalidDateformats ∷ Array { str :: String , pos :: String } -invalidDateformats = - [ { str: "YY-SS-dddd HH:mm Z", pos: "1:4" } - , { str: "YYYY-MM-DD M", pos: "1:12" } - ] - -dateformats ∷ Array { str :: String , lossless :: Boolean, format :: FDT.Formatter } -dateformats = - [ { str: "YYYY-MM-DD" - , lossless: false - , format: - roll $ FDT.YearFull $ - roll $ FDT.Placeholder "-" $ - roll $ FDT.MonthTwoDigits $ - roll $ FDT.Placeholder "-" $ - roll $ FDT.DayOfMonthTwoDigits $ - roll FDT.End - } - , { str: "Y-MM-DD HH:mm:ss:SSS" - , lossless: true - , format: - roll $ FDT.YearAbsolute $ - roll $ FDT.Placeholder "-" $ - roll $ FDT.MonthTwoDigits $ - roll $ FDT.Placeholder "-" $ - roll $ FDT.DayOfMonthTwoDigits $ - roll $ FDT.Placeholder " " $ - roll $ FDT.Hours24 $ - roll $ FDT.Placeholder ":" $ - roll $ FDT.Minutes $ - roll $ FDT.Placeholder ":" $ - roll $ FDT.Seconds $ - roll $ FDT.Placeholder ":" $ - roll $ FDT.Milliseconds $ - roll FDT.End - } - , { str: "YY-Z-DD HH:mm Z" - , lossless: false - , format: - roll $ FDT.YearTwoDigits $ - roll $ FDT.Placeholder "-Z-" $ - roll $ FDT.DayOfMonthTwoDigits $ - roll $ FDT.Placeholder " " $ - roll $ FDT.Hours24 $ - roll $ FDT.Placeholder ":" $ - roll $ FDT.Minutes $ - roll $ FDT.Placeholder " Z" $ - roll FDT.End - } - , { str: "DD-MM-YYYY trololo HH-:-mm" - , lossless: false - , format: - roll $ FDT.DayOfMonthTwoDigits $ - roll $ FDT.Placeholder "-" $ - roll $ FDT.MonthTwoDigits $ - roll $ FDT.Placeholder "-" $ - roll $ FDT.YearFull $ - roll $ FDT.Placeholder " trololo " $ - roll $ FDT.Hours24 $ - roll $ FDT.Placeholder "-:-" $ - roll $ FDT.Minutes $ - roll FDT.End - } - , { str: "YYYY-DD-MM SSS" - , lossless: false - , format: - roll $ FDT.YearFull $ - roll $ FDT.Placeholder "-" $ - roll $ FDT.DayOfMonthTwoDigits $ - roll $ FDT.Placeholder "-" $ - roll $ FDT.MonthTwoDigits $ - roll $ FDT.Placeholder " " $ - roll $ FDT.Milliseconds $ - roll FDT.End - } - ] - -filter :: ∀ m a. Alternative m => Monad m => (a -> Boolean) -> m a -> m a -filter f m = m >>= \x -> if f x then pure x else empty main :: Eff (RunnerEffects ()) Unit main = run [consoleReporter] do intervalTest - timeTest - numeralTests + datetimeTest + numberTest diff --git a/test/src/Number.purs b/test/src/Number.purs new file mode 100644 index 0000000..f0f19e8 --- /dev/null +++ b/test/src/Number.purs @@ -0,0 +1,83 @@ +module Test.Number (numberTest) where + +import Prelude + +import Data.Foldable (for_) +import Data.Formatter.Number (Formatter(..), printFormatter, parseFormatString, format, unformat) +import Control.Monad.Aff (Aff) +import Data.Either (Either(..)) + + +import Test.Spec (describe, it, pending, Spec) +import Test.Spec.Assertions (shouldEqual) + +numberTest :: forall e. Spec e Unit +numberTest = describe "Data.Formatter.Number" do + it "should print formatter" do + for_ numberformatts \({fmt, str}) -> do + printFormatter fmt `shouldEqual` str + + it "parse format string" do + for_ numberformatts \({fmt, str}) -> do + parseFormatString str `shouldEqual` (Right fmt) + + it "unformat (format n) = n" do + let ns = [100.2, 100.1, 100.3, 10004000.0] + for_ ns \n -> do + unformat fmt1 (format fmt1 n) `shouldEqual` (Right n) + + -- TODO fails on negative numbers + pending' "format (unformat n) = n" do + let ns = ["001.12", "-012.12", "-123.12"] + for_ ns \n -> do + (format fmt1 <$> (unformat fmt1 n)) `shouldEqual` (Right n) + -- TODO check for different formatters + -- DT.traceAnyA $ unformat fnThree "+123" + -- DT.traceAnyA $ unformat fnTwo "-100,000.1234" + + +-- TODO remove after https://github.com/owickstrom/purescript-spec/pull/48 +pending' :: forall r. String + -> Aff r Unit + -> Spec r Unit +pending' name _ = pending name + +fmt1 :: Formatter +fmt1 = Formatter + { comma: false + , before: 3 + , after: 2 + , abbreviations: false + , sign: false + } + +fmt2 :: Formatter +fmt2 = Formatter + { comma: true + , before: one + , after: 4 + , abbreviations: false + , sign: true + } + +fmt3 :: Formatter +fmt3 = Formatter + { comma: false + , before: 2 + , after: 2 + , abbreviations: true + , sign: true + } + +numberformatts :: Array { fmt :: Formatter, str :: String } +numberformatts = + [ { str: "000.00" + , fmt: fmt1 + } + , { str: "+0,0.0000" + , fmt: fmt2 + } + , { str: "+00.00a" + , fmt: fmt3 + } + ] From 8e9cbbd2703c78f53e73eece9557e2c28e74423d Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Mon, 24 Apr 2017 15:46:10 +0400 Subject: [PATCH 27/88] merge Parser/DateTime back into DateTime --- src/Data/Formatter/DateTime.purs | 34 ++++++++++++++++++- src/Data/Formatter/Parser/DateTime.purs | 45 ------------------------- src/Data/Formatter/Parser/Number.purs | 20 +++++------ 3 files changed, 43 insertions(+), 56 deletions(-) delete mode 100644 src/Data/Formatter/Parser/DateTime.purs diff --git a/src/Data/Formatter/DateTime.purs b/src/Data/Formatter/DateTime.purs index 56153c1..3cdc035 100644 --- a/src/Data/Formatter/DateTime.purs +++ b/src/Data/Formatter/DateTime.purs @@ -39,7 +39,7 @@ import Data.Time.Duration as Dur import Data.Formatter.Internal (foldDigits) import Data.Formatter.Parser.Utils (runP) import Data.Formatter.Parser.Number (parseDigit) -import Data.Formatter.Parser.DateTime (parseMonth, parseShortMonth) +import Data.Formatter.Parser.Utils (oneOfAs) import Text.Parsing.Parser as P import Text.Parsing.Parser.Combinators as PC @@ -465,6 +465,38 @@ unformatDateTime ∷ String → String → Either String DT.DateTime unformatDateTime pattern str = parseFormatString pattern >>= flip unformat str +parseMonth ∷ ∀ m. Monad m ⇒ P.ParserT String m D.Month +parseMonth = (PC.try <<< PS.string) `oneOfAs` + [ Tuple "January" D.January + , Tuple "February" D.February + , Tuple "March" D.March + , Tuple "April" D.April + , Tuple "May" D.May + , Tuple "June" D.June + , Tuple "July" D.July + , Tuple "August" D.August + , Tuple "September" D.September + , Tuple "October" D.October + , Tuple "November" D.November + , Tuple "December" D.December + ] + +parseShortMonth ∷ ∀ m. Monad m ⇒ P.ParserT String m D.Month +parseShortMonth = (PC.try <<< PS.string) `oneOfAs` + [ Tuple "Jan" D.January + , Tuple "Feb" D.February + , Tuple "Mar" D.March + , Tuple "Apr" D.April + , Tuple "May" D.May + , Tuple "Jun" D.June + , Tuple "Jul" D.July + , Tuple "Aug" D.August + , Tuple "Sep" D.September + , Tuple "Oct" D.October + , Tuple "Nov" D.November + , Tuple "Dec" D.December + ] + printShortMonth ∷ D.Month → String printShortMonth = case _ of D.January → "Jan" diff --git a/src/Data/Formatter/Parser/DateTime.purs b/src/Data/Formatter/Parser/DateTime.purs deleted file mode 100644 index dd1779f..0000000 --- a/src/Data/Formatter/Parser/DateTime.purs +++ /dev/null @@ -1,45 +0,0 @@ -module Data.Formatter.Parser.DateTime - ( parseMonth - , parseShortMonth - ) where - -import Prelude - -import Data.Formatter.Parser.Utils (oneOfAs) -import Text.Parsing.Parser as P -import Data.Tuple (Tuple(..)) -import Text.Parsing.Parser.Combinators as PC -import Text.Parsing.Parser.String as PS -import Data.Date as D - -parseMonth ∷ ∀ m. Monad m ⇒ P.ParserT String m D.Month -parseMonth = (PC.try <<< PS.string) `oneOfAs` - [ Tuple "January" D.January - , Tuple "February" D.February - , Tuple "March" D.March - , Tuple "April" D.April - , Tuple "May" D.May - , Tuple "June" D.June - , Tuple "July" D.July - , Tuple "August" D.August - , Tuple "September" D.September - , Tuple "October" D.October - , Tuple "November" D.November - , Tuple "December" D.December - ] - -parseShortMonth ∷ ∀ m. Monad m ⇒ P.ParserT String m D.Month -parseShortMonth = (PC.try <<< PS.string) `oneOfAs` - [ Tuple "Jan" D.January - , Tuple "Feb" D.February - , Tuple "Mar" D.March - , Tuple "Apr" D.April - , Tuple "May" D.May - , Tuple "Jun" D.June - , Tuple "Jul" D.July - , Tuple "Aug" D.August - , Tuple "Sep" D.September - , Tuple "Oct" D.October - , Tuple "Nov" D.November - , Tuple "Dec" D.December - ] diff --git a/src/Data/Formatter/Parser/Number.purs b/src/Data/Formatter/Parser/Number.purs index dc25c75..ae2db50 100644 --- a/src/Data/Formatter/Parser/Number.purs +++ b/src/Data/Formatter/Parser/Number.purs @@ -49,13 +49,13 @@ log10 n = Math.log10e * Math.log n parseDigit ∷ ∀ s m. Monad m => PS.StringLike s => P.ParserT s m Int parseDigit = PC.try $ PS.char `oneOfAs` - [ Tuple '0' 0 - , Tuple '1' 1 - , Tuple '2' 2 - , Tuple '3' 3 - , Tuple '4' 4 - , Tuple '5' 5 - , Tuple '6' 6 - , Tuple '7' 7 - , Tuple '8' 8 - , Tuple '9' 9] + [ Tuple '0' 0 + , Tuple '1' 1 + , Tuple '2' 2 + , Tuple '3' 3 + , Tuple '4' 4 + , Tuple '5' 5 + , Tuple '6' 6 + , Tuple '7' 7 + , Tuple '8' 8 + , Tuple '9' 9] From fb92d502d9bd37e5301ffa986139341ffd8c8b60 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Mon, 24 Apr 2017 19:19:48 +0400 Subject: [PATCH 28/88] fix warning --- src/Data/Formatter/DateTime.purs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Data/Formatter/DateTime.purs b/src/Data/Formatter/DateTime.purs index 3cdc035..13be4cd 100644 --- a/src/Data/Formatter/DateTime.purs +++ b/src/Data/Formatter/DateTime.purs @@ -37,9 +37,8 @@ import Control.Alt ((<|>)) import Data.Eq (class Eq1) import Data.Time.Duration as Dur import Data.Formatter.Internal (foldDigits) -import Data.Formatter.Parser.Utils (runP) import Data.Formatter.Parser.Number (parseDigit) -import Data.Formatter.Parser.Utils (oneOfAs) +import Data.Formatter.Parser.Utils (runP, oneOfAs) import Text.Parsing.Parser as P import Text.Parsing.Parser.Combinators as PC From 1c8b5191f74a61667855f121cc342e63409a1a50 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Mon, 24 Apr 2017 20:54:24 +0400 Subject: [PATCH 29/88] update ps-parser --- bower.json | 2 +- src/Data/Formatter/DateTime.purs | 7 ------- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/bower.json b/bower.json index 0f56fac..d7d6dd7 100644 --- a/bower.json +++ b/bower.json @@ -17,7 +17,7 @@ ], "dependencies": { "purescript-prelude": "^3.0.0", - "purescript-parsing": "^4.0.0", + "purescript-parsing": "^4.2.0", "purescript-fixed-points": "^4.0.0", "purescript-datetime": "git://github.com/safareli/purescript-datetime.git#interval", "purescript-generics-rep": "^5.0.0" diff --git a/src/Data/Formatter/DateTime.purs b/src/Data/Formatter/DateTime.purs index 13be4cd..6964132 100644 --- a/src/Data/Formatter/DateTime.purs +++ b/src/Data/Formatter/DateTime.purs @@ -440,13 +440,6 @@ unformatFParser cb = case _ of End → pure unit --- TODO remove after merge https://github.com/purescript-contrib/purescript-parsing/pull/54 -mapParserT :: forall b n s a m. - ( m (Tuple (Either P.ParseError a) (P.ParseState s)) - -> n (Tuple (Either P.ParseError b) (P.ParseState s)) - ) -> P.ParserT s m a -> P.ParserT s n b -mapParserT f (P.ParserT m) = P.ParserT (mapExceptT (mapStateT f) m) - unformatParser ∷ ∀ m. Monad m => Formatter → P.ParserT String m DT.DateTime unformatParser f' = do acc <- mapParserT unState $ rec f' From 7ea8b8444003caccae3bee36d2f7e2fc48a7c045 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Mon, 24 Apr 2017 20:55:21 +0400 Subject: [PATCH 30/88] add invalid interval/durations; fix parsing of PT1M --- src/Data/Formatter/Parser/Interval.purs | 21 ++++---- test/src/Interval.purs | 66 +++++++++++++++++++++---- 2 files changed, 65 insertions(+), 22 deletions(-) diff --git a/src/Data/Formatter/Parser/Interval.purs b/src/Data/Formatter/Parser/Interval.purs index 07d8a52..2bee159 100644 --- a/src/Data/Formatter/Parser/Interval.purs +++ b/src/Data/Formatter/Parser/Interval.purs @@ -41,19 +41,19 @@ parseDuration :: P.Parser String I.Duration parseDuration = PS.string "P" *> (weekDuration <|> fullDuration) where weekDuration = mkComponentsParser [ Tuple I.week "W" ] - fullDuration = append <$> durationDatePart <*> durationTimePart - durationDatePart = mkComponentsParser [ Tuple I.year "Y" , Tuple I.month "M" , Tuple I.day "D" ] - durationTimePart = tryM $ PS.string "T" *> - (mkComponentsParser [ Tuple I.hours "H" , Tuple I.minutes "M" , Tuple I.seconds "S" ]) + fullDuration = (append <$> durationDatePart <*> durationTimePart) `notEmpty` "must contain valid duration components" + durationDatePart = PC.option mempty $ PC.try $ mkComponentsParser [ Tuple I.year "Y" , Tuple I.month "M" , Tuple I.day "D" ] + durationTimePart = PC.option mempty $ (PC.try $ PS.string "T") *> (mkComponentsParser [ Tuple I.hours "H" , Tuple I.minutes "M" , Tuple I.seconds "S" ]) +notEmpty :: ∀ a. Monoid a => Eq a => P.Parser String a -> String -> P.Parser String a +notEmpty p str = p >>= \x -> if x == mempty then P.fail str else pure x + mkComponentsParser :: Array (Tuple (Number -> I.Duration) String) -> P.Parser String I.Duration -mkComponentsParser arr = do - dur <- arr <#> applyDurations # sequence <#> foldFoldableMaybe - if dur == mempty - then P.fail $ "none of valid duration components (" <> (show $ snd <$> arr) <> ") were present" - else pure dur +mkComponentsParser arr = p `notEmpty` ("none of valid duration components (" <> (show $ snd <$> arr) <> ") were present") + where + p = arr <#> applyDurations # sequence <#> foldFoldableMaybe applyDurations :: Tuple (Number -> I.Duration) String -> P.Parser String (Maybe I.Duration) applyDurations (Tuple f c) = PC.optionMaybe $ PC.try (f <$> component c) @@ -65,6 +65,3 @@ mkComponentsParser arr = do component ∷ String → P.Parser String Number component designator = parseNumber <* PS.string designator - -tryM :: ∀ a. Monoid a => P.Parser String a → P.Parser String a -tryM p = PC.option mempty $ PC.try p diff --git a/test/src/Interval.purs b/test/src/Interval.purs index d7bc05c..406b84c 100644 --- a/test/src/Interval.purs +++ b/test/src/Interval.purs @@ -4,11 +4,10 @@ import Prelude import Data.DateTime (DateTime(..)) import Data.Interval as I -import Control.MonadZero (guard) import Data.Foldable (for_) import Data.Time (Time(..)) import Data.Date (canonicalDate) -import Data.Formatter.Interval (unformatDuration, unformatRecurringInterval, getDate) +import Data.Formatter.Interval (unformatDuration, unformatInterval, unformatRecurringInterval, getDate) import Data.Either (Either(..)) import Data.Maybe (Maybe(..), fromJust, fromMaybe) import Data.Enum (toEnum) @@ -29,10 +28,16 @@ intervalTest = describe "Data.Formatter.Interval" do for_ dates \d -> do (runP getDate d.str) `shouldEqual` (Right d.date) - it "shouldn't unformat invalid ISO DateTime" do + it "shouldn't unformat invalid Duration" do for_ invalidDurations \d -> do let dur = (unformatDuration d.str) :: Either String I.IsoDuration - dur `shouldEqual` (Left $ "extracted Duration is not valid ISO duration@" <> d.pos) + dur `shouldEqual` (Left $ d.err) + + it "shouldn't unformat invalid Interval" do + for_ invalidDurations \d -> do + let dur = (unformatInterval d.str) :: Either String (I.Interval I.IsoDuration DateTime) + dur `shouldEqual` (Left $ d.err) + describe "Interval variations" do it "should unformat Interval.StartEnd" intervalStartEndTest @@ -60,16 +65,58 @@ durations = , { str: "P1DT1H1M1.5S", dur: I.day 1.0 <> I.hours 1.0 <> I.minutes 1.0 <> I.seconds 1.5 } , { str: "P1DT1H1.5M", dur: I.day 1.0 <> I.hours 1.0 <> I.minutes 1.5 } , { str: "P1DT1.5H", dur: I.day 1.0 <> I.hours 1.5 } + , { str: "PT1M", dur: I.minutes 1.0 } + , { str: "PT1S", dur: I.seconds 1.0 } + , { str: "PT1H1S", dur: I.hours 1.0 <> I.seconds 1.0 } ] <#> (\a -> a { dur = unsafeMkToIsoDuration a.dur }) -invalidDurations :: Array { str :: String, pos :: String} +-- TODO error messages could be improved +invalidDurations :: Array { err :: String, str :: String} invalidDurations = - [ { str: "P1DT1.5H0M1S", pos:"1:13" } -- TODO add some more from https://github.com/arnau/ISO8601/blob/master/spec/iso8601/duration_spec.rb + [ { err: errInvalidISO <> "1:13", str: "P1DT1.5H0M1S" } + , { err: errInvalidISO <> "1:10", str: "P1.5Y0.5M" } + , { err: errInvalidISO <> "1:8", str: "P1.5Y1M" } + , { err: errInvalidISO <> "1:12", str: "P1.5MT10.5S" } + , { err: errInvalidComponent <> "1:2", str: "P" } + , { err: errInvalidComponent <> "1:2", str: "PW" } + , { err: errInvalidComponent <> "1:2", str: "PD" } + , { err: errNoTimeComponent <> "1:3", str: "PT" } + , { err: errNoTimeComponent <> "1:3", str: "PTH" } + , { err: errNoTimeComponent <> "1:5", str: "P1YT" } + , { err: errPrefix <> "1:1", str: "" } + , { err: errPrefix <> "1:1", str: "T" } + , { err: errPrefix <> "1:1", str: "~P1Y" } + , { err: errPrefix <> "1:1", str: ".P1Y" } + , { err: errEOF <> "1:4", str: "P1Y1W" } ] - -invalidIntervals :: Array String + where + errInvalidComponent = "must contain valid duration components@" + errPrefix = "Expected \"P\"@" + errEOF = "Expected EOF@" + errInvalidISO = "extracted Duration is not valid ISO duration@" + errNoTimeComponent = "none of valid duration components ([\"H\",\"M\",\"S\"]) were present@" + +-- TODO error messages could be improved +invalidIntervals :: Array {err :: String, str :: String} invalidIntervals = - [ -- TODO add some more from https://github.com/arnau/ISO8601/blob/master/spec/iso8601/time_interval_spec.rb + -- TODO add some more from https://github.com/arnau/ISO8601/blob/master/spec/iso8601/time_interval_spec.rb + [ { err: "Expected \"P\"@1:1", str: "2007-03-01T13:00:00ZP1Y2M10DT2H30M" } + , { err: "Expected \"P\"@1:1", str: "2007-03-01T13:00:00Z-P1Y2M10D" } + , { err: "Expected \"P\"@1:1", str: "2007-03-01T13:00:00Z~P1Y2M10D" } + , { err: "Expected EOF@1:15", str: "P1Y2M10DT2H30M2007-03-01T13:00:00Z" } + , { err: "Expected EOF@1:9", str: "P1Y2M10D-2007-03-01T13:00:00Z" } + , { err: "Expected EOF@1:9", str: "P1Y2M10D~2007-03-01T13:00:00Z" } + , { err: "Expected \"P\"@1:1", str: "2007-03-01T13:00:00Z2008-05-11T15:30:00Z" } + , { err: "Expected \"P\"@1:1", str: "2007-03-01T13:00:00Z-2008-05-11T15:30:00Z" } + , { err: "Expected \"P\"@1:1", str: "2007-03-01T13:00:00Z~2008-05-11T15:30:00Z" } + , { err: "Expected \"P\"@1:1", str: "2007-03-01T13:00:00Z/" } + , { err: "Expected \"P\"@1:1", str: "2007-03-01T13:00:00Z/P" } + , { err: "Expected \"P\"@1:1", str: "2007-03-01T13:00:00Z/PT" } + , { err: "Expected \"P\"@1:1", str: "2007-03-01T13:00:00Z/2010-0-09" } + , { err: "Expected \"P\"@1:1", str: "2007-03-01T13:00:00Z/2010-05-09T103012+0400" } + , { err: "Expected \"P\"@1:1", str: "2007-03-01T13:00:00Z/2014-W15-02T10:11:12Z" } + , { err: "Expected EOF@1:9", str: "P1Y2M10D/P1Y2M10D" } + , { err: "Expected EOF@1:8", str: "P1Y0.5M/P1Y0.5M" } ] recurrences ∷ Array { str :: String, rec :: Maybe Int } @@ -102,7 +149,6 @@ intervalStartEndTest = for_ items test start <- dates end <- dates rec <- recurrences - guard $ start.str /= end.str -- investigatge if this is needed pure { start, end, rec} intervalDurationEndTest ∷ ∀ e. Aff e Unit From a93731111e495fa37ce3779ba11eb0d5734c1006 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Tue, 25 Apr 2017 15:48:31 +0400 Subject: [PATCH 31/88] update ps-spec --- bower.json | 2 +- test/src/Number.purs | 10 +--------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/bower.json b/bower.json index d7d6dd7..d8eaf99 100644 --- a/bower.json +++ b/bower.json @@ -27,6 +27,6 @@ "purescript-console": "^3.0.0", "purescript-psci-support": "^3.0.0", "purescript-debug": "^3.0.0", - "purescript-spec": "^0.13.0" + "purescript-spec": "^0.14.0" } } diff --git a/test/src/Number.purs b/test/src/Number.purs index f0f19e8..600de61 100644 --- a/test/src/Number.purs +++ b/test/src/Number.purs @@ -7,8 +7,7 @@ import Data.Formatter.Number (Formatter(..), printFormatter, parseFormatString, import Control.Monad.Aff (Aff) import Data.Either (Either(..)) - -import Test.Spec (describe, it, pending, Spec) +import Test.Spec (describe, it, pending', Spec) import Test.Spec.Assertions (shouldEqual) numberTest :: forall e. Spec e Unit @@ -35,13 +34,6 @@ numberTest = describe "Data.Formatter.Number" do -- DT.traceAnyA $ unformat fnThree "+123" -- DT.traceAnyA $ unformat fnTwo "-100,000.1234" - --- TODO remove after https://github.com/owickstrom/purescript-spec/pull/48 -pending' :: forall r. String - -> Aff r Unit - -> Spec r Unit -pending' name _ = pending name - fmt1 :: Formatter fmt1 = Formatter { comma: false From 87f4e33786e055624b1585b1fd979e4115b142a4 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Tue, 25 Apr 2017 18:52:49 +0400 Subject: [PATCH 32/88] update ps-parser --- bower.json | 2 +- src/Data/Formatter/DateTime.purs | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/bower.json b/bower.json index d8eaf99..19029df 100644 --- a/bower.json +++ b/bower.json @@ -17,7 +17,7 @@ ], "dependencies": { "purescript-prelude": "^3.0.0", - "purescript-parsing": "^4.2.0", + "purescript-parsing": "^4.2.1", "purescript-fixed-points": "^4.0.0", "purescript-datetime": "git://github.com/safareli/purescript-datetime.git#interval", "purescript-generics-rep": "^5.0.0" diff --git a/src/Data/Formatter/DateTime.purs b/src/Data/Formatter/DateTime.purs index 6964132..c1e201f 100644 --- a/src/Data/Formatter/DateTime.purs +++ b/src/Data/Formatter/DateTime.purs @@ -14,9 +14,8 @@ module Data.Formatter.DateTime import Prelude import Control.Lazy as Lazy -import Control.Monad.State (State, mapStateT, modify, put, runState) +import Control.Monad.State (State, modify, put, runState) import Control.Monad.Trans.Class (lift) -import Control.Monad.Except.Trans (mapExceptT) import Data.Ord (abs) import Data.Array (some) @@ -442,7 +441,7 @@ unformatFParser cb = case _ of unformatParser ∷ ∀ m. Monad m => Formatter → P.ParserT String m DT.DateTime unformatParser f' = do - acc <- mapParserT unState $ rec f' + acc <- P.mapParserT unState $ rec f' either P.fail pure $ unformatAccumToDateTime acc where rec ∷ Formatter → P.ParserT String (State UnformatAccum) Unit From 4c6be25e0a163c57bbdfb19d814d34b3658e1aeb Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Tue, 25 Apr 2017 18:54:55 +0400 Subject: [PATCH 33/88] remove Has{Duration,Date} class; add EOF to runP we want to fully consume input when parsers are executed --- src/Data/Formatter/Interval.purs | 63 ++++--------------------- src/Data/Formatter/Parser/Interval.purs | 15 ++++++ src/Data/Formatter/Parser/Utils.purs | 5 +- test/src/Interval.purs | 26 +++++----- test/src/Number.purs | 1 - 5 files changed, 38 insertions(+), 72 deletions(-) diff --git a/src/Data/Formatter/Interval.purs b/src/Data/Formatter/Interval.purs index 06d58d6..693b138 100644 --- a/src/Data/Formatter/Interval.purs +++ b/src/Data/Formatter/Interval.purs @@ -1,73 +1,28 @@ module Data.Formatter.Interval ( unformatRecurringInterval , unformatInterval - , unformatDuration - , class HasDuration - , class HasDate - , getDuration - , getDate ) where import Prelude -import Text.Parsing.Parser as P -import Text.Parsing.Parser.String as PS import Data.Formatter.Parser.Utils (runP) import Data.Interval as I -import Data.DateTime as D -import Data.Either (Either(..)) -import Data.Formatter.DateTime (Formatter, unformatParser, parseFormatString) -import Data.Formatter.Parser.Interval (parseRecurringInterval, parseInterval, parseIsoDuration) +import Data.DateTime (DateTime) +import Data.Either (Either) +import Data.Formatter.Parser.Interval (parseRecurringInterval, parseInterval, parseIsoDuration, parseDateTime) -unformatRecurringInterval :: - ∀ a b - . HasDuration a - ⇒ HasDate b - ⇒ String - → Either String (I.RecurringInterval a b) -unformatRecurringInterval = runP $ parseRecurringInterval getDuration getDate <* PS.eof +-- formatRecurringInterval :: ∀ a b. RecurringInterval I.IsoDuration DateTime -> String +-- formatInterval :: ∀ a b. Interval I.IsoDuration DateTime -> String -unformatInterval :: - ∀ a b - . HasDuration a - ⇒ HasDate b - ⇒ String - → Either String (I.Interval a b) -unformatInterval = runP $ parseInterval getDuration getDate <* PS.eof +unformatRecurringInterval :: String → Either String (I.RecurringInterval I.IsoDuration DateTime) +unformatRecurringInterval = runP $ parseRecurringInterval parseIsoDuration parseDateTime -unformatDuration :: - ∀ a - . HasDuration a - ⇒ String - → Either String a -unformatDuration = runP $ getDuration <* PS.eof - - - - -class HasDuration a where - getDuration :: P.Parser String a - -instance hasDurationIsoDuration :: HasDuration I.IsoDuration where - getDuration = parseIsoDuration - - -class HasDate a where - getDate :: P.Parser String a - -isoDateTimeFormatter ∷ Either String Formatter -isoDateTimeFormatter = parseFormatString "YYYY-MM-DDTHH:mm:ssZ" - -instance hasDateDate :: HasDate D.DateTime where - getDate = do - case isoDateTimeFormatter of - Right f -> unformatParser f - Left e -> P.fail $ "(this must be unrechable) error in parsing ISO date format: " <> e +unformatInterval :: String → Either String (I.Interval I.IsoDuration DateTime) +unformatInterval = runP $ parseInterval parseIsoDuration parseDateTime -- TODO read iso spec and check if local datetimes or datetimes with offset are supported -- 2017-04-13T15:36:07+00:00 -- 2017-04-13T15:36:07Z --- TODO instance for Date? -- TODO instance for local versions -- * LocalDate -- * LocalDateTime diff --git a/src/Data/Formatter/Parser/Interval.purs b/src/Data/Formatter/Parser/Interval.purs index 2bee159..294c730 100644 --- a/src/Data/Formatter/Parser/Interval.purs +++ b/src/Data/Formatter/Parser/Interval.purs @@ -2,6 +2,7 @@ module Data.Formatter.Parser.Interval ( parseRecurringInterval , parseInterval , parseIsoDuration + , parseDateTime ) where import Prelude @@ -13,6 +14,9 @@ import Control.Alt ((<|>)) import Data.Foldable (class Foldable, fold) import Data.Maybe (Maybe(..), maybe) import Data.Monoid (class Monoid, mempty) +import Data.Either (Either(..)) +import Data.Formatter.DateTime (unformatParser, Formatter, parseFormatString) +import Data.DateTime (DateTime) import Data.Traversable (sequence) import Data.Tuple (Tuple(..), snd) @@ -65,3 +69,14 @@ mkComponentsParser arr = p `notEmpty` ("none of valid duration components (" <> component ∷ String → P.Parser String Number component designator = parseNumber <* PS.string designator + + +-- parser for DateTime in UTC time zone using "extended format" +parseDateTime :: ∀ m. Monad m => P.ParserT String m DateTime +parseDateTime = do + case format of + Right f -> unformatParser f + Left e -> P.fail $ "(this must be unrechable) error in parsing ISO date format: " <> e + where + format ∷ Either String Formatter + format = parseFormatString "YYYY-MM-DDTHH:mm:ssZ" diff --git a/src/Data/Formatter/Parser/Utils.purs b/src/Data/Formatter/Parser/Utils.purs index 78c9f29..cd827e9 100644 --- a/src/Data/Formatter/Parser/Utils.purs +++ b/src/Data/Formatter/Parser/Utils.purs @@ -9,6 +9,7 @@ import Data.Tuple (Tuple(..)) import Text.Parsing.Parser (ParserT, Parser, runParser, ParseError, parseErrorMessage, parseErrorPosition) import Text.Parsing.Parser.Pos (Position(..)) import Text.Parsing.Parser.Combinators as PC +import Text.Parsing.Parser.String as PS import Data.Bifunctor (lmap) import Data.Foldable (class Foldable) import Data.Either (Either) @@ -16,8 +17,8 @@ import Data.Either (Either) oneOfAs :: ∀ c s m f a b. Functor f => Foldable f => Monad m => (a -> ParserT s m b) -> f (Tuple a c) -> ParserT s m c oneOfAs p xs = PC.choice $ (\(Tuple s r) -> p s $> r) <$> xs -runP :: ∀ s a. Parser s a → s → Either String a -runP p s = lmap printError $ runParser s p +runP :: ∀ s a. PS.StringLike s => Parser s a → s → Either String a +runP p s = lmap printError $ runParser s (p <* PS.eof) printError :: ParseError -> String printError err = parseErrorMessage err <> "@" <> (printPosition $ parseErrorPosition err) diff --git a/test/src/Interval.purs b/test/src/Interval.purs index 406b84c..646279f 100644 --- a/test/src/Interval.purs +++ b/test/src/Interval.purs @@ -7,13 +7,13 @@ import Data.Interval as I import Data.Foldable (for_) import Data.Time (Time(..)) import Data.Date (canonicalDate) -import Data.Formatter.Interval (unformatDuration, unformatInterval, unformatRecurringInterval, getDate) +import Data.Formatter.Interval (unformatInterval, unformatRecurringInterval) +import Data.Formatter.Parser.Interval (parseDateTime, parseIsoDuration) +import Data.Formatter.Parser.Utils (runP) import Data.Either (Either(..)) import Data.Maybe (Maybe(..), fromJust, fromMaybe) import Data.Enum (toEnum) import Partial.Unsafe (unsafePartialBecause) -import Unsafe.Coerce (unsafeCoerce) -import Data.Formatter.Parser.Utils (runP) import Control.Monad.Aff (Aff) import Test.Spec (describe, it, Spec) import Test.Spec.Assertions (shouldEqual) @@ -22,21 +22,20 @@ intervalTest ∷ ∀ e. Spec e Unit intervalTest = describe "Data.Formatter.Interval" do it "should unformat valid durations" do for_ durations \d -> do - (unformatDuration d.str) `shouldEqual` (Right d.dur) + (runP parseIsoDuration d.str) `shouldEqual` (Right d.dur) it "should unformat valid ISO DateTime" do for_ dates \d -> do - (runP getDate d.str) `shouldEqual` (Right d.date) + (runP parseDateTime d.str) `shouldEqual` (Right d.date) it "shouldn't unformat invalid Duration" do for_ invalidDurations \d -> do - let dur = (unformatDuration d.str) :: Either String I.IsoDuration + let dur = (runP parseIsoDuration d.str) :: Either String I.IsoDuration dur `shouldEqual` (Left $ d.err) it "shouldn't unformat invalid Interval" do for_ invalidDurations \d -> do - let dur = (unformatInterval d.str) :: Either String (I.Interval I.IsoDuration DateTime) - dur `shouldEqual` (Left $ d.err) + (unformatInterval d.str) `shouldEqual` (Left $ d.err) describe "Interval variations" do @@ -134,16 +133,13 @@ dates = , { str: "2015-07-29T13:00:00Z", date: makeDateTime 2015 7 29 13 0 0 0 } ] -forceIsoDuration :: ∀ a. I.Interval a DateTime -> I.Interval I.IsoDuration DateTime -forceIsoDuration = unsafeCoerce - intervalStartEndTest ∷ ∀ e. Aff e Unit intervalStartEndTest = for_ items test where test ({ start, end, rec }) = shouldEqual (unformatRecurringInterval $ "R" <> rec.str <> "/" <> start.str <> "/" <> end.str) - (Right $ I.RecurringInterval rec.rec $ forceIsoDuration $ I.StartEnd start.date end.date) + (Right $ I.RecurringInterval rec.rec $ I.StartEnd start.date end.date) items = do start <- dates @@ -157,7 +153,7 @@ intervalDurationEndTest = for_ items test test ({ dur, end, rec }) = shouldEqual (unformatRecurringInterval $ "R" <> rec.str <> "/" <> dur.str <> "/" <> end.str) - (Right $ I.RecurringInterval rec.rec $ forceIsoDuration $ I.DurationEnd dur.dur end.date) + (Right $ I.RecurringInterval rec.rec $ I.DurationEnd dur.dur end.date) items = do dur <- durations @@ -171,7 +167,7 @@ intervalStartDurationTest = for_ items test test ({ dur, start, rec }) = shouldEqual (unformatRecurringInterval $ "R" <> rec.str <> "/" <> start.str <> "/" <> dur.str) - (Right $ I.RecurringInterval rec.rec $ forceIsoDuration $ I.StartDuration start.date dur.dur) + (Right $ I.RecurringInterval rec.rec $ I.StartDuration start.date dur.dur) items = do dur <- durations @@ -185,7 +181,7 @@ intervalJustDurationTest = for_ items test test ({ dur, rec }) = shouldEqual (unformatRecurringInterval $ "R" <> rec.str <> "/" <> dur.str) - (Right $ I.RecurringInterval rec.rec $ forceIsoDuration $ I.JustDuration dur.dur) + (Right $ I.RecurringInterval rec.rec $ I.JustDuration dur.dur) items = do dur <- durations diff --git a/test/src/Number.purs b/test/src/Number.purs index 600de61..137ec06 100644 --- a/test/src/Number.purs +++ b/test/src/Number.purs @@ -4,7 +4,6 @@ import Prelude import Data.Foldable (for_) import Data.Formatter.Number (Formatter(..), printFormatter, parseFormatString, format, unformat) -import Control.Monad.Aff (Aff) import Data.Either (Either(..)) import Test.Spec (describe, it, pending', Spec) From 6fd728f638866a8f792f8226a958dd265d75053b Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Tue, 25 Apr 2017 18:56:59 +0400 Subject: [PATCH 34/88] remove unused ps-debug --- bower.json | 1 - 1 file changed, 1 deletion(-) diff --git a/bower.json b/bower.json index 19029df..8b50376 100644 --- a/bower.json +++ b/bower.json @@ -26,7 +26,6 @@ "purescript-aff": "^3.0.0", "purescript-console": "^3.0.0", "purescript-psci-support": "^3.0.0", - "purescript-debug": "^3.0.0", "purescript-spec": "^0.14.0" } } From e60805cc31744cd635b5e66e3b818aec0fc7cdc1 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Tue, 25 Apr 2017 23:22:33 +0400 Subject: [PATCH 35/88] add format function; refactor interval tests --- src/Data/Formatter/Interval.purs | 52 ++++-- src/Data/Formatter/Parser/Interval.purs | 19 ++- test/src/Interval.purs | 208 ++++++++++++------------ 3 files changed, 160 insertions(+), 119 deletions(-) diff --git a/src/Data/Formatter/Interval.purs b/src/Data/Formatter/Interval.purs index 693b138..6d91af8 100644 --- a/src/Data/Formatter/Interval.purs +++ b/src/Data/Formatter/Interval.purs @@ -1,28 +1,60 @@ module Data.Formatter.Interval ( unformatRecurringInterval , unformatInterval + , formatInterval + , formatRecurringInterval ) where import Prelude import Data.Formatter.Parser.Utils (runP) +import Data.Formatter.DateTime as FDT import Data.Interval as I import Data.DateTime (DateTime) import Data.Either (Either) -import Data.Formatter.Parser.Interval (parseRecurringInterval, parseInterval, parseIsoDuration, parseDateTime) +import Data.Tuple (Tuple(..)) +import Data.Maybe (maybe) +import Data.Monoid (mempty) +import Data.Map (lookup) +import Data.Int as Int +import Data.Foldable (foldMap) +import Data.Formatter.Parser.Interval (parseRecurringInterval, parseInterval, parseIsoDuration, parseDateTime, extendedDateTimeFormatInUTC) --- formatRecurringInterval :: ∀ a b. RecurringInterval I.IsoDuration DateTime -> String --- formatInterval :: ∀ a b. Interval I.IsoDuration DateTime -> String +formatRecurringInterval :: I.RecurringInterval I.IsoDuration DateTime -> String +formatRecurringInterval (I.RecurringInterval n i) = "R" <> (maybe "" formatInteger n) <> "/" <> (formatInterval i) + +formatInterval :: I.Interval I.IsoDuration DateTime -> String +formatInterval (I.StartEnd x y) = (formatDateTime x) <> "/" <> (formatDateTime y) +formatInterval (I.DurationEnd d x) = (formatIsoDuration d) <> "/" <> (formatDateTime x) +formatInterval (I.StartDuration x d) = (formatDateTime x) <> "/" <> (formatIsoDuration d) +formatInterval (I.JustDuration d) = (formatIsoDuration d) + +formatInteger :: Int -> String +formatInteger = show + +formatNumber :: Number -> String +formatNumber n = if Int.toNumber (Int.floor n) == n then show (Int.floor n) else show n + +formatIsoDuration :: I.IsoDuration -> String +formatIsoDuration = formatDuration <<< I.unIsoDuration + +formatDuration :: I.Duration -> String +formatDuration (I.Duration m) = "P" <> datePart <> timePart + where + datePart = componentToString `foldMap` dateComponentsToStr + timePart = ("T" <> _) `ifmempty` (componentToString `foldMap` timeComponentsToStr) + ifmempty _ a | a == mempty = mempty + ifmempty f a = f a + componentToString (Tuple k s) = maybe "" (formatComponent s) $ lookup k m + formatComponent designator num = formatNumber num <> designator + dateComponentsToStr = [ Tuple I.Year "Y", Tuple I.Month "M", Tuple I.Day "D" ] + timeComponentsToStr = [ Tuple I.Hours "H", Tuple I.Minutes "M", Tuple I.Seconds "S" ] + +formatDateTime :: DateTime -> String +formatDateTime = FDT.format extendedDateTimeFormatInUTC unformatRecurringInterval :: String → Either String (I.RecurringInterval I.IsoDuration DateTime) unformatRecurringInterval = runP $ parseRecurringInterval parseIsoDuration parseDateTime unformatInterval :: String → Either String (I.Interval I.IsoDuration DateTime) unformatInterval = runP $ parseInterval parseIsoDuration parseDateTime - --- TODO read iso spec and check if local datetimes or datetimes with offset are supported --- 2017-04-13T15:36:07+00:00 --- 2017-04-13T15:36:07Z --- TODO instance for local versions --- * LocalDate --- * LocalDateTime diff --git a/src/Data/Formatter/Parser/Interval.purs b/src/Data/Formatter/Parser/Interval.purs index 294c730..0fc7af2 100644 --- a/src/Data/Formatter/Parser/Interval.purs +++ b/src/Data/Formatter/Parser/Interval.purs @@ -3,6 +3,7 @@ module Data.Formatter.Parser.Interval , parseInterval , parseIsoDuration , parseDateTime + , extendedDateTimeFormatInUTC ) where import Prelude @@ -14,11 +15,12 @@ import Control.Alt ((<|>)) import Data.Foldable (class Foldable, fold) import Data.Maybe (Maybe(..), maybe) import Data.Monoid (class Monoid, mempty) -import Data.Either (Either(..)) +import Data.Either (Either, fromRight) import Data.Formatter.DateTime (unformatParser, Formatter, parseFormatString) import Data.DateTime (DateTime) import Data.Traversable (sequence) import Data.Tuple (Tuple(..), snd) +import Partial.Unsafe (unsafePartialBecause) import Data.Formatter.Parser.Number (parseNumber, parseMaybeInteger) @@ -73,10 +75,13 @@ mkComponentsParser arr = p `notEmpty` ("none of valid duration components (" <> -- parser for DateTime in UTC time zone using "extended format" parseDateTime :: ∀ m. Monad m => P.ParserT String m DateTime -parseDateTime = do - case format of - Right f -> unformatParser f - Left e -> P.fail $ "(this must be unrechable) error in parsing ISO date format: " <> e +parseDateTime = unformatParser extendedDateTimeFormatInUTC + +extendedDateTimeFormatInUTC ∷ Formatter +extendedDateTimeFormatInUTC = unEither $ parseFormatString "YYYY-MM-DDTHH:mm:ssZ" where - format ∷ Either String Formatter - format = parseFormatString "YYYY-MM-DDTHH:mm:ssZ" + unEither :: Either String Formatter -> Formatter + unEither e = (unsafePartialBecause "(this must be unrechable) error in parsing ISO date format") (fromRight e) + --TODO check why this are not working? + -- unEither = (unsafePartialBecause "(this must be unrechable) error in parsing ISO date format") <<< fromRight + -- unEither = fromRight >>> (unsafePartialBecause "(this must be unrechable) error in parsing ISO date format") diff --git a/test/src/Interval.purs b/test/src/Interval.purs index 646279f..3e6f321 100644 --- a/test/src/Interval.purs +++ b/test/src/Interval.purs @@ -4,69 +4,65 @@ import Prelude import Data.DateTime (DateTime(..)) import Data.Interval as I -import Data.Foldable (for_) +import Data.Foldable (class Foldable, fold, for_) import Data.Time (Time(..)) import Data.Date (canonicalDate) -import Data.Formatter.Interval (unformatInterval, unformatRecurringInterval) -import Data.Formatter.Parser.Interval (parseDateTime, parseIsoDuration) +import Data.Formatter.Interval (unformatInterval, unformatRecurringInterval, formatRecurringInterval) +import Data.Formatter.Parser.Interval (parseIsoDuration) import Data.Formatter.Parser.Utils (runP) import Data.Either (Either(..)) import Data.Maybe (Maybe(..), fromJust, fromMaybe) import Data.Enum (toEnum) import Partial.Unsafe (unsafePartialBecause) -import Control.Monad.Aff (Aff) import Test.Spec (describe, it, Spec) import Test.Spec.Assertions (shouldEqual) +import Control.Monad.Aff (Aff) + +-- forAll :: ∀ e f a. Foldable f => String -> f a -> (a -> Aff e Unit) -> Spec e Unit +-- forAll title arb f = it title do +-- for_ arb f + +forAll :: ∀ e a f. Foldable f => (a -> String) -> String -> f a -> (a -> Aff e Unit) -> Spec e Unit +forAll itTitle title arb f = describe title do + for_ arb \a -> it (itTitle a) (f a) + +prop :: ∀ e e' f. Foldable f => String -> f {str :: String | e'} -> ({str :: String | e'} -> Aff e Unit) -> Spec e Unit +prop = forAll (show <<< _.str) intervalTest ∷ ∀ e. Spec e Unit intervalTest = describe "Data.Formatter.Interval" do - it "should unformat valid durations" do - for_ durations \d -> do - (runP parseIsoDuration d.str) `shouldEqual` (Right d.dur) - - it "should unformat valid ISO DateTime" do - for_ dates \d -> do - (runP parseDateTime d.str) `shouldEqual` (Right d.date) + prop "shouldn't unformat invalid Interval" invalidIntervals \({str, err}) -> do + (unformatInterval str) `shouldEqual` (Left $ err) - it "shouldn't unformat invalid Duration" do - for_ invalidDurations \d -> do - let dur = (runP parseIsoDuration d.str) :: Either String I.IsoDuration - dur `shouldEqual` (Left $ d.err) + prop "shouldn't unformat invalid Duration" invalidDurations \({str, err}) -> do + (runP parseIsoDuration str) `shouldEqual` (Left $ err) - it "shouldn't unformat invalid Interval" do - for_ invalidDurations \d -> do - (unformatInterval d.str) `shouldEqual` (Left $ d.err) + prop "should unformat RecurringInterval" arbRecurringInterval \({ str, interval }) -> do + (unformatRecurringInterval str) `shouldEqual` (Right interval) + prop "format (unformat s) = s" arbRecurringInterval \({ str, interval, formatedStr }) -> do + (formatRecurringInterval <$> (unformatRecurringInterval str)) `shouldEqual` (Right formatedStr) - describe "Interval variations" do - it "should unformat Interval.StartEnd" intervalStartEndTest - it "should unformat Interval.DurationEnd" intervalDurationEndTest - it "should unformat Interval.StartDuration" intervalStartDurationTest - it "should unformat Interval.JustDuration" intervalJustDurationTest + prop "unformat (format s) = s" arbRecurringInterval \({ str, interval, formatedStr }) -> do + (unformatRecurringInterval $ formatRecurringInterval interval) `shouldEqual` (Right interval) unsafeMkToIsoDuration :: I.Duration -> I.IsoDuration unsafeMkToIsoDuration d = unsafePartialBecause "the duration must be valid ISO duration" fromJust $ I.mkIsoDuration d -makeDateTime ∷ Int -> Int -> Int -> Int -> Int -> Int -> Int -> DateTime -makeDateTime year month day h m s ms= - DateTime - (canonicalDate (fromMaybe bottom $ toEnum year) (fromMaybe bottom $ toEnum month) (fromMaybe bottom $ toEnum day)) - (Time (fromMaybe bottom $ toEnum h) (fromMaybe bottom $ toEnum m) (fromMaybe bottom $ toEnum s) (fromMaybe bottom $ toEnum ms)) - -durations :: Array { str:: String, dur :: I.IsoDuration } +durations :: Array { str:: String, formatedStr:: String, dur :: I.IsoDuration } durations = - [ { str: "P1W", dur: I.day 7.0 } - , { str: "P1.0W", dur: I.day 7.0 } - , { str: "P1DT1H1M1S", dur: I.day 1.0 <> I.hours 1.0 <> I.minutes 1.0 <> I.seconds 1.0 } - , { str: "P1.9748600D", dur: I.day 1.97486 } - , { str: "P1DT1H1M0S", dur: I.day 1.0 <> I.hours 1.0 <> I.minutes 1.0 <> I.seconds 0.0 } - , { str: "P1DT1H1M1.5S", dur: I.day 1.0 <> I.hours 1.0 <> I.minutes 1.0 <> I.seconds 1.5 } - , { str: "P1DT1H1.5M", dur: I.day 1.0 <> I.hours 1.0 <> I.minutes 1.5 } - , { str: "P1DT1.5H", dur: I.day 1.0 <> I.hours 1.5 } - , { str: "PT1M", dur: I.minutes 1.0 } - , { str: "PT1S", dur: I.seconds 1.0 } - , { str: "PT1H1S", dur: I.hours 1.0 <> I.seconds 1.0 } + [ { str: "P1W", formatedStr: "P7D", dur: I.day 7.0 } + , { str: "P1.0W", formatedStr: "P7D", dur: I.day 7.0 } + , { str: "P1DT1H1M1S", formatedStr: "P1DT1H1M1S", dur: I.day 1.0 <> I.hours 1.0 <> I.minutes 1.0 <> I.seconds 1.0 } + , { str: "P1.9748600D", formatedStr: "P1.97486D", dur: I.day 1.97486 } + , { str: "P1DT1H1M0S", formatedStr: "P1DT1H1M0S", dur: I.day 1.0 <> I.hours 1.0 <> I.minutes 1.0 <> I.seconds 0.0 } + , { str: "P1DT1H1M1.5S", formatedStr: "P1DT1H1M1.5S", dur: I.day 1.0 <> I.hours 1.0 <> I.minutes 1.0 <> I.seconds 1.5 } + , { str: "P1DT1H1.5M", formatedStr: "P1DT1H1.5M", dur: I.day 1.0 <> I.hours 1.0 <> I.minutes 1.5 } + , { str: "P1DT1.5H", formatedStr: "P1DT1.5H", dur: I.day 1.0 <> I.hours 1.5 } + , { str: "PT1M", formatedStr: "PT1M", dur: I.minutes 1.0 } + , { str: "PT1S", formatedStr: "PT1S", dur: I.seconds 1.0 } + , { str: "PT1H1S", formatedStr: "PT1H1S", dur: I.hours 1.0 <> I.seconds 1.0 } ] <#> (\a -> a { dur = unsafeMkToIsoDuration a.dur }) -- TODO error messages could be improved @@ -120,70 +116,78 @@ invalidIntervals = recurrences ∷ Array { str :: String, rec :: Maybe Int } recurrences = - [ {str: "1", rec: Just 1} - , {str: "", rec: Nothing} - , {str: "99", rec: Just 99} - , {str: "7", rec: Just 7} + [ {str: "", rec: Nothing} + , {str: "18", rec: Just 18} ] dates :: Array { str:: String, date :: DateTime } dates = - [ { str: "2015-07-22T00:00:00Z", date: makeDateTime 2015 7 22 0 0 0 0 } - , { str: "2015-07-23T11:12:13Z", date: makeDateTime 2015 7 23 11 12 13 0 } - , { str: "2015-07-29T13:00:00Z", date: makeDateTime 2015 7 29 13 0 0 0 } + [ { str: "2015-07-23T11:12:13Z", date: makeDateTime 2015 7 23 11 12 13 0 } + -- , { str: "2015-07-22T00:00:00Z", date: makeDateTime 2015 7 22 0 0 0 0 } ] -intervalStartEndTest ∷ ∀ e. Aff e Unit -intervalStartEndTest = for_ items test - where - test ({ start, end, rec }) = - shouldEqual - (unformatRecurringInterval $ "R" <> rec.str <> "/" <> start.str <> "/" <> end.str) - (Right $ I.RecurringInterval rec.rec $ I.StartEnd start.date end.date) - - items = do - start <- dates - end <- dates - rec <- recurrences - pure { start, end, rec} - -intervalDurationEndTest ∷ ∀ e. Aff e Unit -intervalDurationEndTest = for_ items test - where - test ({ dur, end, rec }) = - shouldEqual - (unformatRecurringInterval $ "R" <> rec.str <> "/" <> dur.str <> "/" <> end.str) - (Right $ I.RecurringInterval rec.rec $ I.DurationEnd dur.dur end.date) - - items = do - dur <- durations - end <- dates - rec <- recurrences - pure { dur, end, rec} - -intervalStartDurationTest ∷ ∀ e. Aff e Unit -intervalStartDurationTest = for_ items test - where - test ({ dur, start, rec }) = - shouldEqual - (unformatRecurringInterval $ "R" <> rec.str <> "/" <> start.str <> "/" <> dur.str) - (Right $ I.RecurringInterval rec.rec $ I.StartDuration start.date dur.dur) - - items = do - dur <- durations - start <- dates - rec <- recurrences - pure { dur, start, rec} - -intervalJustDurationTest ∷ ∀ e. Aff e Unit -intervalJustDurationTest = for_ items test - where - test ({ dur, rec }) = - shouldEqual - (unformatRecurringInterval $ "R" <> rec.str <> "/" <> dur.str) - (Right $ I.RecurringInterval rec.rec $ I.JustDuration dur.dur) - - items = do - dur <- durations - rec <- recurrences - pure { dur, rec} +makeDateTime ∷ Int -> Int -> Int -> Int -> Int -> Int -> Int -> DateTime +makeDateTime year month day h m s ms= + DateTime + (canonicalDate (fromMaybe bottom $ toEnum year) (fromMaybe bottom $ toEnum month) (fromMaybe bottom $ toEnum day)) + (Time (fromMaybe bottom $ toEnum h) (fromMaybe bottom $ toEnum m) (fromMaybe bottom $ toEnum s) (fromMaybe bottom $ toEnum ms)) + +type ArbRecurringInterval = Array { str ∷ String, formatedStr ∷ String, interval ∷ I.RecurringInterval I.IsoDuration DateTime} +type ArbInterval = Array { str ∷ String, formatedStr ∷ String, interval ∷ I.Interval I.IsoDuration DateTime} + +arbRecurringInterval ∷ ArbRecurringInterval +arbRecurringInterval = do + rec <- recurrences + i <- arbInterval + pure + { str : "R" <> rec.str <> "/" <> i.str + , formatedStr : "R" <> rec.str <> "/" <> i.formatedStr + , interval: I.RecurringInterval rec.rec i.interval + } + +arbInterval ∷ ArbInterval +arbInterval = fold + [ arbIntervalStartEnd + , arbIntervalDurationEnd + , arbIntervalStartDuration + , arbIntervalJustDuration + ] + +arbIntervalStartEnd ∷ ArbInterval +arbIntervalStartEnd = do + start <- dates + end <- dates + pure + { str: start.str <> "/" <> end.str + , formatedStr: start.str <> "/" <> end.str + , interval: I.StartEnd start.date end.date + } + +arbIntervalDurationEnd ∷ ArbInterval +arbIntervalDurationEnd = do + dur <- durations + end <- dates + pure + { str: dur.str <> "/" <> end.str + , formatedStr: dur.formatedStr <> "/" <> end.str + , interval: I.DurationEnd dur.dur end.date + } + +arbIntervalStartDuration ∷ ArbInterval +arbIntervalStartDuration = do + dur <- durations + start <- dates + pure + { str: start.str <> "/" <> dur.str + , formatedStr: start.str <> "/" <> dur.formatedStr + , interval: I.StartDuration start.date dur.dur + } + +arbIntervalJustDuration ∷ ArbInterval +arbIntervalJustDuration = do + dur <- durations + pure + { str: dur.str + , formatedStr: dur.formatedStr + , interval: I.JustDuration dur.dur + } From 7dd6e1b4c2896c740d4e830be35cc8db9433b3c0 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Wed, 26 Apr 2017 01:02:17 +0400 Subject: [PATCH 36/88] use forAll for testing --- src/Data/Formatter/Interval.purs | 16 ++--- test/src/DateTime.purs | 106 ++++++++++++++----------------- test/src/Interval.purs | 28 ++------ test/src/Number.purs | 42 ++++++------ test/src/Utils.purs | 32 ++++++++++ 5 files changed, 117 insertions(+), 107 deletions(-) create mode 100644 test/src/Utils.purs diff --git a/src/Data/Formatter/Interval.purs b/src/Data/Formatter/Interval.purs index 6d91af8..7f2cef0 100644 --- a/src/Data/Formatter/Interval.purs +++ b/src/Data/Formatter/Interval.purs @@ -1,8 +1,8 @@ module Data.Formatter.Interval ( unformatRecurringInterval , unformatInterval - , formatInterval , formatRecurringInterval + , formatInterval ) where import Prelude @@ -29,11 +29,8 @@ formatInterval (I.DurationEnd d x) = (formatIsoDuration d) <> "/" <> (formatDate formatInterval (I.StartDuration x d) = (formatDateTime x) <> "/" <> (formatIsoDuration d) formatInterval (I.JustDuration d) = (formatIsoDuration d) -formatInteger :: Int -> String -formatInteger = show - -formatNumber :: Number -> String -formatNumber n = if Int.toNumber (Int.floor n) == n then show (Int.floor n) else show n +formatDateTime :: DateTime -> String +formatDateTime = FDT.format extendedDateTimeFormatInUTC formatIsoDuration :: I.IsoDuration -> String formatIsoDuration = formatDuration <<< I.unIsoDuration @@ -50,8 +47,11 @@ formatDuration (I.Duration m) = "P" <> datePart <> timePart dateComponentsToStr = [ Tuple I.Year "Y", Tuple I.Month "M", Tuple I.Day "D" ] timeComponentsToStr = [ Tuple I.Hours "H", Tuple I.Minutes "M", Tuple I.Seconds "S" ] -formatDateTime :: DateTime -> String -formatDateTime = FDT.format extendedDateTimeFormatInUTC +formatInteger :: Int -> String +formatInteger = show + +formatNumber :: Number -> String +formatNumber n = if Int.toNumber (Int.floor n) == n then show (Int.floor n) else show n unformatRecurringInterval :: String → Either String (I.RecurringInterval I.IsoDuration DateTime) unformatRecurringInterval = runP $ parseRecurringInterval parseIsoDuration parseDateTime diff --git a/test/src/DateTime.purs b/test/src/DateTime.purs index c3f4393..b027e50 100644 --- a/test/src/DateTime.purs +++ b/test/src/DateTime.purs @@ -3,81 +3,73 @@ module Test.DateTime (datetimeTest) where import Prelude -import Data.Date as D -import Data.Time as T -import Data.DateTime as DT -import Data.Foldable (for_) import Data.Formatter.DateTime as FDT import Control.Monad.Aff (Aff) import Data.DateTime (DateTime) import Data.Either (Either(..)) import Control.MonadZero (guard) -import Data.Enum (toEnum) import Data.Functor.Mu (roll) -import Data.Maybe (fromMaybe) import Control.Alternative (class Alternative, empty) -import Test.Spec (describe, it, Spec) +import Test.Spec (describe, Spec) import Test.Spec.Assertions (shouldEqual) +import Test.Utils (forAll, makeDateTime) datetimeTest :: forall e. Spec e Unit datetimeTest = describe "Data.Formatter.DateTime" do - describe "formatDateTime" do - it "should formatt dateTime" do - let - items = - [ { format: "MM/DD/YYYY", dateStr: "04/12/2017" , date: makeDateTime 2017 4 12 11 3 4 234} - , { format: "MMMM", dateStr: "April" , date: makeDateTime 2017 4 12 11 3 4 234} - , { format: "YYYY-DD-MM", dateStr: "2017-12-04" , date: makeDateTime 2017 4 12 11 3 4 234} - , { format: "YYYY-MMM", dateStr: "2017-Apr" , date: makeDateTime 2017 4 12 11 3 4 234} - , { format: "MMM D", dateStr: "Apr 1" , date: makeDateTime 2017 4 1 0 0 0 0} - , { format: "hh:mm:ss:SSS a", dateStr: "11:03:04:234 AM" , date: makeDateTime 2017 4 12 11 3 4 234} - , { format: "YY", dateStr: "17" , date: makeDateTime 2017 4 12 11 3 4 234} - , { format: "YY", dateStr: "17" , date: makeDateTime 20017 4 12 0 0 0 0} -- Format 20017 with YY - , { format: "YY", dateStr: "00" , date: makeDateTime 0 4 12 0 0 0 0} -- Format 0 with YY - , { format: "YY", dateStr: "01" , date: makeDateTime (-1) 4 12 0 0 0 0} -- Format -1 with YY - , { format: "hh:m:s a", dateStr: "11:3:4 AM", date: testDateTime } - , { format: "hh:mm:ss a", dateStr: "11:03:04 AM", date: testDateTime } - , { format: "hh:mm:ss.SSS", dateStr: "11:12:30.123", date: makeDateTime 2017 4 10 11 12 30 123 } - , { format: "hh:mm:ss.SSS", dateStr: "11:12:30.023", date: makeDateTime 2017 4 10 11 12 30 23 } - , { format: "hh:mm:ss.SSS", dateStr: "11:12:30.003", date: makeDateTime 2017 4 10 11 12 30 3 } - , { format: "hh:mm:ss.SS", dateStr: "11:12:30.12", date: makeDateTime 2017 4 10 11 12 30 123 } - , { format: "hh:mm:ss.S", dateStr: "11:12:30.1", date: makeDateTime 2017 4 10 11 12 30 123 } - ] - for_ items \({ format, dateStr, date }) -> do - (format `FDT.formatDateTime` date) `shouldEqual` (Right dateStr) + forAll (\a -> a.format <> " | " <> a.dateStr) + "formatDateTime should formatt dateTime" + [ { format: "MM/DD/YYYY", dateStr: "04/12/2017" , date: makeDateTime 2017 4 12 11 3 4 234} + , { format: "MMMM", dateStr: "April" , date: makeDateTime 2017 4 12 11 3 4 234} + , { format: "YYYY-DD-MM", dateStr: "2017-12-04" , date: makeDateTime 2017 4 12 11 3 4 234} + , { format: "YYYY-MMM", dateStr: "2017-Apr" , date: makeDateTime 2017 4 12 11 3 4 234} + , { format: "MMM D", dateStr: "Apr 1" , date: makeDateTime 2017 4 1 0 0 0 0} + , { format: "hh:mm:ss:SSS a", dateStr: "11:03:04:234 AM" , date: makeDateTime 2017 4 12 11 3 4 234} + , { format: "YY", dateStr: "17" , date: makeDateTime 2017 4 12 11 3 4 234} + , { format: "YY", dateStr: "17" , date: makeDateTime 20017 4 12 0 0 0 0} -- Format 20017 with YY + , { format: "YY", dateStr: "00" , date: makeDateTime 0 4 12 0 0 0 0} -- Format 0 with YY + , { format: "YY", dateStr: "01" , date: makeDateTime (-1) 4 12 0 0 0 0} -- Format -1 with YY + , { format: "hh:m:s a", dateStr: "11:3:4 AM", date: makeDateTime 2017 4 12 11 3 4 234 } + , { format: "hh:mm:ss a", dateStr: "11:03:04 AM", date: makeDateTime 2017 4 12 11 3 4 234 } + , { format: "hh:mm:ss.SSS", dateStr: "11:12:30.123", date: makeDateTime 2017 4 10 11 12 30 123 } + , { format: "hh:mm:ss.SSS", dateStr: "11:12:30.023", date: makeDateTime 2017 4 10 11 12 30 23 } + , { format: "hh:mm:ss.SSS", dateStr: "11:12:30.003", date: makeDateTime 2017 4 10 11 12 30 3 } + , { format: "hh:mm:ss.SS", dateStr: "11:12:30.12", date: makeDateTime 2017 4 10 11 12 30 123 } + , { format: "hh:mm:ss.S", dateStr: "11:12:30.1", date: makeDateTime 2017 4 10 11 12 30 123 } + ] + (\({ format, dateStr, date }) -> (format `FDT.formatDateTime` date) `shouldEqual` (Right dateStr)) describe "parseFormatString" do - it "should parse" do - for_ dateformats \f -> (FDT.parseFormatString f.str) `shouldEqual` (Right f.format) + forAll + _.str + "should parse" + dateformats + (\f -> (FDT.parseFormatString f.str) `shouldEqual` (Right f.format)) - it "shouldn't parse" do - for_ invalidDateformats \f -> (FDT.parseFormatString f.str) `shouldEqual` (Left $ "Format contains invalid string@" <> f.pos) + forAll + _.str + "shouldn't parse" + invalidDateformats + (\f -> (FDT.parseFormatString f.str) `shouldEqual` (Left $ "Format contains invalid string@" <> f.pos)) - it "s ≡ format (unformat s)" do - let items = [ {date: "2017-12-04 234", format: "YYYY-DD-MM SSS" } , {date: "3456-09-10 333", format: "YYYY-DD-MM SSS" } ] - for_ items \({date, format}) -> do - (FDT.unformatDateTime format date >>= FDT.formatDateTime format) `shouldEqual` (Right date) - it "s ≡ unformat (format s)" do - let - items = do - format <- dateformats - date <- dates - guard format.lossless - pure { date, format: format.format } - for_ items \({ date, format }) -> do - FDT.unformat format (FDT.format format date) `shouldEqual` (Right date) + forAll + (\a -> a.format <> " | " <> a.date) + "s ≡ format (unformat s)" + [ {date: "2017-12-04 234", format: "YYYY-DD-MM SSS" } + , {date: "3456-09-10 333", format: "YYYY-DD-MM SSS" } + ] + (\({date, format}) -> (FDT.unformatDateTime format date >>= FDT.formatDateTime format) `shouldEqual` (Right date)) + forAll + (\a -> show a.date <> "|" <> FDT.printFormatter a.format) + "s ≡ unformat (format s)" + (do + format <- dateformats + date <- dates + guard format.lossless + pure { date, format: format.format }) + (\({ date, format }) -> FDT.unformat format (FDT.format format date) `shouldEqual` (Right date)) -makeDateTime ∷ Int -> Int -> Int -> Int -> Int -> Int -> Int -> DT.DateTime -makeDateTime year month day hour minute second millisecond = - DT.DateTime - (D.canonicalDate (fromMaybe bottom $ toEnum year) (fromMaybe bottom $ toEnum month) (fromMaybe bottom $ toEnum day)) - (T.Time - (fromMaybe bottom $ toEnum hour ) - (fromMaybe bottom $ toEnum minute ) - (fromMaybe bottom $ toEnum second ) - (fromMaybe bottom $ toEnum millisecond)) assertFormatting :: forall e. String -> String -> DateTime -> Aff e Unit assertFormatting target' format dateTime = result `shouldEqual` target diff --git a/test/src/Interval.purs b/test/src/Interval.purs index 3e6f321..ff4f60c 100644 --- a/test/src/Interval.purs +++ b/test/src/Interval.purs @@ -2,30 +2,20 @@ module Test.Interval (intervalTest) where import Prelude -import Data.DateTime (DateTime(..)) +import Data.DateTime (DateTime) import Data.Interval as I -import Data.Foldable (class Foldable, fold, for_) -import Data.Time (Time(..)) -import Data.Date (canonicalDate) +import Data.Foldable (class Foldable, fold) import Data.Formatter.Interval (unformatInterval, unformatRecurringInterval, formatRecurringInterval) import Data.Formatter.Parser.Interval (parseIsoDuration) import Data.Formatter.Parser.Utils (runP) import Data.Either (Either(..)) -import Data.Maybe (Maybe(..), fromJust, fromMaybe) -import Data.Enum (toEnum) +import Data.Maybe (Maybe(..), fromJust) import Partial.Unsafe (unsafePartialBecause) -import Test.Spec (describe, it, Spec) +import Test.Spec (describe, Spec) import Test.Spec.Assertions (shouldEqual) +import Test.Utils (forAll, makeDateTime) import Control.Monad.Aff (Aff) --- forAll :: ∀ e f a. Foldable f => String -> f a -> (a -> Aff e Unit) -> Spec e Unit --- forAll title arb f = it title do --- for_ arb f - -forAll :: ∀ e a f. Foldable f => (a -> String) -> String -> f a -> (a -> Aff e Unit) -> Spec e Unit -forAll itTitle title arb f = describe title do - for_ arb \a -> it (itTitle a) (f a) - prop :: ∀ e e' f. Foldable f => String -> f {str :: String | e'} -> ({str :: String | e'} -> Aff e Unit) -> Spec e Unit prop = forAll (show <<< _.str) @@ -123,15 +113,9 @@ recurrences = dates :: Array { str:: String, date :: DateTime } dates = [ { str: "2015-07-23T11:12:13Z", date: makeDateTime 2015 7 23 11 12 13 0 } - -- , { str: "2015-07-22T00:00:00Z", date: makeDateTime 2015 7 22 0 0 0 0 } + , { str: "2015-07-22T00:00:00Z", date: makeDateTime 2015 7 22 0 0 0 0 } ] -makeDateTime ∷ Int -> Int -> Int -> Int -> Int -> Int -> Int -> DateTime -makeDateTime year month day h m s ms= - DateTime - (canonicalDate (fromMaybe bottom $ toEnum year) (fromMaybe bottom $ toEnum month) (fromMaybe bottom $ toEnum day)) - (Time (fromMaybe bottom $ toEnum h) (fromMaybe bottom $ toEnum m) (fromMaybe bottom $ toEnum s) (fromMaybe bottom $ toEnum ms)) - type ArbRecurringInterval = Array { str ∷ String, formatedStr ∷ String, interval ∷ I.RecurringInterval I.IsoDuration DateTime} type ArbInterval = Array { str ∷ String, formatedStr ∷ String, interval ∷ I.Interval I.IsoDuration DateTime} diff --git a/test/src/Number.purs b/test/src/Number.purs index 137ec06..9546437 100644 --- a/test/src/Number.purs +++ b/test/src/Number.purs @@ -2,36 +2,38 @@ module Test.Number (numberTest) where import Prelude -import Data.Foldable (for_) import Data.Formatter.Number (Formatter(..), printFormatter, parseFormatString, format, unformat) import Data.Either (Either(..)) -import Test.Spec (describe, it, pending', Spec) +import Test.Spec (describe, Spec) import Test.Spec.Assertions (shouldEqual) +import Test.Utils (forAll) numberTest :: forall e. Spec e Unit numberTest = describe "Data.Formatter.Number" do - it "should print formatter" do - for_ numberformatts \({fmt, str}) -> do - printFormatter fmt `shouldEqual` str + forAll _.str + "should print formatter" + numberformatts + (\({fmt, str}) -> printFormatter fmt `shouldEqual` str) - it "parse format string" do - for_ numberformatts \({fmt, str}) -> do - parseFormatString str `shouldEqual` (Right fmt) + forAll _.str + "parse format string" + numberformatts + (\({fmt, str}) -> parseFormatString str `shouldEqual` (Right fmt)) - it "unformat (format n) = n" do - let ns = [100.2, 100.1, 100.3, 10004000.0] - for_ ns \n -> do - unformat fmt1 (format fmt1 n) `shouldEqual` (Right n) + forAll show + "unformat (format n) = n" + [100.2, 100.1, 100.3, 10004000.0] + (\n -> unformat fmt1 (format fmt1 n) `shouldEqual` (Right n)) - -- TODO fails on negative numbers - pending' "format (unformat n) = n" do - let ns = ["001.12", "-012.12", "-123.12"] - for_ ns \n -> do - (format fmt1 <$> (unformat fmt1 n)) `shouldEqual` (Right n) - -- TODO check for different formatters - -- DT.traceAnyA $ unformat fnThree "+123" - -- DT.traceAnyA $ unformat fnTwo "-100,000.1234" + forAll show + "format (unformat n) = n" + [ "001.12" + -- TODO fails on negative numbers + -- , "-012.12" + -- , "-123.12" + ] + (\n -> (format fmt1 <$> (unformat fmt1 n)) `shouldEqual` (Right n)) fmt1 :: Formatter fmt1 = Formatter diff --git a/test/src/Utils.purs b/test/src/Utils.purs new file mode 100644 index 0000000..6e13b24 --- /dev/null +++ b/test/src/Utils.purs @@ -0,0 +1,32 @@ +module Test.Utils where + +import Prelude + + +import Test.Spec (describe, it, Spec) +import Control.Monad.Aff (Aff) + +import Data.Foldable (class Foldable, for_) +import Data.Enum (toEnum) +import Data.Maybe (fromMaybe) +import Data.DateTime (DateTime(..)) +import Data.Date (canonicalDate) +import Data.Time (Time(..)) + + +forAll :: ∀ e a f. Foldable f => (a -> String) -> String -> f a -> (a -> Aff e Unit) -> Spec e Unit +forAll itTitle title arb f = describe title do + for_ arb \a -> it (itTitle a) (f a) + +makeDateTime ∷ Int -> Int -> Int -> Int -> Int -> Int -> Int -> DateTime +makeDateTime year month day hour minute second millisecond = + DateTime + (canonicalDate + (fromMaybe bottom $ toEnum year) + (fromMaybe bottom $ toEnum month) + (fromMaybe bottom $ toEnum day)) + (Time + (fromMaybe bottom $ toEnum hour ) + (fromMaybe bottom $ toEnum minute ) + (fromMaybe bottom $ toEnum second ) + (fromMaybe bottom $ toEnum millisecond)) From 6e7a1bab574de8ce843d8b4742dace36dcc450da Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Wed, 26 Apr 2017 13:41:50 +0400 Subject: [PATCH 37/88] refactor padDoubleDigit --- src/Data/Formatter/DateTime.purs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Data/Formatter/DateTime.purs b/src/Data/Formatter/DateTime.purs index b84685f..bdf0f48 100644 --- a/src/Data/Formatter/DateTime.purs +++ b/src/Data/Formatter/DateTime.purs @@ -297,8 +297,8 @@ padSingleDigit i padDoubleDigit :: Int -> String padDoubleDigit i - | i < 100 && i > 10 = "0" <> (show i) - | i < 100 && i < 10 = "00" <> (show i) + | i < 10 = "00" <> (show i) + | i < 100 = "0" <> (show i) | otherwise = show i format ∷ Formatter → DT.DateTime → String From 4a88e5a56646561056736bbfddac90c694e92377 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Wed, 26 Apr 2017 14:19:32 +0400 Subject: [PATCH 38/88] add resoliutions --- bower.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bower.json b/bower.json index 8b50376..d3663d4 100644 --- a/bower.json +++ b/bower.json @@ -27,5 +27,8 @@ "purescript-console": "^3.0.0", "purescript-psci-support": "^3.0.0", "purescript-spec": "^0.14.0" + }, + "resolutions": { + "purescript-datetime": "git://github.com/safareli/purescript-datetime.git#interval" } } From cd3365ce3d4bdb5891d3ce76ae27227b3490b137 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Wed, 26 Apr 2017 16:34:01 +0400 Subject: [PATCH 39/88] fix resolution --- bower.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bower.json b/bower.json index d3663d4..06d8c87 100644 --- a/bower.json +++ b/bower.json @@ -29,6 +29,6 @@ "purescript-spec": "^0.14.0" }, "resolutions": { - "purescript-datetime": "git://github.com/safareli/purescript-datetime.git#interval" + "purescript-datetime": "interval" } } From 8f1479072d1683b9b7156d9b4be90f23c32d6ca8 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Wed, 26 Apr 2017 16:34:49 +0400 Subject: [PATCH 40/88] add space to formatterFShow --- src/Data/Formatter/DateTime.purs | 42 ++++++++++++++++---------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/Data/Formatter/DateTime.purs b/src/Data/Formatter/DateTime.purs index bdf0f48..535cc3a 100644 --- a/src/Data/Formatter/DateTime.purs +++ b/src/Data/Formatter/DateTime.purs @@ -93,27 +93,27 @@ instance formatterFFunctor ∷ Functor FormatterF where map f End = End instance formatterFShow ∷ Show a => Show (FormatterF a) where - show (YearFull a) = "(YearFull" <> (show a) <> "c" - show (YearTwoDigits a) = "(YearTwoDigits" <> (show a) <> ")" - show (YearAbsolute a) = "(YearAbsolute" <> (show a) <> ")" - show (MonthFull a) = "(MonthFull" <> (show a) <> ")" - show (MonthShort a) = "(MonthShort" <> (show a) <> ")" - show (MonthTwoDigits a) = "(MonthTwoDigits" <> (show a) <> ")" - show (DayOfMonthTwoDigits a) = "(DayOfMonthTwoDigits" <> (show a) <> ")" - show (DayOfMonth a) = "(DayOfMonth" <> (show a) <> ")" - show (UnixTimestamp a) = "(UnixTimestamp" <> (show a) <> ")" - show (DayOfWeek a) = "(DayOfWeek" <> (show a) <> ")" - show (Hours24 a) = "(Hours24" <> (show a) <> ")" - show (Hours12 a) = "(Hours12" <> (show a) <> ")" - show (Meridiem a) = "(Meridiem" <> (show a) <> ")" - show (Minutes a) = "(Minutes" <> (show a) <> ")" - show (MinutesTwoDigits a) = "(MinutesTwoDigits" <> (show a) <> ")" - show (Seconds a) = "(Seconds" <> (show a) <> ")" - show (SecondsTwoDigits a) = "(SecondsTwoDigits" <> (show a) <> ")" - show (Milliseconds a) = "(Milliseconds" <> (show a) <> ")" - show (MillisecondsShort a) = "(MillisecondsShort" <> (show a) <> ")" - show (MillisecondsTwoDigits a) = "(MillisecondsTwoDigits" <> (show a) <> ")" - show (Placeholder str a) = "(Placeholder" <> (show str) <> " "<> (show a) <> ")" + show (YearFull a) = "(YearFull " <> (show a) <> "c" + show (YearTwoDigits a) = "(YearTwoDigits " <> (show a) <> ")" + show (YearAbsolute a) = "(YearAbsolute " <> (show a) <> ")" + show (MonthFull a) = "(MonthFull " <> (show a) <> ")" + show (MonthShort a) = "(MonthShort " <> (show a) <> ")" + show (MonthTwoDigits a) = "(MonthTwoDigits " <> (show a) <> ")" + show (DayOfMonthTwoDigits a) = "(DayOfMonthTwoDigits " <> (show a) <> ")" + show (DayOfMonth a) = "(DayOfMonth " <> (show a) <> ")" + show (UnixTimestamp a) = "(UnixTimestamp " <> (show a) <> ")" + show (DayOfWeek a) = "(DayOfWeek " <> (show a) <> ")" + show (Hours24 a) = "(Hours24 " <> (show a) <> ")" + show (Hours12 a) = "(Hours12 " <> (show a) <> ")" + show (Meridiem a) = "(Meridiem " <> (show a) <> ")" + show (Minutes a) = "(Minutes " <> (show a) <> ")" + show (MinutesTwoDigits a) = "(MinutesTwoDigits " <> (show a) <> ")" + show (Seconds a) = "(Seconds " <> (show a) <> ")" + show (SecondsTwoDigits a) = "(SecondsTwoDigits " <> (show a) <> ")" + show (Milliseconds a) = "(Milliseconds " <> (show a) <> ")" + show (MillisecondsShort a) = "(MillisecondsShort " <> (show a) <> ")" + show (MillisecondsTwoDigits a) = "(MillisecondsTwoDigits " <> (show a) <> ")" + show (Placeholder str a) = "(Placeholder " <> (show str) <> " "<> (show a) <> ")" show End = "End" instance formatterFEq ∷ Eq a => Eq (FormatterF a) where From 5a66f6e0a3777f4f68cadbd98b9eb1b9855f81ff Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Wed, 26 Apr 2017 16:39:23 +0400 Subject: [PATCH 41/88] use --- src/Data/Formatter/DateTime.purs | 3 +-- test/src/DateTime.purs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Data/Formatter/DateTime.purs b/src/Data/Formatter/DateTime.purs index 535cc3a..7f591e4 100644 --- a/src/Data/Formatter/DateTime.purs +++ b/src/Data/Formatter/DateTime.purs @@ -32,7 +32,6 @@ import Data.Maybe (Maybe(..), maybe, isJust, fromMaybe) import Data.Newtype (unwrap) import Data.String as Str import Data.Time as T -import Control.Alt ((<|>)) import Data.Eq (class Eq1) import Data.Time.Duration as Dur import Data.Formatter.Internal (foldDigits) @@ -230,7 +229,7 @@ formatterFParser cb = , (PC.try $ PS.string "S") *> map MillisecondsShort cb , (Placeholder <$> placeholderContent <*> cb) , (PS.eof $> End) - ] <|> (P.fail "Format contains invalid string") + ] PC. "to contain only valid characters" formatParser ∷ P.Parser String Formatter formatParser = diff --git a/test/src/DateTime.purs b/test/src/DateTime.purs index b027e50..9eb6b55 100644 --- a/test/src/DateTime.purs +++ b/test/src/DateTime.purs @@ -50,7 +50,7 @@ datetimeTest = describe "Data.Formatter.DateTime" do _.str "shouldn't parse" invalidDateformats - (\f -> (FDT.parseFormatString f.str) `shouldEqual` (Left $ "Format contains invalid string@" <> f.pos)) + (\f -> (FDT.parseFormatString f.str) `shouldEqual` (Left $ "Expected to contain only valid characters@" <> f.pos)) forAll (\a -> a.format <> " | " <> a.date) From 2ed021e5600d7e156c90be77914d399928215438 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Wed, 26 Apr 2017 18:56:04 +0400 Subject: [PATCH 42/88] refactor digit parsing --- bower.json | 2 + src/Data/Formatter/DateTime.purs | 101 ++++++++++++++++--------------- test/src/DateTime.purs | 6 ++ 3 files changed, 60 insertions(+), 49 deletions(-) diff --git a/bower.json b/bower.json index 06d8c87..bf2905a 100644 --- a/bower.json +++ b/bower.json @@ -20,6 +20,7 @@ "purescript-parsing": "^4.2.1", "purescript-fixed-points": "^4.0.0", "purescript-datetime": "git://github.com/safareli/purescript-datetime.git#interval", + "purescript-lists": "git://github.com/safareli/purescript-lists.git#somemany", "purescript-generics-rep": "^5.0.0" }, "devDependencies": { @@ -29,6 +30,7 @@ "purescript-spec": "^0.14.0" }, "resolutions": { + "purescript-lists": "somemany", "purescript-datetime": "interval" } } diff --git a/src/Data/Formatter/DateTime.purs b/src/Data/Formatter/DateTime.purs index 7f591e4..f0db2cf 100644 --- a/src/Data/Formatter/DateTime.purs +++ b/src/Data/Formatter/DateTime.purs @@ -19,8 +19,8 @@ import Control.Monad.Trans.Class (lift) import Data.Ord (abs) import Data.Array (some) +import Data.List.Lazy as List import Data.Tuple (Tuple(..)) -import Data.Array as Arr import Data.Date as D import Data.DateTime as DT import Data.DateTime.Instant (instant, toDateTime, fromDateTime, unInstant) @@ -37,6 +37,7 @@ import Data.Time.Duration as Dur import Data.Formatter.Internal (foldDigits) import Data.Formatter.Parser.Number (parseDigit) import Data.Formatter.Parser.Utils (runP, oneOfAs) +import Control.Monad.Reader.Trans (ReaderT, runReaderT, ask) import Text.Parsing.Parser as P import Text.Parsing.Parser.Combinators as PC @@ -198,7 +199,7 @@ placeholderContent ∷ P.Parser String String placeholderContent = map Str.fromCharArray $ PC.try - $ Arr.some + $ some $ PS.noneOf $ Str.toCharArray "YMDEHhamsS" @@ -362,6 +363,38 @@ unformatAccumToDateTime a = | otherwise = Nothing + +-- TODO remove +-- validateLength ∷ ∀ e. (Int → Boolean) → { length ∷ Int | e } → Either String Unit +-- validateLength f {length} = if f length then (Just unit) +-- else Just "Invalid number of digits" + +-- TODO use MonadAsk in signature +exactLength ∷ ∀ e. ReaderT { maxLength ∷ Int, length ∷ Int | e } (Either String) Unit +exactLength = ask >>= \({maxLength, length}) → if maxLength /= length + then lift $ Left $ "Expected " <> (show maxLength) <> " digits but got " <> (show length) + else lift $ Right unit + +validateRange ∷ ∀ e. Int → Int → ReaderT { num ∷ Int | e } (Either String) Unit +validateRange min max = ask >>= \({num}) → if num < min || num > max + then lift $ Left $ "Number is out of range [ " <> (show min) <> ", " <> (show max) <> " ]" + else lift $ Right unit + +parseInt :: ∀ m + . Monad m + ⇒ Int + → ReaderT { length ∷ Int, num ∷ Int, maxLength ∷ Int } (Either String) Unit + → String + → P.ParserT String m Int +parseInt maxLength validators errMsg = do + ds ← List.take maxLength <$> (List.some parseDigit) + let length = List.length ds + let num = foldDigits ds + case runReaderT validators {length, num, maxLength} of + Left err -> P.fail $ errMsg <> "(" <> err <> ")" + Right _ -> pure num + +-- take unformatFParser ∷ ∀ a . (a → P.ParserT String (State UnformatAccum) Unit) @@ -369,14 +402,11 @@ unformatFParser → P.ParserT String (State UnformatAccum) Unit unformatFParser cb = case _ of YearFull a → do - ds ← some parseDigit - when (Arr.length ds /= 4) $ P.fail "Incorrect full year" - lift $ modify _{year = Just $ foldDigits ds} + year ← parseInt 4 exactLength "Incorrect full year" + lift $ modify _{year = Just $ year} cb a YearTwoDigits a → do - ds ← some parseDigit - when (Arr.length ds /= 2) $ P.fail "Incorrect 2-digit year" - let y = foldDigits ds + y ← parseInt 2 exactLength "Incorrect 2-digit year" lift $ modify _{year = Just $ if y > 69 then y + 1900 else y + 2000} cb a YearAbsolute a → do @@ -393,21 +423,15 @@ unformatFParser cb = case _ of lift $ modify _{month = Just $ fromEnum month} cb a MonthTwoDigits a → do - ds ← some parseDigit - let month = foldDigits ds - when (Arr.length ds /= 2 || month > 12 || month < 1) $ P.fail "Incorrect 2-digit month" + month ← parseInt 2 (exactLength *> (validateRange 1 12)) "Incorrect 2-digit month" lift $ modify _{month = Just month} cb a DayOfMonthTwoDigits a → do - ds ← some parseDigit - let dom = foldDigits ds - when (Arr.length ds /= 2 || dom > 31 || dom < 1) $ P.fail "Incorrect day of month" + dom ← parseInt 2 (exactLength *> (validateRange 1 31)) "Incorrect day of month" lift $ modify _{day = Just dom} cb a DayOfMonth a → do - ds ← some parseDigit - let dom = foldDigits ds - when (Arr.length ds > 2 || dom > 31 || dom < 1) $ P.fail "Incorrect day of month" + dom ← parseInt 2 (validateRange 1 31) "Incorrect day of month" lift $ modify _{day = Just dom} cb a UnixTimestamp a → do @@ -426,22 +450,15 @@ unformatFParser cb = case _ of } cb a DayOfWeek a → do - dow ← parseDigit - when (dow > 7 || dow < 1) $ P.fail "Incorrect day of week" + -- TODO we would need to use this value if we support date format using week number + dow ← parseInt 1 (validateRange 1 7) "Incorrect day of week" cb a Hours24 a → do - -- TODO because `some` is parsing digits it will consume more then 2 - -- even when input is properly formatted in case of `HHmmss` - -- which results in need to add some seperators to format `HH:mm:ss` - ds ← some parseDigit - let hh = foldDigits ds - when (Arr.length ds /= 2 || hh < 0 || hh > 23) $ P.fail "Incorrect 24 hour" + hh ← parseInt 2 (exactLength *> (validateRange 0 23)) "Incorrect 24 hour" lift $ modify _{hour = Just hh} cb a Hours12 a → do - ds ← some parseDigit - let hh = foldDigits ds - when (Arr.length ds /= 2 || hh < 0 || hh > 11) $ P.fail "Incorrect 24 hour" + hh ← parseInt 2 (exactLength *> (validateRange 0 11)) "Incorrect 12 hour" lift $ modify _{hour = Just hh} cb a Meridiem a → do @@ -454,47 +471,33 @@ unformatFParser cb = case _ of lift $ modify _{meridiem = Just m} cb a MinutesTwoDigits a → do - ds ← some parseDigit - let mm = foldDigits ds - when (Arr.length ds /= 2 || mm < 0 || mm > 59) $ P.fail "Incorrect 2-digit minute" + mm ← parseInt 2 (exactLength *> (validateRange 0 59)) "Incorrect 2-digit minute" lift $ modify _{minute = Just mm} cb a Minutes a → do - ds ← some parseDigit - let mm = foldDigits ds - when (Arr.length ds > 2 || mm < 0 || mm > 59) $ P.fail "Incorrect minute" + mm ← parseInt 2 (validateRange 0 59) "Incorrect minute" lift $ modify _{minute = Just mm} cb a SecondsTwoDigits a → do - ds ← some parseDigit - let ss = foldDigits ds - when (Arr.length ds /= 2 || ss < 0 || ss > 59) $ P.fail "Incorrect 2-digit second" + ss ← parseInt 2 (exactLength *> (validateRange 0 59)) "Incorrect 2-digit second" lift $ modify _{second = Just ss} cb a Seconds a → do - ds ← some parseDigit - let ss = foldDigits ds - when (Arr.length ds > 2 || ss < 0 || ss > 59) $ P.fail "Incorrect second" + ss ← parseInt 2 (validateRange 0 59) "Incorrect second" lift $ modify _{second = Just ss} cb a Milliseconds a → do - ds ← some parseDigit - let sss = foldDigits ds - when (Arr.length ds /= 3 || sss < 0 || sss > 999) $ P.fail "Incorrect millisecond" + sss ← parseInt 3 (exactLength *> (validateRange 0 999)) "Incorrect millisecond" lift $ modify _{millisecond = Just sss} cb a Placeholder s a → PS.string s *> cb a MillisecondsShort a → do - ds ← some parseDigit - let s = foldDigits ds - when (Arr.length ds /= 1 || s < 0 || s > 9) $ P.fail "Incorrect 1-digit millisecond" + s ← parseInt 1 (exactLength *> (validateRange 0 9)) "Incorrect 1-digit millisecond" lift $ modify _{millisecond = Just s} cb a MillisecondsTwoDigits a → do - ds ← some parseDigit - let ss = foldDigits ds - when (Arr.length ds /= 2 || ss < 0 || ss > 99) $ P.fail "Incorrect 2-digit millisecond" + ss ← parseInt 2 (exactLength *> (validateRange 0 99)) "Incorrect 2-digit millisecond" lift $ modify _{millisecond = Just ss} cb a End → diff --git a/test/src/DateTime.purs b/test/src/DateTime.purs index 9eb6b55..931fb8f 100644 --- a/test/src/DateTime.purs +++ b/test/src/DateTime.purs @@ -36,6 +36,12 @@ datetimeTest = describe "Data.Formatter.DateTime" do , { format: "hh:mm:ss.SSS", dateStr: "11:12:30.003", date: makeDateTime 2017 4 10 11 12 30 3 } , { format: "hh:mm:ss.SS", dateStr: "11:12:30.12", date: makeDateTime 2017 4 10 11 12 30 123 } , { format: "hh:mm:ss.S", dateStr: "11:12:30.1", date: makeDateTime 2017 4 10 11 12 30 123 } + , { format: "hhmmss a", dateStr: "110304 AM", date: makeDateTime 2017 4 12 11 3 4 234 } + , { format: "hhmmssSSS", dateStr: "111230123", date: makeDateTime 2017 4 10 11 12 30 123 } + , { format: "hhmmssSSS", dateStr: "111230023", date: makeDateTime 2017 4 10 11 12 30 23 } + , { format: "hhmmssSSS", dateStr: "111230003", date: makeDateTime 2017 4 10 11 12 30 3 } + , { format: "hhmmssSS", dateStr: "11123012", date: makeDateTime 2017 4 10 11 12 30 123 } + , { format: "hhmmssS", dateStr: "1112301", date: makeDateTime 2017 4 10 11 12 30 123 } ] (\({ format, dateStr, date }) -> (format `FDT.formatDateTime` date) `shouldEqual` (Right dateStr)) From 13d300d493502c21142b4622d87503b9fbbd2d9c Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Wed, 26 Apr 2017 19:21:28 +0400 Subject: [PATCH 43/88] remove unneeded range checks the range check is not relevent then we have know exact length of digits and range is min and max for the number --- src/Data/Formatter/DateTime.purs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Data/Formatter/DateTime.purs b/src/Data/Formatter/DateTime.purs index f0db2cf..e4418d5 100644 --- a/src/Data/Formatter/DateTime.purs +++ b/src/Data/Formatter/DateTime.purs @@ -487,17 +487,17 @@ unformatFParser cb = case _ of lift $ modify _{second = Just ss} cb a Milliseconds a → do - sss ← parseInt 3 (exactLength *> (validateRange 0 999)) "Incorrect millisecond" + sss ← parseInt 3 exactLength "Incorrect millisecond" lift $ modify _{millisecond = Just sss} cb a Placeholder s a → PS.string s *> cb a MillisecondsShort a → do - s ← parseInt 1 (exactLength *> (validateRange 0 9)) "Incorrect 1-digit millisecond" + s ← parseInt 1 exactLength "Incorrect 1-digit millisecond" lift $ modify _{millisecond = Just s} cb a MillisecondsTwoDigits a → do - ss ← parseInt 2 (exactLength *> (validateRange 0 99)) "Incorrect 2-digit millisecond" + ss ← parseInt 2 exactLength "Incorrect 2-digit millisecond" lift $ modify _{millisecond = Just ss} cb a End → From 758d9ba959e71aa96480857ef0a8770f3c39751d Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Wed, 26 Apr 2017 19:24:04 +0400 Subject: [PATCH 44/88] reorder parseInt validations so that we don't need parens --- 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 e4418d5..c2e0305 100644 --- a/src/Data/Formatter/DateTime.purs +++ b/src/Data/Formatter/DateTime.purs @@ -423,11 +423,11 @@ unformatFParser cb = case _ of lift $ modify _{month = Just $ fromEnum month} cb a MonthTwoDigits a → do - month ← parseInt 2 (exactLength *> (validateRange 1 12)) "Incorrect 2-digit month" + month ← parseInt 2 (validateRange 1 12 *> exactLength) "Incorrect 2-digit month" lift $ modify _{month = Just month} cb a DayOfMonthTwoDigits a → do - dom ← parseInt 2 (exactLength *> (validateRange 1 31)) "Incorrect day of month" + dom ← parseInt 2 (validateRange 1 31 *> exactLength) "Incorrect day of month" lift $ modify _{day = Just dom} cb a DayOfMonth a → do @@ -454,11 +454,11 @@ unformatFParser cb = case _ of dow ← parseInt 1 (validateRange 1 7) "Incorrect day of week" cb a Hours24 a → do - hh ← parseInt 2 (exactLength *> (validateRange 0 23)) "Incorrect 24 hour" + hh ← parseInt 2 (validateRange 0 23 *> exactLength) "Incorrect 24 hour" lift $ modify _{hour = Just hh} cb a Hours12 a → do - hh ← parseInt 2 (exactLength *> (validateRange 0 11)) "Incorrect 12 hour" + hh ← parseInt 2 (validateRange 0 11 *> exactLength) "Incorrect 12 hour" lift $ modify _{hour = Just hh} cb a Meridiem a → do @@ -471,7 +471,7 @@ unformatFParser cb = case _ of lift $ modify _{meridiem = Just m} cb a MinutesTwoDigits a → do - mm ← parseInt 2 (exactLength *> (validateRange 0 59)) "Incorrect 2-digit minute" + mm ← parseInt 2 (validateRange 0 59 *> exactLength) "Incorrect 2-digit minute" lift $ modify _{minute = Just mm} cb a Minutes a → do @@ -479,7 +479,7 @@ unformatFParser cb = case _ of lift $ modify _{minute = Just mm} cb a SecondsTwoDigits a → do - ss ← parseInt 2 (exactLength *> (validateRange 0 59)) "Incorrect 2-digit second" + ss ← parseInt 2 (validateRange 0 59 *> exactLength) "Incorrect 2-digit second" lift $ modify _{second = Just ss} cb a Seconds a → do From 98624902f66f0e25e40204bc86cc7b0da6381f47 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Wed, 26 Apr 2017 19:30:58 +0400 Subject: [PATCH 45/88] add noValidate --- src/Data/Formatter/DateTime.purs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/Data/Formatter/DateTime.purs b/src/Data/Formatter/DateTime.purs index c2e0305..0b5f73d 100644 --- a/src/Data/Formatter/DateTime.purs +++ b/src/Data/Formatter/DateTime.purs @@ -363,13 +363,11 @@ unformatAccumToDateTime a = | otherwise = Nothing +-- NOTE `ReaderT s (Either e) Unit` forms Monoid where +-- `mempty = lift $ Right unit` (noValidate) and `concat = (*>)` +noValidate ∷ ∀ e. ReaderT { maxLength ∷ Int, length ∷ Int | e } (Either String) Unit +noValidate = lift $ Right unit --- TODO remove --- validateLength ∷ ∀ e. (Int → Boolean) → { length ∷ Int | e } → Either String Unit --- validateLength f {length} = if f length then (Just unit) --- else Just "Invalid number of digits" - --- TODO use MonadAsk in signature exactLength ∷ ∀ e. ReaderT { maxLength ∷ Int, length ∷ Int | e } (Either String) Unit exactLength = ask >>= \({maxLength, length}) → if maxLength /= length then lift $ Left $ "Expected " <> (show maxLength) <> " digits but got " <> (show length) From ec0d462a459be03f822b88296064e0ef0d08377e Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Wed, 26 Apr 2017 20:07:53 +0400 Subject: [PATCH 46/88] refactor datetime parser --- src/Data/Formatter/DateTime.purs | 149 ++++++++++++------------------- 1 file changed, 58 insertions(+), 91 deletions(-) diff --git a/src/Data/Formatter/DateTime.purs b/src/Data/Formatter/DateTime.purs index 0b5f73d..50f1a14 100644 --- a/src/Data/Formatter/DateTime.purs +++ b/src/Data/Formatter/DateTime.purs @@ -38,7 +38,7 @@ import Data.Formatter.Internal (foldDigits) import Data.Formatter.Parser.Number (parseDigit) import Data.Formatter.Parser.Utils (runP, oneOfAs) import Control.Monad.Reader.Trans (ReaderT, runReaderT, ask) - +import Control.Monad.State.Class (class MonadState) import Text.Parsing.Parser as P import Text.Parsing.Parser.Combinators as PC import Text.Parsing.Parser.String as PS @@ -246,17 +246,15 @@ formatF cb dt@(DT.DateTime d t) = case _ of YearFull a → (show $ fromEnum $ D.year d) <> cb a YearTwoDigits a -> - let y = (fromEnum $ D.year d) - in (formatYearTwoDigits y) <> cb a + (formatYearTwoDigits $ fromEnum $ D.year d) <> cb a YearAbsolute a → show (fromEnum $ D.year d) <> cb a MonthFull a → show (D.month d) <> cb a MonthShort a → - printShortMonth (D.month d) <> cb a + (printShortMonth $ D.month d) <> cb a MonthTwoDigits a → - let month = fromEnum $ D.month d - in (padSingleDigit month) <> cb a + (padSingleDigit $ fromEnum $ D.month d) <> cb a DayOfMonthTwoDigits a → (padSingleDigit $ fromEnum $ D.day d) <> cb a DayOfMonth a → @@ -399,39 +397,24 @@ unformatFParser → FormatterF a → P.ParserT String (State UnformatAccum) Unit unformatFParser cb = case _ of - YearFull a → do - year ← parseInt 4 exactLength "Incorrect full year" - lift $ modify _{year = Just $ year} - cb a - YearTwoDigits a → do - y ← parseInt 2 exactLength "Incorrect 2-digit year" - lift $ modify _{year = Just $ if y > 69 then y + 1900 else y + 2000} - cb a - YearAbsolute a → do - sign ← PC.optionMaybe $ PC.try $ PS.string "-" - year ← map foldDigits $ some parseDigit - lift $ modify _{year = Just $ (if isJust sign then -1 else 1) * year} - cb a - MonthFull a → do - month ← parseMonth - lift $ modify _{month = Just $ fromEnum month} - cb a - MonthShort a → do - month ← parseShortMonth - lift $ modify _{month = Just $ fromEnum month} - cb a - MonthTwoDigits a → do - month ← parseInt 2 (validateRange 1 12 *> exactLength) "Incorrect 2-digit month" - lift $ modify _{month = Just month} - cb a - DayOfMonthTwoDigits a → do - dom ← parseInt 2 (validateRange 1 31 *> exactLength) "Incorrect day of month" - lift $ modify _{day = Just dom} - cb a - DayOfMonth a → do - dom ← parseInt 2 (validateRange 1 31) "Incorrect day of month" - lift $ modify _{day = Just dom} - cb a + YearFull a → _{year = _} `modifyWithParser` + (parseInt 4 exactLength "Incorrect full year") *> cb a + YearTwoDigits a → _{year = _} `modifyWithParser` + (parseInt 2 exactLength "Incorrect 2-digit year") *> cb a + YearAbsolute a → _{year = _} `modifyWithParser` + (lift2 (*) + (PC.option 1 $ PC.try $ PS.string "-" <#> (const (-1))) + (some parseDigit <#> foldDigits)) *> cb a + MonthFull a → _{month = _} `modifyWithParser` + (fromEnum <$> parseMonth) *> cb a + MonthShort a → _{month = _} `modifyWithParser` + (fromEnum <$> parseShortMonth) *> cb a + MonthTwoDigits a → _{month = _} `modifyWithParser` + (parseInt 2 (validateRange 1 12 *> exactLength) "Incorrect 2-digit month") *> cb a + DayOfMonthTwoDigits a → _{day = _} `modifyWithParser` + (parseInt 2 (validateRange 1 31 *> exactLength) "Incorrect day of month") *> cb a + DayOfMonth a → _{day = _} `modifyWithParser` + (parseInt 2 (validateRange 1 31) "Incorrect day of month") *> cb a UnixTimestamp a → do s ← map foldDigits $ some parseDigit case map toDateTime $ instant $ Dur.Milliseconds $ 1000.0 * Int.toNumber s of @@ -447,59 +430,35 @@ unformatFParser cb = case _ of , meridiem: (Nothing ∷ Maybe Meridiem) } cb a - DayOfWeek a → do -- TODO we would need to use this value if we support date format using week number - dow ← parseInt 1 (validateRange 1 7) "Incorrect day of week" - cb a - Hours24 a → do - hh ← parseInt 2 (validateRange 0 23 *> exactLength) "Incorrect 24 hour" - lift $ modify _{hour = Just hh} - cb a - Hours12 a → do - hh ← parseInt 2 (validateRange 0 11 *> exactLength) "Incorrect 12 hour" - lift $ modify _{hour = Just hh} - cb a - Meridiem a → do - m ← - PC.choice [ PC.try $ PS.string "am" $> AM - , PC.try $ PS.string "AM" $> AM - , PC.try $ PS.string "pm" $> PM - , PC.try $ PS.string "PM" $> PM - ] - lift $ modify _{meridiem = Just m} - cb a - MinutesTwoDigits a → do - mm ← parseInt 2 (validateRange 0 59 *> exactLength) "Incorrect 2-digit minute" - lift $ modify _{minute = Just mm} - cb a - Minutes a → do - mm ← parseInt 2 (validateRange 0 59) "Incorrect minute" - lift $ modify _{minute = Just mm} - cb a - SecondsTwoDigits a → do - ss ← parseInt 2 (validateRange 0 59 *> exactLength) "Incorrect 2-digit second" - lift $ modify _{second = Just ss} - cb a - Seconds a → do - ss ← parseInt 2 (validateRange 0 59) "Incorrect second" - lift $ modify _{second = Just ss} - cb a - Milliseconds a → do - sss ← parseInt 3 exactLength "Incorrect millisecond" - lift $ modify _{millisecond = Just sss} - cb a - Placeholder s a → - PS.string s *> cb a - MillisecondsShort a → do - s ← parseInt 1 exactLength "Incorrect 1-digit millisecond" - lift $ modify _{millisecond = Just s} - cb a - MillisecondsTwoDigits a → do - ss ← parseInt 2 exactLength "Incorrect 2-digit millisecond" - lift $ modify _{millisecond = Just ss} - cb a - End → - pure unit + DayOfWeek a → (parseInt 1 (validateRange 1 7) "Incorrect day of week") *> cb a + Hours24 a → _{hour = _} `modifyWithParser` + (parseInt 2 (validateRange 0 23 *> exactLength) "Incorrect 24 hour") *> cb a + Hours12 a → _{hour = _} `modifyWithParser` + (parseInt 2 (validateRange 0 11 *> exactLength) "Incorrect 12 hour") *> cb a + Meridiem a → _{meridiem = _} `modifyWithParser` + parseMeridiem *> cb a + MinutesTwoDigits a → _{minute = _} `modifyWithParser` + (parseInt 2 (validateRange 0 59 *> exactLength) "Incorrect 2-digit minute") *> cb a + Minutes a → _{minute = _} `modifyWithParser` + (parseInt 2 (validateRange 0 59) "Incorrect minute") *> cb a + SecondsTwoDigits a → _{second = _} `modifyWithParser` + (parseInt 2 (validateRange 0 59 *> exactLength) "Incorrect 2-digit second") *> cb a + Seconds a → _{second = _} `modifyWithParser` + (parseInt 2 (validateRange 0 59) "Incorrect second") *> cb a + Milliseconds a → _{millisecond = _} `modifyWithParser` + (parseInt 3 exactLength "Incorrect millisecond") *> cb a + Placeholder s a → PS.string s *> cb a + MillisecondsShort a → _{millisecond = _} `modifyWithParser` + (parseInt 1 exactLength "Incorrect 1-digit millisecond") *> cb a + MillisecondsTwoDigits a → _{millisecond = _} `modifyWithParser` + (parseInt 2 exactLength "Incorrect 2-digit millisecond") *> cb a + End → pure unit + where + modifyWithParser :: ∀ s x m. MonadState s m => (s -> Maybe x -> s) -> m x -> m Unit + modifyWithParser f p = do + v <- p + lift $ modify (flip f (Just v)) unformatParser ∷ ∀ m. Monad m => Formatter → P.ParserT String m DT.DateTime unformatParser f' = do @@ -518,6 +477,14 @@ unformatDateTime ∷ String → String → Either String DT.DateTime unformatDateTime pattern str = parseFormatString pattern >>= flip unformat str +parseMeridiem ∷ ∀ m. Monad m ⇒ P.ParserT String m Meridiem +parseMeridiem = (PC.try <<< PS.string) `oneOfAs` + [ Tuple "am" AM + , Tuple "AM" AM + , Tuple "pm" PM + , Tuple "PM" PM + ] + parseMonth ∷ ∀ m. Monad m ⇒ P.ParserT String m D.Month parseMonth = (PC.try <<< PS.string) `oneOfAs` [ Tuple "January" D.January From 51eb72e8beb227a811942918c40136a9ac316ab9 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Wed, 26 Apr 2017 20:51:55 +0400 Subject: [PATCH 47/88] don't use MonadState with P.ParserT --- src/Data/Formatter/DateTime.purs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Data/Formatter/DateTime.purs b/src/Data/Formatter/DateTime.purs index 50f1a14..18e6374 100644 --- a/src/Data/Formatter/DateTime.purs +++ b/src/Data/Formatter/DateTime.purs @@ -402,9 +402,9 @@ unformatFParser cb = case _ of YearTwoDigits a → _{year = _} `modifyWithParser` (parseInt 2 exactLength "Incorrect 2-digit year") *> cb a YearAbsolute a → _{year = _} `modifyWithParser` - (lift2 (*) - (PC.option 1 $ PC.try $ PS.string "-" <#> (const (-1))) - (some parseDigit <#> foldDigits)) *> cb a + (pure (*) + <*> (PC.option 1 $ PC.try $ PS.string "-" <#> (const (-1))) + <*> (some parseDigit <#> foldDigits)) *> cb a MonthFull a → _{month = _} `modifyWithParser` (fromEnum <$> parseMonth) *> cb a MonthShort a → _{month = _} `modifyWithParser` @@ -455,7 +455,7 @@ unformatFParser cb = case _ of (parseInt 2 exactLength "Incorrect 2-digit millisecond") *> cb a End → pure unit where - modifyWithParser :: ∀ s x m. MonadState s m => (s -> Maybe x -> s) -> m x -> m Unit + modifyWithParser :: ∀ s' s x. (s -> Maybe x -> s) -> P.ParserT s' (State s) x -> P.ParserT s' (State s) Unit modifyWithParser f p = do v <- p lift $ modify (flip f (Just v)) From b2ecdd713b9c993b8cf95ffa8dc644a26b5e0a57 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Wed, 26 Apr 2017 21:05:23 +0400 Subject: [PATCH 48/88] use readFloat in parseFractional --- src/Data/Formatter/DateTime.purs | 3 +-- src/Data/Formatter/Parser/Number.purs | 20 ++++---------------- 2 files changed, 5 insertions(+), 18 deletions(-) diff --git a/src/Data/Formatter/DateTime.purs b/src/Data/Formatter/DateTime.purs index 18e6374..26538d9 100644 --- a/src/Data/Formatter/DateTime.purs +++ b/src/Data/Formatter/DateTime.purs @@ -28,7 +28,7 @@ import Data.Either (Either(..), either) import Data.Enum (fromEnum, toEnum) import Data.Functor.Mu (Mu, unroll, roll) import Data.Int as Int -import Data.Maybe (Maybe(..), maybe, isJust, fromMaybe) +import Data.Maybe (Maybe(..), maybe, fromMaybe) import Data.Newtype (unwrap) import Data.String as Str import Data.Time as T @@ -38,7 +38,6 @@ import Data.Formatter.Internal (foldDigits) import Data.Formatter.Parser.Number (parseDigit) import Data.Formatter.Parser.Utils (runP, oneOfAs) import Control.Monad.Reader.Trans (ReaderT, runReaderT, ask) -import Control.Monad.State.Class (class MonadState) import Text.Parsing.Parser as P import Text.Parsing.Parser.Combinators as PC import Text.Parsing.Parser.String as PS diff --git a/src/Data/Formatter/Parser/Number.purs b/src/Data/Formatter/Parser/Number.purs index ae2db50..590072b 100644 --- a/src/Data/Formatter/Parser/Number.purs +++ b/src/Data/Formatter/Parser/Number.purs @@ -7,19 +7,18 @@ module Data.Formatter.Parser.Number import Prelude -import Data.Int (toNumber, floor) +import Data.Int (toNumber) import Data.Array (some, many, length) import Data.Formatter.Parser.Number (parseDigit) import Data.Formatter.Internal (foldDigits) -import Data.Function (on) import Data.Tuple (Tuple(..)) import Text.Parsing.Parser as P import Text.Parsing.Parser.Combinators as PC import Data.Formatter.Parser.Utils (oneOfAs) import Text.Parsing.Parser.String as PS import Data.Maybe (Maybe(..)) -import Math as Math - +import Data.Foldable (foldMap) +import Global (readFloat) parseInteger ∷ ∀ s m. Monad m => PS.StringLike s => P.ParserT s m Int parseInteger = some parseDigit <#> foldDigits @@ -28,24 +27,13 @@ parseMaybeInteger ∷ ∀ s m. Monad m => PS.StringLike s => P.ParserT s m (Mayb parseMaybeInteger = many parseDigit <#> (\l -> if length l == 0 then Nothing else Just $ foldDigits l) parseFractional ∷ ∀ s m. Monad m => PS.StringLike s => P.ParserT s m Number -parseFractional = parseInteger <#> case _ of - 0 -> 0.0 - n -> (toNumber n) / (pow 10 $ numOfDigits n) +parseFractional = (some parseDigit) <#> (foldMap show >>> ("0." <> _) >>> readFloat) parseNumber ∷ ∀ s m. Monad m => PS.StringLike s => P.ParserT s m Number parseNumber = (+) <$> (parseInteger <#> toNumber) <*> (PC.option 0.0 $ PC.try $ PS.oneOf ['.', ','] *> parseFractional) -pow :: Int -> Int -> Number -pow = Math.pow `on` toNumber - -numOfDigits ∷ Int → Int -numOfDigits 0 = 0 -numOfDigits n = 1 + (floor $ log10 $ toNumber n) - -log10 ∷ Number → Number -log10 n = Math.log10e * Math.log n parseDigit ∷ ∀ s m. Monad m => PS.StringLike s => P.ParserT s m Int parseDigit = PC.try $ PS.char `oneOfAs` From 68baf7f78abeadd09e3b273eb147e5b96d727b0d Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Wed, 26 Apr 2017 21:09:43 +0400 Subject: [PATCH 49/88] refactor parseMaybeInteger --- src/Data/Formatter/Parser/Number.purs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Data/Formatter/Parser/Number.purs b/src/Data/Formatter/Parser/Number.purs index 590072b..4c6ad7a 100644 --- a/src/Data/Formatter/Parser/Number.purs +++ b/src/Data/Formatter/Parser/Number.purs @@ -8,7 +8,7 @@ module Data.Formatter.Parser.Number import Prelude import Data.Int (toNumber) -import Data.Array (some, many, length) +import Data.Array (some) import Data.Formatter.Parser.Number (parseDigit) import Data.Formatter.Internal (foldDigits) import Data.Tuple (Tuple(..)) @@ -16,7 +16,7 @@ import Text.Parsing.Parser as P import Text.Parsing.Parser.Combinators as PC import Data.Formatter.Parser.Utils (oneOfAs) import Text.Parsing.Parser.String as PS -import Data.Maybe (Maybe(..)) +import Data.Maybe (Maybe) import Data.Foldable (foldMap) import Global (readFloat) @@ -24,7 +24,7 @@ parseInteger ∷ ∀ s m. Monad m => PS.StringLike s => P.ParserT s m Int parseInteger = some parseDigit <#> foldDigits parseMaybeInteger ∷ ∀ s m. Monad m => PS.StringLike s => P.ParserT s m (Maybe Int) -parseMaybeInteger = many parseDigit <#> (\l -> if length l == 0 then Nothing else Just $ foldDigits l) +parseMaybeInteger = PC.optionMaybe parseInteger parseFractional ∷ ∀ s m. Monad m => PS.StringLike s => P.ParserT s m Number parseFractional = (some parseDigit) <#> (foldMap show >>> ("0." <> _) >>> readFloat) From 5ebbb31aa577149c1ee64fc4036118387baa6bbc Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Wed, 26 Apr 2017 21:12:52 +0400 Subject: [PATCH 50/88] refactor unEither --- src/Data/Formatter/Parser/Interval.purs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Data/Formatter/Parser/Interval.purs b/src/Data/Formatter/Parser/Interval.purs index 0fc7af2..13d869c 100644 --- a/src/Data/Formatter/Parser/Interval.purs +++ b/src/Data/Formatter/Parser/Interval.purs @@ -81,7 +81,4 @@ extendedDateTimeFormatInUTC ∷ Formatter extendedDateTimeFormatInUTC = unEither $ parseFormatString "YYYY-MM-DDTHH:mm:ssZ" where unEither :: Either String Formatter -> Formatter - unEither e = (unsafePartialBecause "(this must be unrechable) error in parsing ISO date format") (fromRight e) - --TODO check why this are not working? - -- unEither = (unsafePartialBecause "(this must be unrechable) error in parsing ISO date format") <<< fromRight - -- unEither = fromRight >>> (unsafePartialBecause "(this must be unrechable) error in parsing ISO date format") + unEither = unsafePartialBecause "(this must be unrechable) error in parsing ISO date format" fromRight From 6bcd941d166556a6b38c4ceaad666c2d6623158e Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Wed, 26 Apr 2017 21:15:39 +0400 Subject: [PATCH 51/88] refactor foldFoldableMaybe --- src/Data/Formatter/Parser/Interval.purs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/Data/Formatter/Parser/Interval.purs b/src/Data/Formatter/Parser/Interval.purs index 13d869c..24adff8 100644 --- a/src/Data/Formatter/Parser/Interval.purs +++ b/src/Data/Formatter/Parser/Interval.purs @@ -12,8 +12,8 @@ import Text.Parsing.Parser as P import Text.Parsing.Parser.Combinators as PC import Text.Parsing.Parser.String as PS import Control.Alt ((<|>)) -import Data.Foldable (class Foldable, fold) -import Data.Maybe (Maybe(..), maybe) +import Data.Foldable (class Foldable, fold, foldMap) +import Data.Maybe (Maybe(..)) import Data.Monoid (class Monoid, mempty) import Data.Either (Either, fromRight) import Data.Formatter.DateTime (unformatParser, Formatter, parseFormatString) @@ -64,10 +64,7 @@ mkComponentsParser arr = p `notEmpty` ("none of valid duration components (" <> applyDurations (Tuple f c) = PC.optionMaybe $ PC.try (f <$> component c) foldFoldableMaybe :: ∀ f a. Foldable f => Monoid a => f (Maybe a) -> a - foldFoldableMaybe = fold >>> unMaybe - - unMaybe :: ∀ a. Monoid a => Maybe a -> a - unMaybe = maybe mempty id + foldFoldableMaybe = foldMap fold component ∷ String → P.Parser String Number component designator = parseNumber <* PS.string designator From 03b3536b242f6440ff5232c5f42fcbe5b98b4d5c Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Wed, 26 Apr 2017 21:17:14 +0400 Subject: [PATCH 52/88] fix where indentation --- src/Data/Formatter/Parser/Interval.purs | 35 ++++++++++++------------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/src/Data/Formatter/Parser/Interval.purs b/src/Data/Formatter/Parser/Interval.purs index 24adff8..2d6e650 100644 --- a/src/Data/Formatter/Parser/Interval.purs +++ b/src/Data/Formatter/Parser/Interval.purs @@ -31,10 +31,10 @@ parseRecurringInterval duration date = parseInterval :: ∀ a b. P.Parser String a -> P.Parser String b -> P.Parser String (I.Interval a b) parseInterval duration date = [startEnd, durationEnd, startDuration, justDuration] <#> PC.try # PC.choice where - startEnd = I.StartEnd <$> date <* PS.string "/" <*> date - durationEnd = I.DurationEnd <$> duration <* PS.string "/" <*> date - startDuration = I.StartDuration <$> date <* PS.string "/" <*> duration - justDuration = I.JustDuration <$> duration + startEnd = I.StartEnd <$> date <* PS.string "/" <*> date + durationEnd = I.DurationEnd <$> duration <* PS.string "/" <*> date + startDuration = I.StartDuration <$> date <* PS.string "/" <*> duration + justDuration = I.JustDuration <$> duration parseIsoDuration :: P.Parser String I.IsoDuration parseIsoDuration = do @@ -46,10 +46,10 @@ parseIsoDuration = do parseDuration :: P.Parser String I.Duration parseDuration = PS.string "P" *> (weekDuration <|> fullDuration) where - weekDuration = mkComponentsParser [ Tuple I.week "W" ] - fullDuration = (append <$> durationDatePart <*> durationTimePart) `notEmpty` "must contain valid duration components" - durationDatePart = PC.option mempty $ PC.try $ mkComponentsParser [ Tuple I.year "Y" , Tuple I.month "M" , Tuple I.day "D" ] - durationTimePart = PC.option mempty $ (PC.try $ PS.string "T") *> (mkComponentsParser [ Tuple I.hours "H" , Tuple I.minutes "M" , Tuple I.seconds "S" ]) + weekDuration = mkComponentsParser [ Tuple I.week "W" ] + fullDuration = (append <$> durationDatePart <*> durationTimePart) `notEmpty` "must contain valid duration components" + durationDatePart = PC.option mempty $ PC.try $ mkComponentsParser [ Tuple I.year "Y" , Tuple I.month "M" , Tuple I.day "D" ] + durationTimePart = PC.option mempty $ (PC.try $ PS.string "T") *> (mkComponentsParser [ Tuple I.hours "H" , Tuple I.minutes "M" , Tuple I.seconds "S" ]) notEmpty :: ∀ a. Monoid a => Eq a => P.Parser String a -> String -> P.Parser String a @@ -57,17 +57,16 @@ notEmpty p str = p >>= \x -> if x == mempty then P.fail str else pure x mkComponentsParser :: Array (Tuple (Number -> I.Duration) String) -> P.Parser String I.Duration mkComponentsParser arr = p `notEmpty` ("none of valid duration components (" <> (show $ snd <$> arr) <> ") were present") - where - p = arr <#> applyDurations # sequence <#> foldFoldableMaybe - applyDurations :: Tuple (Number -> I.Duration) String -> P.Parser String (Maybe I.Duration) - applyDurations (Tuple f c) = PC.optionMaybe $ PC.try (f <$> component c) + p = arr <#> applyDurations # sequence <#> foldFoldableMaybe + applyDurations :: Tuple (Number -> I.Duration) String -> P.Parser String (Maybe I.Duration) + applyDurations (Tuple f c) = PC.optionMaybe $ PC.try (f <$> component c) - foldFoldableMaybe :: ∀ f a. Foldable f => Monoid a => f (Maybe a) -> a - foldFoldableMaybe = foldMap fold + foldFoldableMaybe :: ∀ f a. Foldable f => Monoid a => f (Maybe a) -> a + foldFoldableMaybe = foldMap fold - component ∷ String → P.Parser String Number - component designator = parseNumber <* PS.string designator + component ∷ String → P.Parser String Number + component designator = parseNumber <* PS.string designator -- parser for DateTime in UTC time zone using "extended format" @@ -77,5 +76,5 @@ parseDateTime = unformatParser extendedDateTimeFormatInUTC extendedDateTimeFormatInUTC ∷ Formatter extendedDateTimeFormatInUTC = unEither $ parseFormatString "YYYY-MM-DDTHH:mm:ssZ" where - unEither :: Either String Formatter -> Formatter - unEither = unsafePartialBecause "(this must be unrechable) error in parsing ISO date format" fromRight + unEither :: Either String Formatter -> Formatter + unEither = unsafePartialBecause "(this must be unrechable) error in parsing ISO date format" fromRight From 58eebcd0b404f904a209867f65f711b598846470 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Wed, 26 Apr 2017 21:21:02 +0400 Subject: [PATCH 53/88] more where indentation --- src/Data/Formatter/DateTime.purs | 14 +++++++------- test/src/Interval.purs | 10 +++++----- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Data/Formatter/DateTime.purs b/src/Data/Formatter/DateTime.purs index 26538d9..d7e6cc1 100644 --- a/src/Data/Formatter/DateTime.purs +++ b/src/Data/Formatter/DateTime.purs @@ -190,8 +190,8 @@ formatYearTwoDigits i = case dateLength of 2 -> dateString _ -> Str.drop (dateLength - 2) dateString where - dateString = show $ abs i - dateLength = Str.length $ dateString + dateString = show $ abs i + dateLength = Str.length $ dateString placeholderContent ∷ P.Parser String String @@ -464,11 +464,11 @@ unformatParser f' = do acc <- P.mapParserT unState $ rec f' either P.fail pure $ unformatAccumToDateTime acc where - rec ∷ Formatter → P.ParserT String (State UnformatAccum) Unit - rec f = unformatFParser rec $ unroll f - unState :: ∀ x y n. Monad n => State UnformatAccum (Tuple (Either y Unit) x) -> n (Tuple (Either y UnformatAccum) x) - unState s = case runState s initialAccum of - Tuple (Tuple e state) res -> pure (Tuple (e $> res) state) + rec ∷ Formatter → P.ParserT String (State UnformatAccum) Unit + rec f = unformatFParser rec $ unroll f + unState :: ∀ x y n. Monad n => State UnformatAccum (Tuple (Either y Unit) x) -> n (Tuple (Either y UnformatAccum) x) + unState s = case runState s initialAccum of + Tuple (Tuple e state) res -> pure (Tuple (e $> res) state) diff --git a/test/src/Interval.purs b/test/src/Interval.purs index ff4f60c..c1ea481 100644 --- a/test/src/Interval.purs +++ b/test/src/Interval.purs @@ -75,11 +75,11 @@ invalidDurations = , { err: errEOF <> "1:4", str: "P1Y1W" } ] where - errInvalidComponent = "must contain valid duration components@" - errPrefix = "Expected \"P\"@" - errEOF = "Expected EOF@" - errInvalidISO = "extracted Duration is not valid ISO duration@" - errNoTimeComponent = "none of valid duration components ([\"H\",\"M\",\"S\"]) were present@" + errInvalidComponent = "must contain valid duration components@" + errPrefix = "Expected \"P\"@" + errEOF = "Expected EOF@" + errInvalidISO = "extracted Duration is not valid ISO duration@" + errNoTimeComponent = "none of valid duration components ([\"H\",\"M\",\"S\"]) were present@" -- TODO error messages could be improved invalidIntervals :: Array {err :: String, str :: String} From 1b7268bf5819e6f8be92af4348def571b114183c Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Wed, 26 Apr 2017 21:28:35 +0400 Subject: [PATCH 54/88] add Newtype instance for Number.Formatter; derive Eq --- src/Data/Formatter/Number.purs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Data/Formatter/Number.purs b/src/Data/Formatter/Number.purs index 375199c..64391ac 100644 --- a/src/Data/Formatter/Number.purs +++ b/src/Data/Formatter/Number.purs @@ -33,9 +33,9 @@ import Text.Parsing.Parser as P import Text.Parsing.Parser.Combinators as PC import Text.Parsing.Parser.String as PS +import Data.Newtype (class Newtype) import Data.Generic.Rep (class Generic) import Data.Generic.Rep.Show (genericShow) -import Data.Generic.Rep.Eq (genericEq) newtype Formatter = Formatter @@ -47,12 +47,12 @@ newtype Formatter = Formatter } derive instance genericFormatter :: Generic Formatter _ +derive instance newtypeFormatter :: Newtype Formatter _ instance showFormatter :: Show Formatter where show = genericShow -instance eqFormatter :: Eq Formatter where - eq = genericEq +derive instance eqFormatter :: Eq Formatter printFormatter ∷ Formatter → String printFormatter (Formatter f) = From ae1a83bad5b5032a9a8f72f031a15c4dbcbcba3b Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Wed, 26 Apr 2017 21:33:54 +0400 Subject: [PATCH 55/88] derive Eq for DateTime.FormatterF --- src/Data/Formatter/DateTime.purs | 33 +++++--------------------------- 1 file changed, 5 insertions(+), 28 deletions(-) diff --git a/src/Data/Formatter/DateTime.purs b/src/Data/Formatter/DateTime.purs index d7e6cc1..6a73a1a 100644 --- a/src/Data/Formatter/DateTime.purs +++ b/src/Data/Formatter/DateTime.purs @@ -67,7 +67,7 @@ data FormatterF a | Placeholder String a | End -instance formatterFFunctor ∷ Functor FormatterF where +instance functorFormatterF ∷ Functor FormatterF where map f (YearFull a) = YearFull $ f a map f (YearTwoDigits a) = YearTwoDigits $ f a map f (YearAbsolute a) = YearAbsolute $ f a @@ -91,7 +91,7 @@ instance formatterFFunctor ∷ Functor FormatterF where map f (Placeholder str a) = Placeholder str $ f a map f End = End -instance formatterFShow ∷ Show a => Show (FormatterF a) where +instance showFormatterF ∷ Show a => Show (FormatterF a) where show (YearFull a) = "(YearFull " <> (show a) <> "c" show (YearTwoDigits a) = "(YearTwoDigits " <> (show a) <> ")" show (YearAbsolute a) = "(YearAbsolute " <> (show a) <> ")" @@ -115,32 +115,9 @@ instance formatterFShow ∷ Show a => Show (FormatterF a) where show (Placeholder str a) = "(Placeholder " <> (show str) <> " "<> (show a) <> ")" show End = "End" -instance formatterFEq ∷ Eq a => Eq (FormatterF a) where - eq (YearFull a) (YearFull b) = eq a b - eq (YearTwoDigits a) (YearTwoDigits b) = eq a b - eq (YearAbsolute a) (YearAbsolute b) = eq a b - eq (MonthFull a) (MonthFull b) = eq a b - eq (MonthShort a) (MonthShort b) = eq a b - eq (MonthTwoDigits a) (MonthTwoDigits b) = eq a b - eq (DayOfMonthTwoDigits a) (DayOfMonthTwoDigits b) = eq a b - eq (DayOfMonth a) (DayOfMonth b) = eq a b - eq (UnixTimestamp a) (UnixTimestamp b) = eq a b - eq (DayOfWeek a) (DayOfWeek b) = eq a b - eq (Hours24 a) (Hours24 b) = eq a b - eq (Hours12 a) (Hours12 b) = eq a b - eq (Meridiem a) (Meridiem b) = eq a b - eq (Minutes a) (Minutes b) = eq a b - eq (MinutesTwoDigits a) (MinutesTwoDigits b) = eq a b - eq (Seconds a) (Seconds b) = eq a b - eq (SecondsTwoDigits a) (SecondsTwoDigits b) = eq a b - eq (Milliseconds a) (Milliseconds b) = eq a b - eq (MillisecondsShort a) (MillisecondsShort b) = eq a b - eq (MillisecondsTwoDigits a) (MillisecondsTwoDigits b) = eq a b - eq (Placeholder stra a) (Placeholder strb b) = eq stra strb && eq a b - eq End End = true - eq _ _ = false - -instance formatterFEq1 :: Eq1 FormatterF where +derive instance eqFormatterF :: Eq a => Eq (FormatterF a) + +instance eq1FormatterF :: Eq1 FormatterF where eq1 = eq From acf9452ba81250cdec4e88837646351ae4f107f0 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Wed, 26 Apr 2017 22:11:24 +0400 Subject: [PATCH 56/88] derive Functor for DateTime.FormatterF --- src/Data/Formatter/DateTime.purs | 24 +----------------------- 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/src/Data/Formatter/DateTime.purs b/src/Data/Formatter/DateTime.purs index 6a73a1a..aff3a1c 100644 --- a/src/Data/Formatter/DateTime.purs +++ b/src/Data/Formatter/DateTime.purs @@ -67,29 +67,7 @@ data FormatterF a | Placeholder String a | End -instance functorFormatterF ∷ Functor FormatterF where - map f (YearFull a) = YearFull $ f a - map f (YearTwoDigits a) = YearTwoDigits $ f a - map f (YearAbsolute a) = YearAbsolute $ f a - map f (MonthFull a) = MonthFull $ f a - map f (MonthShort a) = MonthShort $ f a - map f (MonthTwoDigits a) = MonthTwoDigits $ f a - map f (DayOfMonthTwoDigits a) = DayOfMonthTwoDigits $ f a - map f (DayOfMonth a) = DayOfMonth $ f a - map f (UnixTimestamp a) = UnixTimestamp $ f a - map f (DayOfWeek a) = DayOfWeek $ f a - map f (Hours24 a) = Hours24 $ f a - map f (Hours12 a) = Hours12 $ f a - map f (Meridiem a) = Meridiem $ f a - map f (Minutes a) = Minutes $ f a - map f (MinutesTwoDigits a) = MinutesTwoDigits $ f a - map f (Seconds a) = Seconds $ f a - map f (SecondsTwoDigits a) = SecondsTwoDigits $ f a - map f (Milliseconds a) = Milliseconds $ f a - map f (MillisecondsShort a) = MillisecondsShort $ f a - map f (MillisecondsTwoDigits a) = MillisecondsTwoDigits $ f a - map f (Placeholder str a) = Placeholder str $ f a - map f End = End +derive instance functorFormatterF :: Functor FormatterF instance showFormatterF ∷ Show a => Show (FormatterF a) where show (YearFull a) = "(YearFull " <> (show a) <> "c" From 56dda422e0778dba068ecbae17b57d014a78191a Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Wed, 26 Apr 2017 22:12:53 +0400 Subject: [PATCH 57/88] =?UTF-8?q?replace=20->=20with=20=E2=86=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 16 ++++++++-------- src/Data/Formatter/DateTime.purs | 24 ++++++++++++------------ src/Data/Formatter/Interval.purs | 14 +++++++------- src/Data/Formatter/Parser/Interval.purs | 20 ++++++++++---------- src/Data/Formatter/Parser/Utils.purs | 8 ++++---- test/src/DateTime.purs | 22 +++++++++++----------- test/src/Interval.purs | 16 ++++++++-------- test/src/Number.purs | 8 ++++---- test/src/Utils.purs | 6 +++--- 9 files changed, 67 insertions(+), 67 deletions(-) diff --git a/README.md b/README.md index 8735777..e278816 100644 --- a/README.md +++ b/README.md @@ -22,23 +22,23 @@ Formatter has following properties 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"` ++ `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"` ++ `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"` ++ `+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"` ++ `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"` ++ `0a`: `1234567 → "1M"`, `1 → "1"` ## Date/Time formatters diff --git a/src/Data/Formatter/DateTime.purs b/src/Data/Formatter/DateTime.purs index aff3a1c..c3a4465 100644 --- a/src/Data/Formatter/DateTime.purs +++ b/src/Data/Formatter/DateTime.purs @@ -139,11 +139,11 @@ parseFormatString = runP formatParser -- | Formatting function that accepts a number that is a year, -- | and strips away the non-significant digits, leaving only the -- | ones and tens positions. -formatYearTwoDigits :: Int -> String +formatYearTwoDigits :: Int → String formatYearTwoDigits i = case dateLength of - 1 -> "0" <> dateString - 2 -> dateString - _ -> Str.drop (dateLength - 2) dateString + 1 → "0" <> dateString + 2 → dateString + _ → Str.drop (dateLength - 2) dateString where dateString = show $ abs i dateLength = Str.length $ dateString @@ -199,7 +199,7 @@ formatF formatF cb dt@(DT.DateTime d t) = case _ of YearFull a → (show $ fromEnum $ D.year d) <> cb a - YearTwoDigits a -> + YearTwoDigits a → (formatYearTwoDigits $ fromEnum $ D.year d) <> cb a YearAbsolute a → show (fromEnum $ D.year d) <> cb a @@ -242,12 +242,12 @@ formatF cb dt@(DT.DateTime d t) = case _ of s <> cb a End → "" -padSingleDigit :: Int -> String +padSingleDigit :: Int → String padSingleDigit i | i < 10 = "0" <> (show i) | otherwise = show i -padDoubleDigit :: Int -> String +padDoubleDigit :: Int → String padDoubleDigit i | i < 10 = "00" <> (show i) | i < 100 = "0" <> (show i) @@ -341,8 +341,8 @@ parseInt maxLength validators errMsg = do let length = List.length ds let num = foldDigits ds case runReaderT validators {length, num, maxLength} of - Left err -> P.fail $ errMsg <> "(" <> err <> ")" - Right _ -> pure num + Left err → P.fail $ errMsg <> "(" <> err <> ")" + Right _ → pure num -- take unformatFParser @@ -409,7 +409,7 @@ unformatFParser cb = case _ of (parseInt 2 exactLength "Incorrect 2-digit millisecond") *> cb a End → pure unit where - modifyWithParser :: ∀ s' s x. (s -> Maybe x -> s) -> P.ParserT s' (State s) x -> P.ParserT s' (State s) Unit + modifyWithParser :: ∀ s' s x. (s → Maybe x → s) → P.ParserT s' (State s) x → P.ParserT s' (State s) Unit modifyWithParser f p = do v <- p lift $ modify (flip f (Just v)) @@ -421,9 +421,9 @@ unformatParser f' = do where rec ∷ Formatter → P.ParserT String (State UnformatAccum) Unit rec f = unformatFParser rec $ unroll f - unState :: ∀ x y n. Monad n => State UnformatAccum (Tuple (Either y Unit) x) -> n (Tuple (Either y UnformatAccum) x) + unState :: ∀ x y n. Monad n => State UnformatAccum (Tuple (Either y Unit) x) → n (Tuple (Either y UnformatAccum) x) unState s = case runState s initialAccum of - Tuple (Tuple e state) res -> pure (Tuple (e $> res) state) + Tuple (Tuple e state) res → pure (Tuple (e $> res) state) diff --git a/src/Data/Formatter/Interval.purs b/src/Data/Formatter/Interval.purs index 7f2cef0..fec8c54 100644 --- a/src/Data/Formatter/Interval.purs +++ b/src/Data/Formatter/Interval.purs @@ -20,22 +20,22 @@ import Data.Int as Int import Data.Foldable (foldMap) import Data.Formatter.Parser.Interval (parseRecurringInterval, parseInterval, parseIsoDuration, parseDateTime, extendedDateTimeFormatInUTC) -formatRecurringInterval :: I.RecurringInterval I.IsoDuration DateTime -> String +formatRecurringInterval :: I.RecurringInterval I.IsoDuration DateTime → String formatRecurringInterval (I.RecurringInterval n i) = "R" <> (maybe "" formatInteger n) <> "/" <> (formatInterval i) -formatInterval :: I.Interval I.IsoDuration DateTime -> String +formatInterval :: I.Interval I.IsoDuration DateTime → String formatInterval (I.StartEnd x y) = (formatDateTime x) <> "/" <> (formatDateTime y) formatInterval (I.DurationEnd d x) = (formatIsoDuration d) <> "/" <> (formatDateTime x) formatInterval (I.StartDuration x d) = (formatDateTime x) <> "/" <> (formatIsoDuration d) formatInterval (I.JustDuration d) = (formatIsoDuration d) -formatDateTime :: DateTime -> String +formatDateTime :: DateTime → String formatDateTime = FDT.format extendedDateTimeFormatInUTC -formatIsoDuration :: I.IsoDuration -> String +formatIsoDuration :: I.IsoDuration → String formatIsoDuration = formatDuration <<< I.unIsoDuration -formatDuration :: I.Duration -> String +formatDuration :: I.Duration → String formatDuration (I.Duration m) = "P" <> datePart <> timePart where datePart = componentToString `foldMap` dateComponentsToStr @@ -47,10 +47,10 @@ formatDuration (I.Duration m) = "P" <> datePart <> timePart dateComponentsToStr = [ Tuple I.Year "Y", Tuple I.Month "M", Tuple I.Day "D" ] timeComponentsToStr = [ Tuple I.Hours "H", Tuple I.Minutes "M", Tuple I.Seconds "S" ] -formatInteger :: Int -> String +formatInteger :: Int → String formatInteger = show -formatNumber :: Number -> String +formatNumber :: Number → String formatNumber n = if Int.toNumber (Int.floor n) == n then show (Int.floor n) else show n unformatRecurringInterval :: String → Either String (I.RecurringInterval I.IsoDuration DateTime) diff --git a/src/Data/Formatter/Parser/Interval.purs b/src/Data/Formatter/Parser/Interval.purs index 2d6e650..a78a9fd 100644 --- a/src/Data/Formatter/Parser/Interval.purs +++ b/src/Data/Formatter/Parser/Interval.purs @@ -24,11 +24,11 @@ import Partial.Unsafe (unsafePartialBecause) import Data.Formatter.Parser.Number (parseNumber, parseMaybeInteger) -parseRecurringInterval :: ∀ a b. P.Parser String a -> P.Parser String b -> P.Parser String (I.RecurringInterval a b) +parseRecurringInterval :: ∀ a b. P.Parser String a → P.Parser String b → P.Parser String (I.RecurringInterval a b) parseRecurringInterval duration date = I.RecurringInterval <$> (PS.string "R" *> parseMaybeInteger) <*> (PS.string "/" *> parseInterval duration date) -parseInterval :: ∀ a b. P.Parser String a -> P.Parser String b -> P.Parser String (I.Interval a b) +parseInterval :: ∀ a b. P.Parser String a → P.Parser String b → P.Parser String (I.Interval a b) parseInterval duration date = [startEnd, durationEnd, startDuration, justDuration] <#> PC.try # PC.choice where startEnd = I.StartEnd <$> date <* PS.string "/" <*> date @@ -40,8 +40,8 @@ parseIsoDuration :: P.Parser String I.IsoDuration parseIsoDuration = do dur ← parseDuration case I.mkIsoDuration dur of - Nothing -> P.fail "extracted Duration is not valid ISO duration" - Just a -> pure a + Nothing → P.fail "extracted Duration is not valid ISO duration" + Just a → pure a parseDuration :: P.Parser String I.Duration parseDuration = PS.string "P" *> (weekDuration <|> fullDuration) @@ -52,17 +52,17 @@ parseDuration = PS.string "P" *> (weekDuration <|> fullDuration) durationTimePart = PC.option mempty $ (PC.try $ PS.string "T") *> (mkComponentsParser [ Tuple I.hours "H" , Tuple I.minutes "M" , Tuple I.seconds "S" ]) -notEmpty :: ∀ a. Monoid a => Eq a => P.Parser String a -> String -> P.Parser String a -notEmpty p str = p >>= \x -> if x == mempty then P.fail str else pure x +notEmpty :: ∀ a. Monoid a => Eq a => P.Parser String a → String → P.Parser String a +notEmpty p str = p >>= \x → if x == mempty then P.fail str else pure x -mkComponentsParser :: Array (Tuple (Number -> I.Duration) String) -> P.Parser String I.Duration +mkComponentsParser :: Array (Tuple (Number → I.Duration) String) → P.Parser String I.Duration mkComponentsParser arr = p `notEmpty` ("none of valid duration components (" <> (show $ snd <$> arr) <> ") were present") where p = arr <#> applyDurations # sequence <#> foldFoldableMaybe - applyDurations :: Tuple (Number -> I.Duration) String -> P.Parser String (Maybe I.Duration) + applyDurations :: Tuple (Number → I.Duration) String → P.Parser String (Maybe I.Duration) applyDurations (Tuple f c) = PC.optionMaybe $ PC.try (f <$> component c) - foldFoldableMaybe :: ∀ f a. Foldable f => Monoid a => f (Maybe a) -> a + foldFoldableMaybe :: ∀ f a. Foldable f => Monoid a => f (Maybe a) → a foldFoldableMaybe = foldMap fold component ∷ String → P.Parser String Number @@ -76,5 +76,5 @@ parseDateTime = unformatParser extendedDateTimeFormatInUTC extendedDateTimeFormatInUTC ∷ Formatter extendedDateTimeFormatInUTC = unEither $ parseFormatString "YYYY-MM-DDTHH:mm:ssZ" where - unEither :: Either String Formatter -> Formatter + unEither :: Either String Formatter → Formatter unEither = unsafePartialBecause "(this must be unrechable) error in parsing ISO date format" fromRight diff --git a/src/Data/Formatter/Parser/Utils.purs b/src/Data/Formatter/Parser/Utils.purs index cd827e9..8456e40 100644 --- a/src/Data/Formatter/Parser/Utils.purs +++ b/src/Data/Formatter/Parser/Utils.purs @@ -14,14 +14,14 @@ import Data.Bifunctor (lmap) import Data.Foldable (class Foldable) import Data.Either (Either) -oneOfAs :: ∀ c s m f a b. Functor f => Foldable f => Monad m => (a -> ParserT s m b) -> f (Tuple a c) -> ParserT s m c -oneOfAs p xs = PC.choice $ (\(Tuple s r) -> p s $> r) <$> xs +oneOfAs :: ∀ c s m f a b. Functor f => Foldable f => Monad m => (a → ParserT s m b) → f (Tuple a c) → ParserT s m c +oneOfAs p xs = PC.choice $ (\(Tuple s r) → p s $> r) <$> xs runP :: ∀ s a. PS.StringLike s => Parser s a → s → Either String a runP p s = lmap printError $ runParser s (p <* PS.eof) -printError :: ParseError -> String +printError :: ParseError → String printError err = parseErrorMessage err <> "@" <> (printPosition $ parseErrorPosition err) -printPosition :: Position -> String +printPosition :: Position → String printPosition (Position {line, column}) = show line <> ":" <> show column diff --git a/test/src/DateTime.purs b/test/src/DateTime.purs index 931fb8f..d39fef3 100644 --- a/test/src/DateTime.purs +++ b/test/src/DateTime.purs @@ -17,7 +17,7 @@ import Test.Utils (forAll, makeDateTime) datetimeTest :: forall e. Spec e Unit datetimeTest = describe "Data.Formatter.DateTime" do - forAll (\a -> a.format <> " | " <> a.dateStr) + forAll (\a → a.format <> " | " <> a.dateStr) "formatDateTime should formatt dateTime" [ { format: "MM/DD/YYYY", dateStr: "04/12/2017" , date: makeDateTime 2017 4 12 11 3 4 234} , { format: "MMMM", dateStr: "April" , date: makeDateTime 2017 4 12 11 3 4 234} @@ -43,41 +43,41 @@ datetimeTest = describe "Data.Formatter.DateTime" do , { format: "hhmmssSS", dateStr: "11123012", date: makeDateTime 2017 4 10 11 12 30 123 } , { format: "hhmmssS", dateStr: "1112301", date: makeDateTime 2017 4 10 11 12 30 123 } ] - (\({ format, dateStr, date }) -> (format `FDT.formatDateTime` date) `shouldEqual` (Right dateStr)) + (\({ format, dateStr, date }) → (format `FDT.formatDateTime` date) `shouldEqual` (Right dateStr)) describe "parseFormatString" do forAll _.str "should parse" dateformats - (\f -> (FDT.parseFormatString f.str) `shouldEqual` (Right f.format)) + (\f → (FDT.parseFormatString f.str) `shouldEqual` (Right f.format)) forAll _.str "shouldn't parse" invalidDateformats - (\f -> (FDT.parseFormatString f.str) `shouldEqual` (Left $ "Expected to contain only valid characters@" <> f.pos)) + (\f → (FDT.parseFormatString f.str) `shouldEqual` (Left $ "Expected to contain only valid characters@" <> f.pos)) forAll - (\a -> a.format <> " | " <> a.date) + (\a → a.format <> " | " <> a.date) "s ≡ format (unformat s)" [ {date: "2017-12-04 234", format: "YYYY-DD-MM SSS" } , {date: "3456-09-10 333", format: "YYYY-DD-MM SSS" } ] - (\({date, format}) -> (FDT.unformatDateTime format date >>= FDT.formatDateTime format) `shouldEqual` (Right date)) + (\({date, format}) → (FDT.unformatDateTime format date >>= FDT.formatDateTime format) `shouldEqual` (Right date)) forAll - (\a -> show a.date <> "|" <> FDT.printFormatter a.format) + (\a → show a.date <> "|" <> FDT.printFormatter a.format) "s ≡ unformat (format s)" (do format <- dateformats date <- dates guard format.lossless pure { date, format: format.format }) - (\({ date, format }) -> FDT.unformat format (FDT.format format date) `shouldEqual` (Right date)) + (\({ date, format }) → FDT.unformat format (FDT.format format date) `shouldEqual` (Right date)) -assertFormatting :: forall e. String -> String -> DateTime -> Aff e Unit +assertFormatting :: forall e. String → String → DateTime → Aff e Unit assertFormatting target' format dateTime = result `shouldEqual` target where result = FDT.formatDateTime format dateTime @@ -169,5 +169,5 @@ dateformats = } ] -filter :: ∀ m a. Alternative m => Monad m => (a -> Boolean) -> m a -> m a -filter f m = m >>= \x -> if f x then pure x else empty +filter :: ∀ m a. Alternative m => Monad m => (a → Boolean) → m a → m a +filter f m = m >>= \x → if f x then pure x else empty diff --git a/test/src/Interval.purs b/test/src/Interval.purs index c1ea481..7c7c475 100644 --- a/test/src/Interval.purs +++ b/test/src/Interval.purs @@ -16,28 +16,28 @@ import Test.Spec.Assertions (shouldEqual) import Test.Utils (forAll, makeDateTime) import Control.Monad.Aff (Aff) -prop :: ∀ e e' f. Foldable f => String -> f {str :: String | e'} -> ({str :: String | e'} -> Aff e Unit) -> Spec e Unit +prop :: ∀ e e' f. Foldable f => String → f {str :: String | e'} → ({str :: String | e'} → Aff e Unit) → Spec e Unit prop = forAll (show <<< _.str) intervalTest ∷ ∀ e. Spec e Unit intervalTest = describe "Data.Formatter.Interval" do - prop "shouldn't unformat invalid Interval" invalidIntervals \({str, err}) -> do + prop "shouldn't unformat invalid Interval" invalidIntervals \({str, err}) → do (unformatInterval str) `shouldEqual` (Left $ err) - prop "shouldn't unformat invalid Duration" invalidDurations \({str, err}) -> do + prop "shouldn't unformat invalid Duration" invalidDurations \({str, err}) → do (runP parseIsoDuration str) `shouldEqual` (Left $ err) - prop "should unformat RecurringInterval" arbRecurringInterval \({ str, interval }) -> do + prop "should unformat RecurringInterval" arbRecurringInterval \({ str, interval }) → do (unformatRecurringInterval str) `shouldEqual` (Right interval) - prop "format (unformat s) = s" arbRecurringInterval \({ str, interval, formatedStr }) -> do + prop "format (unformat s) = s" arbRecurringInterval \({ str, interval, formatedStr }) → do (formatRecurringInterval <$> (unformatRecurringInterval str)) `shouldEqual` (Right formatedStr) - prop "unformat (format s) = s" arbRecurringInterval \({ str, interval, formatedStr }) -> do + prop "unformat (format s) = s" arbRecurringInterval \({ str, interval, formatedStr }) → do (unformatRecurringInterval $ formatRecurringInterval interval) `shouldEqual` (Right interval) -unsafeMkToIsoDuration :: I.Duration -> I.IsoDuration +unsafeMkToIsoDuration :: I.Duration → I.IsoDuration unsafeMkToIsoDuration d = unsafePartialBecause "the duration must be valid ISO duration" fromJust $ I.mkIsoDuration d durations :: Array { str:: String, formatedStr:: String, dur :: I.IsoDuration } @@ -53,7 +53,7 @@ durations = , { str: "PT1M", formatedStr: "PT1M", dur: I.minutes 1.0 } , { str: "PT1S", formatedStr: "PT1S", dur: I.seconds 1.0 } , { str: "PT1H1S", formatedStr: "PT1H1S", dur: I.hours 1.0 <> I.seconds 1.0 } - ] <#> (\a -> a { dur = unsafeMkToIsoDuration a.dur }) + ] <#> (\a → a { dur = unsafeMkToIsoDuration a.dur }) -- TODO error messages could be improved invalidDurations :: Array { err :: String, str :: String} diff --git a/test/src/Number.purs b/test/src/Number.purs index 9546437..f3f06b2 100644 --- a/test/src/Number.purs +++ b/test/src/Number.purs @@ -14,17 +14,17 @@ numberTest = describe "Data.Formatter.Number" do forAll _.str "should print formatter" numberformatts - (\({fmt, str}) -> printFormatter fmt `shouldEqual` str) + (\({fmt, str}) → printFormatter fmt `shouldEqual` str) forAll _.str "parse format string" numberformatts - (\({fmt, str}) -> parseFormatString str `shouldEqual` (Right fmt)) + (\({fmt, str}) → parseFormatString str `shouldEqual` (Right fmt)) forAll show "unformat (format n) = n" [100.2, 100.1, 100.3, 10004000.0] - (\n -> unformat fmt1 (format fmt1 n) `shouldEqual` (Right n)) + (\n → unformat fmt1 (format fmt1 n) `shouldEqual` (Right n)) forAll show "format (unformat n) = n" @@ -33,7 +33,7 @@ numberTest = describe "Data.Formatter.Number" do -- , "-012.12" -- , "-123.12" ] - (\n -> (format fmt1 <$> (unformat fmt1 n)) `shouldEqual` (Right n)) + (\n → (format fmt1 <$> (unformat fmt1 n)) `shouldEqual` (Right n)) fmt1 :: Formatter fmt1 = Formatter diff --git a/test/src/Utils.purs b/test/src/Utils.purs index 6e13b24..122dede 100644 --- a/test/src/Utils.purs +++ b/test/src/Utils.purs @@ -14,11 +14,11 @@ import Data.Date (canonicalDate) import Data.Time (Time(..)) -forAll :: ∀ e a f. Foldable f => (a -> String) -> String -> f a -> (a -> Aff e Unit) -> Spec e Unit +forAll :: ∀ e a f. Foldable f => (a → String) → String → f a → (a → Aff e Unit) → Spec e Unit forAll itTitle title arb f = describe title do - for_ arb \a -> it (itTitle a) (f a) + for_ arb \a → it (itTitle a) (f a) -makeDateTime ∷ Int -> Int -> Int -> Int -> Int -> Int -> Int -> DateTime +makeDateTime ∷ Int → Int → Int → Int → Int → Int → Int → DateTime makeDateTime year month day hour minute second millisecond = DateTime (canonicalDate From 01b62423a2a11728a7613769650700b419bace07 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Wed, 26 Apr 2017 22:14:32 +0400 Subject: [PATCH 58/88] =?UTF-8?q?replace=20::=20with=20=E2=88=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Data/Formatter/Interval.purs | 18 +++++++++--------- src/Data/Formatter/Number.purs | 8 ++++---- src/Data/Formatter/Parser/Interval.purs | 20 ++++++++++---------- src/Data/Formatter/Parser/Utils.purs | 8 ++++---- test/src/DateTime.purs | 12 ++++++------ test/src/Interval.purs | 14 +++++++------- test/src/Main.purs | 2 +- test/src/Number.purs | 10 +++++----- test/src/Utils.purs | 2 +- 9 files changed, 47 insertions(+), 47 deletions(-) diff --git a/src/Data/Formatter/Interval.purs b/src/Data/Formatter/Interval.purs index fec8c54..f84be0b 100644 --- a/src/Data/Formatter/Interval.purs +++ b/src/Data/Formatter/Interval.purs @@ -20,22 +20,22 @@ import Data.Int as Int import Data.Foldable (foldMap) import Data.Formatter.Parser.Interval (parseRecurringInterval, parseInterval, parseIsoDuration, parseDateTime, extendedDateTimeFormatInUTC) -formatRecurringInterval :: I.RecurringInterval I.IsoDuration DateTime → String +formatRecurringInterval ∷ I.RecurringInterval I.IsoDuration DateTime → String formatRecurringInterval (I.RecurringInterval n i) = "R" <> (maybe "" formatInteger n) <> "/" <> (formatInterval i) -formatInterval :: I.Interval I.IsoDuration DateTime → String +formatInterval ∷ I.Interval I.IsoDuration DateTime → String formatInterval (I.StartEnd x y) = (formatDateTime x) <> "/" <> (formatDateTime y) formatInterval (I.DurationEnd d x) = (formatIsoDuration d) <> "/" <> (formatDateTime x) formatInterval (I.StartDuration x d) = (formatDateTime x) <> "/" <> (formatIsoDuration d) formatInterval (I.JustDuration d) = (formatIsoDuration d) -formatDateTime :: DateTime → String +formatDateTime ∷ DateTime → String formatDateTime = FDT.format extendedDateTimeFormatInUTC -formatIsoDuration :: I.IsoDuration → String +formatIsoDuration ∷ I.IsoDuration → String formatIsoDuration = formatDuration <<< I.unIsoDuration -formatDuration :: I.Duration → String +formatDuration ∷ I.Duration → String formatDuration (I.Duration m) = "P" <> datePart <> timePart where datePart = componentToString `foldMap` dateComponentsToStr @@ -47,14 +47,14 @@ formatDuration (I.Duration m) = "P" <> datePart <> timePart dateComponentsToStr = [ Tuple I.Year "Y", Tuple I.Month "M", Tuple I.Day "D" ] timeComponentsToStr = [ Tuple I.Hours "H", Tuple I.Minutes "M", Tuple I.Seconds "S" ] -formatInteger :: Int → String +formatInteger ∷ Int → String formatInteger = show -formatNumber :: Number → String +formatNumber ∷ Number → String formatNumber n = if Int.toNumber (Int.floor n) == n then show (Int.floor n) else show n -unformatRecurringInterval :: String → Either String (I.RecurringInterval I.IsoDuration DateTime) +unformatRecurringInterval ∷ String → Either String (I.RecurringInterval I.IsoDuration DateTime) unformatRecurringInterval = runP $ parseRecurringInterval parseIsoDuration parseDateTime -unformatInterval :: String → Either String (I.Interval I.IsoDuration DateTime) +unformatInterval ∷ String → Either String (I.Interval I.IsoDuration DateTime) unformatInterval = runP $ parseInterval parseIsoDuration parseDateTime diff --git a/src/Data/Formatter/Number.purs b/src/Data/Formatter/Number.purs index 64391ac..4c44dab 100644 --- a/src/Data/Formatter/Number.purs +++ b/src/Data/Formatter/Number.purs @@ -46,13 +46,13 @@ newtype Formatter = Formatter , sign ∷ Boolean } -derive instance genericFormatter :: Generic Formatter _ -derive instance newtypeFormatter :: Newtype Formatter _ +derive instance genericFormatter ∷ Generic Formatter _ +derive instance newtypeFormatter ∷ Newtype Formatter _ -instance showFormatter :: Show Formatter where +instance showFormatter ∷ Show Formatter where show = genericShow -derive instance eqFormatter :: Eq Formatter +derive instance eqFormatter ∷ Eq Formatter printFormatter ∷ Formatter → String printFormatter (Formatter f) = diff --git a/src/Data/Formatter/Parser/Interval.purs b/src/Data/Formatter/Parser/Interval.purs index a78a9fd..e2a0139 100644 --- a/src/Data/Formatter/Parser/Interval.purs +++ b/src/Data/Formatter/Parser/Interval.purs @@ -24,11 +24,11 @@ import Partial.Unsafe (unsafePartialBecause) import Data.Formatter.Parser.Number (parseNumber, parseMaybeInteger) -parseRecurringInterval :: ∀ a b. P.Parser String a → P.Parser String b → P.Parser String (I.RecurringInterval a b) +parseRecurringInterval ∷ ∀ a b. P.Parser String a → P.Parser String b → P.Parser String (I.RecurringInterval a b) parseRecurringInterval duration date = I.RecurringInterval <$> (PS.string "R" *> parseMaybeInteger) <*> (PS.string "/" *> parseInterval duration date) -parseInterval :: ∀ a b. P.Parser String a → P.Parser String b → P.Parser String (I.Interval a b) +parseInterval ∷ ∀ a b. P.Parser String a → P.Parser String b → P.Parser String (I.Interval a b) parseInterval duration date = [startEnd, durationEnd, startDuration, justDuration] <#> PC.try # PC.choice where startEnd = I.StartEnd <$> date <* PS.string "/" <*> date @@ -36,14 +36,14 @@ parseInterval duration date = [startEnd, durationEnd, startDuration, justDuratio startDuration = I.StartDuration <$> date <* PS.string "/" <*> duration justDuration = I.JustDuration <$> duration -parseIsoDuration :: P.Parser String I.IsoDuration +parseIsoDuration ∷ P.Parser String I.IsoDuration parseIsoDuration = do dur ← parseDuration case I.mkIsoDuration dur of Nothing → P.fail "extracted Duration is not valid ISO duration" Just a → pure a -parseDuration :: P.Parser String I.Duration +parseDuration ∷ P.Parser String I.Duration parseDuration = PS.string "P" *> (weekDuration <|> fullDuration) where weekDuration = mkComponentsParser [ Tuple I.week "W" ] @@ -52,17 +52,17 @@ parseDuration = PS.string "P" *> (weekDuration <|> fullDuration) durationTimePart = PC.option mempty $ (PC.try $ PS.string "T") *> (mkComponentsParser [ Tuple I.hours "H" , Tuple I.minutes "M" , Tuple I.seconds "S" ]) -notEmpty :: ∀ a. Monoid a => Eq a => P.Parser String a → String → P.Parser String a +notEmpty ∷ ∀ a. Monoid a => Eq a => P.Parser String a → String → P.Parser String a notEmpty p str = p >>= \x → if x == mempty then P.fail str else pure x -mkComponentsParser :: Array (Tuple (Number → I.Duration) String) → P.Parser String I.Duration +mkComponentsParser ∷ Array (Tuple (Number → I.Duration) String) → P.Parser String I.Duration mkComponentsParser arr = p `notEmpty` ("none of valid duration components (" <> (show $ snd <$> arr) <> ") were present") where p = arr <#> applyDurations # sequence <#> foldFoldableMaybe - applyDurations :: Tuple (Number → I.Duration) String → P.Parser String (Maybe I.Duration) + applyDurations ∷ Tuple (Number → I.Duration) String → P.Parser String (Maybe I.Duration) applyDurations (Tuple f c) = PC.optionMaybe $ PC.try (f <$> component c) - foldFoldableMaybe :: ∀ f a. Foldable f => Monoid a => f (Maybe a) → a + foldFoldableMaybe ∷ ∀ f a. Foldable f => Monoid a => f (Maybe a) → a foldFoldableMaybe = foldMap fold component ∷ String → P.Parser String Number @@ -70,11 +70,11 @@ mkComponentsParser arr = p `notEmpty` ("none of valid duration components (" <> -- parser for DateTime in UTC time zone using "extended format" -parseDateTime :: ∀ m. Monad m => P.ParserT String m DateTime +parseDateTime ∷ ∀ m. Monad m => P.ParserT String m DateTime parseDateTime = unformatParser extendedDateTimeFormatInUTC extendedDateTimeFormatInUTC ∷ Formatter extendedDateTimeFormatInUTC = unEither $ parseFormatString "YYYY-MM-DDTHH:mm:ssZ" where - unEither :: Either String Formatter → Formatter + unEither ∷ Either String Formatter → Formatter unEither = unsafePartialBecause "(this must be unrechable) error in parsing ISO date format" fromRight diff --git a/src/Data/Formatter/Parser/Utils.purs b/src/Data/Formatter/Parser/Utils.purs index 8456e40..9c7aaee 100644 --- a/src/Data/Formatter/Parser/Utils.purs +++ b/src/Data/Formatter/Parser/Utils.purs @@ -14,14 +14,14 @@ import Data.Bifunctor (lmap) import Data.Foldable (class Foldable) import Data.Either (Either) -oneOfAs :: ∀ c s m f a b. Functor f => Foldable f => Monad m => (a → ParserT s m b) → f (Tuple a c) → ParserT s m c +oneOfAs ∷ ∀ c s m f a b. Functor f => Foldable f => Monad m => (a → ParserT s m b) → f (Tuple a c) → ParserT s m c oneOfAs p xs = PC.choice $ (\(Tuple s r) → p s $> r) <$> xs -runP :: ∀ s a. PS.StringLike s => Parser s a → s → Either String a +runP ∷ ∀ s a. PS.StringLike s => Parser s a → s → Either String a runP p s = lmap printError $ runParser s (p <* PS.eof) -printError :: ParseError → String +printError ∷ ParseError → String printError err = parseErrorMessage err <> "@" <> (printPosition $ parseErrorPosition err) -printPosition :: Position → String +printPosition ∷ Position → String printPosition (Position {line, column}) = show line <> ":" <> show column diff --git a/test/src/DateTime.purs b/test/src/DateTime.purs index d39fef3..d5eb3aa 100644 --- a/test/src/DateTime.purs +++ b/test/src/DateTime.purs @@ -15,7 +15,7 @@ import Test.Spec (describe, Spec) import Test.Spec.Assertions (shouldEqual) import Test.Utils (forAll, makeDateTime) -datetimeTest :: forall e. Spec e Unit +datetimeTest ∷ forall e. Spec e Unit datetimeTest = describe "Data.Formatter.DateTime" do forAll (\a → a.format <> " | " <> a.dateStr) "formatDateTime should formatt dateTime" @@ -77,13 +77,13 @@ datetimeTest = describe "Data.Formatter.DateTime" do (\({ date, format }) → FDT.unformat format (FDT.format format date) `shouldEqual` (Right date)) -assertFormatting :: forall e. String → String → DateTime → Aff e Unit +assertFormatting ∷ forall e. String → String → DateTime → Aff e Unit assertFormatting target' format dateTime = result `shouldEqual` target where result = FDT.formatDateTime format dateTime target = Right target' -dates :: Array DateTime +dates ∷ Array DateTime dates = [ makeDateTime 2017 4 12 11 3 4 234 , makeDateTime 2017 4 1 0 0 0 0 @@ -92,13 +92,13 @@ dates = , makeDateTime (-1) 4 12 0 0 0 0 ] -invalidDateformats ∷ Array { str :: String , pos :: String } +invalidDateformats ∷ Array { str ∷ String , pos ∷ String } invalidDateformats = [ { str: "YY-h-dddd HH:mm Z", pos: "1:4" } , { str: "YYYY-MM-DD M", pos: "1:12" } ] -dateformats ∷ Array { str :: String , lossless :: Boolean, format :: FDT.Formatter } +dateformats ∷ Array { str ∷ String , lossless ∷ Boolean, format ∷ FDT.Formatter } dateformats = [ { str: "YYYY-MM-DD" , lossless: false @@ -169,5 +169,5 @@ dateformats = } ] -filter :: ∀ m a. Alternative m => Monad m => (a → Boolean) → m a → m a +filter ∷ ∀ m a. Alternative m => Monad m => (a → Boolean) → m a → m a filter f m = m >>= \x → if f x then pure x else empty diff --git a/test/src/Interval.purs b/test/src/Interval.purs index 7c7c475..25f2b50 100644 --- a/test/src/Interval.purs +++ b/test/src/Interval.purs @@ -16,7 +16,7 @@ import Test.Spec.Assertions (shouldEqual) import Test.Utils (forAll, makeDateTime) import Control.Monad.Aff (Aff) -prop :: ∀ e e' f. Foldable f => String → f {str :: String | e'} → ({str :: String | e'} → Aff e Unit) → Spec e Unit +prop ∷ ∀ e e' f. Foldable f => String → f {str ∷ String | e'} → ({str ∷ String | e'} → Aff e Unit) → Spec e Unit prop = forAll (show <<< _.str) intervalTest ∷ ∀ e. Spec e Unit @@ -37,10 +37,10 @@ intervalTest = describe "Data.Formatter.Interval" do (unformatRecurringInterval $ formatRecurringInterval interval) `shouldEqual` (Right interval) -unsafeMkToIsoDuration :: I.Duration → I.IsoDuration +unsafeMkToIsoDuration ∷ I.Duration → I.IsoDuration unsafeMkToIsoDuration d = unsafePartialBecause "the duration must be valid ISO duration" fromJust $ I.mkIsoDuration d -durations :: Array { str:: String, formatedStr:: String, dur :: I.IsoDuration } +durations ∷ Array { str∷ String, formatedStr∷ String, dur ∷ I.IsoDuration } durations = [ { str: "P1W", formatedStr: "P7D", dur: I.day 7.0 } , { str: "P1.0W", formatedStr: "P7D", dur: I.day 7.0 } @@ -56,7 +56,7 @@ durations = ] <#> (\a → a { dur = unsafeMkToIsoDuration a.dur }) -- TODO error messages could be improved -invalidDurations :: Array { err :: String, str :: String} +invalidDurations ∷ Array { err ∷ String, str ∷ String} invalidDurations = [ { err: errInvalidISO <> "1:13", str: "P1DT1.5H0M1S" } , { err: errInvalidISO <> "1:10", str: "P1.5Y0.5M" } @@ -82,7 +82,7 @@ invalidDurations = errNoTimeComponent = "none of valid duration components ([\"H\",\"M\",\"S\"]) were present@" -- TODO error messages could be improved -invalidIntervals :: Array {err :: String, str :: String} +invalidIntervals ∷ Array {err ∷ String, str ∷ String} invalidIntervals = -- TODO add some more from https://github.com/arnau/ISO8601/blob/master/spec/iso8601/time_interval_spec.rb [ { err: "Expected \"P\"@1:1", str: "2007-03-01T13:00:00ZP1Y2M10DT2H30M" } @@ -104,13 +104,13 @@ invalidIntervals = , { err: "Expected EOF@1:8", str: "P1Y0.5M/P1Y0.5M" } ] -recurrences ∷ Array { str :: String, rec :: Maybe Int } +recurrences ∷ Array { str ∷ String, rec ∷ Maybe Int } recurrences = [ {str: "", rec: Nothing} , {str: "18", rec: Just 18} ] -dates :: Array { str:: String, date :: DateTime } +dates ∷ Array { str∷ String, date ∷ DateTime } dates = [ { str: "2015-07-23T11:12:13Z", date: makeDateTime 2015 7 23 11 12 13 0 } , { str: "2015-07-22T00:00:00Z", date: makeDateTime 2015 7 22 0 0 0 0 } diff --git a/test/src/Main.purs b/test/src/Main.purs index 54cc9e4..97bab4e 100644 --- a/test/src/Main.purs +++ b/test/src/Main.purs @@ -9,7 +9,7 @@ import Test.Spec.Reporter.Console (consoleReporter) import Control.Monad.Eff (Eff) import Test.Spec.Runner (RunnerEffects, run) -main :: Eff (RunnerEffects ()) Unit +main ∷ Eff (RunnerEffects ()) Unit main = run [consoleReporter] do intervalTest datetimeTest diff --git a/test/src/Number.purs b/test/src/Number.purs index f3f06b2..0cc5c77 100644 --- a/test/src/Number.purs +++ b/test/src/Number.purs @@ -9,7 +9,7 @@ import Test.Spec (describe, Spec) import Test.Spec.Assertions (shouldEqual) import Test.Utils (forAll) -numberTest :: forall e. Spec e Unit +numberTest ∷ forall e. Spec e Unit numberTest = describe "Data.Formatter.Number" do forAll _.str "should print formatter" @@ -35,7 +35,7 @@ numberTest = describe "Data.Formatter.Number" do ] (\n → (format fmt1 <$> (unformat fmt1 n)) `shouldEqual` (Right n)) -fmt1 :: Formatter +fmt1 ∷ Formatter fmt1 = Formatter { comma: false , before: 3 @@ -44,7 +44,7 @@ fmt1 = Formatter , sign: false } -fmt2 :: Formatter +fmt2 ∷ Formatter fmt2 = Formatter { comma: true , before: one @@ -53,7 +53,7 @@ fmt2 = Formatter , sign: true } -fmt3 :: Formatter +fmt3 ∷ Formatter fmt3 = Formatter { comma: false , before: 2 @@ -62,7 +62,7 @@ fmt3 = Formatter , sign: true } -numberformatts :: Array { fmt :: Formatter, str :: String } +numberformatts ∷ Array { fmt ∷ Formatter, str ∷ String } numberformatts = [ { str: "000.00" , fmt: fmt1 diff --git a/test/src/Utils.purs b/test/src/Utils.purs index 122dede..dcb14b8 100644 --- a/test/src/Utils.purs +++ b/test/src/Utils.purs @@ -14,7 +14,7 @@ import Data.Date (canonicalDate) import Data.Time (Time(..)) -forAll :: ∀ e a f. Foldable f => (a → String) → String → f a → (a → Aff e Unit) → Spec e Unit +forAll ∷ ∀ e a f. Foldable f => (a → String) → String → f a → (a → Aff e Unit) → Spec e Unit forAll itTitle title arb f = describe title do for_ arb \a → it (itTitle a) (f a) From bd578863810ea65a5f6b9441dd6d207db6a5f452 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Wed, 26 Apr 2017 22:15:09 +0400 Subject: [PATCH 59/88] =?UTF-8?q?replace=20<-=20with=20=E2=86=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/src/DateTime.purs | 4 ++-- test/src/Interval.purs | 18 +++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/test/src/DateTime.purs b/test/src/DateTime.purs index d5eb3aa..a5ebfd9 100644 --- a/test/src/DateTime.purs +++ b/test/src/DateTime.purs @@ -70,8 +70,8 @@ datetimeTest = describe "Data.Formatter.DateTime" do (\a → show a.date <> "|" <> FDT.printFormatter a.format) "s ≡ unformat (format s)" (do - format <- dateformats - date <- dates + format ← dateformats + date ← dates guard format.lossless pure { date, format: format.format }) (\({ date, format }) → FDT.unformat format (FDT.format format date) `shouldEqual` (Right date)) diff --git a/test/src/Interval.purs b/test/src/Interval.purs index 25f2b50..a9aa74a 100644 --- a/test/src/Interval.purs +++ b/test/src/Interval.purs @@ -121,8 +121,8 @@ type ArbInterval = Array { str ∷ String, formatedStr ∷ String, interval ∷ arbRecurringInterval ∷ ArbRecurringInterval arbRecurringInterval = do - rec <- recurrences - i <- arbInterval + rec ← recurrences + i ← arbInterval pure { str : "R" <> rec.str <> "/" <> i.str , formatedStr : "R" <> rec.str <> "/" <> i.formatedStr @@ -139,8 +139,8 @@ arbInterval = fold arbIntervalStartEnd ∷ ArbInterval arbIntervalStartEnd = do - start <- dates - end <- dates + start ← dates + end ← dates pure { str: start.str <> "/" <> end.str , formatedStr: start.str <> "/" <> end.str @@ -149,8 +149,8 @@ arbIntervalStartEnd = do arbIntervalDurationEnd ∷ ArbInterval arbIntervalDurationEnd = do - dur <- durations - end <- dates + dur ← durations + end ← dates pure { str: dur.str <> "/" <> end.str , formatedStr: dur.formatedStr <> "/" <> end.str @@ -159,8 +159,8 @@ arbIntervalDurationEnd = do arbIntervalStartDuration ∷ ArbInterval arbIntervalStartDuration = do - dur <- durations - start <- dates + dur ← durations + start ← dates pure { str: start.str <> "/" <> dur.str , formatedStr: start.str <> "/" <> dur.formatedStr @@ -169,7 +169,7 @@ arbIntervalStartDuration = do arbIntervalJustDuration ∷ ArbInterval arbIntervalJustDuration = do - dur <- durations + dur ← durations pure { str: dur.str , formatedStr: dur.formatedStr From a2e6828f7ad6c282e5e6607c7dd72cf5c97d29da Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Wed, 26 Apr 2017 22:17:45 +0400 Subject: [PATCH 60/88] =?UTF-8?q?replace=20=3D>=20with=20=E2=87=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Data/Formatter/Parser/Interval.purs | 6 +++--- src/Data/Formatter/Parser/Number.purs | 10 +++++----- src/Data/Formatter/Parser/Utils.purs | 4 ++-- test/src/DateTime.purs | 2 +- test/src/Interval.purs | 2 +- test/src/Utils.purs | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Data/Formatter/Parser/Interval.purs b/src/Data/Formatter/Parser/Interval.purs index e2a0139..a3ec0a8 100644 --- a/src/Data/Formatter/Parser/Interval.purs +++ b/src/Data/Formatter/Parser/Interval.purs @@ -52,7 +52,7 @@ parseDuration = PS.string "P" *> (weekDuration <|> fullDuration) durationTimePart = PC.option mempty $ (PC.try $ PS.string "T") *> (mkComponentsParser [ Tuple I.hours "H" , Tuple I.minutes "M" , Tuple I.seconds "S" ]) -notEmpty ∷ ∀ a. Monoid a => Eq a => P.Parser String a → String → P.Parser String a +notEmpty ∷ ∀ a. Monoid a ⇒ Eq a ⇒ P.Parser String a → String → P.Parser String a notEmpty p str = p >>= \x → if x == mempty then P.fail str else pure x mkComponentsParser ∷ Array (Tuple (Number → I.Duration) String) → P.Parser String I.Duration @@ -62,7 +62,7 @@ mkComponentsParser arr = p `notEmpty` ("none of valid duration components (" <> applyDurations ∷ Tuple (Number → I.Duration) String → P.Parser String (Maybe I.Duration) applyDurations (Tuple f c) = PC.optionMaybe $ PC.try (f <$> component c) - foldFoldableMaybe ∷ ∀ f a. Foldable f => Monoid a => f (Maybe a) → a + foldFoldableMaybe ∷ ∀ f a. Foldable f ⇒ Monoid a ⇒ f (Maybe a) → a foldFoldableMaybe = foldMap fold component ∷ String → P.Parser String Number @@ -70,7 +70,7 @@ mkComponentsParser arr = p `notEmpty` ("none of valid duration components (" <> -- parser for DateTime in UTC time zone using "extended format" -parseDateTime ∷ ∀ m. Monad m => P.ParserT String m DateTime +parseDateTime ∷ ∀ m. Monad m ⇒ P.ParserT String m DateTime parseDateTime = unformatParser extendedDateTimeFormatInUTC extendedDateTimeFormatInUTC ∷ Formatter diff --git a/src/Data/Formatter/Parser/Number.purs b/src/Data/Formatter/Parser/Number.purs index 4c6ad7a..3136820 100644 --- a/src/Data/Formatter/Parser/Number.purs +++ b/src/Data/Formatter/Parser/Number.purs @@ -20,22 +20,22 @@ import Data.Maybe (Maybe) import Data.Foldable (foldMap) import Global (readFloat) -parseInteger ∷ ∀ s m. Monad m => PS.StringLike s => P.ParserT s m Int +parseInteger ∷ ∀ s m. Monad m ⇒ PS.StringLike s ⇒ P.ParserT s m Int parseInteger = some parseDigit <#> foldDigits -parseMaybeInteger ∷ ∀ s m. Monad m => PS.StringLike s => P.ParserT s m (Maybe Int) +parseMaybeInteger ∷ ∀ s m. Monad m ⇒ PS.StringLike s ⇒ P.ParserT s m (Maybe Int) parseMaybeInteger = PC.optionMaybe parseInteger -parseFractional ∷ ∀ s m. Monad m => PS.StringLike s => P.ParserT s m Number +parseFractional ∷ ∀ s m. Monad m ⇒ PS.StringLike s ⇒ P.ParserT s m Number parseFractional = (some parseDigit) <#> (foldMap show >>> ("0." <> _) >>> readFloat) -parseNumber ∷ ∀ s m. Monad m => PS.StringLike s => P.ParserT s m Number +parseNumber ∷ ∀ s m. Monad m ⇒ PS.StringLike s ⇒ P.ParserT s m Number parseNumber = (+) <$> (parseInteger <#> toNumber) <*> (PC.option 0.0 $ PC.try $ PS.oneOf ['.', ','] *> parseFractional) -parseDigit ∷ ∀ s m. Monad m => PS.StringLike s => P.ParserT s m Int +parseDigit ∷ ∀ s m. Monad m ⇒ PS.StringLike s ⇒ P.ParserT s m Int parseDigit = PC.try $ PS.char `oneOfAs` [ Tuple '0' 0 , Tuple '1' 1 diff --git a/src/Data/Formatter/Parser/Utils.purs b/src/Data/Formatter/Parser/Utils.purs index 9c7aaee..e65af25 100644 --- a/src/Data/Formatter/Parser/Utils.purs +++ b/src/Data/Formatter/Parser/Utils.purs @@ -14,10 +14,10 @@ import Data.Bifunctor (lmap) import Data.Foldable (class Foldable) import Data.Either (Either) -oneOfAs ∷ ∀ c s m f a b. Functor f => Foldable f => Monad m => (a → ParserT s m b) → f (Tuple a c) → ParserT s m c +oneOfAs ∷ ∀ c s m f a b. Functor f ⇒ Foldable f ⇒ Monad m ⇒ (a → ParserT s m b) → f (Tuple a c) → ParserT s m c oneOfAs p xs = PC.choice $ (\(Tuple s r) → p s $> r) <$> xs -runP ∷ ∀ s a. PS.StringLike s => Parser s a → s → Either String a +runP ∷ ∀ s a. PS.StringLike s ⇒ Parser s a → s → Either String a runP p s = lmap printError $ runParser s (p <* PS.eof) printError ∷ ParseError → String diff --git a/test/src/DateTime.purs b/test/src/DateTime.purs index a5ebfd9..eea1ec3 100644 --- a/test/src/DateTime.purs +++ b/test/src/DateTime.purs @@ -169,5 +169,5 @@ dateformats = } ] -filter ∷ ∀ m a. Alternative m => Monad m => (a → Boolean) → m a → m a +filter ∷ ∀ m a. Alternative m ⇒ Monad m ⇒ (a → Boolean) → m a → m a filter f m = m >>= \x → if f x then pure x else empty diff --git a/test/src/Interval.purs b/test/src/Interval.purs index a9aa74a..4d5716e 100644 --- a/test/src/Interval.purs +++ b/test/src/Interval.purs @@ -16,7 +16,7 @@ import Test.Spec.Assertions (shouldEqual) import Test.Utils (forAll, makeDateTime) import Control.Monad.Aff (Aff) -prop ∷ ∀ e e' f. Foldable f => String → f {str ∷ String | e'} → ({str ∷ String | e'} → Aff e Unit) → Spec e Unit +prop ∷ ∀ e e' f. Foldable f ⇒ String → f {str ∷ String | e'} → ({str ∷ String | e'} → Aff e Unit) → Spec e Unit prop = forAll (show <<< _.str) intervalTest ∷ ∀ e. Spec e Unit diff --git a/test/src/Utils.purs b/test/src/Utils.purs index dcb14b8..2bcc6d4 100644 --- a/test/src/Utils.purs +++ b/test/src/Utils.purs @@ -14,7 +14,7 @@ import Data.Date (canonicalDate) import Data.Time (Time(..)) -forAll ∷ ∀ e a f. Foldable f => (a → String) → String → f a → (a → Aff e Unit) → Spec e Unit +forAll ∷ ∀ e a f. Foldable f ⇒ (a → String) → String → f a → (a → Aff e Unit) → Spec e Unit forAll itTitle title arb f = describe title do for_ arb \a → it (itTitle a) (f a) From fb169ace5f800a9c505093e5ef76e7edc86c3112 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Wed, 26 Apr 2017 22:18:34 +0400 Subject: [PATCH 61/88] =?UTF-8?q?replace=20forall=20with=20=E2=88=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/src/DateTime.purs | 4 ++-- test/src/Number.purs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/src/DateTime.purs b/test/src/DateTime.purs index eea1ec3..5141c5c 100644 --- a/test/src/DateTime.purs +++ b/test/src/DateTime.purs @@ -15,7 +15,7 @@ import Test.Spec (describe, Spec) import Test.Spec.Assertions (shouldEqual) import Test.Utils (forAll, makeDateTime) -datetimeTest ∷ forall e. Spec e Unit +datetimeTest ∷ ∀ e. Spec e Unit datetimeTest = describe "Data.Formatter.DateTime" do forAll (\a → a.format <> " | " <> a.dateStr) "formatDateTime should formatt dateTime" @@ -77,7 +77,7 @@ datetimeTest = describe "Data.Formatter.DateTime" do (\({ date, format }) → FDT.unformat format (FDT.format format date) `shouldEqual` (Right date)) -assertFormatting ∷ forall e. String → String → DateTime → Aff e Unit +assertFormatting ∷ ∀ e. String → String → DateTime → Aff e Unit assertFormatting target' format dateTime = result `shouldEqual` target where result = FDT.formatDateTime format dateTime diff --git a/test/src/Number.purs b/test/src/Number.purs index 0cc5c77..08eeac2 100644 --- a/test/src/Number.purs +++ b/test/src/Number.purs @@ -9,7 +9,7 @@ import Test.Spec (describe, Spec) import Test.Spec.Assertions (shouldEqual) import Test.Utils (forAll) -numberTest ∷ forall e. Spec e Unit +numberTest ∷ ∀ e. Spec e Unit numberTest = describe "Data.Formatter.Number" do forAll _.str "should print formatter" From f02803c68b59c88db2251b0c287b5e8dc55474c3 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Thu, 27 Apr 2017 00:14:36 +0400 Subject: [PATCH 62/88] =?UTF-8?q?replace=20more=20=3D>=20with=20=E2=87=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Data/Formatter/DateTime.purs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Data/Formatter/DateTime.purs b/src/Data/Formatter/DateTime.purs index c3a4465..5372a3c 100644 --- a/src/Data/Formatter/DateTime.purs +++ b/src/Data/Formatter/DateTime.purs @@ -67,9 +67,9 @@ data FormatterF a | Placeholder String a | End -derive instance functorFormatterF :: Functor FormatterF +derive instance functorFormatterF ∷ Functor FormatterF -instance showFormatterF ∷ Show a => Show (FormatterF a) where +instance showFormatterF ∷ Show a ⇒ Show (FormatterF a) where show (YearFull a) = "(YearFull " <> (show a) <> "c" show (YearTwoDigits a) = "(YearTwoDigits " <> (show a) <> ")" show (YearAbsolute a) = "(YearAbsolute " <> (show a) <> ")" @@ -93,9 +93,9 @@ instance showFormatterF ∷ Show a => Show (FormatterF a) where show (Placeholder str a) = "(Placeholder " <> (show str) <> " "<> (show a) <> ")" show End = "End" -derive instance eqFormatterF :: Eq a => Eq (FormatterF a) +derive instance eqFormatterF ∷ Eq a ⇒ Eq (FormatterF a) -instance eq1FormatterF :: Eq1 FormatterF where +instance eq1FormatterF ∷ Eq1 FormatterF where eq1 = eq @@ -139,7 +139,7 @@ parseFormatString = runP formatParser -- | Formatting function that accepts a number that is a year, -- | and strips away the non-significant digits, leaving only the -- | ones and tens positions. -formatYearTwoDigits :: Int → String +formatYearTwoDigits ∷ Int → String formatYearTwoDigits i = case dateLength of 1 → "0" <> dateString 2 → dateString @@ -242,12 +242,12 @@ formatF cb dt@(DT.DateTime d t) = case _ of s <> cb a End → "" -padSingleDigit :: Int → String +padSingleDigit ∷ Int → String padSingleDigit i | i < 10 = "0" <> (show i) | otherwise = show i -padDoubleDigit :: Int → String +padDoubleDigit ∷ Int → String padDoubleDigit i | i < 10 = "00" <> (show i) | i < 100 = "0" <> (show i) @@ -330,7 +330,7 @@ validateRange min max = ask >>= \({num}) → if num < min || num > max then lift $ Left $ "Number is out of range [ " <> (show min) <> ", " <> (show max) <> " ]" else lift $ Right unit -parseInt :: ∀ m +parseInt ∷ ∀ m . Monad m ⇒ Int → ReaderT { length ∷ Int, num ∷ Int, maxLength ∷ Int } (Either String) Unit @@ -409,19 +409,19 @@ unformatFParser cb = case _ of (parseInt 2 exactLength "Incorrect 2-digit millisecond") *> cb a End → pure unit where - modifyWithParser :: ∀ s' s x. (s → Maybe x → s) → P.ParserT s' (State s) x → P.ParserT s' (State s) Unit + modifyWithParser ∷ ∀ s' s x. (s → Maybe x → s) → P.ParserT s' (State s) x → P.ParserT s' (State s) Unit modifyWithParser f p = do - v <- p + v ← p lift $ modify (flip f (Just v)) -unformatParser ∷ ∀ m. Monad m => Formatter → P.ParserT String m DT.DateTime +unformatParser ∷ ∀ m. Monad m ⇒ Formatter → P.ParserT String m DT.DateTime unformatParser f' = do - acc <- P.mapParserT unState $ rec f' + acc ← P.mapParserT unState $ rec f' either P.fail pure $ unformatAccumToDateTime acc where rec ∷ Formatter → P.ParserT String (State UnformatAccum) Unit rec f = unformatFParser rec $ unroll f - unState :: ∀ x y n. Monad n => State UnformatAccum (Tuple (Either y Unit) x) → n (Tuple (Either y UnformatAccum) x) + unState ∷ ∀ x y n. Monad n ⇒ State UnformatAccum (Tuple (Either y Unit) x) → n (Tuple (Either y UnformatAccum) x) unState s = case runState s initialAccum of Tuple (Tuple e state) res → pure (Tuple (e $> res) state) From b304aa406c3f79265dc1580f22a48d5df00d8b50 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Thu, 27 Apr 2017 18:33:51 +0400 Subject: [PATCH 63/88] use monoid instance of ReaderT in parser validators --- bower.json | 2 ++ src/Data/Formatter/DateTime.purs | 16 ++++++---------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/bower.json b/bower.json index bf2905a..ff22e13 100644 --- a/bower.json +++ b/bower.json @@ -20,6 +20,7 @@ "purescript-parsing": "^4.2.1", "purescript-fixed-points": "^4.0.0", "purescript-datetime": "git://github.com/safareli/purescript-datetime.git#interval", + "purescript-transformers": "git://github.com/safareli/purescript-transformers.git#readermonoid", "purescript-lists": "git://github.com/safareli/purescript-lists.git#somemany", "purescript-generics-rep": "^5.0.0" }, @@ -31,6 +32,7 @@ }, "resolutions": { "purescript-lists": "somemany", + "purescript-transformers": "readermonoid", "purescript-datetime": "interval" } } diff --git a/src/Data/Formatter/DateTime.purs b/src/Data/Formatter/DateTime.purs index 5372a3c..e0f9a07 100644 --- a/src/Data/Formatter/DateTime.purs +++ b/src/Data/Formatter/DateTime.purs @@ -315,10 +315,6 @@ unformatAccumToDateTime a = | otherwise = Nothing --- NOTE `ReaderT s (Either e) Unit` forms Monoid where --- `mempty = lift $ Right unit` (noValidate) and `concat = (*>)` -noValidate ∷ ∀ e. ReaderT { maxLength ∷ Int, length ∷ Int | e } (Either String) Unit -noValidate = lift $ Right unit exactLength ∷ ∀ e. ReaderT { maxLength ∷ Int, length ∷ Int | e } (Either String) Unit exactLength = ask >>= \({maxLength, length}) → if maxLength /= length @@ -364,9 +360,9 @@ unformatFParser cb = case _ of MonthShort a → _{month = _} `modifyWithParser` (fromEnum <$> parseShortMonth) *> cb a MonthTwoDigits a → _{month = _} `modifyWithParser` - (parseInt 2 (validateRange 1 12 *> exactLength) "Incorrect 2-digit month") *> cb a + (parseInt 2 (validateRange 1 12 <> exactLength) "Incorrect 2-digit month") *> cb a DayOfMonthTwoDigits a → _{day = _} `modifyWithParser` - (parseInt 2 (validateRange 1 31 *> exactLength) "Incorrect day of month") *> cb a + (parseInt 2 (validateRange 1 31 <> exactLength) "Incorrect day of month") *> cb a DayOfMonth a → _{day = _} `modifyWithParser` (parseInt 2 (validateRange 1 31) "Incorrect day of month") *> cb a UnixTimestamp a → do @@ -387,17 +383,17 @@ unformatFParser cb = case _ of -- TODO we would need to use this value if we support date format using week number DayOfWeek a → (parseInt 1 (validateRange 1 7) "Incorrect day of week") *> cb a Hours24 a → _{hour = _} `modifyWithParser` - (parseInt 2 (validateRange 0 23 *> exactLength) "Incorrect 24 hour") *> cb a + (parseInt 2 (validateRange 0 23 <> exactLength) "Incorrect 24 hour") *> cb a Hours12 a → _{hour = _} `modifyWithParser` - (parseInt 2 (validateRange 0 11 *> exactLength) "Incorrect 12 hour") *> cb a + (parseInt 2 (validateRange 0 11 <> exactLength) "Incorrect 12 hour") *> cb a Meridiem a → _{meridiem = _} `modifyWithParser` parseMeridiem *> cb a MinutesTwoDigits a → _{minute = _} `modifyWithParser` - (parseInt 2 (validateRange 0 59 *> exactLength) "Incorrect 2-digit minute") *> cb a + (parseInt 2 (validateRange 0 59 <> exactLength) "Incorrect 2-digit minute") *> cb a Minutes a → _{minute = _} `modifyWithParser` (parseInt 2 (validateRange 0 59) "Incorrect minute") *> cb a SecondsTwoDigits a → _{second = _} `modifyWithParser` - (parseInt 2 (validateRange 0 59 *> exactLength) "Incorrect 2-digit second") *> cb a + (parseInt 2 (validateRange 0 59 <> exactLength) "Incorrect 2-digit second") *> cb a Seconds a → _{second = _} `modifyWithParser` (parseInt 2 (validateRange 0 59) "Incorrect second") *> cb a Milliseconds a → _{millisecond = _} `modifyWithParser` From c1a111a777cc0ee48f598637a4ec7d2be715eb36 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Fri, 28 Apr 2017 01:56:38 +0400 Subject: [PATCH 64/88] change Formatter to List FormatterCommands --- src/Data/Formatter/DateTime.purs | 435 +++++++++++++------------------ test/src/DateTime.purs | 109 ++++---- 2 files changed, 236 insertions(+), 308 deletions(-) diff --git a/src/Data/Formatter/DateTime.purs b/src/Data/Formatter/DateTime.purs index e0f9a07..a27babd 100644 --- a/src/Data/Formatter/DateTime.purs +++ b/src/Data/Formatter/DateTime.purs @@ -1,6 +1,6 @@ module Data.Formatter.DateTime ( Formatter - , FormatterF(..) + , FormatterCommand(..) , Meridiem , printFormatter , parseFormatString @@ -13,26 +13,27 @@ module Data.Formatter.DateTime import Prelude -import Control.Lazy as Lazy import Control.Monad.State (State, modify, put, runState) import Control.Monad.Trans.Class (lift) - +import Control.Monad.State.Class (get) +import Debug.Trace (traceAnyA) import Data.Ord (abs) -import Data.Array (some) -import Data.List.Lazy as List +import Data.Array as Array +import Data.List as List +import Data.List.Lazy as LazyList import Data.Tuple (Tuple(..)) +import Data.Foldable (foldMap) +import Control.Alt ((<|>)) import Data.Date as D import Data.DateTime as DT import Data.DateTime.Instant (instant, toDateTime, fromDateTime, unInstant) import Data.Either (Either(..), either) import Data.Enum (fromEnum, toEnum) -import Data.Functor.Mu (Mu, unroll, roll) import Data.Int as Int import Data.Maybe (Maybe(..), maybe, fromMaybe) import Data.Newtype (unwrap) import Data.String as Str import Data.Time as T -import Data.Eq (class Eq1) import Data.Time.Duration as Dur import Data.Formatter.Internal (foldDigits) import Data.Formatter.Parser.Number (parseDigit) @@ -40,102 +41,105 @@ import Data.Formatter.Parser.Utils (runP, oneOfAs) import Control.Monad.Reader.Trans (ReaderT, runReaderT, ask) import Text.Parsing.Parser as P import Text.Parsing.Parser.Combinators as PC +import Text.Parsing.Parser.Combinators (()) import Text.Parsing.Parser.String as PS - -data FormatterF a - = YearFull a - | YearTwoDigits a - | YearAbsolute a - | MonthFull a - | MonthShort a - | MonthTwoDigits a - | DayOfMonthTwoDigits a - | DayOfMonth a - | UnixTimestamp a - | DayOfWeek a - | Hours24 a - | Hours12 a - | Meridiem a - | Minutes a - | MinutesTwoDigits a - | Seconds a - | SecondsTwoDigits a - | Milliseconds a - | MillisecondsShort a - | MillisecondsTwoDigits a - | Placeholder String a - | End - -derive instance functorFormatterF ∷ Functor FormatterF - -instance showFormatterF ∷ Show a ⇒ Show (FormatterF a) where - show (YearFull a) = "(YearFull " <> (show a) <> "c" - show (YearTwoDigits a) = "(YearTwoDigits " <> (show a) <> ")" - show (YearAbsolute a) = "(YearAbsolute " <> (show a) <> ")" - show (MonthFull a) = "(MonthFull " <> (show a) <> ")" - show (MonthShort a) = "(MonthShort " <> (show a) <> ")" - show (MonthTwoDigits a) = "(MonthTwoDigits " <> (show a) <> ")" - show (DayOfMonthTwoDigits a) = "(DayOfMonthTwoDigits " <> (show a) <> ")" - show (DayOfMonth a) = "(DayOfMonth " <> (show a) <> ")" - show (UnixTimestamp a) = "(UnixTimestamp " <> (show a) <> ")" - show (DayOfWeek a) = "(DayOfWeek " <> (show a) <> ")" - show (Hours24 a) = "(Hours24 " <> (show a) <> ")" - show (Hours12 a) = "(Hours12 " <> (show a) <> ")" - show (Meridiem a) = "(Meridiem " <> (show a) <> ")" - show (Minutes a) = "(Minutes " <> (show a) <> ")" - show (MinutesTwoDigits a) = "(MinutesTwoDigits " <> (show a) <> ")" - show (Seconds a) = "(Seconds " <> (show a) <> ")" - show (SecondsTwoDigits a) = "(SecondsTwoDigits " <> (show a) <> ")" - show (Milliseconds a) = "(Milliseconds " <> (show a) <> ")" - show (MillisecondsShort a) = "(MillisecondsShort " <> (show a) <> ")" - show (MillisecondsTwoDigits a) = "(MillisecondsTwoDigits " <> (show a) <> ")" - show (Placeholder str a) = "(Placeholder " <> (show str) <> " "<> (show a) <> ")" - show End = "End" - -derive instance eqFormatterF ∷ Eq a ⇒ Eq (FormatterF a) - -instance eq1FormatterF ∷ Eq1 FormatterF where - eq1 = eq - - -type Formatter = Mu FormatterF - -printFormatterF - ∷ ∀ a - . (a → String) - → FormatterF a - → String -printFormatterF cb = case _ of - YearFull a → "YYYY" <> cb a - YearTwoDigits a → "YY" <> cb a - YearAbsolute a → "Y" <> cb a - MonthFull a → "MMMM" <> cb a - MonthShort a → "MMM" <> cb a - MonthTwoDigits a → "MM" <> cb a - DayOfMonthTwoDigits a → "DD" <> cb a - DayOfMonth a → "D" <> cb a - UnixTimestamp a → "X" <> cb a - DayOfWeek a → "E" <> cb a - Hours24 a → "HH" <> cb a - Hours12 a → "hh" <> cb a - Meridiem a → "a" <> cb a - Minutes a → "m" <> cb a - MinutesTwoDigits a → "mm" <> cb a - Seconds a → "s" <> cb a - SecondsTwoDigits a → "ss" <> cb a - MillisecondsShort a → "S" <> cb a - MillisecondsTwoDigits a → "SS" <> cb a - Milliseconds a → "SSS" <> cb a - Placeholder s a → s <> cb a - End → "" +import Data.Generic.Rep (class Generic) +import Data.Generic.Rep.Show (genericShow) + +data FormatterCommand + = YearFull + | YearTwoDigits + | YearAbsolute + | MonthFull + | MonthShort + | MonthTwoDigits + | DayOfMonthTwoDigits + | DayOfMonth + | UnixTimestamp + | DayOfWeek + | Hours24 + | Hours12 + | Meridiem + | Minutes + | MinutesTwoDigits + | Seconds + | SecondsTwoDigits + | Milliseconds + | MillisecondsShort + | MillisecondsTwoDigits + | Placeholder String + +derive instance eqFormatterCommand ∷ Eq (FormatterCommand) +derive instance genericFormatter ∷ Generic FormatterCommand _ +instance showFormatter ∷ Show FormatterCommand where + show = genericShow + +type Formatter = List.List FormatterCommand + +printFormatterCommand ∷ FormatterCommand → String +printFormatterCommand = case _ of + YearFull → "YYYY" + YearTwoDigits → "YY" + YearAbsolute → "Y" + MonthFull → "MMMM" + MonthShort → "MMM" + MonthTwoDigits → "MM" + DayOfMonthTwoDigits → "DD" + DayOfMonth → "D" + UnixTimestamp → "X" + DayOfWeek → "E" + Hours24 → "HH" + Hours12 → "hh" + Meridiem → "a" + Minutes → "m" + MinutesTwoDigits → "mm" + Seconds → "s" + SecondsTwoDigits → "ss" + MillisecondsShort → "S" + MillisecondsTwoDigits → "SS" + Milliseconds → "SSS" + Placeholder s → s printFormatter ∷ Formatter → String -printFormatter f = printFormatterF printFormatter $ unroll f +printFormatter = foldMap printFormatterCommand parseFormatString ∷ String → Either String Formatter parseFormatString = runP formatParser +placeholderContent ∷ P.Parser String String +placeholderContent = + Str.toCharArray "YMDEHhamsS" + # PS.noneOf + # Array.some + <#> Str.fromCharArray + +formatterCommandParser ∷ P.Parser String FormatterCommand +formatterCommandParser = (PC.try <<< PS.string) `oneOfAs` + [ Tuple "YYYY" YearFull + , Tuple "YY" YearTwoDigits + , Tuple "Y" YearAbsolute + , Tuple "MMMM" MonthFull + , Tuple "MMM" MonthShort + , Tuple "MM" MonthTwoDigits + , Tuple "DD" DayOfMonthTwoDigits + , Tuple "D" DayOfMonth + , Tuple "E" DayOfWeek + , Tuple "HH" Hours24 + , Tuple "hh" Hours12 + , Tuple "a" Meridiem + , Tuple "mm" MinutesTwoDigits + , Tuple "m" Minutes + , Tuple "ss" SecondsTwoDigits + , Tuple "s" Seconds + , Tuple "SSS" Milliseconds + , Tuple "SS" MillisecondsTwoDigits + , Tuple "S" MillisecondsShort + ] <|> (Placeholder <$> placeholderContent) + +formatParser ∷ P.Parser String Formatter +formatParser = List.some formatterCommandParser + -- | Formatting function that accepts a number that is a year, -- | and strips away the non-significant digits, leaving only the -- | ones and tens positions. @@ -148,99 +152,32 @@ formatYearTwoDigits i = case dateLength of dateString = show $ abs i dateLength = Str.length $ dateString - -placeholderContent ∷ P.Parser String String -placeholderContent = - map Str.fromCharArray - $ PC.try - $ some - $ PS.noneOf - $ Str.toCharArray "YMDEHhamsS" - -formatterFParser - ∷ ∀ a - . P.Parser String a - → P.Parser String (FormatterF a) -formatterFParser cb = - PC.choice - [ (PC.try $ PS.string "YYYY") *> map YearFull cb - , (PC.try $ PS.string "YY") *> map YearTwoDigits cb - , (PC.try $ PS.string "Y") *> map YearAbsolute cb - , (PC.try $ PS.string "MMMM") *> map MonthFull cb - , (PC.try $ PS.string "MMM") *> map MonthShort cb - , (PC.try $ PS.string "MM") *> map MonthTwoDigits cb - , (PC.try $ PS.string "DD") *> map DayOfMonthTwoDigits cb - , (PC.try $ PS.string "D") *> map DayOfMonth cb - , (PC.try $ PS.string "E") *> map DayOfWeek cb - , (PC.try $ PS.string "HH") *> map Hours24 cb - , (PC.try $ PS.string "hh") *> map Hours12 cb - , (PC.try $ PS.string "a") *> map Meridiem cb - , (PC.try $ PS.string "mm") *> map MinutesTwoDigits cb - , (PC.try $ PS.string "m") *> map Minutes cb - , (PC.try $ PS.string "ss") *> map SecondsTwoDigits cb - , (PC.try $ PS.string "s") *> map Seconds cb - , (PC.try $ PS.string "SSS") *> map Milliseconds cb - , (PC.try $ PS.string "SS") *> map MillisecondsTwoDigits cb - , (PC.try $ PS.string "S") *> map MillisecondsShort cb - , (Placeholder <$> placeholderContent <*> cb) - , (PS.eof $> End) - ] PC. "to contain only valid characters" - -formatParser ∷ P.Parser String Formatter -formatParser = - Lazy.fix \f → map roll $ formatterFParser f - -formatF - ∷ ∀ a - . (a → String) - → DT.DateTime - → FormatterF a - → String -formatF cb dt@(DT.DateTime d t) = case _ of - YearFull a → - (show $ fromEnum $ D.year d) <> cb a - YearTwoDigits a → - (formatYearTwoDigits $ fromEnum $ D.year d) <> cb a - YearAbsolute a → - show (fromEnum $ D.year d) <> cb a - MonthFull a → - show (D.month d) <> cb a - MonthShort a → - (printShortMonth $ D.month d) <> cb a - MonthTwoDigits a → - (padSingleDigit $ fromEnum $ D.month d) <> cb a - DayOfMonthTwoDigits a → - (padSingleDigit $ fromEnum $ D.day d) <> cb a - DayOfMonth a → - show (fromEnum $ D.day d) <> cb a - UnixTimestamp a → - (show $ Int.floor $ (_ / 1000.0) $ unwrap $ unInstant $ fromDateTime dt) <> cb a - DayOfWeek a → - show (fromEnum $ D.weekday d) <> cb a - Hours24 a → - padSingleDigit (fromEnum $ T.hour t) <> cb a - Hours12 a → - let fix12 h = if h == 0 then 12 else h - in (padSingleDigit $ fix12 $ (fromEnum $ T.hour t) `mod` 12) <> cb a - Meridiem a → - (if (fromEnum $ T.hour t) >= 12 then "PM" else "AM") <> cb a - Minutes a → - show (fromEnum $ T.minute t) <> cb a - MinutesTwoDigits a → - (padSingleDigit <<< fromEnum $ T.minute t) <> cb a - Seconds a → - show (fromEnum $ T.second t) <> cb a - SecondsTwoDigits a → - (padSingleDigit <<< fromEnum $ T.second t) <> cb a - Milliseconds a → - (padDoubleDigit <<< fromEnum $ T.millisecond t) <> cb a - MillisecondsShort a → - (show $ (_ / 100) $ fromEnum $ T.millisecond t) <> cb a - MillisecondsTwoDigits a → - (padSingleDigit $ (_ / 10) $ fromEnum $ T.millisecond t) <> cb a - Placeholder s a → - s <> cb a - End → "" +fix12 ∷ Int -> Int +fix12 h = if h == 0 then 12 else h + +formatCommand ∷ DT.DateTime → FormatterCommand → String +formatCommand dt@(DT.DateTime d t) = case _ of + YearFull → show $ fromEnum $ D.year d + YearTwoDigits → formatYearTwoDigits $ fromEnum $ D.year d + YearAbsolute → show $ fromEnum $ D.year d + MonthFull → show $ D.month d + MonthShort → printShortMonth $ D.month d + MonthTwoDigits → padSingleDigit $ fromEnum $ D.month d + DayOfMonthTwoDigits → padSingleDigit $ fromEnum $ D.day d + DayOfMonth → show $ fromEnum $ D.day d + UnixTimestamp → show $ Int.floor $ (_ / 1000.0) $ unwrap $ unInstant $ fromDateTime dt + DayOfWeek → show $ fromEnum $ D.weekday d + Hours24 → padSingleDigit (fromEnum $ T.hour t) + Hours12 → padSingleDigit $ fix12 $ (fromEnum $ T.hour t) `mod` 12 + Meridiem → if (fromEnum $ T.hour t) >= 12 then "PM" else "AM" + Minutes → show $ fromEnum $ T.minute t + MinutesTwoDigits → padSingleDigit <<< fromEnum $ T.minute t + Seconds → show $ fromEnum $ T.second t + SecondsTwoDigits → padSingleDigit <<< fromEnum $ T.second t + Milliseconds → padDoubleDigit <<< fromEnum $ T.millisecond t + MillisecondsShort → show $ (_ / 100) $ fromEnum $ T.millisecond t + MillisecondsTwoDigits → padSingleDigit $ (_ / 10) $ fromEnum $ T.millisecond t + Placeholder s → s padSingleDigit ∷ Int → String padSingleDigit i @@ -254,7 +191,7 @@ padDoubleDigit i | otherwise = show i format ∷ Formatter → DT.DateTime → String -format f dt = formatF (flip format dt) dt $ unroll f +format f d = foldMap (formatCommand d) f formatDateTime ∷ String → DT.DateTime → Either String String formatDateTime pattern datetime = @@ -333,77 +270,69 @@ parseInt ∷ ∀ m → String → P.ParserT String m Int parseInt maxLength validators errMsg = do - ds ← List.take maxLength <$> (List.some parseDigit) - let length = List.length ds + ds ← LazyList.take maxLength <$> (LazyList.some parseDigit) + let length = LazyList.length ds let num = foldDigits ds case runReaderT validators {length, num, maxLength} of Left err → P.fail $ errMsg <> "(" <> err <> ")" Right _ → pure num --- take -unformatFParser - ∷ ∀ a - . (a → P.ParserT String (State UnformatAccum) Unit) - → FormatterF a - → P.ParserT String (State UnformatAccum) Unit -unformatFParser cb = case _ of - YearFull a → _{year = _} `modifyWithParser` - (parseInt 4 exactLength "Incorrect full year") *> cb a - YearTwoDigits a → _{year = _} `modifyWithParser` - (parseInt 2 exactLength "Incorrect 2-digit year") *> cb a - YearAbsolute a → _{year = _} `modifyWithParser` +unformatCommandParser ∷ FormatterCommand → P.ParserT String (State UnformatAccum) Unit +unformatCommandParser = case _ of + YearFull → _{year = _} `modifyWithParser` + (parseInt 4 exactLength "Incorrect full year") + YearTwoDigits → _{year = _} `modifyWithParser` + (parseInt 2 exactLength "Incorrect 2-digit year") + YearAbsolute → _{year = _} `modifyWithParser` (pure (*) <*> (PC.option 1 $ PC.try $ PS.string "-" <#> (const (-1))) - <*> (some parseDigit <#> foldDigits)) *> cb a - MonthFull a → _{month = _} `modifyWithParser` - (fromEnum <$> parseMonth) *> cb a - MonthShort a → _{month = _} `modifyWithParser` - (fromEnum <$> parseShortMonth) *> cb a - MonthTwoDigits a → _{month = _} `modifyWithParser` - (parseInt 2 (validateRange 1 12 <> exactLength) "Incorrect 2-digit month") *> cb a - DayOfMonthTwoDigits a → _{day = _} `modifyWithParser` - (parseInt 2 (validateRange 1 31 <> exactLength) "Incorrect day of month") *> cb a - DayOfMonth a → _{day = _} `modifyWithParser` - (parseInt 2 (validateRange 1 31) "Incorrect day of month") *> cb a - UnixTimestamp a → do - s ← map foldDigits $ some parseDigit + <*> (List.some parseDigit <#> foldDigits)) + MonthFull → _{month = _} `modifyWithParser` + (fromEnum <$> parseMonth) + MonthShort → _{month = _} `modifyWithParser` + (fromEnum <$> parseShortMonth) + MonthTwoDigits → _{month = _} `modifyWithParser` + (parseInt 2 (validateRange 1 12 <> exactLength) "Incorrect 2-digit month") + DayOfMonthTwoDigits → _{day = _} `modifyWithParser` + (parseInt 2 (validateRange 1 31 <> exactLength) "Incorrect day of month") + DayOfMonth → _{day = _} `modifyWithParser` + (parseInt 2 (validateRange 1 31) "Incorrect day of month") + UnixTimestamp → do + s ← map foldDigits $ List.some parseDigit case map toDateTime $ instant $ Dur.Milliseconds $ 1000.0 * Int.toNumber s of Nothing → P.fail "Incorrect timestamp" - Just (DT.DateTime d t) → do - lift $ put { year: Just $ fromEnum $ D.year d - , month: Just $ fromEnum $ D.month d - , day: Just $ fromEnum $ D.day d - , hour: Just $ fromEnum $ T.hour t - , minute: Just $ fromEnum $ T.minute t - , second: Just $ fromEnum $ T.second t - , millisecond: Just $ fromEnum $ T.millisecond t - , meridiem: (Nothing ∷ Maybe Meridiem) - } - cb a + Just (DT.DateTime d t) → lift $ put + { year: Just $ fromEnum $ D.year d + , month: Just $ fromEnum $ D.month d + , day: Just $ fromEnum $ D.day d + , hour: Just $ fromEnum $ T.hour t + , minute: Just $ fromEnum $ T.minute t + , second: Just $ fromEnum $ T.second t + , millisecond: Just $ fromEnum $ T.millisecond t + , meridiem: (Nothing ∷ Maybe Meridiem) + } -- TODO we would need to use this value if we support date format using week number - DayOfWeek a → (parseInt 1 (validateRange 1 7) "Incorrect day of week") *> cb a - Hours24 a → _{hour = _} `modifyWithParser` - (parseInt 2 (validateRange 0 23 <> exactLength) "Incorrect 24 hour") *> cb a - Hours12 a → _{hour = _} `modifyWithParser` - (parseInt 2 (validateRange 0 11 <> exactLength) "Incorrect 12 hour") *> cb a - Meridiem a → _{meridiem = _} `modifyWithParser` - parseMeridiem *> cb a - MinutesTwoDigits a → _{minute = _} `modifyWithParser` - (parseInt 2 (validateRange 0 59 <> exactLength) "Incorrect 2-digit minute") *> cb a - Minutes a → _{minute = _} `modifyWithParser` - (parseInt 2 (validateRange 0 59) "Incorrect minute") *> cb a - SecondsTwoDigits a → _{second = _} `modifyWithParser` - (parseInt 2 (validateRange 0 59 <> exactLength) "Incorrect 2-digit second") *> cb a - Seconds a → _{second = _} `modifyWithParser` - (parseInt 2 (validateRange 0 59) "Incorrect second") *> cb a - Milliseconds a → _{millisecond = _} `modifyWithParser` - (parseInt 3 exactLength "Incorrect millisecond") *> cb a - Placeholder s a → PS.string s *> cb a - MillisecondsShort a → _{millisecond = _} `modifyWithParser` - (parseInt 1 exactLength "Incorrect 1-digit millisecond") *> cb a - MillisecondsTwoDigits a → _{millisecond = _} `modifyWithParser` - (parseInt 2 exactLength "Incorrect 2-digit millisecond") *> cb a - End → pure unit + DayOfWeek → void $ parseInt 1 (validateRange 1 7) "Incorrect day of week" + Hours24 → _{hour = _} `modifyWithParser` + (parseInt 2 (validateRange 0 23 <> exactLength) "Incorrect 24 hour") + Hours12 → _{hour = _} `modifyWithParser` + (parseInt 2 (validateRange 0 11 <> exactLength) "Incorrect 12 hour") + Meridiem → _{meridiem = _} `modifyWithParser` parseMeridiem + MinutesTwoDigits → _{minute = _} `modifyWithParser` + (parseInt 2 (validateRange 0 59 <> exactLength) "Incorrect 2-digit minute") + Minutes → _{minute = _} `modifyWithParser` + (parseInt 2 (validateRange 0 59) "Incorrect minute") + SecondsTwoDigits → _{second = _} `modifyWithParser` + (parseInt 2 (validateRange 0 59 <> exactLength) "Incorrect 2-digit second") + Seconds → _{second = _} `modifyWithParser` + (parseInt 2 (validateRange 0 59) "Incorrect second") + Milliseconds → _{millisecond = _} `modifyWithParser` + (parseInt 3 exactLength "Incorrect millisecond") + Placeholder s → void $ PS.string s + MillisecondsShort → _{millisecond = _} `modifyWithParser` + (parseInt 1 exactLength "Incorrect 1-digit millisecond") + MillisecondsTwoDigits → _{millisecond = _} `modifyWithParser` + (parseInt 2 exactLength "Incorrect 2-digit millisecond") where modifyWithParser ∷ ∀ s' s x. (s → Maybe x → s) → P.ParserT s' (State s) x → P.ParserT s' (State s) Unit modifyWithParser f p = do @@ -411,12 +340,10 @@ unformatFParser cb = case _ of lift $ modify (flip f (Just v)) unformatParser ∷ ∀ m. Monad m ⇒ Formatter → P.ParserT String m DT.DateTime -unformatParser f' = do - acc ← P.mapParserT unState $ rec f' +unformatParser f = do + acc ← P.mapParserT unState $ foldMap unformatCommandParser f either P.fail pure $ unformatAccumToDateTime acc where - rec ∷ Formatter → P.ParserT String (State UnformatAccum) Unit - rec f = unformatFParser rec $ unroll f unState ∷ ∀ x y n. Monad n ⇒ State UnformatAccum (Tuple (Either y Unit) x) → n (Tuple (Either y UnformatAccum) x) unState s = case runState s initialAccum of Tuple (Tuple e state) res → pure (Tuple (e $> res) state) diff --git a/test/src/DateTime.purs b/test/src/DateTime.purs index 5141c5c..dfe8ad0 100644 --- a/test/src/DateTime.purs +++ b/test/src/DateTime.purs @@ -5,10 +5,10 @@ import Prelude import Data.Formatter.DateTime as FDT import Control.Monad.Aff (Aff) +import Data.List (fromFoldable) import Data.DateTime (DateTime) import Data.Either (Either(..)) import Control.MonadZero (guard) -import Data.Functor.Mu (roll) import Control.Alternative (class Alternative, empty) import Test.Spec (describe, Spec) @@ -56,7 +56,7 @@ datetimeTest = describe "Data.Formatter.DateTime" do _.str "shouldn't parse" invalidDateformats - (\f → (FDT.parseFormatString f.str) `shouldEqual` (Left $ "Expected to contain only valid characters@" <> f.pos)) + (\f → (FDT.parseFormatString f.str) `shouldEqual` (Left $ "Expected EOF@" <> f.pos)) forAll (\a → a.format <> " | " <> a.date) @@ -96,76 +96,77 @@ invalidDateformats ∷ Array { str ∷ String , pos ∷ String } invalidDateformats = [ { str: "YY-h-dddd HH:mm Z", pos: "1:4" } , { str: "YYYY-MM-DD M", pos: "1:12" } + , { str: "YYYYM", pos: "1:5" } ] dateformats ∷ Array { str ∷ String , lossless ∷ Boolean, format ∷ FDT.Formatter } dateformats = [ { str: "YYYY-MM-DD" , lossless: false - , format: - roll $ FDT.YearFull $ - roll $ FDT.Placeholder "-" $ - roll $ FDT.MonthTwoDigits $ - roll $ FDT.Placeholder "-" $ - roll $ FDT.DayOfMonthTwoDigits $ - roll FDT.End + , format: fromFoldable + [ FDT.YearFull + , FDT.Placeholder "-" + , FDT.MonthTwoDigits + , FDT.Placeholder "-" + , FDT.DayOfMonthTwoDigits + ] } , { str: "Y-MM-DD HH:mm:ss:SSS" , lossless: true - , format: - roll $ FDT.YearAbsolute $ - roll $ FDT.Placeholder "-" $ - roll $ FDT.MonthTwoDigits $ - roll $ FDT.Placeholder "-" $ - roll $ FDT.DayOfMonthTwoDigits $ - roll $ FDT.Placeholder " " $ - roll $ FDT.Hours24 $ - roll $ FDT.Placeholder ":" $ - roll $ FDT.MinutesTwoDigits $ - roll $ FDT.Placeholder ":" $ - roll $ FDT.SecondsTwoDigits $ - roll $ FDT.Placeholder ":" $ - roll $ FDT.Milliseconds $ - roll FDT.End + , format: fromFoldable + [ FDT.YearAbsolute + , FDT.Placeholder "-" + , FDT.MonthTwoDigits + , FDT.Placeholder "-" + , FDT.DayOfMonthTwoDigits + , FDT.Placeholder " " + , FDT.Hours24 + , FDT.Placeholder ":" + , FDT.MinutesTwoDigits + , FDT.Placeholder ":" + , FDT.SecondsTwoDigits + , FDT.Placeholder ":" + , FDT.Milliseconds + ] } , { str: "YY-Z-DD HH:mm Z" , lossless: false - , format: - roll $ FDT.YearTwoDigits $ - roll $ FDT.Placeholder "-Z-" $ - roll $ FDT.DayOfMonthTwoDigits $ - roll $ FDT.Placeholder " " $ - roll $ FDT.Hours24 $ - roll $ FDT.Placeholder ":" $ - roll $ FDT.MinutesTwoDigits $ - roll $ FDT.Placeholder " Z" $ - roll FDT.End + , format: fromFoldable + [ FDT.YearTwoDigits + , FDT.Placeholder "-Z-" + , FDT.DayOfMonthTwoDigits + , FDT.Placeholder " " + , FDT.Hours24 + , FDT.Placeholder ":" + , FDT.MinutesTwoDigits + , FDT.Placeholder " Z" + ] } , { str: "DD-MM-YYYY trololo HH-:-mm" , lossless: false - , format: - roll $ FDT.DayOfMonthTwoDigits $ - roll $ FDT.Placeholder "-" $ - roll $ FDT.MonthTwoDigits $ - roll $ FDT.Placeholder "-" $ - roll $ FDT.YearFull $ - roll $ FDT.Placeholder " trololo " $ - roll $ FDT.Hours24 $ - roll $ FDT.Placeholder "-:-" $ - roll $ FDT.MinutesTwoDigits $ - roll FDT.End + , format: fromFoldable + [ FDT.DayOfMonthTwoDigits + , FDT.Placeholder "-" + , FDT.MonthTwoDigits + , FDT.Placeholder "-" + , FDT.YearFull + , FDT.Placeholder " trololo " + , FDT.Hours24 + , FDT.Placeholder "-:-" + , FDT.MinutesTwoDigits + ] } , { str: "YYYY-DD-MM SSS" , lossless: false - , format: - roll $ FDT.YearFull $ - roll $ FDT.Placeholder "-" $ - roll $ FDT.DayOfMonthTwoDigits $ - roll $ FDT.Placeholder "-" $ - roll $ FDT.MonthTwoDigits $ - roll $ FDT.Placeholder " " $ - roll $ FDT.Milliseconds $ - roll FDT.End + , format: fromFoldable + [ FDT.YearFull + , FDT.Placeholder "-" + , FDT.DayOfMonthTwoDigits + , FDT.Placeholder "-" + , FDT.MonthTwoDigits + , FDT.Placeholder " " + , FDT.Milliseconds + ] } ] From ea5f0384654dc0e43e66732ae011c2d84743aa5c Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Fri, 28 Apr 2017 02:14:04 +0400 Subject: [PATCH 65/88] unflip --- src/Data/Formatter/DateTime.purs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Data/Formatter/DateTime.purs b/src/Data/Formatter/DateTime.purs index a27babd..ae37bc8 100644 --- a/src/Data/Formatter/DateTime.purs +++ b/src/Data/Formatter/DateTime.purs @@ -16,7 +16,6 @@ import Prelude import Control.Monad.State (State, modify, put, runState) import Control.Monad.Trans.Class (lift) import Control.Monad.State.Class (get) -import Debug.Trace (traceAnyA) import Data.Ord (abs) import Data.Array as Array import Data.List as List @@ -195,7 +194,7 @@ format f d = foldMap (formatCommand d) f formatDateTime ∷ String → DT.DateTime → Either String String formatDateTime pattern datetime = - parseFormatString pattern <#> flip format datetime + parseFormatString pattern <#> (_ `format` datetime) unformat ∷ Formatter → String → Either String DT.DateTime unformat = runP <<< unformatParser @@ -352,7 +351,7 @@ unformatParser f = do unformatDateTime ∷ String → String → Either String DT.DateTime unformatDateTime pattern str = - parseFormatString pattern >>= flip unformat str + parseFormatString pattern >>= (_ `unformat` str) parseMeridiem ∷ ∀ m. Monad m ⇒ P.ParserT String m Meridiem parseMeridiem = (PC.try <<< PS.string) `oneOfAs` From 2f61c9287895a4d8c962a1e7ba764b7eca2e946a Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Mon, 1 May 2017 17:01:41 +0400 Subject: [PATCH 66/88] remove unused --- src/Data/Formatter/DateTime.purs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Data/Formatter/DateTime.purs b/src/Data/Formatter/DateTime.purs index ae37bc8..79ca74c 100644 --- a/src/Data/Formatter/DateTime.purs +++ b/src/Data/Formatter/DateTime.purs @@ -15,7 +15,6 @@ import Prelude import Control.Monad.State (State, modify, put, runState) import Control.Monad.Trans.Class (lift) -import Control.Monad.State.Class (get) import Data.Ord (abs) import Data.Array as Array import Data.List as List @@ -40,7 +39,6 @@ import Data.Formatter.Parser.Utils (runP, oneOfAs) import Control.Monad.Reader.Trans (ReaderT, runReaderT, ask) import Text.Parsing.Parser as P import Text.Parsing.Parser.Combinators as PC -import Text.Parsing.Parser.Combinators (()) import Text.Parsing.Parser.String as PS import Data.Generic.Rep (class Generic) From 563c15d74676ca832f8819af3a0ed98a3dba8a12 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Mon, 1 May 2017 21:59:40 +0400 Subject: [PATCH 67/88] fix parsing formats like 'HHmm' (without seperators) --- src/Data/Formatter/DateTime.purs | 27 ++++++++++++++++++++++++--- test/src/DateTime.purs | 16 +++++++--------- 2 files changed, 31 insertions(+), 12 deletions(-) diff --git a/src/Data/Formatter/DateTime.purs b/src/Data/Formatter/DateTime.purs index 79ca74c..fcd8857 100644 --- a/src/Data/Formatter/DateTime.purs +++ b/src/Data/Formatter/DateTime.purs @@ -13,15 +13,17 @@ module Data.Formatter.DateTime import Prelude +import Debug.Trace as Trace import Control.Monad.State (State, modify, put, runState) import Control.Monad.Trans.Class (lift) import Data.Ord (abs) import Data.Array as Array import Data.List as List -import Data.List.Lazy as LazyList import Data.Tuple (Tuple(..)) import Data.Foldable (foldMap) +import Control.Lazy as Z import Control.Alt ((<|>)) +import Control.Alternative (class Alternative) import Data.Date as D import Data.DateTime as DT import Data.DateTime.Instant (instant, toDateTime, fromDateTime, unInstant) @@ -260,6 +262,25 @@ validateRange min max = ask >>= \({num}) → if num < min || num > max then lift $ Left $ "Number is out of range [ " <> (show min) <> ", " <> (show max) <> " ]" else lift $ Right unit +-- NOTE related discussion: https://github.com/purescript-contrib/purescript-parsing/issues/57 +-- | Attempt a computation `n` times, requiring at least one success. +-- | +-- | The `Lazy` constraint is used to generate the result lazily, to ensure +-- | termination. +takeSome :: forall f a. Alternative f => Z.Lazy (f (List.List a)) => Int -> f a -> f (List.List a) +takeSome 0 _ = pure List.Nil +takeSome n v = List.Cons <$> v <*> Z.defer (\_ -> takeMany (n - 1) v) + +-- | Attempt a computation `n` times, returning as many successful results +-- | as possible (possibly zero). +-- | +-- | The `Lazy` constraint is used to generate the result lazily, to ensure +-- | termination. +takeMany :: forall f a. Alternative f => Z.Lazy (f (List.List a)) => Int -> f a -> f (List.List a) +takeMany 0 _ = pure List.Nil +takeMany n v = takeSome n v <|> pure List.Nil + + parseInt ∷ ∀ m . Monad m ⇒ Int @@ -267,8 +288,8 @@ parseInt ∷ ∀ m → String → P.ParserT String m Int parseInt maxLength validators errMsg = do - ds ← LazyList.take maxLength <$> (LazyList.some parseDigit) - let length = LazyList.length ds + ds ← takeSome maxLength parseDigit + let length = List.length ds let num = foldDigits ds case runReaderT validators {length, num, maxLength} of Left err → P.fail $ errMsg <> "(" <> err <> ")" diff --git a/test/src/DateTime.purs b/test/src/DateTime.purs index dfe8ad0..4a5dacb 100644 --- a/test/src/DateTime.purs +++ b/test/src/DateTime.purs @@ -2,7 +2,6 @@ module Test.DateTime (datetimeTest) where import Prelude - import Data.Formatter.DateTime as FDT import Control.Monad.Aff (Aff) import Data.List (fromFoldable) @@ -10,7 +9,6 @@ import Data.DateTime (DateTime) import Data.Either (Either(..)) import Control.MonadZero (guard) import Control.Alternative (class Alternative, empty) - import Test.Spec (describe, Spec) import Test.Spec.Assertions (shouldEqual) import Test.Utils (forAll, makeDateTime) @@ -18,7 +16,7 @@ import Test.Utils (forAll, makeDateTime) datetimeTest ∷ ∀ e. Spec e Unit datetimeTest = describe "Data.Formatter.DateTime" do forAll (\a → a.format <> " | " <> a.dateStr) - "formatDateTime should formatt dateTime" + "formatDateTime/unformaDateTime should formatt/unforma dateTime" [ { format: "MM/DD/YYYY", dateStr: "04/12/2017" , date: makeDateTime 2017 4 12 11 3 4 234} , { format: "MMMM", dateStr: "April" , date: makeDateTime 2017 4 12 11 3 4 234} , { format: "YYYY-DD-MM", dateStr: "2017-12-04" , date: makeDateTime 2017 4 12 11 3 4 234} @@ -42,8 +40,13 @@ datetimeTest = describe "Data.Formatter.DateTime" do , { format: "hhmmssSSS", dateStr: "111230003", date: makeDateTime 2017 4 10 11 12 30 3 } , { format: "hhmmssSS", dateStr: "11123012", date: makeDateTime 2017 4 10 11 12 30 123 } , { format: "hhmmssS", dateStr: "1112301", date: makeDateTime 2017 4 10 11 12 30 123 } + , { format: "HHmmssSSS", dateStr: "134530123", date: makeDateTime 2017 4 10 13 45 30 123 } + , { format: "HHmm", dateStr: "1345", date: makeDateTime 2017 4 10 13 45 30 123 } ] - (\({ format, dateStr, date }) → (format `FDT.formatDateTime` date) `shouldEqual` (Right dateStr)) + (\({ format, dateStr, date }) → do + (format `FDT.formatDateTime` date) `shouldEqual` (Right dateStr) + (void $ format `FDT.unformatDateTime` dateStr) `shouldEqual` (Right unit) + ) describe "parseFormatString" do forAll @@ -77,11 +80,6 @@ datetimeTest = describe "Data.Formatter.DateTime" do (\({ date, format }) → FDT.unformat format (FDT.format format date) `shouldEqual` (Right date)) -assertFormatting ∷ ∀ e. String → String → DateTime → Aff e Unit -assertFormatting target' format dateTime = result `shouldEqual` target - where - result = FDT.formatDateTime format dateTime - target = Right target' dates ∷ Array DateTime dates = From 533b2a0f9e4d186a064b4c34ec0650cb76ed56c1 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Tue, 2 May 2017 17:10:49 +0400 Subject: [PATCH 68/88] fix build --- src/Data/Formatter/DateTime.purs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Data/Formatter/DateTime.purs b/src/Data/Formatter/DateTime.purs index fcd8857..ce11546 100644 --- a/src/Data/Formatter/DateTime.purs +++ b/src/Data/Formatter/DateTime.purs @@ -13,7 +13,6 @@ module Data.Formatter.DateTime import Prelude -import Debug.Trace as Trace import Control.Monad.State (State, modify, put, runState) import Control.Monad.Trans.Class (lift) import Data.Ord (abs) From f74ba26d640d8d694051e102ce0648eb9b1a08a2 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Wed, 3 May 2017 19:58:01 +0400 Subject: [PATCH 69/88] fix 'SS' and 'S' parsing --- src/Data/Formatter/DateTime.purs | 4 ++-- test/src/DateTime.purs | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Data/Formatter/DateTime.purs b/src/Data/Formatter/DateTime.purs index ce11546..dda159e 100644 --- a/src/Data/Formatter/DateTime.purs +++ b/src/Data/Formatter/DateTime.purs @@ -347,9 +347,9 @@ unformatCommandParser = case _ of (parseInt 3 exactLength "Incorrect millisecond") Placeholder s → void $ PS.string s MillisecondsShort → _{millisecond = _} `modifyWithParser` - (parseInt 1 exactLength "Incorrect 1-digit millisecond") + (parseInt 1 exactLength "Incorrect 1-digit millisecond" <#> (_ * 100)) MillisecondsTwoDigits → _{millisecond = _} `modifyWithParser` - (parseInt 2 exactLength "Incorrect 2-digit millisecond") + (parseInt 2 exactLength "Incorrect 2-digit millisecond" <#> (_ * 10)) where modifyWithParser ∷ ∀ s' s x. (s → Maybe x → s) → P.ParserT s' (State s) x → P.ParserT s' (State s) Unit modifyWithParser f p = do diff --git a/test/src/DateTime.purs b/test/src/DateTime.purs index 4a5dacb..dd65f08 100644 --- a/test/src/DateTime.purs +++ b/test/src/DateTime.purs @@ -66,6 +66,10 @@ datetimeTest = describe "Data.Formatter.DateTime" do "s ≡ format (unformat s)" [ {date: "2017-12-04 234", format: "YYYY-DD-MM SSS" } , {date: "3456-09-10 333", format: "YYYY-DD-MM SSS" } + , {date: "111230003", format: "hhmmssSSS"} + , {date: "11123012", format: "hhmmssSS"} + , {date: "1112301", format: "hhmmssS"} + ] (\({date, format}) → (FDT.unformatDateTime format date >>= FDT.formatDateTime format) `shouldEqual` (Right date)) From 7dfe4642527c38f9d781b4a1d6a2ec73196e1075 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Mon, 8 May 2017 15:52:47 +0400 Subject: [PATCH 70/88] export printFormatterCommand --- src/Data/Formatter/DateTime.purs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Data/Formatter/DateTime.purs b/src/Data/Formatter/DateTime.purs index dda159e..98d4766 100644 --- a/src/Data/Formatter/DateTime.purs +++ b/src/Data/Formatter/DateTime.purs @@ -3,6 +3,7 @@ module Data.Formatter.DateTime , FormatterCommand(..) , Meridiem , printFormatter + , printFormatterCommand , parseFormatString , format , formatDateTime From 3067b97b8871126a0c0c8de1b1b9493c0fab4c96 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Tue, 16 May 2017 15:19:30 -0600 Subject: [PATCH 71/88] update ps-datetime --- src/Data/Formatter/Interval.purs | 2 +- src/Data/Formatter/Parser/Interval.purs | 2 +- test/src/Interval.purs | 16 ++++++++-------- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Data/Formatter/Interval.purs b/src/Data/Formatter/Interval.purs index f84be0b..087f44b 100644 --- a/src/Data/Formatter/Interval.purs +++ b/src/Data/Formatter/Interval.purs @@ -45,7 +45,7 @@ formatDuration (I.Duration m) = "P" <> datePart <> timePart componentToString (Tuple k s) = maybe "" (formatComponent s) $ lookup k m formatComponent designator num = formatNumber num <> designator dateComponentsToStr = [ Tuple I.Year "Y", Tuple I.Month "M", Tuple I.Day "D" ] - timeComponentsToStr = [ Tuple I.Hours "H", Tuple I.Minutes "M", Tuple I.Seconds "S" ] + timeComponentsToStr = [ Tuple I.Hour "H", Tuple I.Minute "M", Tuple I.Second "S" ] formatInteger ∷ Int → String formatInteger = show diff --git a/src/Data/Formatter/Parser/Interval.purs b/src/Data/Formatter/Parser/Interval.purs index a3ec0a8..62ecfb8 100644 --- a/src/Data/Formatter/Parser/Interval.purs +++ b/src/Data/Formatter/Parser/Interval.purs @@ -49,7 +49,7 @@ parseDuration = PS.string "P" *> (weekDuration <|> fullDuration) weekDuration = mkComponentsParser [ Tuple I.week "W" ] fullDuration = (append <$> durationDatePart <*> durationTimePart) `notEmpty` "must contain valid duration components" durationDatePart = PC.option mempty $ PC.try $ mkComponentsParser [ Tuple I.year "Y" , Tuple I.month "M" , Tuple I.day "D" ] - durationTimePart = PC.option mempty $ (PC.try $ PS.string "T") *> (mkComponentsParser [ Tuple I.hours "H" , Tuple I.minutes "M" , Tuple I.seconds "S" ]) + durationTimePart = PC.option mempty $ (PC.try $ PS.string "T") *> (mkComponentsParser [ Tuple I.hour "H" , Tuple I.minute "M" , Tuple I.second "S" ]) notEmpty ∷ ∀ a. Monoid a ⇒ Eq a ⇒ P.Parser String a → String → P.Parser String a diff --git a/test/src/Interval.purs b/test/src/Interval.purs index 4d5716e..5b3c15d 100644 --- a/test/src/Interval.purs +++ b/test/src/Interval.purs @@ -44,15 +44,15 @@ durations ∷ Array { str∷ String, formatedStr∷ String, dur ∷ I.IsoDuratio durations = [ { str: "P1W", formatedStr: "P7D", dur: I.day 7.0 } , { str: "P1.0W", formatedStr: "P7D", dur: I.day 7.0 } - , { str: "P1DT1H1M1S", formatedStr: "P1DT1H1M1S", dur: I.day 1.0 <> I.hours 1.0 <> I.minutes 1.0 <> I.seconds 1.0 } + , { str: "P1DT1H1M1S", formatedStr: "P1DT1H1M1S", dur: I.day 1.0 <> I.hour 1.0 <> I.minute 1.0 <> I.second 1.0 } , { str: "P1.9748600D", formatedStr: "P1.97486D", dur: I.day 1.97486 } - , { str: "P1DT1H1M0S", formatedStr: "P1DT1H1M0S", dur: I.day 1.0 <> I.hours 1.0 <> I.minutes 1.0 <> I.seconds 0.0 } - , { str: "P1DT1H1M1.5S", formatedStr: "P1DT1H1M1.5S", dur: I.day 1.0 <> I.hours 1.0 <> I.minutes 1.0 <> I.seconds 1.5 } - , { str: "P1DT1H1.5M", formatedStr: "P1DT1H1.5M", dur: I.day 1.0 <> I.hours 1.0 <> I.minutes 1.5 } - , { str: "P1DT1.5H", formatedStr: "P1DT1.5H", dur: I.day 1.0 <> I.hours 1.5 } - , { str: "PT1M", formatedStr: "PT1M", dur: I.minutes 1.0 } - , { str: "PT1S", formatedStr: "PT1S", dur: I.seconds 1.0 } - , { str: "PT1H1S", formatedStr: "PT1H1S", dur: I.hours 1.0 <> I.seconds 1.0 } + , { str: "P1DT1H1M0S", formatedStr: "P1DT1H1M0S", dur: I.day 1.0 <> I.hour 1.0 <> I.minute 1.0 <> I.second 0.0 } + , { str: "P1DT1H1M1.5S", formatedStr: "P1DT1H1M1.5S", dur: I.day 1.0 <> I.hour 1.0 <> I.minute 1.0 <> I.second 1.5 } + , { str: "P1DT1H1.5M", formatedStr: "P1DT1H1.5M", dur: I.day 1.0 <> I.hour 1.0 <> I.minute 1.5 } + , { str: "P1DT1.5H", formatedStr: "P1DT1.5H", dur: I.day 1.0 <> I.hour 1.5 } + , { str: "PT1M", formatedStr: "PT1M", dur: I.minute 1.0 } + , { str: "PT1S", formatedStr: "PT1S", dur: I.second 1.0 } + , { str: "PT1H1S", formatedStr: "PT1H1S", dur: I.hour 1.0 <> I.second 1.0 } ] <#> (\a → a { dur = unsafeMkToIsoDuration a.dur }) -- TODO error messages could be improved From cfcec22c8cf823bc86b720bae82266cc101f71d9 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Tue, 16 May 2017 16:41:36 -0600 Subject: [PATCH 72/88] remove unused --- test/src/DateTime.purs | 1 - 1 file changed, 1 deletion(-) diff --git a/test/src/DateTime.purs b/test/src/DateTime.purs index dd65f08..6b5df2e 100644 --- a/test/src/DateTime.purs +++ b/test/src/DateTime.purs @@ -3,7 +3,6 @@ module Test.DateTime (datetimeTest) where import Prelude import Data.Formatter.DateTime as FDT -import Control.Monad.Aff (Aff) import Data.List (fromFoldable) import Data.DateTime (DateTime) import Data.Either (Either(..)) From 56d0d38b1550cb3221e83b5b48cfd8c5f567f393 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Fri, 2 Jun 2017 12:17:45 +0400 Subject: [PATCH 73/88] use published version of purescript-lists --- bower.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bower.json b/bower.json index ff22e13..fda83e2 100644 --- a/bower.json +++ b/bower.json @@ -21,7 +21,7 @@ "purescript-fixed-points": "^4.0.0", "purescript-datetime": "git://github.com/safareli/purescript-datetime.git#interval", "purescript-transformers": "git://github.com/safareli/purescript-transformers.git#readermonoid", - "purescript-lists": "git://github.com/safareli/purescript-lists.git#somemany", + "purescript-lists": "^4.3.0", "purescript-generics-rep": "^5.0.0" }, "devDependencies": { @@ -31,7 +31,6 @@ "purescript-spec": "^0.14.0" }, "resolutions": { - "purescript-lists": "somemany", "purescript-transformers": "readermonoid", "purescript-datetime": "interval" } From b74780b7867be33c8398e744af2b18e9facb316e Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Sun, 4 Jun 2017 15:27:25 +0400 Subject: [PATCH 74/88] update ps-transformers --- bower.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bower.json b/bower.json index fda83e2..0301503 100644 --- a/bower.json +++ b/bower.json @@ -20,7 +20,7 @@ "purescript-parsing": "^4.2.1", "purescript-fixed-points": "^4.0.0", "purescript-datetime": "git://github.com/safareli/purescript-datetime.git#interval", - "purescript-transformers": "git://github.com/safareli/purescript-transformers.git#readermonoid", + "purescript-transformers": "^3.4.0", "purescript-lists": "^4.3.0", "purescript-generics-rep": "^5.0.0" }, @@ -31,7 +31,6 @@ "purescript-spec": "^0.14.0" }, "resolutions": { - "purescript-transformers": "readermonoid", "purescript-datetime": "interval" } } From f6601fd74b8492f2d09df7c65f835fb0cac15ff9 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Fri, 23 Jun 2017 15:02:04 +0400 Subject: [PATCH 75/88] use unsafePartial instead of unsafePartialBecause --- src/Data/Formatter/Parser/Interval.purs | 8 +++----- test/src/Interval.purs | 5 +++-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/Data/Formatter/Parser/Interval.purs b/src/Data/Formatter/Parser/Interval.purs index 62ecfb8..02fac8f 100644 --- a/src/Data/Formatter/Parser/Interval.purs +++ b/src/Data/Formatter/Parser/Interval.purs @@ -20,7 +20,7 @@ import Data.Formatter.DateTime (unformatParser, Formatter, parseFormatString) import Data.DateTime (DateTime) import Data.Traversable (sequence) import Data.Tuple (Tuple(..), snd) -import Partial.Unsafe (unsafePartialBecause) +import Partial.Unsafe (unsafePartial) import Data.Formatter.Parser.Number (parseNumber, parseMaybeInteger) @@ -74,7 +74,5 @@ parseDateTime ∷ ∀ m. Monad m ⇒ P.ParserT String m DateTime parseDateTime = unformatParser extendedDateTimeFormatInUTC extendedDateTimeFormatInUTC ∷ Formatter -extendedDateTimeFormatInUTC = unEither $ parseFormatString "YYYY-MM-DDTHH:mm:ssZ" - where - unEither ∷ Either String Formatter → Formatter - unEither = unsafePartialBecause "(this must be unrechable) error in parsing ISO date format" fromRight +extendedDateTimeFormatInUTC = parseFormatString "YYYY-MM-DDTHH:mm:ssZ" + # unsafePartial fromRight -- the format must be valid ISO date format diff --git a/test/src/Interval.purs b/test/src/Interval.purs index 5b3c15d..a0419af 100644 --- a/test/src/Interval.purs +++ b/test/src/Interval.purs @@ -10,7 +10,7 @@ import Data.Formatter.Parser.Interval (parseIsoDuration) import Data.Formatter.Parser.Utils (runP) import Data.Either (Either(..)) import Data.Maybe (Maybe(..), fromJust) -import Partial.Unsafe (unsafePartialBecause) +import Partial.Unsafe (unsafePartial) import Test.Spec (describe, Spec) import Test.Spec.Assertions (shouldEqual) import Test.Utils (forAll, makeDateTime) @@ -38,7 +38,8 @@ intervalTest = describe "Data.Formatter.Interval" do unsafeMkToIsoDuration ∷ I.Duration → I.IsoDuration -unsafeMkToIsoDuration d = unsafePartialBecause "the duration must be valid ISO duration" fromJust $ I.mkIsoDuration d +unsafeMkToIsoDuration d = I.mkIsoDuration d + # unsafePartial fromJust -- the duration must be valid ISO duration durations ∷ Array { str∷ String, formatedStr∷ String, dur ∷ I.IsoDuration } durations = From 1b4351ee2cc6a75ac75d23c0c09026cfe7a0a660 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Fri, 23 Jun 2017 22:31:49 +0400 Subject: [PATCH 76/88] update datetime --- src/Data/Formatter/Interval.purs | 33 +++++++++--------- src/Data/Formatter/Parser/Interval.purs | 36 ++++++++++---------- test/src/Interval.purs | 45 +++++++++++++------------ 3 files changed, 60 insertions(+), 54 deletions(-) diff --git a/src/Data/Formatter/Interval.purs b/src/Data/Formatter/Interval.purs index 087f44b..7da3f44 100644 --- a/src/Data/Formatter/Interval.purs +++ b/src/Data/Formatter/Interval.purs @@ -7,33 +7,34 @@ module Data.Formatter.Interval import Prelude -import Data.Formatter.Parser.Utils (runP) -import Data.Formatter.DateTime as FDT -import Data.Interval as I import Data.DateTime (DateTime) import Data.Either (Either) -import Data.Tuple (Tuple(..)) -import Data.Maybe (maybe) -import Data.Monoid (mempty) -import Data.Map (lookup) -import Data.Int as Int import Data.Foldable (foldMap) +import Data.Formatter.DateTime as FDT import Data.Formatter.Parser.Interval (parseRecurringInterval, parseInterval, parseIsoDuration, parseDateTime, extendedDateTimeFormatInUTC) +import Data.Formatter.Parser.Utils (runP) +import Data.Int as Int +import Data.Interval as I +import Data.Interval.Duration.Iso (IsoDuration, unIsoDuration) +import Data.Map (lookup) +import Data.Maybe (maybe) +import Data.Monoid (mempty) +import Data.Tuple (Tuple(..)) -formatRecurringInterval ∷ I.RecurringInterval I.IsoDuration DateTime → String +formatRecurringInterval ∷ I.RecurringInterval IsoDuration DateTime → String formatRecurringInterval (I.RecurringInterval n i) = "R" <> (maybe "" formatInteger n) <> "/" <> (formatInterval i) -formatInterval ∷ I.Interval I.IsoDuration DateTime → String +formatInterval ∷ I.Interval IsoDuration DateTime → String formatInterval (I.StartEnd x y) = (formatDateTime x) <> "/" <> (formatDateTime y) formatInterval (I.DurationEnd d x) = (formatIsoDuration d) <> "/" <> (formatDateTime x) formatInterval (I.StartDuration x d) = (formatDateTime x) <> "/" <> (formatIsoDuration d) -formatInterval (I.JustDuration d) = (formatIsoDuration d) +formatInterval (I.DurationOnly d) = (formatIsoDuration d) formatDateTime ∷ DateTime → String formatDateTime = FDT.format extendedDateTimeFormatInUTC -formatIsoDuration ∷ I.IsoDuration → String -formatIsoDuration = formatDuration <<< I.unIsoDuration +formatIsoDuration ∷ IsoDuration → String +formatIsoDuration = formatDuration <<< unIsoDuration formatDuration ∷ I.Duration → String formatDuration (I.Duration m) = "P" <> datePart <> timePart @@ -44,7 +45,7 @@ formatDuration (I.Duration m) = "P" <> datePart <> timePart ifmempty f a = f a componentToString (Tuple k s) = maybe "" (formatComponent s) $ lookup k m formatComponent designator num = formatNumber num <> designator - dateComponentsToStr = [ Tuple I.Year "Y", Tuple I.Month "M", Tuple I.Day "D" ] + dateComponentsToStr = [ Tuple I.Year "Y", Tuple I.Month "M", Tuple I.Week "W", Tuple I.Day "D" ] timeComponentsToStr = [ Tuple I.Hour "H", Tuple I.Minute "M", Tuple I.Second "S" ] formatInteger ∷ Int → String @@ -53,8 +54,8 @@ formatInteger = show formatNumber ∷ Number → String formatNumber n = if Int.toNumber (Int.floor n) == n then show (Int.floor n) else show n -unformatRecurringInterval ∷ String → Either String (I.RecurringInterval I.IsoDuration DateTime) +unformatRecurringInterval ∷ String → Either String (I.RecurringInterval IsoDuration DateTime) unformatRecurringInterval = runP $ parseRecurringInterval parseIsoDuration parseDateTime -unformatInterval ∷ String → Either String (I.Interval I.IsoDuration DateTime) +unformatInterval ∷ String → Either String (I.Interval IsoDuration DateTime) unformatInterval = runP $ parseInterval parseIsoDuration parseDateTime diff --git a/src/Data/Formatter/Parser/Interval.purs b/src/Data/Formatter/Parser/Interval.purs index 02fac8f..53c19cd 100644 --- a/src/Data/Formatter/Parser/Interval.purs +++ b/src/Data/Formatter/Parser/Interval.purs @@ -7,41 +7,43 @@ module Data.Formatter.Parser.Interval ) where import Prelude -import Data.Interval as I -import Text.Parsing.Parser as P -import Text.Parsing.Parser.Combinators as PC -import Text.Parsing.Parser.String as PS + import Control.Alt ((<|>)) -import Data.Foldable (class Foldable, fold, foldMap) -import Data.Maybe (Maybe(..)) -import Data.Monoid (class Monoid, mempty) -import Data.Either (Either, fromRight) -import Data.Formatter.DateTime (unformatParser, Formatter, parseFormatString) import Data.DateTime (DateTime) +import Data.Either (Either(..), fromRight) +import Data.Foldable (class Foldable, fold, foldMap, intercalate) +import Data.Formatter.DateTime (unformatParser, Formatter, parseFormatString) +import Data.Formatter.Parser.Number (parseNumber, parseMaybeInteger) +import Data.Interval as I +import Data.Interval.Duration.Iso (IsoDuration, mkIsoDuration, prettyError) +import Data.Maybe (Maybe) +import Data.Monoid (class Monoid, mempty) import Data.Traversable (sequence) import Data.Tuple (Tuple(..), snd) import Partial.Unsafe (unsafePartial) - -import Data.Formatter.Parser.Number (parseNumber, parseMaybeInteger) +import Text.Parsing.Parser as P +import Text.Parsing.Parser.Combinators as PC +import Text.Parsing.Parser.String as PS parseRecurringInterval ∷ ∀ a b. P.Parser String a → P.Parser String b → P.Parser String (I.RecurringInterval a b) parseRecurringInterval duration date = I.RecurringInterval <$> (PS.string "R" *> parseMaybeInteger) <*> (PS.string "/" *> parseInterval duration date) parseInterval ∷ ∀ a b. P.Parser String a → P.Parser String b → P.Parser String (I.Interval a b) -parseInterval duration date = [startEnd, durationEnd, startDuration, justDuration] <#> PC.try # PC.choice +parseInterval duration date = [startEnd, durationEnd, startDuration, durationOnly] <#> PC.try # PC.choice where startEnd = I.StartEnd <$> date <* PS.string "/" <*> date durationEnd = I.DurationEnd <$> duration <* PS.string "/" <*> date startDuration = I.StartDuration <$> date <* PS.string "/" <*> duration - justDuration = I.JustDuration <$> duration + durationOnly = I.DurationOnly <$> duration -parseIsoDuration ∷ P.Parser String I.IsoDuration +parseIsoDuration ∷ P.Parser String IsoDuration parseIsoDuration = do dur ← parseDuration - case I.mkIsoDuration dur of - Nothing → P.fail "extracted Duration is not valid ISO duration" - Just a → pure a + case mkIsoDuration dur of + Left errs → let errorStr = intercalate ", " (prettyError <$> errs) + in P.fail $ "extracted Duration is not valid ISO duration (" <> errorStr <> ")" + Right a → pure a parseDuration ∷ P.Parser String I.Duration parseDuration = PS.string "P" *> (weekDuration <|> fullDuration) diff --git a/test/src/Interval.purs b/test/src/Interval.purs index a0419af..d7eece5 100644 --- a/test/src/Interval.purs +++ b/test/src/Interval.purs @@ -2,19 +2,20 @@ module Test.Interval (intervalTest) where import Prelude +import Control.Monad.Aff (Aff) import Data.DateTime (DateTime) -import Data.Interval as I +import Data.Either (Either(..), fromRight) import Data.Foldable (class Foldable, fold) import Data.Formatter.Interval (unformatInterval, unformatRecurringInterval, formatRecurringInterval) import Data.Formatter.Parser.Interval (parseIsoDuration) import Data.Formatter.Parser.Utils (runP) -import Data.Either (Either(..)) -import Data.Maybe (Maybe(..), fromJust) +import Data.Interval as I +import Data.Interval.Duration.Iso (IsoDuration, mkIsoDuration) +import Data.Maybe (Maybe(..)) import Partial.Unsafe (unsafePartial) import Test.Spec (describe, Spec) import Test.Spec.Assertions (shouldEqual) import Test.Utils (forAll, makeDateTime) -import Control.Monad.Aff (Aff) prop ∷ ∀ e e' f. Foldable f ⇒ String → f {str ∷ String | e'} → ({str ∷ String | e'} → Aff e Unit) → Spec e Unit prop = forAll (show <<< _.str) @@ -37,14 +38,14 @@ intervalTest = describe "Data.Formatter.Interval" do (unformatRecurringInterval $ formatRecurringInterval interval) `shouldEqual` (Right interval) -unsafeMkToIsoDuration ∷ I.Duration → I.IsoDuration -unsafeMkToIsoDuration d = I.mkIsoDuration d - # unsafePartial fromJust -- the duration must be valid ISO duration +unsafeMkToIsoDuration ∷ I.Duration → IsoDuration +unsafeMkToIsoDuration d = mkIsoDuration d + # unsafePartial fromRight -- the duration must be valid ISO duration -durations ∷ Array { str∷ String, formatedStr∷ String, dur ∷ I.IsoDuration } +durations ∷ Array { str∷ String, formatedStr∷ String, dur ∷ IsoDuration } durations = - [ { str: "P1W", formatedStr: "P7D", dur: I.day 7.0 } - , { str: "P1.0W", formatedStr: "P7D", dur: I.day 7.0 } + [ { str: "P1W", formatedStr: "P1W", dur: I.week 1.0 } + , { str: "P1.0W", formatedStr: "P1W", dur: I.week 1.0 } , { str: "P1DT1H1M1S", formatedStr: "P1DT1H1M1S", dur: I.day 1.0 <> I.hour 1.0 <> I.minute 1.0 <> I.second 1.0 } , { str: "P1.9748600D", formatedStr: "P1.97486D", dur: I.day 1.97486 } , { str: "P1DT1H1M0S", formatedStr: "P1DT1H1M0S", dur: I.day 1.0 <> I.hour 1.0 <> I.minute 1.0 <> I.second 0.0 } @@ -59,10 +60,10 @@ durations = -- TODO error messages could be improved invalidDurations ∷ Array { err ∷ String, str ∷ String} invalidDurations = - [ { err: errInvalidISO <> "1:13", str: "P1DT1.5H0M1S" } - , { err: errInvalidISO <> "1:10", str: "P1.5Y0.5M" } - , { err: errInvalidISO <> "1:8", str: "P1.5Y1M" } - , { err: errInvalidISO <> "1:12", str: "P1.5MT10.5S" } + [ { err: errInvalidISO "Hour" <> "1:13", str: "P1DT1.5H0M1S" } + , { err: errInvalidISO "Year" <> "1:10", str: "P1.5Y0.5M" } + , { err: errInvalidISO "Year" <> "1:8", str: "P1.5Y1M" } + , { err: errInvalidISO "Month" <> "1:12", str: "P1.5MT10.5S" } , { err: errInvalidComponent <> "1:2", str: "P" } , { err: errInvalidComponent <> "1:2", str: "PW" } , { err: errInvalidComponent <> "1:2", str: "PD" } @@ -79,7 +80,9 @@ invalidDurations = errInvalidComponent = "must contain valid duration components@" errPrefix = "Expected \"P\"@" errEOF = "Expected EOF@" - errInvalidISO = "extracted Duration is not valid ISO duration@" + errInvalidISO c = + "extracted Duration is not valid ISO duration " <> + "(Invalid usage of Fractional value at component `" <> c <> "`)@" errNoTimeComponent = "none of valid duration components ([\"H\",\"M\",\"S\"]) were present@" -- TODO error messages could be improved @@ -117,8 +120,8 @@ dates = , { str: "2015-07-22T00:00:00Z", date: makeDateTime 2015 7 22 0 0 0 0 } ] -type ArbRecurringInterval = Array { str ∷ String, formatedStr ∷ String, interval ∷ I.RecurringInterval I.IsoDuration DateTime} -type ArbInterval = Array { str ∷ String, formatedStr ∷ String, interval ∷ I.Interval I.IsoDuration DateTime} +type ArbRecurringInterval = Array { str ∷ String, formatedStr ∷ String, interval ∷ I.RecurringInterval IsoDuration DateTime} +type ArbInterval = Array { str ∷ String, formatedStr ∷ String, interval ∷ I.Interval IsoDuration DateTime} arbRecurringInterval ∷ ArbRecurringInterval arbRecurringInterval = do @@ -135,7 +138,7 @@ arbInterval = fold [ arbIntervalStartEnd , arbIntervalDurationEnd , arbIntervalStartDuration - , arbIntervalJustDuration + , arbIntervalDurationOnly ] arbIntervalStartEnd ∷ ArbInterval @@ -168,11 +171,11 @@ arbIntervalStartDuration = do , interval: I.StartDuration start.date dur.dur } -arbIntervalJustDuration ∷ ArbInterval -arbIntervalJustDuration = do +arbIntervalDurationOnly ∷ ArbInterval +arbIntervalDurationOnly = do dur ← durations pure { str: dur.str , formatedStr: dur.formatedStr - , interval: I.JustDuration dur.dur + , interval: I.DurationOnly dur.dur } From a1338cf882954bb1a5f75842b0685d830b1fba25 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Mon, 26 Jun 2017 18:44:25 +0400 Subject: [PATCH 77/88] fix number test --- test/src/Number.purs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/test/src/Number.purs b/test/src/Number.purs index 08eeac2..87f2692 100644 --- a/test/src/Number.purs +++ b/test/src/Number.purs @@ -29,12 +29,16 @@ numberTest = describe "Data.Formatter.Number" do forAll show "format (unformat n) = n" [ "001.12" - -- TODO fails on negative numbers - -- , "-012.12" - -- , "-123.12" ] (\n → (format fmt1 <$> (unformat fmt1 n)) `shouldEqual` (Right n)) + forAll show + "format (unformat n) = n" + [ "-02.12" + , "-13.12" + ] + (\n → (format fmt3 <$> (unformat fmt3 n)) `shouldEqual` (Right n)) + fmt1 ∷ Formatter fmt1 = Formatter { comma: false @@ -47,7 +51,7 @@ fmt1 = Formatter fmt2 ∷ Formatter fmt2 = Formatter { comma: true - , before: one + , before: 1 , after: 4 , abbreviations: false , sign: true From 30255e4ccd96c55da8ecb906088a7860c8308b42 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Mon, 26 Jun 2017 18:45:44 +0400 Subject: [PATCH 78/88] remove some old todos --- test/src/Interval.purs | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/src/Interval.purs b/test/src/Interval.purs index d7eece5..f7c1243 100644 --- a/test/src/Interval.purs +++ b/test/src/Interval.purs @@ -57,7 +57,6 @@ durations = , { str: "PT1H1S", formatedStr: "PT1H1S", dur: I.hour 1.0 <> I.second 1.0 } ] <#> (\a → a { dur = unsafeMkToIsoDuration a.dur }) --- TODO error messages could be improved invalidDurations ∷ Array { err ∷ String, str ∷ String} invalidDurations = [ { err: errInvalidISO "Hour" <> "1:13", str: "P1DT1.5H0M1S" } @@ -85,10 +84,8 @@ invalidDurations = "(Invalid usage of Fractional value at component `" <> c <> "`)@" errNoTimeComponent = "none of valid duration components ([\"H\",\"M\",\"S\"]) were present@" --- TODO error messages could be improved invalidIntervals ∷ Array {err ∷ String, str ∷ String} invalidIntervals = - -- TODO add some more from https://github.com/arnau/ISO8601/blob/master/spec/iso8601/time_interval_spec.rb [ { err: "Expected \"P\"@1:1", str: "2007-03-01T13:00:00ZP1Y2M10DT2H30M" } , { err: "Expected \"P\"@1:1", str: "2007-03-01T13:00:00Z-P1Y2M10D" } , { err: "Expected \"P\"@1:1", str: "2007-03-01T13:00:00Z~P1Y2M10D" } From ce390b3d67068c10c7f6bfc45d29d11d551e2c41 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Mon, 26 Jun 2017 18:45:53 +0400 Subject: [PATCH 79/88] use released version of ps-datetime --- bower.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bower.json b/bower.json index 0301503..04a3a74 100644 --- a/bower.json +++ b/bower.json @@ -19,7 +19,7 @@ "purescript-prelude": "^3.0.0", "purescript-parsing": "^4.2.1", "purescript-fixed-points": "^4.0.0", - "purescript-datetime": "git://github.com/safareli/purescript-datetime.git#interval", + "purescript-datetime": "^v3.3.0", "purescript-transformers": "^3.4.0", "purescript-lists": "^4.3.0", "purescript-generics-rep": "^5.0.0" From 63d9283e48e1694a6c569ebb2e38bd2e662fb8eb Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Mon, 26 Jun 2017 18:49:22 +0400 Subject: [PATCH 80/88] fix first char case in errors. --- src/Data/Formatter/Parser/Interval.purs | 6 +++--- test/src/Interval.purs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Data/Formatter/Parser/Interval.purs b/src/Data/Formatter/Parser/Interval.purs index 53c19cd..4dd4ec8 100644 --- a/src/Data/Formatter/Parser/Interval.purs +++ b/src/Data/Formatter/Parser/Interval.purs @@ -42,14 +42,14 @@ parseIsoDuration = do dur ← parseDuration case mkIsoDuration dur of Left errs → let errorStr = intercalate ", " (prettyError <$> errs) - in P.fail $ "extracted Duration is not valid ISO duration (" <> errorStr <> ")" + in P.fail $ "Extracted Duration is not valid ISO duration (" <> errorStr <> ")" Right a → pure a parseDuration ∷ P.Parser String I.Duration parseDuration = PS.string "P" *> (weekDuration <|> fullDuration) where weekDuration = mkComponentsParser [ Tuple I.week "W" ] - fullDuration = (append <$> durationDatePart <*> durationTimePart) `notEmpty` "must contain valid duration components" + fullDuration = (append <$> durationDatePart <*> durationTimePart) `notEmpty` "Must contain valid duration components" durationDatePart = PC.option mempty $ PC.try $ mkComponentsParser [ Tuple I.year "Y" , Tuple I.month "M" , Tuple I.day "D" ] durationTimePart = PC.option mempty $ (PC.try $ PS.string "T") *> (mkComponentsParser [ Tuple I.hour "H" , Tuple I.minute "M" , Tuple I.second "S" ]) @@ -58,7 +58,7 @@ notEmpty ∷ ∀ a. Monoid a ⇒ Eq a ⇒ P.Parser String a → String → P.Par notEmpty p str = p >>= \x → if x == mempty then P.fail str else pure x mkComponentsParser ∷ Array (Tuple (Number → I.Duration) String) → P.Parser String I.Duration -mkComponentsParser arr = p `notEmpty` ("none of valid duration components (" <> (show $ snd <$> arr) <> ") were present") +mkComponentsParser arr = p `notEmpty` ("None of valid duration components (" <> (show $ snd <$> arr) <> ") were present") where p = arr <#> applyDurations # sequence <#> foldFoldableMaybe applyDurations ∷ Tuple (Number → I.Duration) String → P.Parser String (Maybe I.Duration) diff --git a/test/src/Interval.purs b/test/src/Interval.purs index f7c1243..281db91 100644 --- a/test/src/Interval.purs +++ b/test/src/Interval.purs @@ -76,13 +76,13 @@ invalidDurations = , { err: errEOF <> "1:4", str: "P1Y1W" } ] where - errInvalidComponent = "must contain valid duration components@" + errInvalidComponent = "Must contain valid duration components@" errPrefix = "Expected \"P\"@" errEOF = "Expected EOF@" errInvalidISO c = - "extracted Duration is not valid ISO duration " <> + "Extracted Duration is not valid ISO duration " <> "(Invalid usage of Fractional value at component `" <> c <> "`)@" - errNoTimeComponent = "none of valid duration components ([\"H\",\"M\",\"S\"]) were present@" + errNoTimeComponent = "None of valid duration components ([\"H\",\"M\",\"S\"]) were present@" invalidIntervals ∷ Array {err ∷ String, str ∷ String} invalidIntervals = From 06db0d1c34ee9a62fcad34d1c316a444e0220128 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Mon, 26 Jun 2017 18:52:11 +0400 Subject: [PATCH 81/88] add todo on negative numbers --- test/src/Number.purs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/src/Number.purs b/test/src/Number.purs index 87f2692..4689bda 100644 --- a/test/src/Number.purs +++ b/test/src/Number.purs @@ -34,8 +34,9 @@ numberTest = describe "Data.Formatter.Number" do forAll show "format (unformat n) = n" - [ "-02.12" - , "-13.12" + --TODO negative nubers fail https://github.com/slamdata/purescript-formatters/issues/16 + [-- "-02.12" -- (Right "0-3.88") ≠ (Right "-02.12") + --, "-13.12" -- (Right "-14.88") ≠ (Right "-13.12") ] (\n → (format fmt3 <$> (unformat fmt3 n)) `shouldEqual` (Right n)) From c0e35f00590c6e9e19e2234a9354709bab7fef76 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Mon, 26 Jun 2017 20:10:54 +0400 Subject: [PATCH 82/88] fix lifts --- 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 98d4766..a9efcd6 100644 --- a/src/Data/Formatter/DateTime.purs +++ b/src/Data/Formatter/DateTime.purs @@ -253,14 +253,14 @@ unformatAccumToDateTime a = exactLength ∷ ∀ e. ReaderT { maxLength ∷ Int, length ∷ Int | e } (Either String) Unit -exactLength = ask >>= \({maxLength, length}) → if maxLength /= length - then lift $ Left $ "Expected " <> (show maxLength) <> " digits but got " <> (show length) - else lift $ Right unit +exactLength = ask >>= \({maxLength, length}) → lift if maxLength /= length + then Left $ "Expected " <> (show maxLength) <> " digits but got " <> (show length) + else Right unit validateRange ∷ ∀ e. Int → Int → ReaderT { num ∷ Int | e } (Either String) Unit -validateRange min max = ask >>= \({num}) → if num < min || num > max - then lift $ Left $ "Number is out of range [ " <> (show min) <> ", " <> (show max) <> " ]" - else lift $ Right unit +validateRange min max = ask >>= \({num}) → lift if num < min || num > max + then Left $ "Number is out of range [ " <> (show min) <> ", " <> (show max) <> " ]" + else Right unit -- NOTE related discussion: https://github.com/purescript-contrib/purescript-parsing/issues/57 -- | Attempt a computation `n` times, requiring at least one success. From fdfa05e0ef06a4a3e141f37eefef9b84cb921453 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Tue, 27 Jun 2017 16:16:26 +0400 Subject: [PATCH 83/88] fix arrows and forall --- src/Data/Formatter/DateTime.purs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Data/Formatter/DateTime.purs b/src/Data/Formatter/DateTime.purs index a9efcd6..67c504f 100644 --- a/src/Data/Formatter/DateTime.purs +++ b/src/Data/Formatter/DateTime.purs @@ -151,7 +151,7 @@ formatYearTwoDigits i = case dateLength of dateString = show $ abs i dateLength = Str.length $ dateString -fix12 ∷ Int -> Int +fix12 ∷ Int → Int fix12 h = if h == 0 then 12 else h formatCommand ∷ DT.DateTime → FormatterCommand → String @@ -267,16 +267,16 @@ validateRange min max = ask >>= \({num}) → lift if num < min || num > max -- | -- | The `Lazy` constraint is used to generate the result lazily, to ensure -- | termination. -takeSome :: forall f a. Alternative f => Z.Lazy (f (List.List a)) => Int -> f a -> f (List.List a) +takeSome :: ∀ f a. Alternative f ⇒ Z.Lazy (f (List.List a)) ⇒ Int → f a → f (List.List a) takeSome 0 _ = pure List.Nil -takeSome n v = List.Cons <$> v <*> Z.defer (\_ -> takeMany (n - 1) v) +takeSome n v = List.Cons <$> v <*> Z.defer (\_ → takeMany (n - 1) v) -- | Attempt a computation `n` times, returning as many successful results -- | as possible (possibly zero). -- | -- | The `Lazy` constraint is used to generate the result lazily, to ensure -- | termination. -takeMany :: forall f a. Alternative f => Z.Lazy (f (List.List a)) => Int -> f a -> f (List.List a) +takeMany :: ∀ f a. Alternative f ⇒ Z.Lazy (f (List.List a)) ⇒ Int → f a → f (List.List a) takeMany 0 _ = pure List.Nil takeMany n v = takeSome n v <|> pure List.Nil From d46e98694d8cc46b0d09f0dec9f999f4669e7620 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Tue, 27 Jun 2017 16:16:48 +0400 Subject: [PATCH 84/88] fix double let --- 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 67c504f..a8d4bcf 100644 --- a/src/Data/Formatter/DateTime.purs +++ b/src/Data/Formatter/DateTime.purs @@ -289,8 +289,9 @@ parseInt ∷ ∀ m → P.ParserT String m Int parseInt maxLength validators errMsg = do ds ← takeSome maxLength parseDigit - let length = List.length ds - let num = foldDigits ds + let + length = List.length ds + num = foldDigits ds case runReaderT validators {length, num, maxLength} of Left err → P.fail $ errMsg <> "(" <> err <> ")" Right _ → pure num From f97e811ca4c0a9c61cfcaff3ca59c17512433ce1 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Tue, 27 Jun 2017 16:18:33 +0400 Subject: [PATCH 85/88] use lift2 and sort imports --- src/Data/Formatter/DateTime.purs | 34 ++++++++++++++++---------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/Data/Formatter/DateTime.purs b/src/Data/Formatter/DateTime.purs index a8d4bcf..b47e319 100644 --- a/src/Data/Formatter/DateTime.purs +++ b/src/Data/Formatter/DateTime.purs @@ -14,38 +14,38 @@ module Data.Formatter.DateTime import Prelude +import Control.Alt ((<|>)) +import Control.Alternative (class Alternative) +import Control.Apply (lift2) +import Control.Lazy as Z +import Control.Monad.Reader.Trans (ReaderT, runReaderT, ask) import Control.Monad.State (State, modify, put, runState) import Control.Monad.Trans.Class (lift) -import Data.Ord (abs) import Data.Array as Array -import Data.List as List -import Data.Tuple (Tuple(..)) -import Data.Foldable (foldMap) -import Control.Lazy as Z -import Control.Alt ((<|>)) -import Control.Alternative (class Alternative) import Data.Date as D import Data.DateTime as DT import Data.DateTime.Instant (instant, toDateTime, fromDateTime, unInstant) import Data.Either (Either(..), either) import Data.Enum (fromEnum, toEnum) +import Data.Foldable (foldMap) +import Data.Formatter.Internal (foldDigits) +import Data.Formatter.Parser.Number (parseDigit) +import Data.Formatter.Parser.Utils (runP, oneOfAs) +import Data.Generic.Rep (class Generic) +import Data.Generic.Rep.Show (genericShow) import Data.Int as Int +import Data.List as List import Data.Maybe (Maybe(..), maybe, fromMaybe) import Data.Newtype (unwrap) +import Data.Ord (abs) import Data.String as Str import Data.Time as T import Data.Time.Duration as Dur -import Data.Formatter.Internal (foldDigits) -import Data.Formatter.Parser.Number (parseDigit) -import Data.Formatter.Parser.Utils (runP, oneOfAs) -import Control.Monad.Reader.Trans (ReaderT, runReaderT, ask) +import Data.Tuple (Tuple(..)) import Text.Parsing.Parser as P import Text.Parsing.Parser.Combinators as PC import Text.Parsing.Parser.String as PS -import Data.Generic.Rep (class Generic) -import Data.Generic.Rep.Show (genericShow) - data FormatterCommand = YearFull | YearTwoDigits @@ -303,9 +303,9 @@ unformatCommandParser = case _ of YearTwoDigits → _{year = _} `modifyWithParser` (parseInt 2 exactLength "Incorrect 2-digit year") YearAbsolute → _{year = _} `modifyWithParser` - (pure (*) - <*> (PC.option 1 $ PC.try $ PS.string "-" <#> (const (-1))) - <*> (List.some parseDigit <#> foldDigits)) + (lift2 (*) + (PC.option 1 $ PC.try $ PS.string "-" <#> (const (-1))) + (List.some parseDigit <#> foldDigits)) MonthFull → _{month = _} `modifyWithParser` (fromEnum <$> parseMonth) MonthShort → _{month = _} `modifyWithParser` From 94f64d42d35c667a08d64528a49b36db12f1ade0 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Tue, 27 Jun 2017 16:19:25 +0400 Subject: [PATCH 86/88] remove outdated anotation --- src/Data/Formatter/DateTime.purs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Data/Formatter/DateTime.purs b/src/Data/Formatter/DateTime.purs index b47e319..39fd492 100644 --- a/src/Data/Formatter/DateTime.purs +++ b/src/Data/Formatter/DateTime.purs @@ -328,7 +328,7 @@ unformatCommandParser = case _ of , minute: Just $ fromEnum $ T.minute t , second: Just $ fromEnum $ T.second t , millisecond: Just $ fromEnum $ T.millisecond t - , meridiem: (Nothing ∷ Maybe Meridiem) + , meridiem: Nothing } -- TODO we would need to use this value if we support date format using week number DayOfWeek → void $ parseInt 1 (validateRange 1 7) "Incorrect day of week" From 0add9bcb4d5c363a6c579a59f235125b16e192da Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Tue, 27 Jun 2017 16:20:03 +0400 Subject: [PATCH 87/88] let on next line --- src/Data/Formatter/Parser/Interval.purs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Data/Formatter/Parser/Interval.purs b/src/Data/Formatter/Parser/Interval.purs index 4dd4ec8..4fb5eda 100644 --- a/src/Data/Formatter/Parser/Interval.purs +++ b/src/Data/Formatter/Parser/Interval.purs @@ -41,7 +41,8 @@ parseIsoDuration ∷ P.Parser String IsoDuration parseIsoDuration = do dur ← parseDuration case mkIsoDuration dur of - Left errs → let errorStr = intercalate ", " (prettyError <$> errs) + Left errs → + let errorStr = intercalate ", " (prettyError <$> errs) in P.fail $ "Extracted Duration is not valid ISO duration (" <> errorStr <> ")" Right a → pure a From 44c39eba4c9e7a75ad1868b8d8af55ef20b0bcc3 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Tue, 27 Jun 2017 16:23:00 +0400 Subject: [PATCH 88/88] rename notEmpty to failIfEmpty --- src/Data/Formatter/Parser/Interval.purs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Data/Formatter/Parser/Interval.purs b/src/Data/Formatter/Parser/Interval.purs index 4fb5eda..3446b04 100644 --- a/src/Data/Formatter/Parser/Interval.purs +++ b/src/Data/Formatter/Parser/Interval.purs @@ -50,16 +50,16 @@ parseDuration ∷ P.Parser String I.Duration parseDuration = PS.string "P" *> (weekDuration <|> fullDuration) where weekDuration = mkComponentsParser [ Tuple I.week "W" ] - fullDuration = (append <$> durationDatePart <*> durationTimePart) `notEmpty` "Must contain valid duration components" + fullDuration = (append <$> durationDatePart <*> durationTimePart) `failIfEmpty` "Must contain valid duration components" durationDatePart = PC.option mempty $ PC.try $ mkComponentsParser [ Tuple I.year "Y" , Tuple I.month "M" , Tuple I.day "D" ] durationTimePart = PC.option mempty $ (PC.try $ PS.string "T") *> (mkComponentsParser [ Tuple I.hour "H" , Tuple I.minute "M" , Tuple I.second "S" ]) -notEmpty ∷ ∀ a. Monoid a ⇒ Eq a ⇒ P.Parser String a → String → P.Parser String a -notEmpty p str = p >>= \x → if x == mempty then P.fail str else pure x +failIfEmpty ∷ ∀ a. Monoid a ⇒ Eq a ⇒ P.Parser String a → String → P.Parser String a +failIfEmpty p str = p >>= \x → if x == mempty then P.fail str else pure x mkComponentsParser ∷ Array (Tuple (Number → I.Duration) String) → P.Parser String I.Duration -mkComponentsParser arr = p `notEmpty` ("None of valid duration components (" <> (show $ snd <$> arr) <> ") were present") +mkComponentsParser arr = p `failIfEmpty` ("None of valid duration components (" <> (show $ snd <$> arr) <> ") were present") where p = arr <#> applyDurations # sequence <#> foldFoldableMaybe applyDurations ∷ Tuple (Number → I.Duration) String → P.Parser String (Maybe I.Duration)