Harden failure signal handler in the face of memory corruptions
[folly.git] / folly / experimental / cron / date_time_utils.h
1 /*
2  * Copyright 2014 Facebook, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *   http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 /**
18  * This is a small extension on top of boost::date_time, which handles
19  * conversions between "local time labels" (e.g.  "2am, March 10, 2013") and
20  * POSIX UTC timestamps.
21  *
22  * Our library hides two sources of complexity:
23  *
24  *  - Time zones. You can easily use the system time zone (pass a NULL
25  *    pointer), or provide a boost::time_zone_ptr (usually created via a
26  *    POSIX-like timezone format, or from the boost::date_time timezone DB).
27  *
28  *  - The one-to-many relationship between time labels and UTC timestamps.
29  *
30  *    UTC timestamps are effectively monotonic (aside from leap seconds,
31  *    which are ignored in POSIX time, and are irrelevant for Cron).
32  *
33  *    Local time labels, on the other hand, can move forward or backward due
34  *    to daylight-savings changes.  Thus, when the local clock rewinds due
35  *    to DST, some local time labels become ambiguous (is it 1:30am before
36  *    or after the DST rewind?).  When the local time moves forward due to
37  *    DST, some local time labels are skipped (in the US Pacific timezone,
38  *    2:30am never happened on March 10, 2013).
39  *
40  *    As a consequence, timezoneLocalPTimeToUTCTimestamps() returns a struct
41  *    UTCTimestampsForLocalTime that can represent 0, 1, or 2 UTC timestamps.
42  *
43  *    The ambiguity could be avoided by adding an 'is_dst' flag to the local
44  *    time label, but this is not useful for the purposes of Cron (and is
45  *    handled adequately in existing libraries).
46  *
47  *    Going from UTC to a local time label is easy and unambiguous, see
48  *    utcPTimeToTimezoneLocalPTime().
49  *
50  * CAVEAT: We use boost::posix_time::ptime to represent both UTC timestamps,
51  * *and* local time labels.  This is confusing -- it would be better if
52  * local time labels should used a separate type.  However, a ptime is very
53  * convenient for the purpose, since it supports all the usual time
54  * operations you might want to do.  Patches are welcome.
55  *
56  * Our library thus accounts for the following deficiencies of
57  * boost::date_time:
58  *
59  *  - boost::date_time has almost no support for the system timezone (the only
60  *    related feature is the hacky "c_local_adjustor"). In contrast, our
61  *    library interprets a time_zone_ptr value of NULL as referring to the
62  *    system timezone, and then does the right thing.
63  *
64  *  - boost::date_time has a rather annoying exception-based API for
65  *    determining whether a local time label is ambiguous, nonexistent, or
66  *    unique.  Our struct is much more usable.
67  */
68
69 #pragma once
70
71 #include <boost/date_time/local_time/local_time_types.hpp>
72 #include <boost/date_time/posix_time/posix_time_types.hpp>
73 #include <ctime>
74 #include <stdexcept>
75 #include <utility>
76
77 #include "folly/Format.h"
78
79 namespace folly { namespace cron {
80
81 /**
82  * How many seconds must be added to UTC in order to get the local time for
83  * the given time point?
84  */
85 time_t getUTCOffset(time_t utc_time, boost::local_time::time_zone_ptr tz);
86
87 /**
88  * Convert a UTC ptime into a timezone-local ptime.
89  *
90  * If tz is a null pointer, use the local timezone.
91  *
92  * This is a lossy transformation, since the UTC offset of a timezone
93  * is not constant -- see timezoneLocalPTimeToUTCTimestamps()
94  * for a detailed explanation.
95  */
96 boost::posix_time::ptime utcPTimeToTimezoneLocalPTime(
97   boost::posix_time::ptime utc_pt,
98   boost::local_time::time_zone_ptr tz
99 );
100
101 /**
102  * A local time label can correspond to 0, 1, or 2 UTC timestamps due to
103  * DST time shifts:
104  *  - If the clock went back and your label lands in the repeated interval,
105  *    you'll get both timestamps.
106  *  - If the clock went forward, and your label landed in the skipped time,
107  *    you get neither.
108  *  - For all other labels you get exactly one timestamp.
109  * See also timezoneLocalPTimeToUTCTimestamps().
110  */
111 struct UTCTimestampsForLocalTime {
112   static const time_t kNotATime = -1;  // Might not portable, but easy.
113
114   UTCTimestampsForLocalTime() : dst_time{kNotATime}, non_dst_time{kNotATime} {}
115
116   bool isNotATime() const {
117     return dst_time == kNotATime && non_dst_time == kNotATime;
118   }
119
120   bool isAmbiguous() const {
121     return dst_time != kNotATime && non_dst_time != kNotATime;
122   }
123
124   time_t getUnique() const {
125     if (isAmbiguous()) {
126       throw std::runtime_error(folly::format(
127         "Local time maps to both {} and {}", dst_time, non_dst_time
128       ).str());
129     } else if (dst_time != kNotATime) {
130       return dst_time;
131     } else if (non_dst_time != kNotATime) {
132       return non_dst_time;
133     } else {
134       throw std::runtime_error("This local time was skipped due to DST");
135     }
136   }
137
138   /**
139    * For ambiguous local time labels, return the pair of (lesser UTC
140    * timestamp, greater UTC timestamp).
141    *
142    * NOTE: This may not be strictly necessary, since DST is probably less
143    * than non-DST in all real timezones, but it's better to be safe than
144    * sorry.
145    *
146    * More specifically, the POSIX timezone specification (IEEE Std 1003.1)
147    * allows DST to be either ahead or behind of the regular timezone, so the
148    * local timezone could shift either way.  The docs for
149    * boost::local_time::posix_time_zone (which is not even a POSIX-compliant
150    * implementation, see README) are ambiguous, but can be read as intending
151    * to forbid DST that sets the clock backwards.
152    */
153   std::pair<time_t, time_t> getBothInOrder() const {
154     if (!isAmbiguous()) {
155       throw std::runtime_error(folly::format(
156         "{} and {} is not ambiguous", dst_time, non_dst_time
157       ).str());
158     }
159     if (dst_time < non_dst_time) {
160       return std::make_pair(dst_time, non_dst_time);
161     }
162     return std::make_pair(non_dst_time, dst_time);
163   }
164
165   time_t dst_time;
166   time_t non_dst_time;
167 };
168
169 /**
170  * Convert a timezone-local ptime into UTC epoch timestamp(s).
171  *
172  * If tz is a null pointer, use the local timezone.
173  *
174  * WARNING 1: When DST sets back the clock, some local times become
175  * ambiguous -- you cannot tell if the timestamp lies before or after the
176  * DST change.  For example, "November 3 01:30:00 2013" could be either PST
177  * or PDT, with a difference of one hour.
178  *
179  * WARNING 2: You can inadvertently make a local time that does not exist
180  * because a daylight savings change skips that time period.
181  */
182 UTCTimestampsForLocalTime timezoneLocalPTimeToUTCTimestamps(
183   boost::posix_time::ptime local_pt,
184   boost::local_time::time_zone_ptr tz
185 );
186
187 }}