-
Notifications
You must be signed in to change notification settings - Fork 22
/
Timezone.cpp
243 lines (219 loc) · 10.3 KB
/
Timezone.cpp
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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
/*----------------------------------------------------------------------*
* Arduino Timezone Library *
* Jack Christensen Mar 2012 *
* *
* Arduino Timezone Library Copyright (C) 2018 by Jack Christensen and *
* licensed under GNU GPL v3.0, https://www.gnu.org/licenses/gpl.html *
*----------------------------------------------------------------------*/
#include "Timezone.h"
#ifdef __AVR__
#include <avr/eeprom.h>
#endif
/*----------------------------------------------------------------------*
* Create a Timezone object from the given time change rules. *
*----------------------------------------------------------------------*/
Timezone::Timezone(TimeChangeRule dstStart, TimeChangeRule stdStart)
: m_dst(dstStart), m_std(stdStart)
{
initTimeChanges();
}
/*----------------------------------------------------------------------*
* Create a Timezone object for a zone that does not observe *
* daylight time. *
*----------------------------------------------------------------------*/
Timezone::Timezone(TimeChangeRule stdTime)
: m_dst(stdTime), m_std(stdTime)
{
initTimeChanges();
}
#ifdef __AVR__
/*----------------------------------------------------------------------*
* Create a Timezone object from time change rules stored in EEPROM *
* at the given address. *
*----------------------------------------------------------------------*/
Timezone::Timezone(int address)
{
readRules(address);
}
#endif
/*----------------------------------------------------------------------*
* Convert the given UTC time to local time, standard or *
* daylight time, as appropriate. *
*----------------------------------------------------------------------*/
time_t Timezone::toLocal(time_t utc)
{
// recalculate the time change points if needed
if (year(utc) != year(m_dstUTC)) calcTimeChanges(year(utc));
if (utcIsDST(utc))
return utc + m_dst.offset * SECS_PER_MIN;
else
return utc + m_std.offset * SECS_PER_MIN;
}
/*----------------------------------------------------------------------*
* Convert the given UTC time to local time, standard or *
* daylight time, as appropriate, and return a pointer to the time *
* change rule used to do the conversion. The caller must take care *
* not to alter this rule. *
*----------------------------------------------------------------------*/
time_t Timezone::toLocal(time_t utc, TimeChangeRule **tcr)
{
// recalculate the time change points if needed
if (year(utc) != year(m_dstUTC)) calcTimeChanges(year(utc));
if (utcIsDST(utc)) {
*tcr = &m_dst;
return utc + m_dst.offset * SECS_PER_MIN;
}
else {
*tcr = &m_std;
return utc + m_std.offset * SECS_PER_MIN;
}
}
/*----------------------------------------------------------------------*
* Convert the given local time to UTC time. *
* *
* WARNING: *
* This function is provided for completeness, but should seldom be *
* needed and should be used sparingly and carefully. *
* *
* Ambiguous situations occur after the Standard-to-DST and the *
* DST-to-Standard time transitions. When changing to DST, there is *
* one hour of local time that does not exist, since the clock moves *
* forward one hour. Similarly, when changing to standard time, there *
* is one hour of local times that occur twice since the clock moves *
* back one hour. *
* *
* This function does not test whether it is passed an erroneous time *
* value during the Local -> DST transition that does not exist. *
* If passed such a time, an incorrect UTC time value will be returned. *
* *
* If passed a local time value during the DST -> Local transition *
* that occurs twice, it will be treated as the earlier time, i.e. *
* the time that occurs before the transistion. *
* *
* Calling this function with local times during a transition interval *
* should be avoided! *
*----------------------------------------------------------------------*/
time_t Timezone::toUTC(time_t local)
{
// recalculate the time change points if needed
if (year(local) != year(m_dstLoc)) calcTimeChanges(year(local));
if (locIsDST(local))
return local - m_dst.offset * SECS_PER_MIN;
else
return local - m_std.offset * SECS_PER_MIN;
}
/*----------------------------------------------------------------------*
* Determine whether the given UTC time_t is within the DST interval *
* or the Standard time interval. *
*----------------------------------------------------------------------*/
bool Timezone::utcIsDST(time_t utc)
{
// recalculate the time change points if needed
if (year(utc) != year(m_dstUTC)) calcTimeChanges(year(utc));
if (m_stdUTC == m_dstUTC) // daylight time not observed in this tz
return false;
else if (m_stdUTC > m_dstUTC) // northern hemisphere
return (utc >= m_dstUTC && utc < m_stdUTC);
else // southern hemisphere
return !(utc >= m_stdUTC && utc < m_dstUTC);
}
/*----------------------------------------------------------------------*
* Determine whether the given Local time_t is within the DST interval *
* or the Standard time interval. *
*----------------------------------------------------------------------*/
bool Timezone::locIsDST(time_t local)
{
// recalculate the time change points if needed
if (year(local) != year(m_dstLoc)) calcTimeChanges(year(local));
if (m_stdUTC == m_dstUTC) // daylight time not observed in this tz
return false;
else if (m_stdLoc > m_dstLoc) // northern hemisphere
return (local >= m_dstLoc && local < m_stdLoc);
else // southern hemisphere
return !(local >= m_stdLoc && local < m_dstLoc);
}
/*----------------------------------------------------------------------*
* Calculate the DST and standard time change points for the given *
* given year as local and UTC time_t values. *
*----------------------------------------------------------------------*/
void Timezone::calcTimeChanges(int yr)
{
m_dstLoc = toTime_t(m_dst, yr);
m_stdLoc = toTime_t(m_std, yr);
m_dstUTC = m_dstLoc - m_std.offset * SECS_PER_MIN;
m_stdUTC = m_stdLoc - m_dst.offset * SECS_PER_MIN;
}
/*----------------------------------------------------------------------*
* Initialize the DST and standard time change points. *
*----------------------------------------------------------------------*/
void Timezone::initTimeChanges()
{
m_dstLoc = 0;
m_stdLoc = 0;
m_dstUTC = 0;
m_stdUTC = 0;
}
/*----------------------------------------------------------------------*
* Convert the given time change rule to a time_t value *
* for the given year. *
*----------------------------------------------------------------------*/
time_t Timezone::toTime_t(TimeChangeRule r, int yr)
{
uint8_t m = r.month; // temp copies of r.month and r.week
uint8_t w = r.week;
if (w == 0) // is this a "Last week" rule?
{
if (++m > 12) // yes, for "Last", go to the next month
{
m = 1;
++yr;
}
w = 1; // and treat as first week of next month, subtract 7 days later
}
// calculate first day of the month, or for "Last" rules, first day of the next month
tmElements_t tm;
tm.Hour = r.hour;
tm.Minute = 0;
tm.Second = 0;
tm.Day = 1;
tm.Month = m;
tm.Year = yr - 1970;
time_t t = makeTime(tm);
// add offset from the first of the month to r.dow, and offset for the given week
t += ( (r.dow - weekday(t) + 7) % 7 + (w - 1) * 7 ) * SECS_PER_DAY;
// back up a week if this is a "Last" rule
if (r.week == 0) t -= 7 * SECS_PER_DAY;
return t;
}
/*----------------------------------------------------------------------*
* Read or update the daylight and standard time rules from RAM. *
*----------------------------------------------------------------------*/
void Timezone::setRules(TimeChangeRule dstStart, TimeChangeRule stdStart)
{
m_dst = dstStart;
m_std = stdStart;
initTimeChanges(); // force calcTimeChanges() at next conversion call
}
#ifdef __AVR__
/*----------------------------------------------------------------------*
* Read the daylight and standard time rules from EEPROM at *
* the given address. *
*----------------------------------------------------------------------*/
void Timezone::readRules(int address)
{
eeprom_read_block((void *) &m_dst, (void *) address, sizeof(m_dst));
address += sizeof(m_dst);
eeprom_read_block((void *) &m_std, (void *) address, sizeof(m_std));
initTimeChanges(); // force calcTimeChanges() at next conversion call
}
/*----------------------------------------------------------------------*
* Write the daylight and standard time rules to EEPROM at *
* the given address. *
*----------------------------------------------------------------------*/
void Timezone::writeRules(int address)
{
eeprom_write_block((void *) &m_dst, (void *) address, sizeof(m_dst));
address += sizeof(m_dst);
eeprom_write_block((void *) &m_std, (void *) address, sizeof(m_std));
}
#endif