source: webkit/trunk/JavaScriptCore/kjs/DateMath.cpp@ 18337

Last change on this file since 18337 was 17520, checked in by kmccullo, 19 years ago

Reviewed by Brady.

  • Fixes many JavaScriptCore tests in other timezones. The root problem is that on mac localtime() returns historically accurate information for DST, but the JavaScript spec explicitly states to not take into account historical information but rather to interpolate from valid years.
  • kjs/DateMath.cpp: (KJS::equivalentYearForDST): (KJS::getDSTOffsetSimple): (KJS::getDSTOffset):
File size: 11.1 KB
Line 
1/*
2 * Copyright (C) 1999-2000 Harri Porten ([email protected])
3 * Copyright (C) 2006 Apple Computer
4 *
5 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 *
7 * The contents of this file are subject to the Mozilla Public License Version
8 * 1.1 (the "License"); you may not use this file except in compliance with
9 * the License. You may obtain a copy of the License at
10 * http://www.mozilla.org/MPL/
11 *
12 * Software distributed under the License is distributed on an "AS IS" basis,
13 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
14 * for the specific language governing rights and limitations under the
15 * License.
16 *
17 * The Original Code is Mozilla Communicator client code, released
18 * March 31, 1998.
19 *
20 * The Initial Developer of the Original Code is
21 * Netscape Communications Corporation.
22 * Portions created by the Initial Developer are Copyright (C) 1998
23 * the Initial Developer. All Rights Reserved.
24 *
25 * Contributor(s):
26 *
27 * Alternatively, the contents of this file may be used under the terms of
28 * either of the GNU General Public License Version 2 or later (the "GPL"),
29 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
30 * in which case the provisions of the GPL or the LGPL are applicable instead
31 * of those above. If you wish to allow use of your version of this file only
32 * under the terms of either the GPL or the LGPL, and not to allow others to
33 * use your version of this file under the terms of the MPL, indicate your
34 * decision by deleting the provisions above and replace them with the notice
35 * and other provisions required by the GPL or the LGPL. If you do not delete
36 * the provisions above, a recipient may use your version of this file under
37 * the terms of any one of the MPL, the GPL or the LGPL.
38 *
39 */
40
41#include "config.h"
42#include "DateMath.h"
43
44#include <math.h>
45#include <stdint.h>
46#include <wtf/OwnPtr.h>
47
48namespace KJS {
49
50/* Constants */
51
52static const double minutesPerDay = 24.0 * 60.0;
53static const double secondsPerDay = 24.0 * 60.0 * 60.0;
54static const double secondsPerYear = 24.0 * 60.0 * 60.0 * 365.0;
55
56static const double usecPerSec = 1000000.0;
57
58static const double maxUnixTime = 2145859200.0; /*equivalent to 12/31/2037 */
59
60/*
61 * The following array contains the day of year for the first day of
62 * each month, where index 0 is January, and day 0 is January 1.
63 */
64static int firstDayOfMonth[2][12] = {
65 {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334},
66 {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335}
67};
68
69static inline int daysInYear(int year)
70{
71 if (year % 4 != 0)
72 return 365;
73 if (year % 400 == 0)
74 return 366;
75 if (year % 100 == 0)
76 return 365;
77 return 366;
78}
79
80static inline double daysFrom1970ToYear(int year)
81{
82 return 365.0 * (year - 1970)
83 + floor((year - 1969) / 4.0)
84 - floor((year - 1901) / 100.0)
85 + floor((year - 1601) / 400.0);
86}
87
88static inline double msFrom1970ToYear(int year)
89{
90 return msPerDay * daysFrom1970ToYear(year);
91}
92
93static inline double msToDays(double ms)
94{
95 return floor(ms / msPerDay);
96}
97
98static inline int msToYear(double ms)
99{
100 int y = static_cast<int>(floor(ms /(msPerDay*365.2425)) + 1970);
101 double t2 = msFrom1970ToYear(y);
102
103 if (t2 > ms) {
104 y--;
105 } else {
106 if (t2 + msPerDay * daysInYear(y) <= ms)
107 y++;
108 }
109 return y;
110}
111
112static inline bool isLeapYear(int year)
113{
114 if (year % 4 != 0)
115 return false;
116 if (year % 400 == 0)
117 return true;
118 if (year % 100 == 0)
119 return false;
120 return true;
121}
122
123static inline bool isInLeapYear(double ms)
124{
125 return isLeapYear(msToYear(ms));
126}
127
128static inline int dayInYear(double ms, int year)
129{
130 return static_cast<int>(msToDays(ms) - daysFrom1970ToYear(year));
131}
132
133static inline double msToMilliseconds(double ms)
134{
135 double result;
136 result = fmod(ms, msPerDay);
137 if (result < 0)
138 result += msPerDay;
139 return result;
140}
141
142// 0: Sunday, 1: Monday, etc.
143static inline int msToWeekDay(double ms)
144{
145 int wd = ((int)msToDays(ms) + 4) % 7;
146 if (wd < 0)
147 wd += 7;
148 return wd;
149}
150
151static inline int msToSeconds(double ms)
152{
153 int result = (int) fmod(floor(ms / msPerSecond), secondsPerMinute);
154 if (result < 0)
155 result += (int)secondsPerMinute;
156 return result;
157}
158
159static inline int msToMinutes(double ms)
160{
161 int result = (int) fmod(floor(ms / msPerMinute), minutesPerHour);
162 if (result < 0)
163 result += (int)minutesPerHour;
164 return result;
165}
166
167static inline int msToHours(double ms)
168{
169 int result = (int) fmod(floor(ms/msPerHour), hoursPerDay);
170 if (result < 0)
171 result += (int)hoursPerDay;
172 return result;
173}
174
175static inline int msToMonth(double ms)
176{
177 int d, step;
178 int year = msToYear(ms);
179 d = dayInYear(ms, year);
180
181 if (d < (step = 31))
182 return 0;
183 step += (isInLeapYear(ms) ? 29 : 28);
184 if (d < step)
185 return 1;
186 if (d < (step += 31))
187 return 2;
188 if (d < (step += 30))
189 return 3;
190 if (d < (step += 31))
191 return 4;
192 if (d < (step += 30))
193 return 5;
194 if (d < (step += 31))
195 return 6;
196 if (d < (step += 31))
197 return 7;
198 if (d < (step += 30))
199 return 8;
200 if (d < (step += 31))
201 return 9;
202 if (d < (step += 30))
203 return 10;
204 return 11;
205}
206
207static inline int msToDayInMonth(double ms)
208{
209 int d, step, next;
210 int year = msToYear(ms);
211 d = dayInYear(ms, year);
212
213 if (d <= (next = 30))
214 return d + 1;
215 step = next;
216 next += (isInLeapYear(ms) ? 29 : 28);
217 if (d <= next)
218 return d - step;
219 step = next;
220 if (d <= (next += 31))
221 return d - step;
222 step = next;
223 if (d <= (next += 30))
224 return d - step;
225 step = next;
226 if (d <= (next += 31))
227 return d - step;
228 step = next;
229 if (d <= (next += 30))
230 return d - step;
231 step = next;
232 if (d <= (next += 31))
233 return d - step;
234 step = next;
235 if (d <= (next += 31))
236 return d - step;
237 step = next;
238 if (d <= (next += 30))
239 return d - step;
240 step = next;
241 if (d <= (next += 31))
242 return d - step;
243 step = next;
244 if (d <= (next += 30))
245 return d - step;
246 step = next;
247 return d - step;
248}
249
250static inline int monthToDayInYear(int month, bool isLeapYear)
251{
252 return firstDayOfMonth[isLeapYear][month];
253}
254
255static inline double timeToMS(double hour, double min, double sec, double ms)
256{
257 return (((hour * minutesPerHour + min) * secondsPerMinute + sec) * msPerSecond + ms);
258}
259
260static int dateToDayInYear(int year, int month, int day)
261{
262 year += month / 12;
263
264 month %= 12;
265 if (month < 0) {
266 month += 12;
267 --year;
268 }
269
270 int yearday = static_cast<int>(floor(msFrom1970ToYear(year) / msPerDay));
271 int monthday = monthToDayInYear(month, isLeapYear(year));
272
273 return yearday + monthday + day - 1;
274}
275
276/*
277 * Find a year for which any given date will fall on the same weekday.
278 *
279 * This function should be used with caution when used other than
280 * for determining DST; it hasn't been proven not to produce an
281 * incorrect year for times near year boundaries.
282 */
283int equivalentYearForDST(int year)
284{
285 int difference = 2000 - year; // Arbitrary year around which most dates equivalence is correct
286 int quotient = difference / 28; // Integer division, no remainder.
287 int product = quotient * 28;
288 return year + product;
289}
290
291/*
292 * Get the difference in milliseconds between this time zone and UTC (GMT)
293 * NOT including DST.
294 */
295double getUTCOffset() {
296 static double utcOffset;
297 static bool utcOffsetInitialized = false;
298 if (!utcOffsetInitialized) {
299 tm localt;
300
301 memset(&localt, 0, sizeof(localt));
302
303 // get the difference between this time zone and GMT
304 localt.tm_mday = 2;
305 localt.tm_year = 70;
306
307 utcOffset = mktime(&localt) - (hoursPerDay * secondsPerHour);
308 utcOffset *= -msPerSecond;
309
310 utcOffsetInitialized = true;
311 }
312 return utcOffset;
313}
314
315/*
316 * Get the DST offset for the time passed in. Takes
317 * seconds (not milliseconds) and cannot handle dates before 1970
318 * on some OS'
319 */
320static double getDSTOffsetSimple(double localTimeSeconds)
321{
322 if(localTimeSeconds > maxUnixTime)
323 localTimeSeconds = maxUnixTime;
324 else if(localTimeSeconds < 0) // Go ahead a day to make localtime work (does not work with 0)
325 localTimeSeconds += secondsPerDay;
326
327 //input is UTC so we have to shift back to local time to determine DST thus the + getUTCOffset()
328 double offsetTime = (localTimeSeconds * msPerSecond) + getUTCOffset() ;
329
330 // Offset from UTC but doesn't include DST obviously
331 int offsetHour = msToHours(offsetTime);
332 int offsetMinute = msToMinutes(offsetTime);
333
334 // FIXME: time_t has a potential problem in 2038
335 time_t localTime = static_cast<time_t>(localTimeSeconds);
336
337 tm localTM;
338 #if PLATFORM(WIN_OS)
339 localtime_s(&localTM, &localTime);
340 #else
341 localtime_r(&localTime, &localTM);
342 #endif
343
344 double diff = ((localTM.tm_hour - offsetHour) * secondsPerHour) + ((localTM.tm_min - offsetMinute) * 60);
345
346 if(diff < 0)
347 diff += secondsPerDay;
348
349 return (diff * msPerSecond);
350}
351
352// Get the DST offset the time passed in
353// ms is in UTC
354static double getDSTOffset(double ms)
355{
356 // On mac the call to localtime (see getDSTOffsetSimple) will return historically accurate
357 // DST information (e.g. New Zealand did not have DST from 1946 to 1974) however the JavaScript
358 // standard explicitly dictates that historical information should not be considered when
359 // determining DST. For this reason we shift years that localtime can handle but would
360 // return historically accurate information.
361
362 //if before 2000 or after 2038
363 if (ms < 946684800000.0 || ms > 2145916800000.0) {
364 int year;
365 int day;
366
367 year = equivalentYearForDST(msToYear(ms));
368 day = dateToDayInYear(year, msToMonth(ms), msToDayInMonth(ms));
369 ms = (day * msPerDay) + msToMilliseconds(ms);
370 }
371
372 return getDSTOffsetSimple(ms / msPerSecond);
373}
374
375double gregorianDateTimeToMS(const GregorianDateTime& t, double milliSeconds, bool inputIsUTC)
376{
377
378 int day = dateToDayInYear(t.year + 1900, t.month, t.monthDay);
379 double ms = timeToMS(t.hour, t.minute, t.second, milliSeconds);
380 double result = (day * msPerDay) + ms;
381
382 if(!inputIsUTC) { // convert to UTC
383 result -= getUTCOffset();
384 result -= getDSTOffset(result);
385 }
386
387 return result;
388}
389
390void msToGregorianDateTime(double ms, bool outputIsUTC, struct GregorianDateTime& tm)
391{
392 // input is UTC
393 double dstOff = 0.0;
394
395 if(!outputIsUTC) { // convert to local time
396 dstOff = getDSTOffset(ms);
397 ms += dstOff + getUTCOffset();
398 }
399
400 tm.second = msToSeconds(ms);
401 tm.minute = msToMinutes(ms);
402 tm.hour = msToHours(ms);
403 tm.weekDay = msToWeekDay(ms);
404 tm.monthDay = msToDayInMonth(ms);
405 tm.yearDay = dayInYear(ms, msToYear(ms));
406 tm.month = msToMonth(ms);
407 tm.year = msToYear(ms) - 1900;
408 tm.isDST = dstOff != 0.0;
409
410 tm.utcOffset = static_cast<long>((dstOff + getUTCOffset()) / msPerSecond);
411 tm.timeZone = NULL;
412}
413
414} // namespace KJS
415
Note: See TracBrowser for help on using the repository browser.