git.iko.soy/typeable/postgresql-simple
on master help sign in
Revisions to the new Time documentation
fc8d43aLeon P Smith
src/Database/PostgreSQL/Simple/Time.hs+82−59
@@ -9,23 +9,23 @@ This module provides time types that supports positive and negative
9infinity, as well as some functions for converting to and from strings.
9infinity, as well as some functions for converting to and from strings.
10
10
11Also, this module also contains commentary regarding postgresql's timestamp
11Also, this module also contains commentary regarding postgresql's timestamp
12−types, timekeeping in general, and how it relates to postgresql-simple.
12+types, civil timekeeping in general, and how it relates to
13−You can read more about PostgreSQL's date and time types at
13+postgresql-simple. You can read more about PostgreSQL's date and time types
14−<http://www.postgresql.org/docs/9.1/static/datatype-datetime.html>,
14+at <http://www.postgresql.org/docs/9.1/static/datatype-datetime.html>,
15−and the IANA timezone database at <https://en.wikipedia.org/wiki/Tz_database>.
15+and the IANA time zone database at <https://en.wikipedia.org/wiki/Tz_database>.
16
16
17Stack Overflow also has some excellent commentary on time, if it is a
17Stack Overflow also has some excellent commentary on time, if it is a
18wiki page or a highly upvoted question and answer. If the answer regarding
18wiki page or a highly upvoted question and answer. If the answer regarding
19−time has not received hundreds of upvotes, then the answer is almost
19+time has not received about a hundred upvotes at least, then the answer is
20−invariably completely and painfully wrong, even if it's the chosen answer
20+almost invariably completely and painfully wrong, even if it's the chosen
21−or the most highly upvoted answer to a question.
21+answer or the most highly upvoted answer to a question.
22
22
23PostgreSQL's @timestamp with time zone@ (hereafter, @timestamptz@) can be
23PostgreSQL's @timestamp with time zone@ (hereafter, @timestamptz@) can be
24converted to Haskell's 'Data.Time.UTCTime' and 'Data.Time.ZonedTime' types,
24converted to Haskell's 'Data.Time.UTCTime' and 'Data.Time.ZonedTime' types,
25−because their values represent an unambiguous point in time. PostgreSQL's
25+because values of these types represent a self-contained, unambiguous point
26−@timestamp without time zone@ (hereafter, @timestamp@) can be converted
26+in time. PostgreSQL's @timestamp without time zone@ (hereafter, @timestamp@)
27−to Haskell's 'Data.Time.LocalTime', because values of these types can only be
27+can be converted to Haskell's 'Data.Time.LocalTime', because values of these
28−disambiguated in a larger context.
28+types are ambiguous by themselves, and require context to disambiguate.
29
29
30While this behavior may be superficially counterintuitive because the
30While this behavior may be superficially counterintuitive because the
31names might suggest otherwise, this behavior is correct. In fact,
31names might suggest otherwise, this behavior is correct. In fact,
@@ -42,43 +42,53 @@ such as @+00@, @-05@, or @+05:30@. A standard time specifies an offset
42(which may vary throughout the year, due to daylight savings) that a
42(which may vary throughout the year, due to daylight savings) that a
43region follows, such as Universal Coordinated Time (UTC), Eastern Standard
43region follows, such as Universal Coordinated Time (UTC), Eastern Standard
44Time\/Eastern Daylight Time (EST\/EDT), or India Standard Time (IST).
44Time\/Eastern Daylight Time (EST\/EDT), or India Standard Time (IST).
45−And a timezone, much like a standard time, is a function from
45+And a time zone, much like a standard time, is a function from
46timestamps to offsets.
46timestamps to offsets.
47
47
48−A timezone is different from a standard time because different regions
48+A time zone is different from a standard time because different regions
49−inside a standard time are governed by different civil authorities and
49+inside a standard time can be governed by different civil authorities with
50−have different histories of time. Thus, an IANA timezone is any region
50+different laws and thus have different histories of civil time. An IANA
51−of the world that has had the same history of time since
51+time zone is any region of the world that has had the same history of
52−@1970-01-01 00:00+00@.
52+civil time since @1970-01-01 00:00+00@.
53
53
54For example, as of today, both @America\/New_York@ and
54For example, as of today, both @America\/New_York@ and
55@America\/Indiana\/Indianapolis@ are on the EST\/EDT time standard, but
55@America\/Indiana\/Indianapolis@ are on the EST\/EDT time standard, but
56Indiana used to be on Central Standard Time until 1942, and did not observe
56Indiana used to be on Central Standard Time until 1942, and did not observe
57daylight savings time (EST only) until 2006. Thus, the choice between
57daylight savings time (EST only) until 2006. Thus, the choice between
58−these two timezones still matters if you are dealing with timestamps
58+these two time zones still matters if you are dealing with timestamps
59prior to 2006, and could become relevant again if (most of) Indiana
59prior to 2006, and could become relevant again if (most of) Indiana
60moves back to Central Time. (Of course, if the Central to Eastern switch
60moves back to Central Time. (Of course, if the Central to Eastern switch
61−was the only difference, then these two timezones would be the same in
61+was the only difference, then these two time zones would be the same in
62IANA's eyes, due to their cutoff date of 1970-01-01.)
62IANA's eyes, due to their cutoff date of 1970-01-01.)
63
63
64−Getting back to the coding practicalities, PostgreSQL's @timestamptz@
64+Getting back to practicalities, PostgreSQL's @timestamptz@ type does not
65−type does not actually store an offset; rather, it uses the offset
65+actually store an offset; rather, it uses the offset provided to calculate
66−provided to calculate UTC, and stores the timestamp as UTC. If an
66+UTC, and stores the timestamp as UTC. If an offset is not provided, the
67−offset is not provided, the given timestamp is assumed to be a local
67+given timestamp is assumed to be a local time for whatever the @timezone@
68−time for whatever the @timezone@ variable is set to, and the IANA TZ
69−database is consulted to calculate an offset from UTC for the time in
68+variable is set to, and the IANA TZ database is consulted to calculate an
70−question. Note that some local timestamps are ambiguous, in which
69+offset from UTC for the time in question.
71−case PostgreSQL chooses one of the possible offsets.
70+
72−
71+Note that while most (local timestamp, time zone) pairs correspond to exactly
72+one UTC timestamp, some correspond to two UTC timestamps, while others
73+correspond to none at all. The ambiguous case occurs when the civil time
74+is rolled back, making a calendar day longer than 24 hours. In this case,
75+PostgreSQL silently chooses the second, later possibility. The inconsistent
76+case occurs when the civil time is moved forward, making a calendar day less
77+than 24 hours. In this case, PostgreSQL silently assumes the local time
78+was read off a clock that had not been moved forward at the prescribed time,
79+and moves the clock forward for you. Thus, converting from local time
80+to UTC need not be monotonic, if these inconsistent cases are allowed.
81+
73−When retrieving a @timestamptz@, the backend looks at the @timezone@
82+When retrieving a @timestamptz@, the backend looks at the @time zone@
74connection variable and then consults the IANA TZ database to calculate
83connection variable and then consults the IANA TZ database to calculate
75−an offset for the timestamp in the given timezone. Note that while some
84+an offset for the timestamp in the given time zone.
85+
76−of the information contained in the IANA TZ database is a bit of a
86+Note that while some of the information contained in the IANA TZ database
77−standardized fiction, the conversion from UTC time to a
87+is a bit of a standardized fiction, the conversion from UTC time to a
78−(local time, offset) pair is always unambiguous, and the result can
88+(local time, offset) pair in a particular time zone is always unambiguous,
79−always be unambiguously converted back to UTC. Thus, postgresql-simple
89+and the result can always be unambiguously converted back to UTC. Thus,
80−can interpret this result as a 'Data.Time.ZonedTime', or use the offset to
90+postgresql-simple can interpret such a result as a 'Data.Time.ZonedTime',
81−convert to 'Data.Time.UTCTime'.
91+or use the offset to convert back to 'Data.Time.UTCTime'.
82
92
83By contrast, the @timestamp@ type ignores any offsets provided to it,
93By contrast, the @timestamp@ type ignores any offsets provided to it,
84and never sends back an offset. Thus, postgresql-simple equates this
94and never sends back an offset. Thus, postgresql-simple equates this
@@ -87,55 +97,68 @@ convert between @timestamptz@ and @timestamp@ using the @AT TIME ZONE@
87operator, whose semantics also demonstrates that @timestamptz@ is
97operator, whose semantics also demonstrates that @timestamptz@ is
88'Data.Time.UTCTime' whereas @timestamp@ is 'Data.Time.LocalTime'.
98'Data.Time.UTCTime' whereas @timestamp@ is 'Data.Time.LocalTime'.
89
99
90−The default default setting for the @timezone@ variable is @\'localtime\'@,
100+PostgreSQL's @timezone@ is a per-connection variable that by default is
91−which normally corresponds to the server's local timezone. However,
101+initialized to @\'localtime\'@, which normally corresponds to the server's
92−this default can be modified on the server side for an entire cluster, or
102+time zone. However, this default can be modified on the server side for an
93−on a per-user or per-database basis. On the client side, the timezone can
103+entire cluster, or on a per-user or per-database basis. Moreover, a client
94−be set via the @PGTZ@ environment variable on the client, or be modified at
104+can modify their instance of the variable at any time, and can apply that
95−any time for the duration of the entire connection, just a single
105+change to the remaining duration of the connection, the current transaction,
96−transaction, or the execution context of a server-side function.
106+or the execution context of a server-side function. In addition, upon
107+connection initialization, the libpq client checks for the existence of
108+the @PGTZ@ environment variable, and if it exists, modifies @timezone@
109+accordingly.
97
110
98With a few caveats, postgresql-simple is designed so that you can both send
111With a few caveats, postgresql-simple is designed so that you can both send
99and receive timestamps with the server and get a correct result, no matter
112and receive timestamps with the server and get a correct result, no matter
100what the @timezone@ setting is. But it is important to understand the caveats:
113what the @timezone@ setting is. But it is important to understand the caveats:
101
114
1021. The correctness of server-side computations can depend on the @timezone@
1151. The correctness of server-side computations can depend on the @timezone@
103− setting, for example when adding an @interval@ to a @timestamptz@, or
116+ setting. Examples include adding an @interval@ to a @timestamptz@, or
104− when type casting between @timestamp@ and @timestamptz@.
117+ type casting between @timestamp@ and @timestamptz@, or applying
118+ the @DATE@ function to a @timestamptz@.
105
119
106−2. The offset in a 'Data.Time.ZonedTime' result will depend on the
120+2. The (localtime, offset) pair contained in a 'Data.Time.ZonedTime' result
107− @timezone@ setting, although the result will always represent the
121+ will depend on the @timezone@ setting, although the result will always
108− same instant in time regardless of the offset.
122+ represent the same instant in time regardless of the time zone.
109
123
1103. Sending a 'Data.Time.LocalTime' and interpreting it as a @timestamptz@
1243. Sending a 'Data.Time.LocalTime' and interpreting it as a @timestamptz@
111 can be useful, as it will be converted to UTC via the tz database,
125 can be useful, as it will be converted to UTC via the tz database,
112 but correctness will depend on the @timezone@ setting. You may prefer
126 but correctness will depend on the @timezone@ setting. You may prefer
113− to use an explicit @AT TIME ZONE@ instead.
127+ to use an explicit @AT TIME ZONE@ conversion instead, which would avoid
128+ this contextual dependence.
114
129
115−Furthermore, although these points don't involve the @timezone@ setting,
130+Furthermore, although these following points don't involve the @timezone@
116−they are related to the last point above:
131+setting, they are related to the last point above:
117
132
1181. Sending a 'Data.Time.UTCTime' and interpreting it as a @timestamp@ can
1331. Sending a 'Data.Time.UTCTime' and interpreting it as a @timestamp@ can
119 be useful. In practice, the most common context used to disambiguate
134 be useful. In practice, the most common context used to disambiguate
120 @timestamp@ is that it represents UTC, and this coding technique will
135 @timestamp@ is that it represents UTC, and this coding technique will
121− work as expected in this context.
136+ work as expected in this situation.
122
137
1232. Sending a 'Data.Time.ZonedTime' and interpreting it as a @timestamp@ is
1382. Sending a 'Data.Time.ZonedTime' and interpreting it as a @timestamp@ is
124 almost always the wrong thing to do, as the offset will be ignored and
139 almost always the wrong thing to do, as the offset will be ignored and
125 discarded. This is likely to lead to inconsistencies in the database,
140 discarded. This is likely to lead to inconsistencies in the database,
126− and may lead to partial data loss. Moreover, one should keep in mind
141+ and may lead to partial data loss.
127− that "ZonedTime" is misnamed, and one should not conflate an offset
142+
128− with a timezone.
143+When dealing with local timestamps that refer to the future, it is often
144+useful to store it as a local time in a @timestamp@ column and store the
145+time zone in a second column. One reason to do this is so that you can
146+convert to UTC on the fly as needed, and be protected against future changes
147+to the TZ database due to changes in local time standards. In any case,
148+'Data.Time.ZonedTime' is not suitable for this application, because despite
149+its name, it represents an offset and not a time zone. Time zones can change;
150+offsets do not. In reality, we can't convert a local timestamp that occurs
151+sufficiently far in the future to UTC, because we don't know how to do it yet.
129
152
130There are a few limitations and caveats that one might need to be aware
153There are a few limitations and caveats that one might need to be aware
131of with the current implementation when dealing with older timestamps:
154of with the current implementation when dealing with older timestamps:
132
155
133−For sufficiently old timestamps in almost all timezones, the IANA TZ
156+For sufficiently old timestamps in almost all time zones, the IANA TZ
134database specifies offsets from UTC that is not an integral number of
157database specifies offsets from UTC that is not an integral number of
135minutes. This corresponds to local mean time; that is, astronomical
158minutes. This corresponds to local mean time; that is, astronomical
136−time in the city that defines the timezone. Different timezones moved
159+time in the city that defines the time zone. Different time zones moved
137away from local mean time to a standard time at different points in
160away from local mean time to a standard time at different points in
138−history, so \"sufficiently old\" depends on the timezone in question.
161+history, so \"sufficiently old\" depends on the time zone in question.
139
162
140Thus, when retrieving a @timestamptz@ postgresql will in some cases
163Thus, when retrieving a @timestamptz@ postgresql will in some cases
141provide seconds in the offset. For example:
164provide seconds in the offset. For example:
@@ -176,7 +199,7 @@ generate this syntax. Unfortunately this syntax isn't convenient to
176print or especially parse.
199print or especially parse.
177
200
178Also, postgresql itself cannot parse or print dates before @4714-11-24 BC@,
201Also, postgresql itself cannot parse or print dates before @4714-11-24 BC@,
179− which is the Julian date on the proleptic Gregorian Calendar. Although
202+which is the Julian date on the proleptic Gregorian Calendar. Although
180postgresql's timestamp types are perfectly capable of representing timestamps
203postgresql's timestamp types are perfectly capable of representing timestamps
181nearly 300,000 years in the past, using this would require postgresql-simple
204nearly 300,000 years in the past, using this would require postgresql-simple
182and other client programs to support binary parameters and results.
205and other client programs to support binary parameters and results.