Skip to content

Commit b847301

Browse files
committed
WIP Interval formatters
1 parent 2e2d2d6 commit b847301

File tree

4 files changed

+222
-1
lines changed

4 files changed

+222
-1
lines changed

src/Data/Formatter/Interval.purs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
module Data.Formatter.Interval
2+
-- TODO parser should't be exposed
3+
( parseDuration
4+
) where
5+
6+
import Prelude
7+
import Data.Interval as I
8+
import Text.Parsing.Parser as P
9+
import Text.Parsing.Parser.Combinators as PC
10+
import Text.Parsing.Parser.String as PS
11+
import Control.Alt ((<|>))
12+
import Data.Array (length, some)
13+
import Data.Formatter.Internal (digit, foldDigits)
14+
import Data.Int (toNumber)
15+
import Data.Monoid (mempty)
16+
17+
18+
nums P.Parser String Int
19+
nums = foldDigits <$> some digit
20+
21+
-- TODO try to use unformatNumberParser here
22+
number P.Parser String Number
23+
number = do
24+
whole ← nums
25+
_ ← (PC.try $ PS.string ".") <|> (PC.try $ PS.string ",") <|> pure ""
26+
restdigits ← PC.try (some digit) <|> pure [0]
27+
let rest = foldDigits restdigits
28+
pure $ if rest == 0 then toNumber whole else toNumber whole + ((toNumber rest) / (toNumber $ length restdigits))
29+
30+
31+
32+
component String P.Parser String Number
33+
component designator = number <* PS.string designator
34+
35+
tryOr :: a. a P.Parser String a P.Parser String a
36+
tryOr a p = PC.option a $ PC.try p
37+
38+
parseDuration :: P.Parser String (I.Duration)
39+
-- parseDuration = PS.string "P" *> weekDuration
40+
parseDuration = PS.string "P" *> (weekDuration <|> fullDuration)-- <* PS.eof
41+
where
42+
weekDuration :: P.Parser String I.Duration
43+
weekDuration = PC.try $ I.week <$> component "W"
44+
45+
fullDuration P.Parser String I.Duration
46+
fullDuration = append <$> durationDatePart <*> durationTimePart
47+
48+
durationDatePart P.Parser String I.Duration
49+
durationDatePart = (\y m d → I.year y <> I.month m <> I.day d)
50+
<$> (tryOr 0.0 $ component "Y")
51+
<*> (tryOr 0.0 $ component "M")
52+
<*> (tryOr 0.0 $ component "D")
53+
54+
durationTimePart P.Parser String I.Duration
55+
durationTimePart = tryOr mempty $
56+
PS.string "T" *>
57+
pure (\h m s → I.hours h <> I.minutes m <> I.seconds s)
58+
<*> (tryOr 0.0 $ component "H")
59+
<*> (tryOr 0.0 $ component "M")
60+
<*> (tryOr 0.0 $ component "S")

src/Data/Formatter/Number.purs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ module Data.Formatter.Number
1111
, formatNumber
1212
, formatOrShowNumber
1313
, unformatNumber
14+
-- TODO move to internal or add doc
15+
, unformatNumberParser
1416
) where
1517

1618
import Prelude
@@ -20,7 +22,7 @@ import Data.Array as Arr
2022
import Data.Array (many, some)
2123
import Data.Maybe (Maybe(..), fromMaybe, isJust)
2224
import Data.Traversable (for)
23-
import Data.Either (Either, either)
25+
import Data.Either (Either(..), either)
2426
import Data.Int as Int
2527
import Data.String as Str
2628

@@ -217,6 +219,11 @@ unformatNumber ∷ String → String → Either String Number
217219
unformatNumber pattern str =
218220
parseFormatString pattern >>= flip unformat str
219221

222+
unformatNumberParser String P.Parser String Number
223+
unformatNumberParser pattern = case P.runParser pattern formatParser of
224+
Left e → P.fail $ P.parseErrorMessage e
225+
Right p → unformatParser p
226+
220227
-- Supposed to be used in chaining, because after calling format number there is no
221228
-- good way to extract number back to show.
222229
formatOrShowNumber String Number String

src/Data/Interval.purs

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
-- TODO commiting this temporarly as depending on my fork of datetime is
2+
-- not possibel as this module is not updated to [email protected]
3+
module Data.Interval
4+
( Duration
5+
, Interval(..)
6+
, RecurringInterval(..)
7+
, year
8+
, month
9+
, week
10+
, day
11+
, hours
12+
, minutes
13+
, seconds
14+
, milliseconds
15+
) where
16+
17+
import Prelude
18+
19+
import Data.Foldable (class Foldable, foldrDefault, foldMapDefaultL)
20+
import Data.Traversable (class Traversable, sequenceDefault)
21+
import Data.Monoid (class Monoid, mempty)
22+
import Control.Extend (class Extend)
23+
24+
import Data.Maybe (Maybe)
25+
import Data.List (List(..), (:))
26+
import Data.Tuple (Tuple(..))
27+
28+
29+
data RecurringInterval a = RecurringInterval (Maybe Int) (Interval a)
30+
31+
data Interval a
32+
= StartEnd a a
33+
| DurationEnd Duration a
34+
| StartDuration a Duration
35+
| JustDuration Duration
36+
37+
instance showInterval ∷ (Show a) => Show (Interval a) where
38+
show (StartEnd x y) = "(StartEnd " <> show x <> " " <> show y <> ")"
39+
show (DurationEnd d x) = "(DurationEnd " <> show d <> " " <> show x <> ")"
40+
show (StartDuration x d) = "(StartDuration " <> show x <> " " <> show d <> ")"
41+
show (JustDuration d) = "(JustDuration " <> show d <> ")"
42+
43+
instance functorIntervalFunctor Interval where
44+
map f (StartEnd x y) = StartEnd (f x) (f y )
45+
map f (DurationEnd d x) = DurationEnd d (f x )
46+
map f (StartDuration x d) = StartDuration (f x) d
47+
map _ (JustDuration d) = JustDuration d
48+
49+
instance foldableIntervalFoldable Interval where
50+
foldl f z (StartEnd x y) = (z `f` x) `f` y
51+
foldl f z (DurationEnd d x) = z `f` x
52+
foldl f z (StartDuration x d) = z `f` x
53+
foldl _ z _ = z
54+
foldr x = foldrDefault x
55+
foldMap = foldMapDefaultL
56+
57+
instance traversableIntervalTraversable Interval where
58+
traverse f (StartEnd x y) = StartEnd <$> f x <*> f y
59+
traverse f (DurationEnd d x) = f x <#> DurationEnd d
60+
traverse f (StartDuration x d) = f x <#> (_ `StartDuration` d)
61+
traverse _ (JustDuration d) = pure (JustDuration d)
62+
sequence = sequenceDefault
63+
64+
instance extendIntervalExtend Interval where
65+
extend f a@(StartEnd x y) = StartEnd (f a) (f a )
66+
extend f a@(DurationEnd d x) = DurationEnd d (f a )
67+
extend f a@(StartDuration x d) = StartDuration (f a) d
68+
extend f (JustDuration d) = JustDuration d
69+
70+
71+
data Duration = Duration DurationIn
72+
type DurationIn = List (Tuple DurationComponent Number)
73+
74+
-- TODO `day 1 == hours 24`
75+
derive instance eqDurationEq Duration
76+
instance showDurationShow Duration where
77+
show (Duration d)= "(Duration " <> show d <> ")"
78+
79+
instance semigroupDurationSemigroup Duration where
80+
append (Duration a) (Duration b) = Duration (appendComponents a b)
81+
82+
instance monoidDurationMonoid Duration where
83+
mempty = Duration mempty
84+
85+
appendComponents DurationIn DurationIn DurationIn
86+
appendComponents Nil x = x
87+
appendComponents x Nil = x
88+
appendComponents ass@(a:as) bss@(b:bs) = case a, b of
89+
Tuple aC aV, Tuple bC bV
90+
| aC > bC → a : appendComponents as bss
91+
| aC < bC → b : appendComponents ass bs
92+
| otherwise → Tuple aC (aV + bV) : appendComponents as bs
93+
94+
data DurationComponent = Seconds | Minutes | Hours | Day | Month | Year
95+
96+
instance showDurationComponentShow DurationComponent where
97+
show Year = "Year"
98+
show Month = "Month"
99+
show Day = "Day"
100+
show Hours = "Hours"
101+
show Minutes = "Minutes"
102+
show Seconds = "Seconds"
103+
104+
derive instance eqDurationComponentEq DurationComponent
105+
derive instance ordDurationComponentOrd DurationComponent
106+
107+
108+
week Number Duration
109+
week = durationFromComponent Day <<< (_ * 7.0)
110+
111+
year Number Duration
112+
year = durationFromComponent Year
113+
114+
month Number Duration
115+
month = durationFromComponent Month
116+
117+
day Number Duration
118+
day = durationFromComponent Day
119+
120+
hours Number Duration
121+
hours = durationFromComponent Hours
122+
123+
minutes Number Duration
124+
minutes = durationFromComponent Minutes
125+
126+
seconds Number Duration
127+
seconds = durationFromComponent Seconds
128+
129+
milliseconds Number Duration
130+
milliseconds = durationFromComponent Seconds <<< (_ / 1000.0)
131+
132+
durationFromComponent DurationComponent Number Duration
133+
durationFromComponent c 0.0 = mempty
134+
durationFromComponent c n = Duration $ pure $ Tuple c n

test/src/Main.purs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ import Prelude
44
import Control.Monad.Aff.Console as AffC
55
import Data.Date as D
66
import Data.DateTime as DTi
7+
8+
-- TODO parser should't be exposed so this should be removed
9+
import Text.Parsing.Parser as P
10+
import Data.Interval as I
11+
import Data.Formatter.Interval as FI
12+
713
import Data.Formatter.DateTime as FDT
814
import Data.Formatter.Number as FN
915
import Data.Time as T
@@ -155,7 +161,20 @@ assertFormatting target' format dateTime = do
155161
((show result) <> " equals " <> (show target))
156162
(result == target)
157163

164+
assertParserRes :: forall a e. (Show a, Eq a) => a -> a -> Tests e Unit
165+
assertParserRes result target =
166+
assert
167+
((show result) <> " does not equal " <> (show target))
168+
((show result) <> " equals " <> (show target))
169+
(result == target)
158170

171+
timeInterval :: forall e. Tests e Unit
172+
timeInterval = do
173+
log "- Data.Formatter.Interval.parseDuration"
174+
assertParserRes (P.runParser "P1W" FI.parseDuration) (Right $ I.day 7.0)
175+
assertParserRes (P.runParser "P1.0W" FI.parseDuration) (Right $ I.day 7.0)
176+
assertParserRes (P.runParser "P1.0D" FI.parseDuration) (Right $ I.day 1.0)
177+
assertParserRes (P.runParser "P1DT1H1M1S" FI.parseDuration) (Right $ I.day 1.0 <> I.hours 1.0 <> I.minutes 1.0 <> I.seconds 1.0)
159178
timeTest :: forall e. Tests e Unit
160179
timeTest = do
161180
log "- Data.Formatter.DateTime.formatDateTime"
@@ -217,6 +236,7 @@ main = execTests tests true
217236
tests = do
218237
log "Testing time functions..."
219238
timeTest
239+
timeInterval
220240
passed <- get
221241
when (passed /= true) (throwError (error "Tests did not pass."))
222242
--numeralTests

0 commit comments

Comments
 (0)