gallery/make-pages/Date.hs

143 lines
3.9 KiB
Haskell
Raw Normal View History

2020-09-25 17:08:44 -04:00
module Date
(Date (..),
Day (..), dayNum, exact,
formatLong, formatShort, formatRSS, formatSlash,
parseP, parseS, parseA)
where
2021-03-20 07:46:32 -04:00
import Records ()
2020-09-25 17:08:44 -04:00
import Control.Applicative
import qualified Text.ParserCombinators.ReadP as ReadP
import Text.ParserCombinators.ReadP (ReadP, readS_to_P, readP_to_S, (<++))
import Data.Time hiding (Day)
import Data.Char (isSpace)
import Data.Function (on)
import Data.Maybe (fromMaybe)
import Data.YAML (FromYAML (..))
import qualified Data.YAML as YAML
2021-03-20 07:46:32 -04:00
import Text.Ginger (GVal, ToGVal (..), (~>))
import qualified Text.Ginger as Ginger
import qualified Text.Ginger.Html as Ginger
2021-03-20 11:19:56 -04:00
import Data.Text (Text, pack, unpack)
import Data.Text.Lazy (toStrict)
import Data.Text.Lazy.Builder (Builder)
import qualified Data.Text.Lazy.Builder as Builder
2020-09-25 17:08:44 -04:00
data Date = Date {year, month :: Int, day :: Day}
deriving (Eq, Ord, Show)
data Day =
Exact Int
| Approx Int
| Unknown
deriving (Eq, Show)
dayNum :: Day -> Maybe Int
dayNum (Exact x) = Just x
dayNum (Approx x) = Just x
dayNum Unknown = Nothing
instance Ord Day where compare = compare `on` dayNum
exact :: Day -> Bool
exact (Exact _) = True
exact _ = False
2021-03-20 11:19:56 -04:00
formatLong :: Date -> Text
2020-09-25 17:08:44 -04:00
formatLong (Date {year, month, day}) =
2021-03-20 11:19:56 -04:00
toText case dayN of
2020-09-25 17:08:44 -04:00
Nothing -> monthYear
2021-03-20 11:19:56 -04:00
Just (nth -> d) -> mconcat [approx, weekday, " ", d, " ", monthYear]
2020-09-25 17:08:44 -04:00
where
dayN = dayNum day
day' = fromMaybe 1 dayN
2021-03-20 11:19:56 -04:00
formatted str = Builder.fromString $
2020-09-25 17:08:44 -04:00
formatTime defaultTimeLocale str $
fromGregorian (toInteger year) month day'
monthYear = formatted "%B %Y"
weekday = formatted "%a"
2021-03-20 11:19:56 -04:00
approx = if exact day then "" else "approx. "
2020-09-25 17:08:44 -04:00
2021-03-20 11:19:56 -04:00
formatShort :: Date -> Text
formatShort (Date {month, day}) = toText $ day' <> month' where
2020-09-25 17:08:44 -04:00
day' = case day of
2021-03-20 11:19:56 -04:00
Exact d -> bshow d <> " "
Approx d -> bshow d <> "? "
Unknown -> mempty
month' = Builder.fromString $
formatTime defaultTimeLocale "%b" $ fromGregorian 1 month 1
formatRSS :: Date -> Text
formatRSS = pack . format . toTime where
2020-09-25 17:08:44 -04:00
format = formatTime defaultTimeLocale "%a, %d %b %_Y %T GMT"
toTime (Date {year, month, day}) =
let year' = toInteger year; day' = fromMaybe 1 $ dayNum day in
UTCTime (fromGregorian year' month day') 15600
2021-03-20 11:19:56 -04:00
formatSlash :: Date -> Text
formatSlash (Date {year, month, day}) =
toText $ mconcat [bshow year, "/", bshow month, "/", bshow day', bshow ex]
where day' = dayNum day; ex = exact day
2020-09-25 17:08:44 -04:00
nth :: Int -> Builder
2021-03-20 11:19:56 -04:00
nth n = bshow n <> suf
where
suf | n >= 10, n <= 19 = "th"
| n `mod` 10 == 1 = "st"
| n `mod` 10 == 2 = "nd"
| n `mod` 10 == 3 = "rd"
| otherwise = "th"
2020-09-25 17:08:44 -04:00
parseP :: ReadP Date
parseP = do
year <- readp
char_ '-'
month <- readp
day <- option Unknown do
char_ '-'
d <- readp
approx <- option Exact (Approx <$ ReadP.char '?')
pure $ approx d
pure $ Date year month day
where
readp = readS_to_P reads
char_ c = () <$ ReadP.char c
option k p = p <++ pure k
parseS :: ReadS Date
parseS = readP_to_S parseP
parseA :: Alternative f => String -> f Date
parseA str = case parseS str of
[(d, rest)] | all isSpace rest -> pure d
_ -> empty
instance FromYAML Date where
parseYAML = YAML.withStr "date" $ parseA . unpack
2021-03-20 07:46:32 -04:00
instance ToGVal m Date where
toGVal d@(Date {year, month, day=day'}) =
dict {Ginger.asText = long, Ginger.asHtml = Ginger.html long}
where
dict = Ginger.dict
["year" ~> year, "month" ~> month, "day" ~> day, "exact" ~> exact day',
"long" ~> long, "short" ~> short, "rss" ~> rss, "slash" ~> slash]
day :: GVal m
day = case day' of
Exact x -> toGVal x
Approx x -> toGVal $ show x <> "?"
Unknown -> "?"
2021-03-20 11:19:56 -04:00
long = formatLong d
short = formatShort d
rss = formatRSS d
slash = formatSlash d
toText :: Builder -> Text
toText = toStrict . Builder.toLazyText
bshow :: Show a => a -> Builder
bshow = Builder.fromString . show