Merge branch 'term2days' into 'master'

Term2days

See merge request FraDrive/fradrive!5
This commit is contained in:
Steffen Jost 2021-10-28 13:13:39 +02:00
commit 21f217d0a2
16 changed files with 337 additions and 950 deletions

View File

@ -1,11 +1,3 @@
Quarter1st year@Integer: Erstes Quartal #{year}
Quarter2nd year@Integer: Zweites Quartal #{year}
Quarter3rd year@Integer: Drittes Quartal #{year}
Quarter4th year@Integer: Viertes Quartal #{year}
Quarter1stShort year@Integer: #{year}/Q1
Quarter2ndShort year@Integer: #{year}/Q2
Quarter3rdShort year@Integer: #{year}/Q3
Quarter4thShort year@Integer: #{year}/Q4
CorByProportionOnly proportion@Rational: #{rationalToFixed3 proportion} Anteile CorByProportionOnly proportion@Rational: #{rationalToFixed3 proportion} Anteile
CorByProportionIncludingTutorial proportion@Rational: #{rationalToFixed3 proportion} Anteile - Tutorium CorByProportionIncludingTutorial proportion@Rational: #{rationalToFixed3 proportion} Anteile - Tutorium
CorByProportionExcludingTutorial proportion@Rational: #{rationalToFixed3 proportion} Anteile + Tutorium CorByProportionExcludingTutorial proportion@Rational: #{rationalToFixed3 proportion} Anteile + Tutorium

View File

@ -1,11 +1,3 @@
Quarter1st year@Integer: First Quarter of #{year}
Quarter2nd year@Integer: Second Quarter of #{year}
Quarter3rd year@Integer: Third Quarter of #{year}
Quarter4th year@Integer: Last Quarter of #{year}
Quarter1stShort year@Integer: #{year}/Q1st
Quarter2ndShort year@Integer: #{year}/Q2nd
Quarter3rdShort year@Integer: #{year}/Q3rd
Quarter4thShort year@Integer: #{year}/Q4th
CorByProportionOnly proportion: #{rationalToFixed3 proportion} parts CorByProportionOnly proportion: #{rationalToFixed3 proportion} parts
CorByProportionIncludingTutorial proportion: #{rationalToFixed3 proportion} parts - tutorials CorByProportionIncludingTutorial proportion: #{rationalToFixed3 proportion} parts - tutorials
CorByProportionExcludingTutorial proportion: #{rationalToFixed3 proportion} parts + tutorials CorByProportionExcludingTutorial proportion: #{rationalToFixed3 proportion} parts + tutorials

View File

@ -21,7 +21,11 @@ let
contents = with final; [ contents = with final; [
uniworx.uniworx.components.exes.uniworx uniworx.uniworx.components.exes.uniworx
prev.dockerTools.binSh findutils coreutils prev.dockerTools.binSh findutils coreutils
curl wget cups # needed for interface with print center
texlive.combined.scheme-medium # probably needed by pandoc library to produce PDFs?
# For manual testing, maybe remove for production?
curl wget pandoc
] ++ optionals isDemo [ postgresql_12 memcached uniworx.uniworx.components.exes.uniworxdb ]; ] ++ optionals isDemo [ postgresql_12 memcached uniworx.uniworx.components.exes.uniworxdb ];
runAsRoot = '' runAsRoot = ''

1
routes
View File

@ -69,6 +69,7 @@
/info/glossary GlossaryR GET !free /info/glossary GlossaryR GET !free
/info/faq FaqR GET !free /info/faq FaqR GET !free
/version VersionR GET !free /version VersionR GET !free
/status StatusR GET !free
/help HelpR GET POST !free /help HelpR GET POST !free

View File

@ -1,6 +1,6 @@
{-# OPTIONS_GHC -fno-warn-orphans #-} {-# OPTIONS_GHC -fno-warn-orphans #-}
module Application module Application
( getAppSettings, getAppDevSettings ( getAppSettings, getAppDevSettings
, appMain , appMain
, develMain , develMain

View File

@ -195,20 +195,10 @@ mkMessageVariant ''UniWorX ''ButtonMessage "messages/button" "de"
mkMessageVariant ''UniWorX ''FrontendMessage "messages/frontend" "de-de-formal" mkMessageVariant ''UniWorX ''FrontendMessage "messages/frontend" "de-de-formal"
instance RenderMessage UniWorX TermIdentifier where instance RenderMessage UniWorX TermIdentifier where
renderMessage foundation ls TermIdentifier{..} = case season of renderMessage _foundation _ls = termToText -- TODO: respect user selected Datetime Format
Q1 -> renderMessage' $ MsgQuarter1st year
Q2 -> renderMessage' $ MsgQuarter2nd year
Q3 -> renderMessage' $ MsgQuarter3rd year
Q4 -> renderMessage' $ MsgQuarter4th year
where renderMessage' = renderMessage foundation ls
instance RenderMessage UniWorX ShortTermIdentifier where instance RenderMessage UniWorX ShortTermIdentifier where
renderMessage foundation ls (ShortTermIdentifier TermIdentifier{..}) = case season of renderMessage _foundation _ls (ShortTermIdentifier tid) = termToText tid -- TODO: implement shorttermidentifier properly
Q1 -> renderMessage' $ MsgQuarter1stShort year
Q2 -> renderMessage' $ MsgQuarter2ndShort year
Q3 -> renderMessage' $ MsgQuarter3rdShort year
Q4 -> renderMessage' $ MsgQuarter4thShort year
where renderMessage' = renderMessage foundation ls
instance RenderMessage UniWorX String where instance RenderMessage UniWorX String where
renderMessage f ls str = renderMessage f ls $ Text.pack str renderMessage f ls str = renderMessage f ls $ Text.pack str

View File

@ -132,6 +132,7 @@ breadcrumb HelpR = i18nCrumb MsgMenuHelp Nothing
breadcrumb HealthR = i18nCrumb MsgMenuHealth Nothing breadcrumb HealthR = i18nCrumb MsgMenuHealth Nothing
breadcrumb InstanceR = i18nCrumb MsgMenuInstance Nothing breadcrumb InstanceR = i18nCrumb MsgMenuInstance Nothing
breadcrumb StatusR = i18nCrumb MsgMenuHealth Nothing -- never displayed
breadcrumb ProfileR = i18nCrumb MsgBreadcrumbProfile Nothing breadcrumb ProfileR = i18nCrumb MsgBreadcrumbProfile Nothing
breadcrumb SetDisplayEmailR = i18nCrumb MsgUserDisplayEmail $ Just ProfileR breadcrumb SetDisplayEmailR = i18nCrumb MsgUserDisplayEmail $ Just ProfileR

View File

@ -159,8 +159,7 @@ suggestAllocationForm (Set.fromList . map optionInternalValue . olOptions -> ter
newTerm <- MaybeT $ get tid newTerm <- MaybeT $ get tid
Entity _ Allocation{..} <- MaybeT . getBy $ TermSchoolAllocationShort oldTid ssh ash Entity _ Allocation{..} <- MaybeT . getBy $ TermSchoolAllocationShort oldTid ssh ash
let dayOffset = (diffDays `on` termLectureStart) newTerm oldTerm let addTime = addLocalDays $ (diffDays `on` termLectureStart) newTerm oldTerm
addTime = addLocalDays dayOffset
return AllocationForm return AllocationForm
{ afTerm = tid { afTerm = tid

View File

@ -435,7 +435,7 @@ getCourseNewR = do
let newTemplate = courseToForm oldTemplate mempty mempty Nothing in let newTemplate = courseToForm oldTemplate mempty mempty Nothing in
return $ Just $ newTemplate return $ Just $ newTemplate
{ cfCourseId = Nothing { cfCourseId = Nothing
, cfTerm = TermKey $ TermIdentifier 0 Q1 -- invalid, will be ignored; undefined won't work due to strictness , cfTerm = TermKey $ termFromRational 0 -- invalid, will be ignored; undefined won't work due to strictness
, cfRegFrom = Nothing , cfRegFrom = Nothing
, cfRegTo = Nothing , cfRegTo = Nothing
, cfDeRegUntil = Nothing , cfDeRegUntil = Nothing

View File

@ -11,6 +11,9 @@ import qualified Data.Set as Set
import Control.Concurrent.STM.Delay import Control.Concurrent.STM.Delay
import Language.Haskell.TH (stringE,runIO)
-- import Data.FileEmbed (embedStringFile)
getHealthR :: Handler TypedContent getHealthR :: Handler TypedContent
getHealthR = do getHealthR = do
@ -94,3 +97,31 @@ getInstanceR = do
|] |]
provideJson instanceInfo provideJson instanceInfo
provideRep . return $ tshow instanceInfo provideRep . return $ tshow instanceInfo
-- Most simple page for simple liveness checks
getStatusR :: Handler Html
getStatusR = withUrlRenderer
[hamlet|
$doctype 5
<html lang=en>
<head>
<title>Alive
<body>
<p>
Compile Time {#{comptime}}
|]
{-
<p>
Alive #{vnr_full}
<p>
Demo #{vnr_demo}
<p>
CI #{vnr_ci}
-}
where
-- vnr_full :: Text = $(embedStringFile "./nix/docker/version.json")
-- vnr_demo :: Text = $(embedStringFile "./nix/docker/demo-version.json")
-- vnr_ci :: Text = $(embedStringFile "./nix/docker/ci-version.json")
comptime :: Text = $(stringE =<< runIO (show <$> getCurrentTime))

View File

@ -25,8 +25,7 @@ import qualified Control.Monad.State.Class as State
validateTerm :: (MonadHandler m, HandlerSite m ~ UniWorX) validateTerm :: (MonadHandler m, HandlerSite m ~ UniWorX)
=> FormValidator TermForm m () => FormValidator TermForm m ()
validateTerm = do validateTerm = do
TermForm{..} <- State.get TermForm{..} <- State.get
guardValidation MsgTermStartMustMatchName $ tfStart `withinTermYear` tfName
guardValidation MsgTermEndMustBeAfterStart $ tfStart < tfEnd guardValidation MsgTermEndMustBeAfterStart $ tfStart < tfEnd
guardValidation MsgTermLectureEndMustBeAfterStart $ tfLectureStart < tfLectureEnd guardValidation MsgTermLectureEndMustBeAfterStart $ tfLectureStart < tfLectureEnd
guardValidation MsgTermStartMustBeBeforeLectureStart $ tfStart <= tfLectureStart guardValidation MsgTermStartMustBeBeforeLectureStart $ tfStart <= tfLectureStart

View File

@ -16,6 +16,7 @@ module Handler.Utils.DateTime
, addOneWeek, addWeeks , addOneWeek, addWeeks
, weeksToAdd , weeksToAdd
, setYear, getYear , setYear, getYear
, firstDayOfWeekOnAfter
, ceilingQuarterHour , ceilingQuarterHour
, formatGregorianW , formatGregorianW
) where ) where
@ -224,6 +225,14 @@ getYear date = y
where where
(y,_,_) = toGregorian date (y,_,_) = toGregorian date
dayOfWeekDiff :: DayOfWeek -> DayOfWeek -> Int
dayOfWeekDiff a b = mod (fromEnum a - fromEnum b) 7
-- | The first day-of-week on or after some day
-- | from time-compat-1.9.5, not included
firstDayOfWeekOnAfter :: DayOfWeek -> Day -> Day
firstDayOfWeekOnAfter dw d = addDays (toInteger $ dayOfWeekDiff dw $ dayOfWeek d) d
addOneWeek :: UTCTime -> UTCTime addOneWeek :: UTCTime -> UTCTime
addOneWeek = addWeeks 1 addOneWeek = addWeeks 1

View File

@ -12,11 +12,10 @@ module Model.Types.DateTime
import Import.NoModel import Import.NoModel
import qualified Data.Text as Text import qualified Data.Text as Text
import qualified Data.CaseInsensitive as CI
import Data.Either.Combinators (maybeToRight) import Data.Either.Combinators (maybeToRight)
import Text.Read (readMaybe)
import Data.Time.Calendar.WeekDate import Data.Time.Calendar.WeekDate
import Data.Time.Format.ISO8601
import Database.Persist.Sql import Database.Persist.Sql
@ -26,45 +25,13 @@ import Data.Aeson.Types as Aeson
---- ----
-- Terms, Seaons, anything loosely related to time -- Terms and anything loosely related to time
data Season = Q1 | Q2 | Q3 | Q4 newtype TermIdentifier = TermIdentifier { getTermDay :: Day }
deriving (Show, Read, Eq, Ord, Enum, Bounded, Generic, Typeable) deriving (Show, Read, Eq, Ord, Generic, Typeable, Enum)
deriving anyclass (Binary, Universe, Finite, NFData) deriving newtype (Binary, ISO8601, PersistField, PersistFieldSql)
deriving anyclass (NFData)
numSeasons :: Int -- to be flexible -- ought to be equivalent to deriving stock (Show, Read, Eq, Ord, Generic, Typeable, Enum, Binary, NFData)
numSeasons = succ $ fromEnum(maxBound::Season)
seasonFromText' :: Text -> Either Text Season
seasonFromText' t = maybeToRight errmsg (readMaybe $ Text.unpack $ Text.toUpper t)
where
errmsg = "Invalid season: " <> tshow t <> ""
seasonFromText :: Text -> Either Text Season
seasonFromText t
| Just (q, ne) <- Text.uncons t
, q ~= 'Q'
, Just (n, e) <- Text.uncons ne
, Text.null e = case n of '1' -> Right Q1
'2' -> Right Q2
'3' -> Right Q3
'4' -> Right Q4
_ -> Left $ "Invalid quarter number: " <> tshow t <> ""
| otherwise = Left $ "Invalid season: " <> tshow t <> ""
where
(~=) :: Char -> Char -> Bool
(~=) = (==) `on` CI.mk
data TermIdentifier = TermIdentifier
{ year :: Integer -- ^ Using 'Integer' to model years is consistent with 'Data.Time.Calendar'
, season :: Season
} deriving (Show, Read, Eq, Ord, Generic, Typeable)
deriving anyclass (Binary, NFData)
instance Enum TermIdentifier where
-- ^ Do not use for conversion Enumeration only
toEnum int = let (toInteger -> year, toEnum -> season) = int `divMod` numSeasons in TermIdentifier{..}
fromEnum TermIdentifier{..} = fromInteger year * numSeasons + fromEnum season
-- Conversion TermId <-> TermIdentifier:: -- Conversion TermId <-> TermIdentifier::
-- from_TermId_to_TermIdentifier = unTermKey -- from_TermId_to_TermIdentifier = unTermKey
@ -94,40 +61,56 @@ shortened = iso shorten expand
, year < $currentYear + 50 = year `mod` 100 , year < $currentYear + 50 = year `mod` 100
| otherwise = year | otherwise = year
-- Option 1: date in iso8601
termToText :: TermIdentifier -> Text termToText :: TermIdentifier -> Text
termToText TermIdentifier{..} = Text.pack $ show (year ^. shortened) ++ show season termToText = Text.pack . iso8601Show
-- also see Hander.Utils.tidFromText -- also see Hander.Utils.tidFromText
termFromText :: Text -> Either Text TermIdentifier termFromText :: Text -> Either Text TermIdentifier
termFromText t termFromText t = maybeToRight errm $ iso8601ParseM $ Text.unpack t
| (ys,s) <- Text.break (~= 'Q') t where
, Right season <- seasonFromText s errm = "Invalid TermIdentifier: “" <> t <> ""
, Just (review shortened -> year) <- readMaybe $ Text.unpack ys
= Right TermIdentifier{..}
| otherwise = Left $ "Invalid TermIdentifier: “" <> t <> "" -- TODO: Could be improved, I.e. say "W"/"S" from Number
where
(~=) :: Char -> Char -> Bool
(~=) = (==) `on` CI.mk
termToRational :: TermIdentifier -> Rational -- Option 2: show as WeekNr-DayOfWeek-Year, e.g. 22Mon2021?
termToRational TermIdentifier{..} = toRational year + seasonOffset termToText' :: TermIdentifier -> Text
where termToText' TermIdentifier{..} = Text.pack $ show weeknr ++ wd ++ show year
seasonOffset = fromIntegral (fromEnum season) % fromIntegral numSeasons where
wd = take 3 $ show $ dayOfWeek getTermDay
(year,weeknr,_wd_) = toWeekDate getTermDay
{- TODO
termFromText' :: Text -> Either Text TermIdentifier
termFromText' t = error "not implemented"
where
errm = "Invalid TermIdentifier: “" <> t <> ""
-}
daysPerYear :: Rational
daysPerYear = 365 + (97 % 400)
dayOffset :: Rational
dayOffset = fromIntegral yearzero + (fromIntegral diffstart / daysPerYear)
where
dayzero = toEnum 0
yearzero = fst3 $ toGregorian dayzero
diffstart = diffDays dayzero $ fromGregorian yearzero 1 1
-- Attempt to ensure that ``truncate . termToRational == fst3 . toGregorian . getTermDay´´ holds
termToRational :: TermIdentifier -> Rational
termToRational = (dayOffset +) . (/ daysPerYear) . fromIntegral . fromEnum
termFromRational :: Rational -> TermIdentifier termFromRational :: Rational -> TermIdentifier
termFromRational n = TermIdentifier{..} termFromRational = toEnum . round . (daysPerYear *) . subtract dayOffset
where
year = floor n
remainder = n - fromInteger (floor n) -- properFraction problematic for negative year values
season = toEnum $ floor $ remainder * fromIntegral numSeasons
{- -- For newtype Day, PersistField instance can be derived automatically
instance PersistField TermIdentifier where instance PersistField TermIdentifier where
toPersistValue = PersistRational . termToRational toPersistValue = PersistRational . termToRational
fromPersistValue (PersistRational t) = Right $ termFromRational t fromPersistValue (PersistRational t) = Right $ termFromRational t
fromPersistValue x = Left $ "Expected TermIdentifier, received: " <> tshow x fromPersistValue x = Left $ "Expected TermIdentifier, received: " <> tshow x
instance PersistFieldSql TermIdentifier where instance PersistFieldSql TermIdentifier where
sqlType _ = SqlNumeric 5 1 sqlType _ = SqlNumeric 9 5 -- total significant digits; significant digits after decimal point
-}
instance ToHttpApiData TermIdentifier where instance ToHttpApiData TermIdentifier where
toUrlPiece = termToText toUrlPiece = termToText
@ -162,27 +145,16 @@ data TermDay
guessDay :: TermIdentifier guessDay :: TermIdentifier
-> TermDay -> TermDay
-> Day -> Day
guessDay TermIdentifier{ year, season = Q1 } TermDayStart = fromGregorian year 1 1 guessDay TermIdentifier{..} TermDayLectureStart = getTermDay
guessDay TermIdentifier{ year, season = Q2 } TermDayStart = fromGregorian year 4 1 guessDay TermIdentifier{..} TermDayLectureEnd = addDays 8 getTermDay -- courses last only a week
guessDay TermIdentifier{ year, season = Q3 } TermDayStart = fromGregorian year 7 1 guessDay tid TermDayStart = fromWeekDate year week 1 -- Monday before lecture time
guessDay TermIdentifier{ year, season = Q4 } TermDayStart = fromGregorian year 10 1 where ( year, week, _) = toWeekDate $ addDays (-7*4*3) $ guessDay tid TermDayLectureStart
guessDay tid TermDayEnd = pred $ guessDay (succ tid) TermDayStart guessDay tid TermDayEnd = fromWeekDate year week 7 -- Sunday after lecture time
guessDay tid TermDayLectureStart = fromWeekDate year weekStart 1 -- first Monday within Quarter where ( year, week, _) = toWeekDate $ addDays (7*3) $ guessDay tid TermDayLectureEnd
where ( year, weekStart, _) = toWeekDate $ guessDay tid TermDayStart
guessDay tid TermDayLectureEnd = fromWeekDate year weekStart 5 -- Friday of last week within Quarter
where ( year, weekStart, _) = toWeekDate $ guessDay tid TermDayEnd
withinTerm :: Day -> TermIdentifier -> Bool withinTerm :: Day -> TermIdentifier -> Bool
withinTerm d tid = guessDay tid TermDayStart <= d && d <= guessDay tid TermDayEnd withinTerm d tid = guessDay tid TermDayStart <= d && d <= guessDay tid TermDayEnd
-- | Check only if last two digits within the year numbers match
withinTermYear :: Day -> TermIdentifier -> Bool
time `withinTermYear` term = timeYear `mod` 100 == termYear `mod` 100
where
timeYear = fst3 $ toGregorian time
termYear = year term
data OccurrenceSchedule = ScheduleWeekly data OccurrenceSchedule = ScheduleWeekly
{ scheduleDayOfWeek :: WeekDay { scheduleDayOfWeek :: WeekDay
, scheduleStart :: TimeOfDay , scheduleStart :: TimeOfDay

View File

@ -10,6 +10,8 @@ module Utils.Holidays
, feiertage , feiertage
, bankHolidays, bankHolidaysArea, bankHolidaysAreaSet , bankHolidays, bankHolidaysArea, bankHolidaysAreaSet
, isBankHoliday, isBankHolidayArea , isBankHoliday, isBankHolidayArea
, isWeekend
, addBusinessDays
) where ) where
import Import.NoModel import Import.NoModel
@ -107,13 +109,16 @@ isBankHolidayArea land dd = dd `Set.member` holidays
-- | Returns whether a day is a bank holiday for years >= 1995 -- | Returns whether a day is a bank holiday for years >= 1995
-- | Repeated calls are handled efficiently using lazy memoization -- | Repeated calls are handled efficiently using lazy memoization
isBankHolidayArea :: Feiertagsgebiet -> Day -> Bool isBankHolidayArea :: Feiertagsgebiet -> Day -> Bool
isBankHolidayArea land dd = dd `Set.member` holidays isBankHolidayArea land = ibha
where where
(year, _, _) = toGregorian dd landHoliday = Map.lookup land memoHolidays
holidays ibha dd = dd `Set.member` holidays
| (Just hys) <- Map.lookup land memoHolidays where
, (Just hds) <- index hys $ fromInteger $ year2index year = hds (year, _, _) = toGregorian dd
| otherwise = bankHolidaysAreaSet land year holidays
| (Just hys) <- landHoliday
, (Just hds) <- index hys $ fromInteger $ year2index year = hds
| otherwise = bankHolidaysAreaSet land year
-- memoize holidays -- memoize holidays
memoHolidays :: Map.Map Feiertagsgebiet [Set.Set Day] memoHolidays :: Map.Map Feiertagsgebiet [Set.Set Day]
@ -137,3 +142,27 @@ index2year y = result
(x,r) = y `divMod` 2 (x,r) = y `divMod` 2
result | r == 0 = memoTip + x result | r == 0 = memoTip + x
| otherwise = memoTip - x - 1 | otherwise = memoTip - x - 1
-- | Test for Saturday/Sunday
isWeekend :: Day -> Bool
isWeekend = isWeekend' . dayOfWeek
where
isWeekend' :: WeekDay -> Bool
isWeekend' Sunday = True
isWeekend' Saturday = True
isWeekend' _ = False
-- | Always returns a business day.
-- | Saturday/Sunday/Holiday treated like next (n>=0) or previous (n<0) working day
addBusinessDays :: Feiertagsgebiet -> Integer -> Day -> Day
addBusinessDays land = abd
where
ibhal = isBankHolidayArea land
freeday dd = isWeekend dd || ibhal dd
abd n = abd' n
where
(fwd, bwd) | n >= 0 = (succ, pred)
| otherwise = (pred, succ)
abd' m dd | freeday dd = abd' m (fwd dd)
| m == 0 = dd
| otherwise = abd' (bwd m) (fwd dd)

File diff suppressed because it is too large Load Diff

View File

@ -46,20 +46,26 @@ import qualified Data.Text.Lazy as LT
import Text.Blaze.Html.Renderer.Text (renderHtml) import Text.Blaze.Html.Renderer.Text (renderHtml)
import Handler.Utils.DateTime (getYear)
{-
instance Arbitrary Day where
arbitrary = ModifiedJulianDay <$> choose (-313698, 2973483) -- 1000-01-1 to 9999-12-31
-- arbitrary = ModifiedJulianDay <$> choose (15020, 2973483) -- 1900-01-1 to 9999-12-31
shrink day = let
(y, m, d) = toGregorian day
dayShrink = [fromGregorian y m (d - 1) | d > 1]
monthShrink = [fromGregorian y (m - 1) d | m > 1]
yearShrink = [fromGregorian (y - 1) m d | y > 2000]
in dayShrink ++ monthShrink ++ yearShrink
instance Arbitrary Season where instance CoArbitrary Day where
arbitrary = genericArbitrary coarbitrary (ModifiedJulianDay d) = coarbitrary d
shrink = genericShrink -}
instance CoArbitrary Season
instance Function Season
instance Arbitrary TermIdentifier where instance Arbitrary TermIdentifier where
arbitrary = do arbitrary = TermIdentifier <$> arbitrary
season <- arbitrary shrink = fmap TermIdentifier . shrink . getTermDay
year <- arbitrary `suchThat` (\y -> abs y >= 100)
return $ TermIdentifier{..}
shrink = filter ((\y -> abs y >= 100) . year) . genericShrink
instance CoArbitrary TermIdentifier instance CoArbitrary TermIdentifier
instance Function TermIdentifier instance Function TermIdentifier
@ -383,8 +389,6 @@ spec = do
[ eqLaws, showReadLaws, ordLaws, boundedEnumLaws, finiteLaws, pathPieceLaws ] [ eqLaws, showReadLaws, ordLaws, boundedEnumLaws, finiteLaws, pathPieceLaws ]
lawsCheckHspec (Proxy @Load) lawsCheckHspec (Proxy @Load)
[ eqLaws, showReadLaws, ordLaws, jsonLaws, persistFieldLaws, commutativeSemigroupLaws, commutativeMonoidLaws ] [ eqLaws, showReadLaws, ordLaws, jsonLaws, persistFieldLaws, commutativeSemigroupLaws, commutativeMonoidLaws ]
lawsCheckHspec (Proxy @Season)
[ eqLaws, showReadLaws, ordLaws, boundedEnumLaws ]
lawsCheckHspec (Proxy @TermIdentifier) lawsCheckHspec (Proxy @TermIdentifier)
[ eqLaws, showReadLaws, ordLaws, enumLaws, persistFieldLaws, jsonLaws, httpApiDataLaws, pathPieceLaws ] [ eqLaws, showReadLaws, ordLaws, enumLaws, persistFieldLaws, jsonLaws, httpApiDataLaws, pathPieceLaws ]
lawsCheckHspec (Proxy @StudyFieldType) lawsCheckHspec (Proxy @StudyFieldType)
@ -460,14 +464,14 @@ spec = do
describe "TermIdentifier" $ do describe "TermIdentifier" $ do
it "has compatible encoding/decoding to/from Text" . property $ it "has compatible encoding/decoding to/from Text" . property $
\term -> termFromText (termToText term) == Right term \term -> termFromText (termToText term) == Right term
it "works for some examples" . mapM_ termExample $ it "has compatible encoding/decoding to/from Rational" . property $
[ (TermIdentifier 2017 Q2, "17Q2")
, (TermIdentifier 1995 Q4, "95Q4")
, (TermIdentifier 3068 Q1, "3068Q1")
]
it "has compatbile encoding/decoding to/from Rational" . property $
\term -> termFromRational (termToRational term) == term \term -> termFromRational (termToRational term) == term
-- This is not sufficient
--it "has compatible encoding/decoding to/from PersistValue" . property $
-- \term -> fromPersistValue (toPersistValue term) == term
it "has human readable year encoding to Rational" . property $
\term -> truncate (termToRational term) == getYear (getTermDay term)
describe "Pseudonym" $ do describe "Pseudonym" $ do
it "has sufficient vocabulary" $ it "has sufficient vocabulary" $
(length pseudonymWordlist ^ 2) `shouldSatisfy` (> (fromIntegral (maxBound :: Pseudonym) - fromIntegral (minBound :: Pseudonym))) (length pseudonymWordlist ^ 2) `shouldSatisfy` (> (fromIntegral (maxBound :: Pseudonym) - fromIntegral (minBound :: Pseudonym)))