{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE DeriveGeneric #-}
module Codec.Xlsx.Types.AutoFilter where
import Control.Arrow (first)
import Control.DeepSeq (NFData)
import Control.Lens (makeLenses)
import Data.Bool (bool)
import Data.ByteString (ByteString)
import Data.Default
import Data.Foldable (asum)
import Data.Map (Map)
import qualified Data.Map as M
import Data.Maybe (catMaybes)
import Data.Monoid ((<>))
import Data.Text (Text)
import qualified Data.Text as T
import GHC.Generics (Generic)
import Text.XML
import Text.XML.Cursor hiding (bool)
import qualified Xeno.DOM as Xeno
import Codec.Xlsx.Parser.Internal
import Codec.Xlsx.Types.Common
import Codec.Xlsx.Types.ConditionalFormatting (IconSetType)
import Codec.Xlsx.Writer.Internal
data FilterColumn
= Filters FilterByBlank [FilterCriterion]
| ColorFilter ColorFilterOptions
| ACustomFilter CustomFilter
| CustomFiltersOr CustomFilter CustomFilter
| CustomFiltersAnd CustomFilter CustomFilter
| DynamicFilter DynFilterOptions
| IconFilter (Maybe Int) IconSetType
| BottomNFilter EdgeFilterOptions
| TopNFilter EdgeFilterOptions
deriving (Eq, Show, Generic)
instance NFData FilterColumn
data FilterByBlank
= FilterByBlank
| DontFilterByBlank
deriving (Eq, Show, Generic)
instance NFData FilterByBlank
data FilterCriterion
= FilterValue Text
| FilterDateGroup DateGroup
deriving (Eq, Show, Generic)
instance NFData FilterCriterion
data DateGroup
= DateGroupByYear Int
| DateGroupByMonth Int Int
| DateGroupByDay Int Int Int
| DateGroupByHour Int Int Int Int
| DateGroupByMinute Int Int Int Int Int
| DateGroupBySecond Int Int Int Int Int Int
deriving (Eq, Show, Generic)
instance NFData DateGroup
data CustomFilter = CustomFilter
{ cfltOperator :: CustomFilterOperator
, cfltValue :: Text
} deriving (Eq, Show, Generic)
instance NFData CustomFilter
data CustomFilterOperator
= FltrEqual
| FltrGreaterThan
| FltrGreaterThanOrEqual
| FltrLessThan
| FltrLessThanOrEqual
| FltrNotEqual
deriving (Eq, Show, Generic)
instance NFData CustomFilterOperator
data EdgeFilterOptions = EdgeFilterOptions
{ _efoUsePercents :: Bool
, _efoVal :: Double
, _efoFilterVal :: Maybe Double
} deriving (Eq, Show, Generic)
instance NFData EdgeFilterOptions
data ColorFilterOptions = ColorFilterOptions
{ _cfoCellColor :: Bool
, _cfoDxfId :: Maybe Int
} deriving (Eq, Show, Generic)
instance NFData ColorFilterOptions
data DynFilterOptions = DynFilterOptions
{ _dfoType :: DynFilterType
, _dfoVal :: Maybe Double
, _dfoMaxVal :: Maybe Double
} deriving (Eq, Show, Generic)
instance NFData DynFilterOptions
data DynFilterType
= DynFilterAboveAverage
| DynFilterBelowAverage
| DynFilterLastMonth
| DynFilterLastQuarter
| DynFilterLastWeek
| DynFilterLastYear
| DynFilterM1
| DynFilterM10
| DynFilterM11
| DynFilterM12
| DynFilterM2
| DynFilterM3
| DynFilterM4
| DynFilterM5
| DynFilterM6
| DynFilterM7
| DynFilterM8
| DynFilterM9
| DynFilterNextMonth
| DynFilterNextQuarter
| DynFilterNextWeek
| DynFilterNextYear
| DynFilterNull
| DynFilterQ1
| DynFilterQ2
| DynFilterQ3
| DynFilterQ4
| DynFilterThisMonth
| DynFilterThisQuarter
| DynFilterThisWeek
| DynFilterThisYear
| DynFilterToday
| DynFilterTomorrow
| DynFilterYearToDate
| DynFilterYesterday
deriving (Eq, Show, Generic)
instance NFData DynFilterType
data AutoFilter = AutoFilter
{ _afRef :: Maybe CellRef
, _afFilterColumns :: Map Int FilterColumn
} deriving (Eq, Show, Generic)
instance NFData AutoFilter
makeLenses ''AutoFilter
instance Default AutoFilter where
def = AutoFilter Nothing M.empty
instance FromCursor AutoFilter where
fromCursor cur = do
_afRef <- maybeAttribute "ref" cur
let _afFilterColumns = M.fromList $ cur $/ element (n_ "filterColumn") >=> \c -> do
colId <- fromAttribute "colId" c
fcol <- c $/ anyElement >=> fltColFromNode . node
return (colId, fcol)
return AutoFilter {..}
instance FromXenoNode AutoFilter where
fromXenoNode root = do
_afRef <- parseAttributes root $ maybeAttr "ref"
_afFilterColumns <-
fmap M.fromList . collectChildren root $ fromChildList "filterColumn"
return AutoFilter {..}
instance FromXenoNode (Int, FilterColumn) where
fromXenoNode root = do
colId <- parseAttributes root $ fromAttr "colId"
fCol <-
collectChildren root $ asum [filters, color, custom, dynamic, icon, top10]
return (colId, fCol)
where
filters =
requireAndParse "filters" $ \node -> do
filterBlank <-
parseAttributes node $ fromAttrDef "blank" DontFilterByBlank
filterCriteria <- childListAny node
return $ Filters filterBlank filterCriteria
color =
requireAndParse "colorFilter" $ \node ->
parseAttributes node $ do
_cfoCellColor <- fromAttrDef "cellColor" True
_cfoDxfId <- maybeAttr "dxfId"
return $ ColorFilter ColorFilterOptions {..}
custom =
requireAndParse "customFilters" $ \node -> do
isAnd <- parseAttributes node $ fromAttrDef "and" False
cfilters <- collectChildren node $ fromChildList "customFilter"
case cfilters of
[f] -> return $ ACustomFilter f
[f1, f2] ->
if isAnd
then return $ CustomFiltersAnd f1 f2
else return $ CustomFiltersOr f1 f2
_ ->
Left $
"expected 1 or 2 custom filters but found " <>
T.pack (show $ length cfilters)
dynamic =
requireAndParse "dynamicFilter" . flip parseAttributes $ do
_dfoType <- fromAttr "type"
_dfoVal <- maybeAttr "val"
_dfoMaxVal <- maybeAttr "maxVal"
return $ DynamicFilter DynFilterOptions {..}
icon =
requireAndParse "iconFilter" . flip parseAttributes $
IconFilter <$> maybeAttr "iconId" <*> fromAttr "iconSet"
top10 =
requireAndParse "top10" . flip parseAttributes $ do
top <- fromAttrDef "top" True
percent <- fromAttrDef "percent" False
val <- fromAttr "val"
filterVal <- maybeAttr "filterVal"
let opts = EdgeFilterOptions percent val filterVal
if top
then return $ TopNFilter opts
else return $ BottomNFilter opts
instance FromXenoNode CustomFilter where
fromXenoNode root =
parseAttributes root $
CustomFilter <$> fromAttrDef "operator" FltrEqual <*> fromAttr "val"
fltColFromNode :: Node -> [FilterColumn]
fltColFromNode n | n `nodeElNameIs` (n_ "filters") = do
let filterCriteria = cur $/ anyElement >=> fromCursor
filterBlank <- fromAttributeDef "blank" DontFilterByBlank cur
return $ Filters filterBlank filterCriteria
| n `nodeElNameIs` (n_ "colorFilter") = do
_cfoCellColor <- fromAttributeDef "cellColor" True cur
_cfoDxfId <- maybeAttribute "dxfId" cur
return $ ColorFilter ColorFilterOptions {..}
| n `nodeElNameIs` (n_ "customFilters") = do
isAnd <- fromAttributeDef "and" False cur
let cFilters = cur $/ element (n_ "customFilter") >=> \c -> do
op <- fromAttributeDef "operator" FltrEqual c
val <- fromAttribute "val" c
return $ CustomFilter op val
case cFilters of
[f] ->
return $ ACustomFilter f
[f1, f2] ->
if isAnd
then return $ CustomFiltersAnd f1 f2
else return $ CustomFiltersOr f1 f2
_ ->
fail "bad custom filter"
| n `nodeElNameIs` (n_ "dynamicFilter") = do
_dfoType <- fromAttribute "type" cur
_dfoVal <- maybeAttribute "val" cur
_dfoMaxVal <- maybeAttribute "maxVal" cur
return $ DynamicFilter DynFilterOptions{..}
| n `nodeElNameIs` (n_ "iconFilter") = do
iconId <- maybeAttribute "iconId" cur
iconSet <- fromAttribute "iconSet" cur
return $ IconFilter iconId iconSet
| n `nodeElNameIs` (n_ "top10") = do
top <- fromAttributeDef "top" True cur
let percent = fromAttributeDef "percent" False cur
val = fromAttribute "val" cur
filterVal = maybeAttribute "filterVal" cur
if top
then fmap TopNFilter $
EdgeFilterOptions <$> percent <*> val <*> filterVal
else fmap BottomNFilter $
EdgeFilterOptions <$> percent <*> val <*> filterVal
| otherwise = fail "no matching nodes"
where
cur = fromNode n
instance FromCursor FilterCriterion where
fromCursor = filterCriterionFromNode . node
instance FromXenoNode FilterCriterion where
fromXenoNode root =
case Xeno.name root of
"filter" -> parseAttributes root $ do FilterValue <$> fromAttr "val"
"dateGroupItem" ->
parseAttributes root $ do
grouping <- fromAttr "dateTimeGrouping"
group <- case grouping of
("year" :: ByteString) ->
DateGroupByYear <$> fromAttr "year"
"month" ->
DateGroupByMonth <$> fromAttr "year"
<*> fromAttr "month"
"day" ->
DateGroupByDay <$> fromAttr "year"
<*> fromAttr "month"
<*> fromAttr "day"
"hour" ->
DateGroupByHour <$> fromAttr "year"
<*> fromAttr "month"
<*> fromAttr "day"
<*> fromAttr "hour"
"minute" ->
DateGroupByMinute <$> fromAttr "year"
<*> fromAttr "month"
<*> fromAttr "day"
<*> fromAttr "hour"
<*> fromAttr "minute"
"second" ->
DateGroupBySecond <$> fromAttr "year"
<*> fromAttr "month"
<*> fromAttr "day"
<*> fromAttr "hour"
<*> fromAttr "minute"
<*> fromAttr "second"
_ -> toAttrParser . Left $ "Unexpected date grouping"
return $ FilterDateGroup group
_ -> Left "Bad FilterCriterion"
filterCriterionFromNode :: Node -> [FilterCriterion]
filterCriterionFromNode n
| n `nodeElNameIs` (n_ "filter") = do
v <- fromAttribute "val" cur
return $ FilterValue v
| n `nodeElNameIs` (n_ "dateGroupItem") = do
g <- fromAttribute "dateTimeGrouping" cur
let year = fromAttribute "year" cur
month = fromAttribute "month" cur
day = fromAttribute "day" cur
hour = fromAttribute "hour" cur
minute = fromAttribute "minute" cur
second = fromAttribute "second" cur
FilterDateGroup <$>
case g of
"year" -> DateGroupByYear <$> year
"month" -> DateGroupByMonth <$> year <*> month
"day" -> DateGroupByDay <$> year <*> month <*> day
"hour" -> DateGroupByHour <$> year <*> month <*> day <*> hour
"minute" ->
DateGroupByMinute <$> year <*> month <*> day <*> hour <*> minute
"second" ->
DateGroupBySecond <$> year <*> month <*> day <*> hour <*> minute <*>
second
_ -> fail $ "unexpected dateTimeGrouping " ++ show (g :: Text)
| otherwise = fail "no matching nodes"
where
cur = fromNode n
instance FromAttrVal CustomFilterOperator where
fromAttrVal "equal" = readSuccess FltrEqual
fromAttrVal "greaterThan" = readSuccess FltrGreaterThan
fromAttrVal "greaterThanOrEqual" = readSuccess FltrGreaterThanOrEqual
fromAttrVal "lessThan" = readSuccess FltrLessThan
fromAttrVal "lessThanOrEqual" = readSuccess FltrLessThanOrEqual
fromAttrVal "notEqual" = readSuccess FltrNotEqual
fromAttrVal t = invalidText "CustomFilterOperator" t
instance FromAttrBs CustomFilterOperator where
fromAttrBs "equal" = return FltrEqual
fromAttrBs "greaterThan" = return FltrGreaterThan
fromAttrBs "greaterThanOrEqual" = return FltrGreaterThanOrEqual
fromAttrBs "lessThan" = return FltrLessThan
fromAttrBs "lessThanOrEqual" = return FltrLessThanOrEqual
fromAttrBs "notEqual" = return FltrNotEqual
fromAttrBs x = unexpectedAttrBs "CustomFilterOperator" x
instance FromAttrVal FilterByBlank where
fromAttrVal =
fmap (first $ bool DontFilterByBlank FilterByBlank) . fromAttrVal
instance FromAttrBs FilterByBlank where
fromAttrBs = fmap (bool DontFilterByBlank FilterByBlank) . fromAttrBs
instance FromAttrVal DynFilterType where
fromAttrVal "aboveAverage" = readSuccess DynFilterAboveAverage
fromAttrVal "belowAverage" = readSuccess DynFilterBelowAverage
fromAttrVal "lastMonth" = readSuccess DynFilterLastMonth
fromAttrVal "lastQuarter" = readSuccess DynFilterLastQuarter
fromAttrVal "lastWeek" = readSuccess DynFilterLastWeek
fromAttrVal "lastYear" = readSuccess DynFilterLastYear
fromAttrVal "M1" = readSuccess DynFilterM1
fromAttrVal "M10" = readSuccess DynFilterM10
fromAttrVal "M11" = readSuccess DynFilterM11
fromAttrVal "M12" = readSuccess DynFilterM12
fromAttrVal "M2" = readSuccess DynFilterM2
fromAttrVal "M3" = readSuccess DynFilterM3
fromAttrVal "M4" = readSuccess DynFilterM4
fromAttrVal "M5" = readSuccess DynFilterM5
fromAttrVal "M6" = readSuccess DynFilterM6
fromAttrVal "M7" = readSuccess DynFilterM7
fromAttrVal "M8" = readSuccess DynFilterM8
fromAttrVal "M9" = readSuccess DynFilterM9
fromAttrVal "nextMonth" = readSuccess DynFilterNextMonth
fromAttrVal "nextQuarter" = readSuccess DynFilterNextQuarter
fromAttrVal "nextWeek" = readSuccess DynFilterNextWeek
fromAttrVal "nextYear" = readSuccess DynFilterNextYear
fromAttrVal "null" = readSuccess DynFilterNull
fromAttrVal "Q1" = readSuccess DynFilterQ1
fromAttrVal "Q2" = readSuccess DynFilterQ2
fromAttrVal "Q3" = readSuccess DynFilterQ3
fromAttrVal "Q4" = readSuccess DynFilterQ4
fromAttrVal "thisMonth" = readSuccess DynFilterThisMonth
fromAttrVal "thisQuarter" = readSuccess DynFilterThisQuarter
fromAttrVal "thisWeek" = readSuccess DynFilterThisWeek
fromAttrVal "thisYear" = readSuccess DynFilterThisYear
fromAttrVal "today" = readSuccess DynFilterToday
fromAttrVal "tomorrow" = readSuccess DynFilterTomorrow
fromAttrVal "yearToDate" = readSuccess DynFilterYearToDate
fromAttrVal "yesterday" = readSuccess DynFilterYesterday
fromAttrVal t = invalidText "DynFilterType" t
instance FromAttrBs DynFilterType where
fromAttrBs "aboveAverage" = return DynFilterAboveAverage
fromAttrBs "belowAverage" = return DynFilterBelowAverage
fromAttrBs "lastMonth" = return DynFilterLastMonth
fromAttrBs "lastQuarter" = return DynFilterLastQuarter
fromAttrBs "lastWeek" = return DynFilterLastWeek
fromAttrBs "lastYear" = return DynFilterLastYear
fromAttrBs "M1" = return DynFilterM1
fromAttrBs "M10" = return DynFilterM10
fromAttrBs "M11" = return DynFilterM11
fromAttrBs "M12" = return DynFilterM12
fromAttrBs "M2" = return DynFilterM2
fromAttrBs "M3" = return DynFilterM3
fromAttrBs "M4" = return DynFilterM4
fromAttrBs "M5" = return DynFilterM5
fromAttrBs "M6" = return DynFilterM6
fromAttrBs "M7" = return DynFilterM7
fromAttrBs "M8" = return DynFilterM8
fromAttrBs "M9" = return DynFilterM9
fromAttrBs "nextMonth" = return DynFilterNextMonth
fromAttrBs "nextQuarter" = return DynFilterNextQuarter
fromAttrBs "nextWeek" = return DynFilterNextWeek
fromAttrBs "nextYear" = return DynFilterNextYear
fromAttrBs "null" = return DynFilterNull
fromAttrBs "Q1" = return DynFilterQ1
fromAttrBs "Q2" = return DynFilterQ2
fromAttrBs "Q3" = return DynFilterQ3
fromAttrBs "Q4" = return DynFilterQ4
fromAttrBs "thisMonth" = return DynFilterThisMonth
fromAttrBs "thisQuarter" = return DynFilterThisQuarter
fromAttrBs "thisWeek" = return DynFilterThisWeek
fromAttrBs "thisYear" = return DynFilterThisYear
fromAttrBs "today" = return DynFilterToday
fromAttrBs "tomorrow" = return DynFilterTomorrow
fromAttrBs "yearToDate" = return DynFilterYearToDate
fromAttrBs "yesterday" = return DynFilterYesterday
fromAttrBs x = unexpectedAttrBs "DynFilterType" x
instance ToElement AutoFilter where
toElement nm AutoFilter {..} =
elementList
nm
(catMaybes ["ref" .=? _afRef])
[ elementList
(n_ "filterColumn")
["colId" .= colId]
[fltColToElement fCol]
| (colId, fCol) <- M.toList _afFilterColumns
]
fltColToElement :: FilterColumn -> Element
fltColToElement (Filters filterBlank filterCriteria) =
let attrs = catMaybes ["blank" .=? justNonDef DontFilterByBlank filterBlank]
in elementList
(n_ "filters") attrs $ map filterCriterionToElement filterCriteria
fltColToElement (ColorFilter opts) = toElement (n_ "colorFilter") opts
fltColToElement (ACustomFilter f) =
elementListSimple (n_ "customFilters") [toElement (n_ "customFilter") f]
fltColToElement (CustomFiltersOr f1 f2) =
elementListSimple
(n_ "customFilters")
[toElement (n_ "customFilter") f | f <- [f1, f2]]
fltColToElement (CustomFiltersAnd f1 f2) =
elementList
(n_ "customFilters")
["and" .= True]
[toElement (n_ "customFilter") f | f <- [f1, f2]]
fltColToElement (DynamicFilter opts) = toElement (n_ "dynamicFilter") opts
fltColToElement (IconFilter iconId iconSet) =
leafElement (n_ "iconFilter") $
["iconSet" .= iconSet] ++ catMaybes ["iconId" .=? iconId]
fltColToElement (BottomNFilter opts) = edgeFilter False opts
fltColToElement (TopNFilter opts) = edgeFilter True opts
edgeFilter :: Bool -> EdgeFilterOptions -> Element
edgeFilter top EdgeFilterOptions {..} =
leafElement (n_ "top10") $
["top" .= top, "percent" .= _efoUsePercents, "val" .= _efoVal] ++
catMaybes ["filterVal" .=? _efoFilterVal]
filterCriterionToElement :: FilterCriterion -> Element
filterCriterionToElement (FilterValue v) =
leafElement (n_ "filter") ["val" .= v]
filterCriterionToElement (FilterDateGroup (DateGroupByYear y)) =
leafElement
(n_ "dateGroupItem")
["dateTimeGrouping" .= ("year" :: Text), "year" .= y]
filterCriterionToElement (FilterDateGroup (DateGroupByMonth y m)) =
leafElement
(n_ "dateGroupItem")
["dateTimeGrouping" .= ("month" :: Text), "year" .= y, "month" .= m]
filterCriterionToElement (FilterDateGroup (DateGroupByDay y m d)) =
leafElement
(n_ "dateGroupItem")
["dateTimeGrouping" .= ("day" :: Text), "year" .= y, "month" .= m, "day" .= d]
filterCriterionToElement (FilterDateGroup (DateGroupByHour y m d h)) =
leafElement
(n_ "dateGroupItem")
[ "dateTimeGrouping" .= ("hour" :: Text)
, "year" .= y
, "month" .= m
, "day" .= d
, "hour" .= h
]
filterCriterionToElement (FilterDateGroup (DateGroupByMinute y m d h mi)) =
leafElement
(n_ "dateGroupItem")
[ "dateTimeGrouping" .= ("minute" :: Text)
, "year" .= y
, "month" .= m
, "day" .= d
, "hour" .= h
, "minute" .= mi
]
filterCriterionToElement (FilterDateGroup (DateGroupBySecond y m d h mi s)) =
leafElement
(n_ "dateGroupItem")
[ "dateTimeGrouping" .= ("second" :: Text)
, "year" .= y
, "month" .= m
, "day" .= d
, "hour" .= h
, "minute" .= mi
, "second" .= s
]
instance ToElement CustomFilter where
toElement nm CustomFilter {..} =
leafElement nm ["operator" .= cfltOperator, "val" .= cfltValue]
instance ToAttrVal CustomFilterOperator where
toAttrVal FltrEqual = "equal"
toAttrVal FltrGreaterThan = "greaterThan"
toAttrVal FltrGreaterThanOrEqual = "greaterThanOrEqual"
toAttrVal FltrLessThan = "lessThan"
toAttrVal FltrLessThanOrEqual = "lessThanOrEqual"
toAttrVal FltrNotEqual = "notEqual"
instance ToAttrVal FilterByBlank where
toAttrVal FilterByBlank = toAttrVal True
toAttrVal DontFilterByBlank = toAttrVal False
instance ToElement ColorFilterOptions where
toElement nm ColorFilterOptions {..} =
leafElement nm $
catMaybes ["cellColor" .=? justFalse _cfoCellColor, "dxfId" .=? _cfoDxfId]
instance ToElement DynFilterOptions where
toElement nm DynFilterOptions {..} =
leafElement nm $
["type" .= _dfoType] ++
catMaybes ["val" .=? _dfoVal, "maxVal" .=? _dfoMaxVal]
instance ToAttrVal DynFilterType where
toAttrVal DynFilterAboveAverage = "aboveAverage"
toAttrVal DynFilterBelowAverage = "belowAverage"
toAttrVal DynFilterLastMonth = "lastMonth"
toAttrVal DynFilterLastQuarter = "lastQuarter"
toAttrVal DynFilterLastWeek = "lastWeek"
toAttrVal DynFilterLastYear = "lastYear"
toAttrVal DynFilterM1 = "M1"
toAttrVal DynFilterM10 = "M10"
toAttrVal DynFilterM11 = "M11"
toAttrVal DynFilterM12 = "M12"
toAttrVal DynFilterM2 = "M2"
toAttrVal DynFilterM3 = "M3"
toAttrVal DynFilterM4 = "M4"
toAttrVal DynFilterM5 = "M5"
toAttrVal DynFilterM6 = "M6"
toAttrVal DynFilterM7 = "M7"
toAttrVal DynFilterM8 = "M8"
toAttrVal DynFilterM9 = "M9"
toAttrVal DynFilterNextMonth = "nextMonth"
toAttrVal DynFilterNextQuarter = "nextQuarter"
toAttrVal DynFilterNextWeek = "nextWeek"
toAttrVal DynFilterNextYear = "nextYear"
toAttrVal DynFilterNull = "null"
toAttrVal DynFilterQ1 = "Q1"
toAttrVal DynFilterQ2 = "Q2"
toAttrVal DynFilterQ3 = "Q3"
toAttrVal DynFilterQ4 = "Q4"
toAttrVal DynFilterThisMonth = "thisMonth"
toAttrVal DynFilterThisQuarter = "thisQuarter"
toAttrVal DynFilterThisWeek = "thisWeek"
toAttrVal DynFilterThisYear = "thisYear"
toAttrVal DynFilterToday = "today"
toAttrVal DynFilterTomorrow = "tomorrow"
toAttrVal DynFilterYearToDate = "yearToDate"
toAttrVal DynFilterYesterday = "yesterday"