1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
|
{-# LANGUAGE ForeignFunctionInterface #-}
{-# LANGUAGE Safe #-}
module Data.Time.LocalTime.Internal.TimeZone (
-- * Time zones
TimeZone (..),
timeZoneOffsetString,
timeZoneOffsetString',
timeZoneOffsetString'',
minutesToTimeZone,
hoursToTimeZone,
utc,
-- getting the locale time zone
getTimeZone,
getCurrentTimeZone,
) where
import Control.DeepSeq
import Data.Data
import Data.Time.Calendar.Private
import Data.Time.Clock.Internal.UTCTime
import Data.Time.Clock.POSIX
import Data.Time.Clock.System
import Foreign
import Foreign.C
-- | A TimeZone is a whole number of minutes offset from UTC, together with a name and a \"just for summer\" flag.
data TimeZone = TimeZone
{ timeZoneMinutes :: Int
-- ^ The number of minutes offset from UTC. Positive means local time will be later in the day than UTC.
, timeZoneSummerOnly :: Bool
-- ^ Is this time zone just persisting for the summer?
, timeZoneName :: String
-- ^ The name of the zone, typically a three- or four-letter acronym.
}
deriving (Eq, Ord, Data, Typeable)
instance NFData TimeZone where
rnf (TimeZone m so n) = rnf m `seq` rnf so `seq` rnf n `seq` ()
-- | Create a nameless non-summer timezone for this number of minutes.
minutesToTimeZone :: Int -> TimeZone
minutesToTimeZone m = TimeZone m False ""
-- | Create a nameless non-summer timezone for this number of hours.
hoursToTimeZone :: Int -> TimeZone
hoursToTimeZone i = minutesToTimeZone (60 * i)
showT :: Bool -> PadOption -> Int -> String
showT False opt t = showPaddedNum opt ((div t 60) * 100 + (mod t 60))
showT True opt t = let
opt' = case opt of
NoPad -> NoPad
Pad i c -> Pad (max 0 $ i - 3) c
in showPaddedNum opt' (div t 60) ++ ":" ++ show2 (mod t 60)
timeZoneOffsetString'' :: Bool -> PadOption -> TimeZone -> String
timeZoneOffsetString'' colon opt (TimeZone t _ _)
| t < 0 = '-' : (showT colon opt (negate t))
timeZoneOffsetString'' colon opt (TimeZone t _ _) = '+' : (showT colon opt t)
-- | Text representing the offset of this timezone, such as \"-0800\" or \"+0400\" (like @%z@ in formatTime), with arbitrary padding.
timeZoneOffsetString' :: Maybe Char -> TimeZone -> String
timeZoneOffsetString' Nothing = timeZoneOffsetString'' False NoPad
timeZoneOffsetString' (Just c) = timeZoneOffsetString'' False $ Pad 4 c
-- | Text representing the offset of this timezone, such as \"-0800\" or \"+0400\" (like @%z@ in formatTime).
timeZoneOffsetString :: TimeZone -> String
timeZoneOffsetString = timeZoneOffsetString'' False (Pad 4 '0')
-- | This only shows the time zone name, or offset if the name is empty.
instance Show TimeZone where
show zone@(TimeZone _ _ "") = timeZoneOffsetString zone
show (TimeZone _ _ name) = name
-- | The UTC time zone.
utc :: TimeZone
utc = TimeZone 0 False "UTC"
{-# CFILES cbits/HsTime.c #-}
foreign import ccall unsafe "HsTime.h get_current_timezone_seconds"
get_current_timezone_seconds ::
CTime -> Ptr CInt -> Ptr CString -> IO CLong
getTimeZoneCTime :: CTime -> IO TimeZone
getTimeZoneCTime ctime =
with
0
( \pdst ->
with
nullPtr
( \pcname -> do
secs <- get_current_timezone_seconds ctime pdst pcname
case secs of
0x80000000 -> fail "localtime_r failed"
_ -> do
dst <- peek pdst
cname <- peek pcname
name <- peekCString cname
return (TimeZone (div (fromIntegral secs) 60) (dst == 1) name)
)
)
-- there's no instance Bounded CTime, so this is the easiest way to check for overflow
toCTime :: Int64 -> IO CTime
toCTime t = let
tt = fromIntegral t
t' = fromIntegral tt
in if t' == t
then return $ CTime tt
else fail "Data.Time.LocalTime.Internal.TimeZone.toCTime: Overflow"
-- | Get the configured time-zone for a given time (varying as per summertime adjustments).
getTimeZoneSystem :: SystemTime -> IO TimeZone
getTimeZoneSystem t = do
ctime <- toCTime $ systemSeconds t
getTimeZoneCTime ctime
-- | Get the configured time-zone for a given time (varying as per summertime adjustments).
--
-- On Unix systems the output of this function depends on:
--
-- 1. The value of @TZ@ environment variable (if set)
--
-- 2. The system time zone (usually configured by @\/etc\/localtime@ symlink)
--
-- For details see tzset(3) and localtime(3).
--
-- Example:
--
-- @
-- > let t = `UTCTime` (`Data.Time.Calendar.fromGregorian` 2021 7 1) 0
-- > `getTimeZone` t
-- CEST
-- > `System.Environment.setEnv` \"TZ\" \"America/New_York\" >> `getTimeZone` t
-- EDT
-- > `System.Environment.setEnv` \"TZ\" \"Europe/Berlin\" >> `getTimeZone` t
-- CEST
-- @
--
-- On Windows systems the output of this function depends on:
--
-- 1. The value of @TZ@ environment variable (if set).
-- See [here](https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/tzset) for how Windows interprets this variable.
--
-- 2. The system time zone, configured in Settings
getTimeZone :: UTCTime -> IO TimeZone
getTimeZone t = do
ctime <- toCTime $ floor $ utcTimeToPOSIXSeconds t
getTimeZoneCTime ctime
-- | Get the configured time-zone for the current time.
getCurrentTimeZone :: IO TimeZone
getCurrentTimeZone = getSystemTime >>= getTimeZoneSystem
|