packages feed

tzdata-0.2.20211021.0: Data/Time/Zones/DB.hs.template

-- -*- haskell -*-
{- |
Module      : Data.Time.Zones.DB
Copyright   : (C) 2014 Mihaly Barasz
License     : Apache-2.0, see LICENSE
Maintainer  : Janus Troelsen <ysangkok@gmail.com>
Stability   : experimental
-}

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE StandaloneDeriving #-}

module Data.Time.Zones.DB (
  toTZName,
  fromTZName,
  tzNameLabelMap,
  tzDataByLabel,
  tzDataByName,
  TZLabel(..),
  -- * Access to raw data
  TZDescription,
  tzDescriptions,
  -- * Type-level time zone labels
  KnownTZLabel(..),
  SomeTZLabel(..),
  someTZLabelVal,
  promoteTZLabel,
  ) where

import           Control.DeepSeq (NFData, rnf)
import qualified Data.ByteString.Char8 as BS
import qualified Data.ByteString.Lazy.Char8 as BL
import qualified Data.Map.Lazy as M
import           Data.Proxy (Proxy(..))
import           Data.Data (Data, Typeable)
import qualified Data.Vector as V
import           GHC.Generics (Generic)

{-# ANN module "HLint: ignore Use camelCase" #-}
-- | Enumeration of time zone locations.
data TZLabel
  TZ_LABEL_DECL
  deriving (Eq,Ord,Enum,Bounded,Show,Read,Data,Typeable,Generic)

instance NFData TZLabel where
  rnf = (`seq` ())

-- | Type of the elements of the compiled-in time zone info database.
--
-- @Right@ is a primary location;
-- fields: name, label, content of the Time Zone info file
--
-- @Left@ is a location which is an alias for a primary location;
-- fields: name, name of the target
type TZDescription = Either (BS.ByteString, BS.ByteString) (BS.ByteString, TZLabel, BL.ByteString)

-- | Convert a `TZLabel` to the usual name of the location.
--
-- >> toTZName Europe__Paris
-- >"Europe/Paris"
toTZName :: TZLabel -> BS.ByteString
toTZName = (v V.!) . fromEnum
  where
    v = V.fromList $ go tzDescriptions
    go [] = []
    go (Right (name, _, _) : zs) = name : go zs
    go (Left _ : zs) = go zs

-- | Lookup the `TZLabel` by the name of the location.
--
-- Returns `Nothing` if the location is unknown.
--
-- >> :set -XOverloadedStrings
-- >> fromTZName "Europe/Paris"
-- >Just Europe__Paris
-- >> fromTZName "Foo/Bar"
-- >Nothing
fromTZName :: BS.ByteString -> Maybe TZLabel
fromTZName = flip M.lookup tzNameLabelMap

-- | `M.Map` mapping know time zone locations to their canonical
-- `TZLabel`s.
tzNameLabelMap :: M.Map BS.ByteString TZLabel
tzNameLabelMap = M.fromList $ map g tzDescriptions
  where
    g (Right (name,label,_)) = (name, label)
    g (Left (name, target)) = (name, tzNameLabelMap M.! target)

-- | Lookup time zone data by its label.
tzDataByLabel :: TZLabel -> BL.ByteString
tzDataByLabel = (v V.!) . fromEnum
  where
    v = V.fromList $ go tzDescriptions
    go [] = []
    go (Right (_, _, desc) : zs) = desc : go zs
    go (Left _ : zs) = go zs

-- | Lookup time zone data by the name of it's location.
--
-- Returns `Nothing` if the location is unknown.
tzDataByName :: BS.ByteString -> Maybe BL.ByteString
tzDataByName n = tzDataByLabel `fmap` fromTZName n

-- | The list with raw data of the compiled-in time zone info database.
--
-- The list is sorted by name. (And the `Right` elements are
-- guaranteed to be in the order of `TZLabel` constructors.)
tzDescriptions :: [TZDescription]
tzDescriptions = zones
  where
    {-# NOINLINE p #-}
    p name label desc = Right (BS.pack name, label, BL.pack desc)
    {-# NOINLINE l #-}
    l name target = Left (BS.pack name, BS.pack target)
    zones = [
      TZ_DESCRIPTIONS
      ]

-- | This class gives the value associated with a type-level time zone label.
--
-- >> :set -XDataKinds
-- >> :module + Data.Proxy
-- >> tzLabelVal (Proxy :: Proxy 'America__New_York)
-- >America__New_York
class KnownTZLabel (label :: TZLabel) where
  tzLabelVal :: Proxy label -> TZLabel

TZ_INSTANCES

-- | This type represents an unknown type-level time zone label.
data SomeTZLabel where
  SomeTZLabel :: KnownTZLabel label => Proxy label -> SomeTZLabel

instance Eq SomeTZLabel where
  SomeTZLabel proxy == SomeTZLabel proxy' = tzLabelVal proxy == tzLabelVal proxy'

deriving instance Show SomeTZLabel

-- | Convert a value into an unknown type-level time zone label.
--
-- >> someTZLabelVal America__New_York
-- >SomeTZLabel Proxy
someTZLabelVal :: TZLabel -> SomeTZLabel
someTZLabelVal = \ case
  TZ_CASES

-- | Promote a time zone label value to the type-level.
--
-- >> case someTZLabelVal America__New_York of SomeTZLabel proxy -> promoteTZLabel proxy SomeTZLabel
-- >SomeTZLabel Proxy
promoteTZLabel :: forall proxy label a . KnownTZLabel label => proxy label -> (Proxy label -> a) -> a
promoteTZLabel _ f = f Proxy