datepicker

An fzf-like tool to interactively select a date in a provided format

git clone https://git.8pit.net/datepicker.git

  1{-# LANGUAGE PatternSynonyms #-}
  2
  3module DatePicker.CmdLine
  4  ( Opts (..),
  5    CmdTime,
  6    getTime,
  7    optsPeriod,
  8    getCmdArgs,
  9  )
 10where
 11
 12import Control.Applicative ((<|>))
 13import Data.Time.Calendar qualified as Cal
 14import Data.Time.Calendar.Month (Month, addMonths, pattern YearMonth)
 15import Data.Time.Calendar.OrdinalDate (Day, toOrdinalDate)
 16import Data.Time.Format qualified as Fmt
 17import Data.Time.LocalTime (LocalTime (LocalTime))
 18import DatePicker.Util (locale, periodAllMonths)
 19import Options.Applicative qualified as OPT
 20
 21data CmdTime = CmdTime String (Maybe String)
 22
 23firstDayInMY :: Cal.Year -> Cal.MonthOfYear -> Day
 24firstDayInMY y my = Cal.periodFirstDay $ YearMonth y my
 25
 26parseMonth :: String -> IO Month
 27parseMonth input =
 28  Fmt.parseTimeM False locale "%B" input
 29    <|> Fmt.parseTimeM False locale "%b" input
 30    <|> Fmt.parseTimeM False locale "%m" input
 31
 32getTime :: LocalTime -> CmdTime -> IO Day
 33getTime _ (CmdTime month (Just year)) = do
 34  (YearMonth _ my) <- parseMonth month
 35  pure (firstDayInMY (read year) my)
 36getTime (LocalTime cd _) (CmdTime month Nothing) = do
 37  (YearMonth _ my) <- parseMonth month
 38  pure (firstDayInMY (fst $ toOrdinalDate cd) my)
 39
 40------------------------------------------------------------------------
 41
 42data Duration = OneMonth | ThreeMonths | TwelveMonths | OneYear
 43
 44data Opts = Opts
 45  { optNoTime :: Bool,
 46    optLogical :: Bool,
 47    optFormat :: String,
 48    optMonday :: Bool,
 49    optDuration :: Duration,
 50    optTime :: Maybe CmdTime
 51  }
 52
 53durationParser :: OPT.Parser Duration
 54durationParser =
 55  OPT.flag
 56    OneMonth
 57    OneMonth
 58    ( OPT.long "one"
 59        <> OPT.short '1'
 60        <> OPT.help "Display a single month"
 61    )
 62    OPT.<|> OPT.flag'
 63      ThreeMonths
 64      ( OPT.long "three"
 65          <> OPT.short '3'
 66          <> OPT.help "Display next/previous month for current month"
 67      )
 68    OPT.<|> OPT.flag'
 69      TwelveMonths
 70      ( OPT.long "twelve"
 71          <> OPT.short 'Y'
 72          <> OPT.help "Display the next twelve months"
 73      )
 74    OPT.<|> OPT.flag'
 75      OneYear
 76      ( OPT.long "year"
 77          <> OPT.short 'y'
 78          <> OPT.help "Display the entire year"
 79      )
 80
 81timeParser :: OPT.Parser CmdTime
 82timeParser =
 83  CmdTime
 84    <$> OPT.argument OPT.str (OPT.metavar "month")
 85    <*> OPT.optional
 86      (OPT.argument OPT.str (OPT.metavar "year"))
 87
 88optsParser :: OPT.Parser Opts
 89optsParser =
 90  Opts
 91    <$> OPT.switch
 92      ( OPT.long "date-only"
 93          <> OPT.short 'd'
 94          <> OPT.help "Only require date selection, omitting time"
 95      )
 96    <*> OPT.switch
 97      ( OPT.long "logical-move"
 98          <> OPT.short 'l'
 99          <> OPT.help "Always move cursor logically by week/date"
100      )
101    <*> OPT.option
102      OPT.str
103      ( OPT.long "format"
104          <> OPT.short 'f'
105          -- RFC 1123 format as per Go's time package
106          <> OPT.value "%a, %d %b %Y %T %Z"
107          <> OPT.metavar "FORMAT"
108          <> OPT.help "Format in which the date should be output"
109      )
110    <*> OPT.switch
111      ( OPT.long "monday"
112          <> OPT.short 'm'
113          <> OPT.help "Treat monday as the first day of the week"
114      )
115    <*> durationParser
116    <*> OPT.optional timeParser
117
118------------------------------------------------------------------------
119
120optsPeriod :: Duration -> Cal.Day -> [Month]
121optsPeriod OneMonth day = [Cal.dayPeriod day]
122optsPeriod ThreeMonths day =
123  let month = Cal.dayPeriod day
124   in [addMonths (-1) month, month, addMonths 1 month]
125optsPeriod TwelveMonths day =
126  let month = Cal.dayPeriod day
127   in map (`addMonths` month) [0 .. 11]
128optsPeriod OneYear day = periodAllMonths (fst $ toOrdinalDate day)
129
130cmdOpts :: OPT.ParserInfo Opts
131cmdOpts =
132  OPT.info
133    (optsParser OPT.<**> OPT.helper)
134    ( OPT.fullDesc
135        <> OPT.progDesc "Interactively select a date to be printed to stdout"
136        <> OPT.header "datepicker - a utility for interactive date selection"
137    )
138
139getCmdArgs :: IO Opts
140getCmdArgs = OPT.execParser cmdOpts