File: TimeZone.hs

package info (click to toggle)
ghc 9.6.6-4
  • links: PTS, VCS
  • area: main
  • in suites: forky, trixie
  • size: 158,216 kB
  • sloc: haskell: 648,228; ansic: 81,656; cpp: 11,808; javascript: 8,444; sh: 5,831; fortran: 3,527; python: 3,277; asm: 2,523; makefile: 2,298; yacc: 1,570; lisp: 532; xml: 196; perl: 145; csh: 2
file content (154 lines) | stat: -rw-r--r-- 5,459 bytes parent folder | download | duplicates (2)
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