nsprpub/pr/src/misc/prtime.c

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 3 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 5
michael@0 6 /*
michael@0 7 * prtime.c --
michael@0 8 *
michael@0 9 * NSPR date and time functions
michael@0 10 *
michael@0 11 */
michael@0 12
michael@0 13 #include "prinit.h"
michael@0 14 #include "prtime.h"
michael@0 15 #include "prlock.h"
michael@0 16 #include "prprf.h"
michael@0 17 #include "prlog.h"
michael@0 18
michael@0 19 #include <string.h>
michael@0 20 #include <ctype.h>
michael@0 21 #include <errno.h> /* for EINVAL */
michael@0 22 #include <time.h>
michael@0 23
michael@0 24 /*
michael@0 25 * The COUNT_LEAPS macro counts the number of leap years passed by
michael@0 26 * till the start of the given year Y. At the start of the year 4
michael@0 27 * A.D. the number of leap years passed by is 0, while at the start of
michael@0 28 * the year 5 A.D. this count is 1. The number of years divisible by
michael@0 29 * 100 but not divisible by 400 (the non-leap years) is deducted from
michael@0 30 * the count to get the correct number of leap years.
michael@0 31 *
michael@0 32 * The COUNT_DAYS macro counts the number of days since 01/01/01 till the
michael@0 33 * start of the given year Y. The number of days at the start of the year
michael@0 34 * 1 is 0 while the number of days at the start of the year 2 is 365
michael@0 35 * (which is ((2)-1) * 365) and so on. The reference point is 01/01/01
michael@0 36 * midnight 00:00:00.
michael@0 37 */
michael@0 38
michael@0 39 #define COUNT_LEAPS(Y) ( ((Y)-1)/4 - ((Y)-1)/100 + ((Y)-1)/400 )
michael@0 40 #define COUNT_DAYS(Y) ( ((Y)-1)*365 + COUNT_LEAPS(Y) )
michael@0 41 #define DAYS_BETWEEN_YEARS(A, B) (COUNT_DAYS(B) - COUNT_DAYS(A))
michael@0 42
michael@0 43 /*
michael@0 44 * Static variables used by functions in this file
michael@0 45 */
michael@0 46
michael@0 47 /*
michael@0 48 * The following array contains the day of year for the last day of
michael@0 49 * each month, where index 1 is January, and day 0 is January 1.
michael@0 50 */
michael@0 51
michael@0 52 static const int lastDayOfMonth[2][13] = {
michael@0 53 {-1, 30, 58, 89, 119, 150, 180, 211, 242, 272, 303, 333, 364},
michael@0 54 {-1, 30, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365}
michael@0 55 };
michael@0 56
michael@0 57 /*
michael@0 58 * The number of days in a month
michael@0 59 */
michael@0 60
michael@0 61 static const PRInt8 nDays[2][12] = {
michael@0 62 {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
michael@0 63 {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
michael@0 64 };
michael@0 65
michael@0 66 /*
michael@0 67 * Declarations for internal functions defined later in this file.
michael@0 68 */
michael@0 69
michael@0 70 static void ComputeGMT(PRTime time, PRExplodedTime *gmt);
michael@0 71 static int IsLeapYear(PRInt16 year);
michael@0 72 static void ApplySecOffset(PRExplodedTime *time, PRInt32 secOffset);
michael@0 73
michael@0 74 /*
michael@0 75 *------------------------------------------------------------------------
michael@0 76 *
michael@0 77 * ComputeGMT --
michael@0 78 *
michael@0 79 * Caveats:
michael@0 80 * - we ignore leap seconds
michael@0 81 *
michael@0 82 *------------------------------------------------------------------------
michael@0 83 */
michael@0 84
michael@0 85 static void
michael@0 86 ComputeGMT(PRTime time, PRExplodedTime *gmt)
michael@0 87 {
michael@0 88 PRInt32 tmp, rem;
michael@0 89 PRInt32 numDays;
michael@0 90 PRInt64 numDays64, rem64;
michael@0 91 int isLeap;
michael@0 92 PRInt64 sec;
michael@0 93 PRInt64 usec;
michael@0 94 PRInt64 usecPerSec;
michael@0 95 PRInt64 secPerDay;
michael@0 96
michael@0 97 /*
michael@0 98 * We first do the usec, sec, min, hour thing so that we do not
michael@0 99 * have to do LL arithmetic.
michael@0 100 */
michael@0 101
michael@0 102 LL_I2L(usecPerSec, 1000000L);
michael@0 103 LL_DIV(sec, time, usecPerSec);
michael@0 104 LL_MOD(usec, time, usecPerSec);
michael@0 105 LL_L2I(gmt->tm_usec, usec);
michael@0 106 /* Correct for weird mod semantics so the remainder is always positive */
michael@0 107 if (gmt->tm_usec < 0) {
michael@0 108 PRInt64 one;
michael@0 109
michael@0 110 LL_I2L(one, 1L);
michael@0 111 LL_SUB(sec, sec, one);
michael@0 112 gmt->tm_usec += 1000000L;
michael@0 113 }
michael@0 114
michael@0 115 LL_I2L(secPerDay, 86400L);
michael@0 116 LL_DIV(numDays64, sec, secPerDay);
michael@0 117 LL_MOD(rem64, sec, secPerDay);
michael@0 118 /* We are sure both of these numbers can fit into PRInt32 */
michael@0 119 LL_L2I(numDays, numDays64);
michael@0 120 LL_L2I(rem, rem64);
michael@0 121 if (rem < 0) {
michael@0 122 numDays--;
michael@0 123 rem += 86400L;
michael@0 124 }
michael@0 125
michael@0 126 /* Compute day of week. Epoch started on a Thursday. */
michael@0 127
michael@0 128 gmt->tm_wday = (numDays + 4) % 7;
michael@0 129 if (gmt->tm_wday < 0) {
michael@0 130 gmt->tm_wday += 7;
michael@0 131 }
michael@0 132
michael@0 133 /* Compute the time of day. */
michael@0 134
michael@0 135 gmt->tm_hour = rem / 3600;
michael@0 136 rem %= 3600;
michael@0 137 gmt->tm_min = rem / 60;
michael@0 138 gmt->tm_sec = rem % 60;
michael@0 139
michael@0 140 /*
michael@0 141 * Compute the year by finding the 400 year period, then working
michael@0 142 * down from there.
michael@0 143 *
michael@0 144 * Since numDays is originally the number of days since January 1, 1970,
michael@0 145 * we must change it to be the number of days from January 1, 0001.
michael@0 146 */
michael@0 147
michael@0 148 numDays += 719162; /* 719162 = days from year 1 up to 1970 */
michael@0 149 tmp = numDays / 146097; /* 146097 = days in 400 years */
michael@0 150 rem = numDays % 146097;
michael@0 151 gmt->tm_year = tmp * 400 + 1;
michael@0 152
michael@0 153 /* Compute the 100 year period. */
michael@0 154
michael@0 155 tmp = rem / 36524; /* 36524 = days in 100 years */
michael@0 156 rem %= 36524;
michael@0 157 if (tmp == 4) { /* the 400th year is a leap year */
michael@0 158 tmp = 3;
michael@0 159 rem = 36524;
michael@0 160 }
michael@0 161 gmt->tm_year += tmp * 100;
michael@0 162
michael@0 163 /* Compute the 4 year period. */
michael@0 164
michael@0 165 tmp = rem / 1461; /* 1461 = days in 4 years */
michael@0 166 rem %= 1461;
michael@0 167 gmt->tm_year += tmp * 4;
michael@0 168
michael@0 169 /* Compute which year in the 4. */
michael@0 170
michael@0 171 tmp = rem / 365;
michael@0 172 rem %= 365;
michael@0 173 if (tmp == 4) { /* the 4th year is a leap year */
michael@0 174 tmp = 3;
michael@0 175 rem = 365;
michael@0 176 }
michael@0 177
michael@0 178 gmt->tm_year += tmp;
michael@0 179 gmt->tm_yday = rem;
michael@0 180 isLeap = IsLeapYear(gmt->tm_year);
michael@0 181
michael@0 182 /* Compute the month and day of month. */
michael@0 183
michael@0 184 for (tmp = 1; lastDayOfMonth[isLeap][tmp] < gmt->tm_yday; tmp++) {
michael@0 185 }
michael@0 186 gmt->tm_month = --tmp;
michael@0 187 gmt->tm_mday = gmt->tm_yday - lastDayOfMonth[isLeap][tmp];
michael@0 188
michael@0 189 gmt->tm_params.tp_gmt_offset = 0;
michael@0 190 gmt->tm_params.tp_dst_offset = 0;
michael@0 191 }
michael@0 192
michael@0 193
michael@0 194 /*
michael@0 195 *------------------------------------------------------------------------
michael@0 196 *
michael@0 197 * PR_ExplodeTime --
michael@0 198 *
michael@0 199 * Cf. struct tm *gmtime(const time_t *tp) and
michael@0 200 * struct tm *localtime(const time_t *tp)
michael@0 201 *
michael@0 202 *------------------------------------------------------------------------
michael@0 203 */
michael@0 204
michael@0 205 PR_IMPLEMENT(void)
michael@0 206 PR_ExplodeTime(
michael@0 207 PRTime usecs,
michael@0 208 PRTimeParamFn params,
michael@0 209 PRExplodedTime *exploded)
michael@0 210 {
michael@0 211 ComputeGMT(usecs, exploded);
michael@0 212 exploded->tm_params = params(exploded);
michael@0 213 ApplySecOffset(exploded, exploded->tm_params.tp_gmt_offset
michael@0 214 + exploded->tm_params.tp_dst_offset);
michael@0 215 }
michael@0 216
michael@0 217
michael@0 218 /*
michael@0 219 *------------------------------------------------------------------------
michael@0 220 *
michael@0 221 * PR_ImplodeTime --
michael@0 222 *
michael@0 223 * Cf. time_t mktime(struct tm *tp)
michael@0 224 * Note that 1 year has < 2^25 seconds. So an PRInt32 is large enough.
michael@0 225 *
michael@0 226 *------------------------------------------------------------------------
michael@0 227 */
michael@0 228 PR_IMPLEMENT(PRTime)
michael@0 229 PR_ImplodeTime(const PRExplodedTime *exploded)
michael@0 230 {
michael@0 231 PRExplodedTime copy;
michael@0 232 PRTime retVal;
michael@0 233 PRInt64 secPerDay, usecPerSec;
michael@0 234 PRInt64 temp;
michael@0 235 PRInt64 numSecs64;
michael@0 236 PRInt32 numDays;
michael@0 237 PRInt32 numSecs;
michael@0 238
michael@0 239 /* Normalize first. Do this on our copy */
michael@0 240 copy = *exploded;
michael@0 241 PR_NormalizeTime(&copy, PR_GMTParameters);
michael@0 242
michael@0 243 numDays = DAYS_BETWEEN_YEARS(1970, copy.tm_year);
michael@0 244
michael@0 245 numSecs = copy.tm_yday * 86400 + copy.tm_hour * 3600
michael@0 246 + copy.tm_min * 60 + copy.tm_sec;
michael@0 247
michael@0 248 LL_I2L(temp, numDays);
michael@0 249 LL_I2L(secPerDay, 86400);
michael@0 250 LL_MUL(temp, temp, secPerDay);
michael@0 251 LL_I2L(numSecs64, numSecs);
michael@0 252 LL_ADD(numSecs64, numSecs64, temp);
michael@0 253
michael@0 254 /* apply the GMT and DST offsets */
michael@0 255 LL_I2L(temp, copy.tm_params.tp_gmt_offset);
michael@0 256 LL_SUB(numSecs64, numSecs64, temp);
michael@0 257 LL_I2L(temp, copy.tm_params.tp_dst_offset);
michael@0 258 LL_SUB(numSecs64, numSecs64, temp);
michael@0 259
michael@0 260 LL_I2L(usecPerSec, 1000000L);
michael@0 261 LL_MUL(temp, numSecs64, usecPerSec);
michael@0 262 LL_I2L(retVal, copy.tm_usec);
michael@0 263 LL_ADD(retVal, retVal, temp);
michael@0 264
michael@0 265 return retVal;
michael@0 266 }
michael@0 267
michael@0 268 /*
michael@0 269 *-------------------------------------------------------------------------
michael@0 270 *
michael@0 271 * IsLeapYear --
michael@0 272 *
michael@0 273 * Returns 1 if the year is a leap year, 0 otherwise.
michael@0 274 *
michael@0 275 *-------------------------------------------------------------------------
michael@0 276 */
michael@0 277
michael@0 278 static int IsLeapYear(PRInt16 year)
michael@0 279 {
michael@0 280 if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0)
michael@0 281 return 1;
michael@0 282 else
michael@0 283 return 0;
michael@0 284 }
michael@0 285
michael@0 286 /*
michael@0 287 * 'secOffset' should be less than 86400 (i.e., a day).
michael@0 288 * 'time' should point to a normalized PRExplodedTime.
michael@0 289 */
michael@0 290
michael@0 291 static void
michael@0 292 ApplySecOffset(PRExplodedTime *time, PRInt32 secOffset)
michael@0 293 {
michael@0 294 time->tm_sec += secOffset;
michael@0 295
michael@0 296 /* Note that in this implementation we do not count leap seconds */
michael@0 297 if (time->tm_sec < 0 || time->tm_sec >= 60) {
michael@0 298 time->tm_min += time->tm_sec / 60;
michael@0 299 time->tm_sec %= 60;
michael@0 300 if (time->tm_sec < 0) {
michael@0 301 time->tm_sec += 60;
michael@0 302 time->tm_min--;
michael@0 303 }
michael@0 304 }
michael@0 305
michael@0 306 if (time->tm_min < 0 || time->tm_min >= 60) {
michael@0 307 time->tm_hour += time->tm_min / 60;
michael@0 308 time->tm_min %= 60;
michael@0 309 if (time->tm_min < 0) {
michael@0 310 time->tm_min += 60;
michael@0 311 time->tm_hour--;
michael@0 312 }
michael@0 313 }
michael@0 314
michael@0 315 if (time->tm_hour < 0) {
michael@0 316 /* Decrement mday, yday, and wday */
michael@0 317 time->tm_hour += 24;
michael@0 318 time->tm_mday--;
michael@0 319 time->tm_yday--;
michael@0 320 if (time->tm_mday < 1) {
michael@0 321 time->tm_month--;
michael@0 322 if (time->tm_month < 0) {
michael@0 323 time->tm_month = 11;
michael@0 324 time->tm_year--;
michael@0 325 if (IsLeapYear(time->tm_year))
michael@0 326 time->tm_yday = 365;
michael@0 327 else
michael@0 328 time->tm_yday = 364;
michael@0 329 }
michael@0 330 time->tm_mday = nDays[IsLeapYear(time->tm_year)][time->tm_month];
michael@0 331 }
michael@0 332 time->tm_wday--;
michael@0 333 if (time->tm_wday < 0)
michael@0 334 time->tm_wday = 6;
michael@0 335 } else if (time->tm_hour > 23) {
michael@0 336 /* Increment mday, yday, and wday */
michael@0 337 time->tm_hour -= 24;
michael@0 338 time->tm_mday++;
michael@0 339 time->tm_yday++;
michael@0 340 if (time->tm_mday >
michael@0 341 nDays[IsLeapYear(time->tm_year)][time->tm_month]) {
michael@0 342 time->tm_mday = 1;
michael@0 343 time->tm_month++;
michael@0 344 if (time->tm_month > 11) {
michael@0 345 time->tm_month = 0;
michael@0 346 time->tm_year++;
michael@0 347 time->tm_yday = 0;
michael@0 348 }
michael@0 349 }
michael@0 350 time->tm_wday++;
michael@0 351 if (time->tm_wday > 6)
michael@0 352 time->tm_wday = 0;
michael@0 353 }
michael@0 354 }
michael@0 355
michael@0 356 PR_IMPLEMENT(void)
michael@0 357 PR_NormalizeTime(PRExplodedTime *time, PRTimeParamFn params)
michael@0 358 {
michael@0 359 int daysInMonth;
michael@0 360 PRInt32 numDays;
michael@0 361
michael@0 362 /* Get back to GMT */
michael@0 363 time->tm_sec -= time->tm_params.tp_gmt_offset
michael@0 364 + time->tm_params.tp_dst_offset;
michael@0 365 time->tm_params.tp_gmt_offset = 0;
michael@0 366 time->tm_params.tp_dst_offset = 0;
michael@0 367
michael@0 368 /* Now normalize GMT */
michael@0 369
michael@0 370 if (time->tm_usec < 0 || time->tm_usec >= 1000000) {
michael@0 371 time->tm_sec += time->tm_usec / 1000000;
michael@0 372 time->tm_usec %= 1000000;
michael@0 373 if (time->tm_usec < 0) {
michael@0 374 time->tm_usec += 1000000;
michael@0 375 time->tm_sec--;
michael@0 376 }
michael@0 377 }
michael@0 378
michael@0 379 /* Note that we do not count leap seconds in this implementation */
michael@0 380 if (time->tm_sec < 0 || time->tm_sec >= 60) {
michael@0 381 time->tm_min += time->tm_sec / 60;
michael@0 382 time->tm_sec %= 60;
michael@0 383 if (time->tm_sec < 0) {
michael@0 384 time->tm_sec += 60;
michael@0 385 time->tm_min--;
michael@0 386 }
michael@0 387 }
michael@0 388
michael@0 389 if (time->tm_min < 0 || time->tm_min >= 60) {
michael@0 390 time->tm_hour += time->tm_min / 60;
michael@0 391 time->tm_min %= 60;
michael@0 392 if (time->tm_min < 0) {
michael@0 393 time->tm_min += 60;
michael@0 394 time->tm_hour--;
michael@0 395 }
michael@0 396 }
michael@0 397
michael@0 398 if (time->tm_hour < 0 || time->tm_hour >= 24) {
michael@0 399 time->tm_mday += time->tm_hour / 24;
michael@0 400 time->tm_hour %= 24;
michael@0 401 if (time->tm_hour < 0) {
michael@0 402 time->tm_hour += 24;
michael@0 403 time->tm_mday--;
michael@0 404 }
michael@0 405 }
michael@0 406
michael@0 407 /* Normalize month and year before mday */
michael@0 408 if (time->tm_month < 0 || time->tm_month >= 12) {
michael@0 409 time->tm_year += time->tm_month / 12;
michael@0 410 time->tm_month %= 12;
michael@0 411 if (time->tm_month < 0) {
michael@0 412 time->tm_month += 12;
michael@0 413 time->tm_year--;
michael@0 414 }
michael@0 415 }
michael@0 416
michael@0 417 /* Now that month and year are in proper range, normalize mday */
michael@0 418
michael@0 419 if (time->tm_mday < 1) {
michael@0 420 /* mday too small */
michael@0 421 do {
michael@0 422 /* the previous month */
michael@0 423 time->tm_month--;
michael@0 424 if (time->tm_month < 0) {
michael@0 425 time->tm_month = 11;
michael@0 426 time->tm_year--;
michael@0 427 }
michael@0 428 time->tm_mday += nDays[IsLeapYear(time->tm_year)][time->tm_month];
michael@0 429 } while (time->tm_mday < 1);
michael@0 430 } else {
michael@0 431 daysInMonth = nDays[IsLeapYear(time->tm_year)][time->tm_month];
michael@0 432 while (time->tm_mday > daysInMonth) {
michael@0 433 /* mday too large */
michael@0 434 time->tm_mday -= daysInMonth;
michael@0 435 time->tm_month++;
michael@0 436 if (time->tm_month > 11) {
michael@0 437 time->tm_month = 0;
michael@0 438 time->tm_year++;
michael@0 439 }
michael@0 440 daysInMonth = nDays[IsLeapYear(time->tm_year)][time->tm_month];
michael@0 441 }
michael@0 442 }
michael@0 443
michael@0 444 /* Recompute yday and wday */
michael@0 445 time->tm_yday = time->tm_mday +
michael@0 446 lastDayOfMonth[IsLeapYear(time->tm_year)][time->tm_month];
michael@0 447
michael@0 448 numDays = DAYS_BETWEEN_YEARS(1970, time->tm_year) + time->tm_yday;
michael@0 449 time->tm_wday = (numDays + 4) % 7;
michael@0 450 if (time->tm_wday < 0) {
michael@0 451 time->tm_wday += 7;
michael@0 452 }
michael@0 453
michael@0 454 /* Recompute time parameters */
michael@0 455
michael@0 456 time->tm_params = params(time);
michael@0 457
michael@0 458 ApplySecOffset(time, time->tm_params.tp_gmt_offset
michael@0 459 + time->tm_params.tp_dst_offset);
michael@0 460 }
michael@0 461
michael@0 462
michael@0 463 /*
michael@0 464 *-------------------------------------------------------------------------
michael@0 465 *
michael@0 466 * PR_LocalTimeParameters --
michael@0 467 *
michael@0 468 * returns the time parameters for the local time zone
michael@0 469 *
michael@0 470 * The following uses localtime() from the standard C library.
michael@0 471 * (time.h) This is our fallback implementation. Unix, PC, and BeOS
michael@0 472 * use this version. A platform may have its own machine-dependent
michael@0 473 * implementation of this function.
michael@0 474 *
michael@0 475 *-------------------------------------------------------------------------
michael@0 476 */
michael@0 477
michael@0 478 #if defined(HAVE_INT_LOCALTIME_R)
michael@0 479
michael@0 480 /*
michael@0 481 * In this case we could define the macro as
michael@0 482 * #define MT_safe_localtime(timer, result) \
michael@0 483 * (localtime_r(timer, result) == 0 ? result : NULL)
michael@0 484 * I chose to compare the return value of localtime_r with -1 so
michael@0 485 * that I can catch the cases where localtime_r returns a pointer
michael@0 486 * to struct tm. The macro definition above would not be able to
michael@0 487 * detect such mistakes because it is legal to compare a pointer
michael@0 488 * with 0.
michael@0 489 */
michael@0 490
michael@0 491 #define MT_safe_localtime(timer, result) \
michael@0 492 (localtime_r(timer, result) == -1 ? NULL: result)
michael@0 493
michael@0 494 #elif defined(HAVE_POINTER_LOCALTIME_R)
michael@0 495
michael@0 496 #define MT_safe_localtime localtime_r
michael@0 497
michael@0 498 #else
michael@0 499
michael@0 500 #define HAVE_LOCALTIME_MONITOR 1 /* We use 'monitor' to serialize our calls
michael@0 501 * to localtime(). */
michael@0 502 static PRLock *monitor = NULL;
michael@0 503
michael@0 504 static struct tm *MT_safe_localtime(const time_t *clock, struct tm *result)
michael@0 505 {
michael@0 506 struct tm *tmPtr;
michael@0 507 int needLock = PR_Initialized(); /* We need to use a lock to protect
michael@0 508 * against NSPR threads only when the
michael@0 509 * NSPR thread system is activated. */
michael@0 510
michael@0 511 if (needLock) PR_Lock(monitor);
michael@0 512
michael@0 513 /*
michael@0 514 * Microsoft (all flavors) localtime() returns a NULL pointer if 'clock'
michael@0 515 * represents a time before midnight January 1, 1970. In
michael@0 516 * that case, we also return a NULL pointer and the struct tm
michael@0 517 * object pointed to by 'result' is not modified.
michael@0 518 *
michael@0 519 * Watcom C/C++ 11.0 localtime() treats time_t as unsigned long
michael@0 520 * hence, does not recognize negative values of clock as pre-1/1/70.
michael@0 521 * We have to manually check (WIN16 only) for negative value of
michael@0 522 * clock and return NULL.
michael@0 523 *
michael@0 524 * With negative values of clock, OS/2 returns the struct tm for
michael@0 525 * clock plus ULONG_MAX. So we also have to check for the invalid
michael@0 526 * structs returned for timezones west of Greenwich when clock == 0.
michael@0 527 */
michael@0 528
michael@0 529 tmPtr = localtime(clock);
michael@0 530
michael@0 531 #if defined(WIN16) || defined(XP_OS2)
michael@0 532 if ( (PRInt32) *clock < 0 ||
michael@0 533 ( (PRInt32) *clock == 0 && tmPtr->tm_year != 70))
michael@0 534 result = NULL;
michael@0 535 else
michael@0 536 *result = *tmPtr;
michael@0 537 #else
michael@0 538 if (tmPtr) {
michael@0 539 *result = *tmPtr;
michael@0 540 } else {
michael@0 541 result = NULL;
michael@0 542 }
michael@0 543 #endif /* WIN16 */
michael@0 544
michael@0 545 if (needLock) PR_Unlock(monitor);
michael@0 546
michael@0 547 return result;
michael@0 548 }
michael@0 549
michael@0 550 #endif /* definition of MT_safe_localtime() */
michael@0 551
michael@0 552 void _PR_InitTime(void)
michael@0 553 {
michael@0 554 #ifdef HAVE_LOCALTIME_MONITOR
michael@0 555 monitor = PR_NewLock();
michael@0 556 #endif
michael@0 557 #ifdef WINCE
michael@0 558 _MD_InitTime();
michael@0 559 #endif
michael@0 560 }
michael@0 561
michael@0 562 void _PR_CleanupTime(void)
michael@0 563 {
michael@0 564 #ifdef HAVE_LOCALTIME_MONITOR
michael@0 565 if (monitor) {
michael@0 566 PR_DestroyLock(monitor);
michael@0 567 monitor = NULL;
michael@0 568 }
michael@0 569 #endif
michael@0 570 #ifdef WINCE
michael@0 571 _MD_CleanupTime();
michael@0 572 #endif
michael@0 573 }
michael@0 574
michael@0 575 #if defined(XP_UNIX) || defined(XP_PC) || defined(XP_BEOS)
michael@0 576
michael@0 577 PR_IMPLEMENT(PRTimeParameters)
michael@0 578 PR_LocalTimeParameters(const PRExplodedTime *gmt)
michael@0 579 {
michael@0 580
michael@0 581 PRTimeParameters retVal;
michael@0 582 struct tm localTime;
michael@0 583 time_t secs;
michael@0 584 PRTime secs64;
michael@0 585 PRInt64 usecPerSec;
michael@0 586 PRInt64 usecPerSec_1;
michael@0 587 PRInt64 maxInt32;
michael@0 588 PRInt64 minInt32;
michael@0 589 PRInt32 dayOffset;
michael@0 590 PRInt32 offset2Jan1970;
michael@0 591 PRInt32 offsetNew;
michael@0 592 int isdst2Jan1970;
michael@0 593
michael@0 594 /*
michael@0 595 * Calculate the GMT offset. First, figure out what is
michael@0 596 * 00:00:00 Jan. 2, 1970 GMT (which is exactly a day, or 86400
michael@0 597 * seconds, since the epoch) in local time. Then we calculate
michael@0 598 * the difference between local time and GMT in seconds:
michael@0 599 * gmt_offset = local_time - GMT
michael@0 600 *
michael@0 601 * Caveat: the validity of this calculation depends on two
michael@0 602 * assumptions:
michael@0 603 * 1. Daylight saving time was not in effect on Jan. 2, 1970.
michael@0 604 * 2. The time zone of the geographic location has not changed
michael@0 605 * since Jan. 2, 1970.
michael@0 606 */
michael@0 607
michael@0 608 secs = 86400L;
michael@0 609 (void) MT_safe_localtime(&secs, &localTime);
michael@0 610
michael@0 611 /* GMT is 00:00:00, 2nd of Jan. */
michael@0 612
michael@0 613 offset2Jan1970 = (PRInt32)localTime.tm_sec
michael@0 614 + 60L * (PRInt32)localTime.tm_min
michael@0 615 + 3600L * (PRInt32)localTime.tm_hour
michael@0 616 + 86400L * (PRInt32)((PRInt32)localTime.tm_mday - 2L);
michael@0 617
michael@0 618 isdst2Jan1970 = localTime.tm_isdst;
michael@0 619
michael@0 620 /*
michael@0 621 * Now compute DST offset. We calculate the overall offset
michael@0 622 * of local time from GMT, similar to above. The overall
michael@0 623 * offset has two components: gmt offset and dst offset.
michael@0 624 * We subtract gmt offset from the overall offset to get
michael@0 625 * the dst offset.
michael@0 626 * overall_offset = local_time - GMT
michael@0 627 * overall_offset = gmt_offset + dst_offset
michael@0 628 * ==> dst_offset = local_time - GMT - gmt_offset
michael@0 629 */
michael@0 630
michael@0 631 secs64 = PR_ImplodeTime(gmt); /* This is still in microseconds */
michael@0 632 LL_I2L(usecPerSec, PR_USEC_PER_SEC);
michael@0 633 LL_I2L(usecPerSec_1, PR_USEC_PER_SEC - 1);
michael@0 634 /* Convert to seconds, truncating down (3.1 -> 3 and -3.1 -> -4) */
michael@0 635 if (LL_GE_ZERO(secs64)) {
michael@0 636 LL_DIV(secs64, secs64, usecPerSec);
michael@0 637 } else {
michael@0 638 LL_NEG(secs64, secs64);
michael@0 639 LL_ADD(secs64, secs64, usecPerSec_1);
michael@0 640 LL_DIV(secs64, secs64, usecPerSec);
michael@0 641 LL_NEG(secs64, secs64);
michael@0 642 }
michael@0 643 LL_I2L(maxInt32, PR_INT32_MAX);
michael@0 644 LL_I2L(minInt32, PR_INT32_MIN);
michael@0 645 if (LL_CMP(secs64, >, maxInt32) || LL_CMP(secs64, <, minInt32)) {
michael@0 646 /* secs64 is too large or too small for time_t (32-bit integer) */
michael@0 647 retVal.tp_gmt_offset = offset2Jan1970;
michael@0 648 retVal.tp_dst_offset = 0;
michael@0 649 return retVal;
michael@0 650 }
michael@0 651 LL_L2I(secs, secs64);
michael@0 652
michael@0 653 /*
michael@0 654 * On Windows, localtime() (and our MT_safe_localtime() too)
michael@0 655 * returns a NULL pointer for time before midnight January 1,
michael@0 656 * 1970 GMT. In that case, we just use the GMT offset for
michael@0 657 * Jan 2, 1970 and assume that DST was not in effect.
michael@0 658 */
michael@0 659
michael@0 660 if (MT_safe_localtime(&secs, &localTime) == NULL) {
michael@0 661 retVal.tp_gmt_offset = offset2Jan1970;
michael@0 662 retVal.tp_dst_offset = 0;
michael@0 663 return retVal;
michael@0 664 }
michael@0 665
michael@0 666 /*
michael@0 667 * dayOffset is the offset between local time and GMT in
michael@0 668 * the day component, which can only be -1, 0, or 1. We
michael@0 669 * use the day of the week to compute dayOffset.
michael@0 670 */
michael@0 671
michael@0 672 dayOffset = (PRInt32) localTime.tm_wday - gmt->tm_wday;
michael@0 673
michael@0 674 /*
michael@0 675 * Need to adjust for wrapping around of day of the week from
michael@0 676 * 6 back to 0.
michael@0 677 */
michael@0 678
michael@0 679 if (dayOffset == -6) {
michael@0 680 /* Local time is Sunday (0) and GMT is Saturday (6) */
michael@0 681 dayOffset = 1;
michael@0 682 } else if (dayOffset == 6) {
michael@0 683 /* Local time is Saturday (6) and GMT is Sunday (0) */
michael@0 684 dayOffset = -1;
michael@0 685 }
michael@0 686
michael@0 687 offsetNew = (PRInt32)localTime.tm_sec - gmt->tm_sec
michael@0 688 + 60L * ((PRInt32)localTime.tm_min - gmt->tm_min)
michael@0 689 + 3600L * ((PRInt32)localTime.tm_hour - gmt->tm_hour)
michael@0 690 + 86400L * (PRInt32)dayOffset;
michael@0 691
michael@0 692 if (localTime.tm_isdst <= 0) {
michael@0 693 /* DST is not in effect */
michael@0 694 retVal.tp_gmt_offset = offsetNew;
michael@0 695 retVal.tp_dst_offset = 0;
michael@0 696 } else {
michael@0 697 /* DST is in effect */
michael@0 698 if (isdst2Jan1970 <=0) {
michael@0 699 /*
michael@0 700 * DST was not in effect back in 2 Jan. 1970.
michael@0 701 * Use the offset back then as the GMT offset,
michael@0 702 * assuming the time zone has not changed since then.
michael@0 703 */
michael@0 704 retVal.tp_gmt_offset = offset2Jan1970;
michael@0 705 retVal.tp_dst_offset = offsetNew - offset2Jan1970;
michael@0 706 } else {
michael@0 707 /*
michael@0 708 * DST was also in effect back in 2 Jan. 1970.
michael@0 709 * Then our clever trick (or rather, ugly hack) fails.
michael@0 710 * We will just assume DST offset is an hour.
michael@0 711 */
michael@0 712 retVal.tp_gmt_offset = offsetNew - 3600;
michael@0 713 retVal.tp_dst_offset = 3600;
michael@0 714 }
michael@0 715 }
michael@0 716
michael@0 717 return retVal;
michael@0 718 }
michael@0 719
michael@0 720 #endif /* defined(XP_UNIX) || defined(XP_PC) || defined(XP_BEOS) */
michael@0 721
michael@0 722 /*
michael@0 723 *------------------------------------------------------------------------
michael@0 724 *
michael@0 725 * PR_USPacificTimeParameters --
michael@0 726 *
michael@0 727 * The time parameters function for the US Pacific Time Zone.
michael@0 728 *
michael@0 729 *------------------------------------------------------------------------
michael@0 730 */
michael@0 731
michael@0 732 /*
michael@0 733 * Returns the mday of the first sunday of the month, where
michael@0 734 * mday and wday are for a given day in the month.
michael@0 735 * mdays start with 1 (e.g. 1..31).
michael@0 736 * wdays start with 0 and are in the range 0..6. 0 = Sunday.
michael@0 737 */
michael@0 738 #define firstSunday(mday, wday) (((mday - wday + 7 - 1) % 7) + 1)
michael@0 739
michael@0 740 /*
michael@0 741 * Returns the mday for the N'th Sunday of the month, where
michael@0 742 * mday and wday are for a given day in the month.
michael@0 743 * mdays start with 1 (e.g. 1..31).
michael@0 744 * wdays start with 0 and are in the range 0..6. 0 = Sunday.
michael@0 745 * N has the following values: 0 = first, 1 = second (etc), -1 = last.
michael@0 746 * ndays is the number of days in that month, the same value as the
michael@0 747 * mday of the last day of the month.
michael@0 748 */
michael@0 749 static PRInt32
michael@0 750 NthSunday(PRInt32 mday, PRInt32 wday, PRInt32 N, PRInt32 ndays)
michael@0 751 {
michael@0 752 PRInt32 firstSun = firstSunday(mday, wday);
michael@0 753
michael@0 754 if (N < 0)
michael@0 755 N = (ndays - firstSun) / 7;
michael@0 756 return firstSun + (7 * N);
michael@0 757 }
michael@0 758
michael@0 759 typedef struct DSTParams {
michael@0 760 PRInt8 dst_start_month; /* 0 = January */
michael@0 761 PRInt8 dst_start_Nth_Sunday; /* N as defined above */
michael@0 762 PRInt8 dst_start_month_ndays; /* ndays as defined above */
michael@0 763 PRInt8 dst_end_month; /* 0 = January */
michael@0 764 PRInt8 dst_end_Nth_Sunday; /* N as defined above */
michael@0 765 PRInt8 dst_end_month_ndays; /* ndays as defined above */
michael@0 766 } DSTParams;
michael@0 767
michael@0 768 static const DSTParams dstParams[2] = {
michael@0 769 /* year < 2007: First April Sunday - Last October Sunday */
michael@0 770 { 3, 0, 30, 9, -1, 31 },
michael@0 771 /* year >= 2007: Second March Sunday - First November Sunday */
michael@0 772 { 2, 1, 31, 10, 0, 30 }
michael@0 773 };
michael@0 774
michael@0 775 PR_IMPLEMENT(PRTimeParameters)
michael@0 776 PR_USPacificTimeParameters(const PRExplodedTime *gmt)
michael@0 777 {
michael@0 778 const DSTParams *dst;
michael@0 779 PRTimeParameters retVal;
michael@0 780 PRExplodedTime st;
michael@0 781
michael@0 782 /*
michael@0 783 * Based on geographic location and GMT, figure out offset of
michael@0 784 * standard time from GMT. In this example implementation, we
michael@0 785 * assume the local time zone is US Pacific Time.
michael@0 786 */
michael@0 787
michael@0 788 retVal.tp_gmt_offset = -8L * 3600L;
michael@0 789
michael@0 790 /*
michael@0 791 * Make a copy of GMT. Note that the tm_params field of this copy
michael@0 792 * is ignored.
michael@0 793 */
michael@0 794
michael@0 795 st.tm_usec = gmt->tm_usec;
michael@0 796 st.tm_sec = gmt->tm_sec;
michael@0 797 st.tm_min = gmt->tm_min;
michael@0 798 st.tm_hour = gmt->tm_hour;
michael@0 799 st.tm_mday = gmt->tm_mday;
michael@0 800 st.tm_month = gmt->tm_month;
michael@0 801 st.tm_year = gmt->tm_year;
michael@0 802 st.tm_wday = gmt->tm_wday;
michael@0 803 st.tm_yday = gmt->tm_yday;
michael@0 804
michael@0 805 /* Apply the offset to GMT to obtain the local standard time */
michael@0 806 ApplySecOffset(&st, retVal.tp_gmt_offset);
michael@0 807
michael@0 808 if (st.tm_year < 2007) { /* first April Sunday - Last October Sunday */
michael@0 809 dst = &dstParams[0];
michael@0 810 } else { /* Second March Sunday - First November Sunday */
michael@0 811 dst = &dstParams[1];
michael@0 812 }
michael@0 813
michael@0 814 /*
michael@0 815 * Apply the rules on standard time or GMT to obtain daylight saving
michael@0 816 * time offset. In this implementation, we use the US DST rule.
michael@0 817 */
michael@0 818 if (st.tm_month < dst->dst_start_month) {
michael@0 819 retVal.tp_dst_offset = 0L;
michael@0 820 } else if (st.tm_month == dst->dst_start_month) {
michael@0 821 int NthSun = NthSunday(st.tm_mday, st.tm_wday,
michael@0 822 dst->dst_start_Nth_Sunday,
michael@0 823 dst->dst_start_month_ndays);
michael@0 824 if (st.tm_mday < NthSun) { /* Before starting Sunday */
michael@0 825 retVal.tp_dst_offset = 0L;
michael@0 826 } else if (st.tm_mday == NthSun) { /* Starting Sunday */
michael@0 827 /* 01:59:59 PST -> 03:00:00 PDT */
michael@0 828 if (st.tm_hour < 2) {
michael@0 829 retVal.tp_dst_offset = 0L;
michael@0 830 } else {
michael@0 831 retVal.tp_dst_offset = 3600L;
michael@0 832 }
michael@0 833 } else { /* After starting Sunday */
michael@0 834 retVal.tp_dst_offset = 3600L;
michael@0 835 }
michael@0 836 } else if (st.tm_month < dst->dst_end_month) {
michael@0 837 retVal.tp_dst_offset = 3600L;
michael@0 838 } else if (st.tm_month == dst->dst_end_month) {
michael@0 839 int NthSun = NthSunday(st.tm_mday, st.tm_wday,
michael@0 840 dst->dst_end_Nth_Sunday,
michael@0 841 dst->dst_end_month_ndays);
michael@0 842 if (st.tm_mday < NthSun) { /* Before ending Sunday */
michael@0 843 retVal.tp_dst_offset = 3600L;
michael@0 844 } else if (st.tm_mday == NthSun) { /* Ending Sunday */
michael@0 845 /* 01:59:59 PDT -> 01:00:00 PST */
michael@0 846 if (st.tm_hour < 1) {
michael@0 847 retVal.tp_dst_offset = 3600L;
michael@0 848 } else {
michael@0 849 retVal.tp_dst_offset = 0L;
michael@0 850 }
michael@0 851 } else { /* After ending Sunday */
michael@0 852 retVal.tp_dst_offset = 0L;
michael@0 853 }
michael@0 854 } else {
michael@0 855 retVal.tp_dst_offset = 0L;
michael@0 856 }
michael@0 857 return retVal;
michael@0 858 }
michael@0 859
michael@0 860 /*
michael@0 861 *------------------------------------------------------------------------
michael@0 862 *
michael@0 863 * PR_GMTParameters --
michael@0 864 *
michael@0 865 * Returns the PRTimeParameters for Greenwich Mean Time.
michael@0 866 * Trivially, both the tp_gmt_offset and tp_dst_offset fields are 0.
michael@0 867 *
michael@0 868 *------------------------------------------------------------------------
michael@0 869 */
michael@0 870
michael@0 871 PR_IMPLEMENT(PRTimeParameters)
michael@0 872 PR_GMTParameters(const PRExplodedTime *gmt)
michael@0 873 {
michael@0 874 PRTimeParameters retVal = { 0, 0 };
michael@0 875 return retVal;
michael@0 876 }
michael@0 877
michael@0 878 /*
michael@0 879 * The following code implements PR_ParseTimeString(). It is based on
michael@0 880 * ns/lib/xp/xp_time.c, revision 1.25, by Jamie Zawinski <jwz@netscape.com>.
michael@0 881 */
michael@0 882
michael@0 883 /*
michael@0 884 * We only recognize the abbreviations of a small subset of time zones
michael@0 885 * in North America, Europe, and Japan.
michael@0 886 *
michael@0 887 * PST/PDT: Pacific Standard/Daylight Time
michael@0 888 * MST/MDT: Mountain Standard/Daylight Time
michael@0 889 * CST/CDT: Central Standard/Daylight Time
michael@0 890 * EST/EDT: Eastern Standard/Daylight Time
michael@0 891 * AST: Atlantic Standard Time
michael@0 892 * NST: Newfoundland Standard Time
michael@0 893 * GMT: Greenwich Mean Time
michael@0 894 * BST: British Summer Time
michael@0 895 * MET: Middle Europe Time
michael@0 896 * EET: Eastern Europe Time
michael@0 897 * JST: Japan Standard Time
michael@0 898 */
michael@0 899
michael@0 900 typedef enum
michael@0 901 {
michael@0 902 TT_UNKNOWN,
michael@0 903
michael@0 904 TT_SUN, TT_MON, TT_TUE, TT_WED, TT_THU, TT_FRI, TT_SAT,
michael@0 905
michael@0 906 TT_JAN, TT_FEB, TT_MAR, TT_APR, TT_MAY, TT_JUN,
michael@0 907 TT_JUL, TT_AUG, TT_SEP, TT_OCT, TT_NOV, TT_DEC,
michael@0 908
michael@0 909 TT_PST, TT_PDT, TT_MST, TT_MDT, TT_CST, TT_CDT, TT_EST, TT_EDT,
michael@0 910 TT_AST, TT_NST, TT_GMT, TT_BST, TT_MET, TT_EET, TT_JST
michael@0 911 } TIME_TOKEN;
michael@0 912
michael@0 913 /*
michael@0 914 * This parses a time/date string into a PRTime
michael@0 915 * (microseconds after "1-Jan-1970 00:00:00 GMT").
michael@0 916 * It returns PR_SUCCESS on success, and PR_FAILURE
michael@0 917 * if the time/date string can't be parsed.
michael@0 918 *
michael@0 919 * Many formats are handled, including:
michael@0 920 *
michael@0 921 * 14 Apr 89 03:20:12
michael@0 922 * 14 Apr 89 03:20 GMT
michael@0 923 * Fri, 17 Mar 89 4:01:33
michael@0 924 * Fri, 17 Mar 89 4:01 GMT
michael@0 925 * Mon Jan 16 16:12 PDT 1989
michael@0 926 * Mon Jan 16 16:12 +0130 1989
michael@0 927 * 6 May 1992 16:41-JST (Wednesday)
michael@0 928 * 22-AUG-1993 10:59:12.82
michael@0 929 * 22-AUG-1993 10:59pm
michael@0 930 * 22-AUG-1993 12:59am
michael@0 931 * 22-AUG-1993 12:59 PM
michael@0 932 * Friday, August 04, 1995 3:54 PM
michael@0 933 * 06/21/95 04:24:34 PM
michael@0 934 * 20/06/95 21:07
michael@0 935 * 95-06-08 19:32:48 EDT
michael@0 936 *
michael@0 937 * If the input string doesn't contain a description of the timezone,
michael@0 938 * we consult the `default_to_gmt' to decide whether the string should
michael@0 939 * be interpreted relative to the local time zone (PR_FALSE) or GMT (PR_TRUE).
michael@0 940 * The correct value for this argument depends on what standard specified
michael@0 941 * the time string which you are parsing.
michael@0 942 */
michael@0 943
michael@0 944 PR_IMPLEMENT(PRStatus)
michael@0 945 PR_ParseTimeStringToExplodedTime(
michael@0 946 const char *string,
michael@0 947 PRBool default_to_gmt,
michael@0 948 PRExplodedTime *result)
michael@0 949 {
michael@0 950 TIME_TOKEN dotw = TT_UNKNOWN;
michael@0 951 TIME_TOKEN month = TT_UNKNOWN;
michael@0 952 TIME_TOKEN zone = TT_UNKNOWN;
michael@0 953 int zone_offset = -1;
michael@0 954 int dst_offset = 0;
michael@0 955 int date = -1;
michael@0 956 PRInt32 year = -1;
michael@0 957 int hour = -1;
michael@0 958 int min = -1;
michael@0 959 int sec = -1;
michael@0 960
michael@0 961 const char *rest = string;
michael@0 962
michael@0 963 int iterations = 0;
michael@0 964
michael@0 965 PR_ASSERT(string && result);
michael@0 966 if (!string || !result) return PR_FAILURE;
michael@0 967
michael@0 968 while (*rest)
michael@0 969 {
michael@0 970
michael@0 971 if (iterations++ > 1000)
michael@0 972 {
michael@0 973 return PR_FAILURE;
michael@0 974 }
michael@0 975
michael@0 976 switch (*rest)
michael@0 977 {
michael@0 978 case 'a': case 'A':
michael@0 979 if (month == TT_UNKNOWN &&
michael@0 980 (rest[1] == 'p' || rest[1] == 'P') &&
michael@0 981 (rest[2] == 'r' || rest[2] == 'R'))
michael@0 982 month = TT_APR;
michael@0 983 else if (zone == TT_UNKNOWN &&
michael@0 984 (rest[1] == 's' || rest[1] == 'S') &&
michael@0 985 (rest[2] == 't' || rest[2] == 'T'))
michael@0 986 zone = TT_AST;
michael@0 987 else if (month == TT_UNKNOWN &&
michael@0 988 (rest[1] == 'u' || rest[1] == 'U') &&
michael@0 989 (rest[2] == 'g' || rest[2] == 'G'))
michael@0 990 month = TT_AUG;
michael@0 991 break;
michael@0 992 case 'b': case 'B':
michael@0 993 if (zone == TT_UNKNOWN &&
michael@0 994 (rest[1] == 's' || rest[1] == 'S') &&
michael@0 995 (rest[2] == 't' || rest[2] == 'T'))
michael@0 996 zone = TT_BST;
michael@0 997 break;
michael@0 998 case 'c': case 'C':
michael@0 999 if (zone == TT_UNKNOWN &&
michael@0 1000 (rest[1] == 'd' || rest[1] == 'D') &&
michael@0 1001 (rest[2] == 't' || rest[2] == 'T'))
michael@0 1002 zone = TT_CDT;
michael@0 1003 else if (zone == TT_UNKNOWN &&
michael@0 1004 (rest[1] == 's' || rest[1] == 'S') &&
michael@0 1005 (rest[2] == 't' || rest[2] == 'T'))
michael@0 1006 zone = TT_CST;
michael@0 1007 break;
michael@0 1008 case 'd': case 'D':
michael@0 1009 if (month == TT_UNKNOWN &&
michael@0 1010 (rest[1] == 'e' || rest[1] == 'E') &&
michael@0 1011 (rest[2] == 'c' || rest[2] == 'C'))
michael@0 1012 month = TT_DEC;
michael@0 1013 break;
michael@0 1014 case 'e': case 'E':
michael@0 1015 if (zone == TT_UNKNOWN &&
michael@0 1016 (rest[1] == 'd' || rest[1] == 'D') &&
michael@0 1017 (rest[2] == 't' || rest[2] == 'T'))
michael@0 1018 zone = TT_EDT;
michael@0 1019 else if (zone == TT_UNKNOWN &&
michael@0 1020 (rest[1] == 'e' || rest[1] == 'E') &&
michael@0 1021 (rest[2] == 't' || rest[2] == 'T'))
michael@0 1022 zone = TT_EET;
michael@0 1023 else if (zone == TT_UNKNOWN &&
michael@0 1024 (rest[1] == 's' || rest[1] == 'S') &&
michael@0 1025 (rest[2] == 't' || rest[2] == 'T'))
michael@0 1026 zone = TT_EST;
michael@0 1027 break;
michael@0 1028 case 'f': case 'F':
michael@0 1029 if (month == TT_UNKNOWN &&
michael@0 1030 (rest[1] == 'e' || rest[1] == 'E') &&
michael@0 1031 (rest[2] == 'b' || rest[2] == 'B'))
michael@0 1032 month = TT_FEB;
michael@0 1033 else if (dotw == TT_UNKNOWN &&
michael@0 1034 (rest[1] == 'r' || rest[1] == 'R') &&
michael@0 1035 (rest[2] == 'i' || rest[2] == 'I'))
michael@0 1036 dotw = TT_FRI;
michael@0 1037 break;
michael@0 1038 case 'g': case 'G':
michael@0 1039 if (zone == TT_UNKNOWN &&
michael@0 1040 (rest[1] == 'm' || rest[1] == 'M') &&
michael@0 1041 (rest[2] == 't' || rest[2] == 'T'))
michael@0 1042 zone = TT_GMT;
michael@0 1043 break;
michael@0 1044 case 'j': case 'J':
michael@0 1045 if (month == TT_UNKNOWN &&
michael@0 1046 (rest[1] == 'a' || rest[1] == 'A') &&
michael@0 1047 (rest[2] == 'n' || rest[2] == 'N'))
michael@0 1048 month = TT_JAN;
michael@0 1049 else if (zone == TT_UNKNOWN &&
michael@0 1050 (rest[1] == 's' || rest[1] == 'S') &&
michael@0 1051 (rest[2] == 't' || rest[2] == 'T'))
michael@0 1052 zone = TT_JST;
michael@0 1053 else if (month == TT_UNKNOWN &&
michael@0 1054 (rest[1] == 'u' || rest[1] == 'U') &&
michael@0 1055 (rest[2] == 'l' || rest[2] == 'L'))
michael@0 1056 month = TT_JUL;
michael@0 1057 else if (month == TT_UNKNOWN &&
michael@0 1058 (rest[1] == 'u' || rest[1] == 'U') &&
michael@0 1059 (rest[2] == 'n' || rest[2] == 'N'))
michael@0 1060 month = TT_JUN;
michael@0 1061 break;
michael@0 1062 case 'm': case 'M':
michael@0 1063 if (month == TT_UNKNOWN &&
michael@0 1064 (rest[1] == 'a' || rest[1] == 'A') &&
michael@0 1065 (rest[2] == 'r' || rest[2] == 'R'))
michael@0 1066 month = TT_MAR;
michael@0 1067 else if (month == TT_UNKNOWN &&
michael@0 1068 (rest[1] == 'a' || rest[1] == 'A') &&
michael@0 1069 (rest[2] == 'y' || rest[2] == 'Y'))
michael@0 1070 month = TT_MAY;
michael@0 1071 else if (zone == TT_UNKNOWN &&
michael@0 1072 (rest[1] == 'd' || rest[1] == 'D') &&
michael@0 1073 (rest[2] == 't' || rest[2] == 'T'))
michael@0 1074 zone = TT_MDT;
michael@0 1075 else if (zone == TT_UNKNOWN &&
michael@0 1076 (rest[1] == 'e' || rest[1] == 'E') &&
michael@0 1077 (rest[2] == 't' || rest[2] == 'T'))
michael@0 1078 zone = TT_MET;
michael@0 1079 else if (dotw == TT_UNKNOWN &&
michael@0 1080 (rest[1] == 'o' || rest[1] == 'O') &&
michael@0 1081 (rest[2] == 'n' || rest[2] == 'N'))
michael@0 1082 dotw = TT_MON;
michael@0 1083 else if (zone == TT_UNKNOWN &&
michael@0 1084 (rest[1] == 's' || rest[1] == 'S') &&
michael@0 1085 (rest[2] == 't' || rest[2] == 'T'))
michael@0 1086 zone = TT_MST;
michael@0 1087 break;
michael@0 1088 case 'n': case 'N':
michael@0 1089 if (month == TT_UNKNOWN &&
michael@0 1090 (rest[1] == 'o' || rest[1] == 'O') &&
michael@0 1091 (rest[2] == 'v' || rest[2] == 'V'))
michael@0 1092 month = TT_NOV;
michael@0 1093 else if (zone == TT_UNKNOWN &&
michael@0 1094 (rest[1] == 's' || rest[1] == 'S') &&
michael@0 1095 (rest[2] == 't' || rest[2] == 'T'))
michael@0 1096 zone = TT_NST;
michael@0 1097 break;
michael@0 1098 case 'o': case 'O':
michael@0 1099 if (month == TT_UNKNOWN &&
michael@0 1100 (rest[1] == 'c' || rest[1] == 'C') &&
michael@0 1101 (rest[2] == 't' || rest[2] == 'T'))
michael@0 1102 month = TT_OCT;
michael@0 1103 break;
michael@0 1104 case 'p': case 'P':
michael@0 1105 if (zone == TT_UNKNOWN &&
michael@0 1106 (rest[1] == 'd' || rest[1] == 'D') &&
michael@0 1107 (rest[2] == 't' || rest[2] == 'T'))
michael@0 1108 zone = TT_PDT;
michael@0 1109 else if (zone == TT_UNKNOWN &&
michael@0 1110 (rest[1] == 's' || rest[1] == 'S') &&
michael@0 1111 (rest[2] == 't' || rest[2] == 'T'))
michael@0 1112 zone = TT_PST;
michael@0 1113 break;
michael@0 1114 case 's': case 'S':
michael@0 1115 if (dotw == TT_UNKNOWN &&
michael@0 1116 (rest[1] == 'a' || rest[1] == 'A') &&
michael@0 1117 (rest[2] == 't' || rest[2] == 'T'))
michael@0 1118 dotw = TT_SAT;
michael@0 1119 else if (month == TT_UNKNOWN &&
michael@0 1120 (rest[1] == 'e' || rest[1] == 'E') &&
michael@0 1121 (rest[2] == 'p' || rest[2] == 'P'))
michael@0 1122 month = TT_SEP;
michael@0 1123 else if (dotw == TT_UNKNOWN &&
michael@0 1124 (rest[1] == 'u' || rest[1] == 'U') &&
michael@0 1125 (rest[2] == 'n' || rest[2] == 'N'))
michael@0 1126 dotw = TT_SUN;
michael@0 1127 break;
michael@0 1128 case 't': case 'T':
michael@0 1129 if (dotw == TT_UNKNOWN &&
michael@0 1130 (rest[1] == 'h' || rest[1] == 'H') &&
michael@0 1131 (rest[2] == 'u' || rest[2] == 'U'))
michael@0 1132 dotw = TT_THU;
michael@0 1133 else if (dotw == TT_UNKNOWN &&
michael@0 1134 (rest[1] == 'u' || rest[1] == 'U') &&
michael@0 1135 (rest[2] == 'e' || rest[2] == 'E'))
michael@0 1136 dotw = TT_TUE;
michael@0 1137 break;
michael@0 1138 case 'u': case 'U':
michael@0 1139 if (zone == TT_UNKNOWN &&
michael@0 1140 (rest[1] == 't' || rest[1] == 'T') &&
michael@0 1141 !(rest[2] >= 'A' && rest[2] <= 'Z') &&
michael@0 1142 !(rest[2] >= 'a' && rest[2] <= 'z'))
michael@0 1143 /* UT is the same as GMT but UTx is not. */
michael@0 1144 zone = TT_GMT;
michael@0 1145 break;
michael@0 1146 case 'w': case 'W':
michael@0 1147 if (dotw == TT_UNKNOWN &&
michael@0 1148 (rest[1] == 'e' || rest[1] == 'E') &&
michael@0 1149 (rest[2] == 'd' || rest[2] == 'D'))
michael@0 1150 dotw = TT_WED;
michael@0 1151 break;
michael@0 1152
michael@0 1153 case '+': case '-':
michael@0 1154 {
michael@0 1155 const char *end;
michael@0 1156 int sign;
michael@0 1157 if (zone_offset != -1)
michael@0 1158 {
michael@0 1159 /* already got one... */
michael@0 1160 rest++;
michael@0 1161 break;
michael@0 1162 }
michael@0 1163 if (zone != TT_UNKNOWN && zone != TT_GMT)
michael@0 1164 {
michael@0 1165 /* GMT+0300 is legal, but PST+0300 is not. */
michael@0 1166 rest++;
michael@0 1167 break;
michael@0 1168 }
michael@0 1169
michael@0 1170 sign = ((*rest == '+') ? 1 : -1);
michael@0 1171 rest++; /* move over sign */
michael@0 1172 end = rest;
michael@0 1173 while (*end >= '0' && *end <= '9')
michael@0 1174 end++;
michael@0 1175 if (rest == end) /* no digits here */
michael@0 1176 break;
michael@0 1177
michael@0 1178 if ((end - rest) == 4)
michael@0 1179 /* offset in HHMM */
michael@0 1180 zone_offset = (((((rest[0]-'0')*10) + (rest[1]-'0')) * 60) +
michael@0 1181 (((rest[2]-'0')*10) + (rest[3]-'0')));
michael@0 1182 else if ((end - rest) == 2)
michael@0 1183 /* offset in hours */
michael@0 1184 zone_offset = (((rest[0]-'0')*10) + (rest[1]-'0')) * 60;
michael@0 1185 else if ((end - rest) == 1)
michael@0 1186 /* offset in hours */
michael@0 1187 zone_offset = (rest[0]-'0') * 60;
michael@0 1188 else
michael@0 1189 /* 3 or >4 */
michael@0 1190 break;
michael@0 1191
michael@0 1192 zone_offset *= sign;
michael@0 1193 zone = TT_GMT;
michael@0 1194 break;
michael@0 1195 }
michael@0 1196
michael@0 1197 case '0': case '1': case '2': case '3': case '4':
michael@0 1198 case '5': case '6': case '7': case '8': case '9':
michael@0 1199 {
michael@0 1200 int tmp_hour = -1;
michael@0 1201 int tmp_min = -1;
michael@0 1202 int tmp_sec = -1;
michael@0 1203 const char *end = rest + 1;
michael@0 1204 while (*end >= '0' && *end <= '9')
michael@0 1205 end++;
michael@0 1206
michael@0 1207 /* end is now the first character after a range of digits. */
michael@0 1208
michael@0 1209 if (*end == ':')
michael@0 1210 {
michael@0 1211 if (hour >= 0 && min >= 0) /* already got it */
michael@0 1212 break;
michael@0 1213
michael@0 1214 /* We have seen "[0-9]+:", so this is probably HH:MM[:SS] */
michael@0 1215 if ((end - rest) > 2)
michael@0 1216 /* it is [0-9][0-9][0-9]+: */
michael@0 1217 break;
michael@0 1218 else if ((end - rest) == 2)
michael@0 1219 tmp_hour = ((rest[0]-'0')*10 +
michael@0 1220 (rest[1]-'0'));
michael@0 1221 else
michael@0 1222 tmp_hour = (rest[0]-'0');
michael@0 1223
michael@0 1224 /* move over the colon, and parse minutes */
michael@0 1225
michael@0 1226 rest = ++end;
michael@0 1227 while (*end >= '0' && *end <= '9')
michael@0 1228 end++;
michael@0 1229
michael@0 1230 if (end == rest)
michael@0 1231 /* no digits after first colon? */
michael@0 1232 break;
michael@0 1233 else if ((end - rest) > 2)
michael@0 1234 /* it is [0-9][0-9][0-9]+: */
michael@0 1235 break;
michael@0 1236 else if ((end - rest) == 2)
michael@0 1237 tmp_min = ((rest[0]-'0')*10 +
michael@0 1238 (rest[1]-'0'));
michael@0 1239 else
michael@0 1240 tmp_min = (rest[0]-'0');
michael@0 1241
michael@0 1242 /* now go for seconds */
michael@0 1243 rest = end;
michael@0 1244 if (*rest == ':')
michael@0 1245 rest++;
michael@0 1246 end = rest;
michael@0 1247 while (*end >= '0' && *end <= '9')
michael@0 1248 end++;
michael@0 1249
michael@0 1250 if (end == rest)
michael@0 1251 /* no digits after second colon - that's ok. */
michael@0 1252 ;
michael@0 1253 else if ((end - rest) > 2)
michael@0 1254 /* it is [0-9][0-9][0-9]+: */
michael@0 1255 break;
michael@0 1256 else if ((end - rest) == 2)
michael@0 1257 tmp_sec = ((rest[0]-'0')*10 +
michael@0 1258 (rest[1]-'0'));
michael@0 1259 else
michael@0 1260 tmp_sec = (rest[0]-'0');
michael@0 1261
michael@0 1262 /* If we made it here, we've parsed hour and min,
michael@0 1263 and possibly sec, so it worked as a unit. */
michael@0 1264
michael@0 1265 /* skip over whitespace and see if there's an AM or PM
michael@0 1266 directly following the time.
michael@0 1267 */
michael@0 1268 if (tmp_hour <= 12)
michael@0 1269 {
michael@0 1270 const char *s = end;
michael@0 1271 while (*s && (*s == ' ' || *s == '\t'))
michael@0 1272 s++;
michael@0 1273 if ((s[0] == 'p' || s[0] == 'P') &&
michael@0 1274 (s[1] == 'm' || s[1] == 'M'))
michael@0 1275 /* 10:05pm == 22:05, and 12:05pm == 12:05 */
michael@0 1276 tmp_hour = (tmp_hour == 12 ? 12 : tmp_hour + 12);
michael@0 1277 else if (tmp_hour == 12 &&
michael@0 1278 (s[0] == 'a' || s[0] == 'A') &&
michael@0 1279 (s[1] == 'm' || s[1] == 'M'))
michael@0 1280 /* 12:05am == 00:05 */
michael@0 1281 tmp_hour = 0;
michael@0 1282 }
michael@0 1283
michael@0 1284 hour = tmp_hour;
michael@0 1285 min = tmp_min;
michael@0 1286 sec = tmp_sec;
michael@0 1287 rest = end;
michael@0 1288 break;
michael@0 1289 }
michael@0 1290 else if ((*end == '/' || *end == '-') &&
michael@0 1291 end[1] >= '0' && end[1] <= '9')
michael@0 1292 {
michael@0 1293 /* Perhaps this is 6/16/95, 16/6/95, 6-16-95, or 16-6-95
michael@0 1294 or even 95-06-05...
michael@0 1295 #### But it doesn't handle 1995-06-22.
michael@0 1296 */
michael@0 1297 int n1, n2, n3;
michael@0 1298 const char *s;
michael@0 1299
michael@0 1300 if (month != TT_UNKNOWN)
michael@0 1301 /* if we saw a month name, this can't be. */
michael@0 1302 break;
michael@0 1303
michael@0 1304 s = rest;
michael@0 1305
michael@0 1306 n1 = (*s++ - '0'); /* first 1 or 2 digits */
michael@0 1307 if (*s >= '0' && *s <= '9')
michael@0 1308 n1 = n1*10 + (*s++ - '0');
michael@0 1309
michael@0 1310 if (*s != '/' && *s != '-') /* slash */
michael@0 1311 break;
michael@0 1312 s++;
michael@0 1313
michael@0 1314 if (*s < '0' || *s > '9') /* second 1 or 2 digits */
michael@0 1315 break;
michael@0 1316 n2 = (*s++ - '0');
michael@0 1317 if (*s >= '0' && *s <= '9')
michael@0 1318 n2 = n2*10 + (*s++ - '0');
michael@0 1319
michael@0 1320 if (*s != '/' && *s != '-') /* slash */
michael@0 1321 break;
michael@0 1322 s++;
michael@0 1323
michael@0 1324 if (*s < '0' || *s > '9') /* third 1, 2, 4, or 5 digits */
michael@0 1325 break;
michael@0 1326 n3 = (*s++ - '0');
michael@0 1327 if (*s >= '0' && *s <= '9')
michael@0 1328 n3 = n3*10 + (*s++ - '0');
michael@0 1329
michael@0 1330 if (*s >= '0' && *s <= '9') /* optional digits 3, 4, and 5 */
michael@0 1331 {
michael@0 1332 n3 = n3*10 + (*s++ - '0');
michael@0 1333 if (*s < '0' || *s > '9')
michael@0 1334 break;
michael@0 1335 n3 = n3*10 + (*s++ - '0');
michael@0 1336 if (*s >= '0' && *s <= '9')
michael@0 1337 n3 = n3*10 + (*s++ - '0');
michael@0 1338 }
michael@0 1339
michael@0 1340 if ((*s >= '0' && *s <= '9') || /* followed by non-alphanum */
michael@0 1341 (*s >= 'A' && *s <= 'Z') ||
michael@0 1342 (*s >= 'a' && *s <= 'z'))
michael@0 1343 break;
michael@0 1344
michael@0 1345 /* Ok, we parsed three 1-2 digit numbers, with / or -
michael@0 1346 between them. Now decide what the hell they are
michael@0 1347 (DD/MM/YY or MM/DD/YY or YY/MM/DD.)
michael@0 1348 */
michael@0 1349
michael@0 1350 if (n1 > 31 || n1 == 0) /* must be YY/MM/DD */
michael@0 1351 {
michael@0 1352 if (n2 > 12) break;
michael@0 1353 if (n3 > 31) break;
michael@0 1354 year = n1;
michael@0 1355 if (year < 70)
michael@0 1356 year += 2000;
michael@0 1357 else if (year < 100)
michael@0 1358 year += 1900;
michael@0 1359 month = (TIME_TOKEN)(n2 + ((int)TT_JAN) - 1);
michael@0 1360 date = n3;
michael@0 1361 rest = s;
michael@0 1362 break;
michael@0 1363 }
michael@0 1364
michael@0 1365 if (n1 > 12 && n2 > 12) /* illegal */
michael@0 1366 {
michael@0 1367 rest = s;
michael@0 1368 break;
michael@0 1369 }
michael@0 1370
michael@0 1371 if (n3 < 70)
michael@0 1372 n3 += 2000;
michael@0 1373 else if (n3 < 100)
michael@0 1374 n3 += 1900;
michael@0 1375
michael@0 1376 if (n1 > 12) /* must be DD/MM/YY */
michael@0 1377 {
michael@0 1378 date = n1;
michael@0 1379 month = (TIME_TOKEN)(n2 + ((int)TT_JAN) - 1);
michael@0 1380 year = n3;
michael@0 1381 }
michael@0 1382 else /* assume MM/DD/YY */
michael@0 1383 {
michael@0 1384 /* #### In the ambiguous case, should we consult the
michael@0 1385 locale to find out the local default? */
michael@0 1386 month = (TIME_TOKEN)(n1 + ((int)TT_JAN) - 1);
michael@0 1387 date = n2;
michael@0 1388 year = n3;
michael@0 1389 }
michael@0 1390 rest = s;
michael@0 1391 }
michael@0 1392 else if ((*end >= 'A' && *end <= 'Z') ||
michael@0 1393 (*end >= 'a' && *end <= 'z'))
michael@0 1394 /* Digits followed by non-punctuation - what's that? */
michael@0 1395 ;
michael@0 1396 else if ((end - rest) == 5) /* five digits is a year */
michael@0 1397 year = (year < 0
michael@0 1398 ? ((rest[0]-'0')*10000L +
michael@0 1399 (rest[1]-'0')*1000L +
michael@0 1400 (rest[2]-'0')*100L +
michael@0 1401 (rest[3]-'0')*10L +
michael@0 1402 (rest[4]-'0'))
michael@0 1403 : year);
michael@0 1404 else if ((end - rest) == 4) /* four digits is a year */
michael@0 1405 year = (year < 0
michael@0 1406 ? ((rest[0]-'0')*1000L +
michael@0 1407 (rest[1]-'0')*100L +
michael@0 1408 (rest[2]-'0')*10L +
michael@0 1409 (rest[3]-'0'))
michael@0 1410 : year);
michael@0 1411 else if ((end - rest) == 2) /* two digits - date or year */
michael@0 1412 {
michael@0 1413 int n = ((rest[0]-'0')*10 +
michael@0 1414 (rest[1]-'0'));
michael@0 1415 /* If we don't have a date (day of the month) and we see a number
michael@0 1416 less than 32, then assume that is the date.
michael@0 1417
michael@0 1418 Otherwise, if we have a date and not a year, assume this is the
michael@0 1419 year. If it is less than 70, then assume it refers to the 21st
michael@0 1420 century. If it is two digits (>= 70), assume it refers to this
michael@0 1421 century. Otherwise, assume it refers to an unambiguous year.
michael@0 1422
michael@0 1423 The world will surely end soon.
michael@0 1424 */
michael@0 1425 if (date < 0 && n < 32)
michael@0 1426 date = n;
michael@0 1427 else if (year < 0)
michael@0 1428 {
michael@0 1429 if (n < 70)
michael@0 1430 year = 2000 + n;
michael@0 1431 else if (n < 100)
michael@0 1432 year = 1900 + n;
michael@0 1433 else
michael@0 1434 year = n;
michael@0 1435 }
michael@0 1436 /* else what the hell is this. */
michael@0 1437 }
michael@0 1438 else if ((end - rest) == 1) /* one digit - date */
michael@0 1439 date = (date < 0 ? (rest[0]-'0') : date);
michael@0 1440 /* else, three or more than five digits - what's that? */
michael@0 1441
michael@0 1442 break;
michael@0 1443 }
michael@0 1444 }
michael@0 1445
michael@0 1446 /* Skip to the end of this token, whether we parsed it or not.
michael@0 1447 Tokens are delimited by whitespace, or ,;-/
michael@0 1448 But explicitly not :+-.
michael@0 1449 */
michael@0 1450 while (*rest &&
michael@0 1451 *rest != ' ' && *rest != '\t' &&
michael@0 1452 *rest != ',' && *rest != ';' &&
michael@0 1453 *rest != '-' && *rest != '+' &&
michael@0 1454 *rest != '/' &&
michael@0 1455 *rest != '(' && *rest != ')' && *rest != '[' && *rest != ']')
michael@0 1456 rest++;
michael@0 1457 /* skip over uninteresting chars. */
michael@0 1458 SKIP_MORE:
michael@0 1459 while (*rest &&
michael@0 1460 (*rest == ' ' || *rest == '\t' ||
michael@0 1461 *rest == ',' || *rest == ';' || *rest == '/' ||
michael@0 1462 *rest == '(' || *rest == ')' || *rest == '[' || *rest == ']'))
michael@0 1463 rest++;
michael@0 1464
michael@0 1465 /* "-" is ignored at the beginning of a token if we have not yet
michael@0 1466 parsed a year (e.g., the second "-" in "30-AUG-1966"), or if
michael@0 1467 the character after the dash is not a digit. */
michael@0 1468 if (*rest == '-' && ((rest > string &&
michael@0 1469 isalpha((unsigned char)rest[-1]) && year < 0) ||
michael@0 1470 rest[1] < '0' || rest[1] > '9'))
michael@0 1471 {
michael@0 1472 rest++;
michael@0 1473 goto SKIP_MORE;
michael@0 1474 }
michael@0 1475
michael@0 1476 }
michael@0 1477
michael@0 1478 if (zone != TT_UNKNOWN && zone_offset == -1)
michael@0 1479 {
michael@0 1480 switch (zone)
michael@0 1481 {
michael@0 1482 case TT_PST: zone_offset = -8 * 60; break;
michael@0 1483 case TT_PDT: zone_offset = -8 * 60; dst_offset = 1 * 60; break;
michael@0 1484 case TT_MST: zone_offset = -7 * 60; break;
michael@0 1485 case TT_MDT: zone_offset = -7 * 60; dst_offset = 1 * 60; break;
michael@0 1486 case TT_CST: zone_offset = -6 * 60; break;
michael@0 1487 case TT_CDT: zone_offset = -6 * 60; dst_offset = 1 * 60; break;
michael@0 1488 case TT_EST: zone_offset = -5 * 60; break;
michael@0 1489 case TT_EDT: zone_offset = -5 * 60; dst_offset = 1 * 60; break;
michael@0 1490 case TT_AST: zone_offset = -4 * 60; break;
michael@0 1491 case TT_NST: zone_offset = -3 * 60 - 30; break;
michael@0 1492 case TT_GMT: zone_offset = 0 * 60; break;
michael@0 1493 case TT_BST: zone_offset = 0 * 60; dst_offset = 1 * 60; break;
michael@0 1494 case TT_MET: zone_offset = 1 * 60; break;
michael@0 1495 case TT_EET: zone_offset = 2 * 60; break;
michael@0 1496 case TT_JST: zone_offset = 9 * 60; break;
michael@0 1497 default:
michael@0 1498 PR_ASSERT (0);
michael@0 1499 break;
michael@0 1500 }
michael@0 1501 }
michael@0 1502
michael@0 1503 /* If we didn't find a year, month, or day-of-the-month, we can't
michael@0 1504 possibly parse this, and in fact, mktime() will do something random
michael@0 1505 (I'm seeing it return "Tue Feb 5 06:28:16 2036", which is no doubt
michael@0 1506 a numerologically significant date... */
michael@0 1507 if (month == TT_UNKNOWN || date == -1 || year == -1 || year > PR_INT16_MAX)
michael@0 1508 return PR_FAILURE;
michael@0 1509
michael@0 1510 memset(result, 0, sizeof(*result));
michael@0 1511 if (sec != -1)
michael@0 1512 result->tm_sec = sec;
michael@0 1513 if (min != -1)
michael@0 1514 result->tm_min = min;
michael@0 1515 if (hour != -1)
michael@0 1516 result->tm_hour = hour;
michael@0 1517 if (date != -1)
michael@0 1518 result->tm_mday = date;
michael@0 1519 if (month != TT_UNKNOWN)
michael@0 1520 result->tm_month = (((int)month) - ((int)TT_JAN));
michael@0 1521 if (year != -1)
michael@0 1522 result->tm_year = year;
michael@0 1523 if (dotw != TT_UNKNOWN)
michael@0 1524 result->tm_wday = (((int)dotw) - ((int)TT_SUN));
michael@0 1525 /*
michael@0 1526 * Mainly to compute wday and yday, but normalized time is also required
michael@0 1527 * by the check below that works around a Visual C++ 2005 mktime problem.
michael@0 1528 */
michael@0 1529 PR_NormalizeTime(result, PR_GMTParameters);
michael@0 1530 /* The remaining work is to set the gmt and dst offsets in tm_params. */
michael@0 1531
michael@0 1532 if (zone == TT_UNKNOWN && default_to_gmt)
michael@0 1533 {
michael@0 1534 /* No zone was specified, so pretend the zone was GMT. */
michael@0 1535 zone = TT_GMT;
michael@0 1536 zone_offset = 0;
michael@0 1537 }
michael@0 1538
michael@0 1539 if (zone_offset == -1)
michael@0 1540 {
michael@0 1541 /* no zone was specified, and we're to assume that everything
michael@0 1542 is local. */
michael@0 1543 struct tm localTime;
michael@0 1544 time_t secs;
michael@0 1545
michael@0 1546 PR_ASSERT(result->tm_month > -1 &&
michael@0 1547 result->tm_mday > 0 &&
michael@0 1548 result->tm_hour > -1 &&
michael@0 1549 result->tm_min > -1 &&
michael@0 1550 result->tm_sec > -1);
michael@0 1551
michael@0 1552 /*
michael@0 1553 * To obtain time_t from a tm structure representing the local
michael@0 1554 * time, we call mktime(). However, we need to see if we are
michael@0 1555 * on 1-Jan-1970 or before. If we are, we can't call mktime()
michael@0 1556 * because mktime() will crash on win16. In that case, we
michael@0 1557 * calculate zone_offset based on the zone offset at
michael@0 1558 * 00:00:00, 2 Jan 1970 GMT, and subtract zone_offset from the
michael@0 1559 * date we are parsing to transform the date to GMT. We also
michael@0 1560 * do so if mktime() returns (time_t) -1 (time out of range).
michael@0 1561 */
michael@0 1562
michael@0 1563 /* month, day, hours, mins and secs are always non-negative
michael@0 1564 so we dont need to worry about them. */
michael@0 1565 if(result->tm_year >= 1970)
michael@0 1566 {
michael@0 1567 PRInt64 usec_per_sec;
michael@0 1568
michael@0 1569 localTime.tm_sec = result->tm_sec;
michael@0 1570 localTime.tm_min = result->tm_min;
michael@0 1571 localTime.tm_hour = result->tm_hour;
michael@0 1572 localTime.tm_mday = result->tm_mday;
michael@0 1573 localTime.tm_mon = result->tm_month;
michael@0 1574 localTime.tm_year = result->tm_year - 1900;
michael@0 1575 /* Set this to -1 to tell mktime "I don't care". If you set
michael@0 1576 it to 0 or 1, you are making assertions about whether the
michael@0 1577 date you are handing it is in daylight savings mode or not;
michael@0 1578 and if you're wrong, it will "fix" it for you. */
michael@0 1579 localTime.tm_isdst = -1;
michael@0 1580
michael@0 1581 #if _MSC_VER == 1400 /* 1400 = Visual C++ 2005 (8.0) */
michael@0 1582 /*
michael@0 1583 * mktime will return (time_t) -1 if the input is a date
michael@0 1584 * after 23:59:59, December 31, 3000, US Pacific Time (not
michael@0 1585 * UTC as documented):
michael@0 1586 * http://msdn.microsoft.com/en-us/library/d1y53h2a(VS.80).aspx
michael@0 1587 * But if the year is 3001, mktime also invokes the invalid
michael@0 1588 * parameter handler, causing the application to crash. This
michael@0 1589 * problem has been reported in
michael@0 1590 * http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=266036.
michael@0 1591 * We avoid this crash by not calling mktime if the date is
michael@0 1592 * out of range. To use a simple test that works in any time
michael@0 1593 * zone, we consider year 3000 out of range as well. (See
michael@0 1594 * bug 480740.)
michael@0 1595 */
michael@0 1596 if (result->tm_year >= 3000) {
michael@0 1597 /* Emulate what mktime would have done. */
michael@0 1598 errno = EINVAL;
michael@0 1599 secs = (time_t) -1;
michael@0 1600 } else {
michael@0 1601 secs = mktime(&localTime);
michael@0 1602 }
michael@0 1603 #else
michael@0 1604 secs = mktime(&localTime);
michael@0 1605 #endif
michael@0 1606 if (secs != (time_t) -1)
michael@0 1607 {
michael@0 1608 PRTime usecs64;
michael@0 1609 LL_I2L(usecs64, secs);
michael@0 1610 LL_I2L(usec_per_sec, PR_USEC_PER_SEC);
michael@0 1611 LL_MUL(usecs64, usecs64, usec_per_sec);
michael@0 1612 PR_ExplodeTime(usecs64, PR_LocalTimeParameters, result);
michael@0 1613 return PR_SUCCESS;
michael@0 1614 }
michael@0 1615 }
michael@0 1616
michael@0 1617 /* So mktime() can't handle this case. We assume the
michael@0 1618 zone_offset for the date we are parsing is the same as
michael@0 1619 the zone offset on 00:00:00 2 Jan 1970 GMT. */
michael@0 1620 secs = 86400;
michael@0 1621 (void) MT_safe_localtime(&secs, &localTime);
michael@0 1622 zone_offset = localTime.tm_min
michael@0 1623 + 60 * localTime.tm_hour
michael@0 1624 + 1440 * (localTime.tm_mday - 2);
michael@0 1625 }
michael@0 1626
michael@0 1627 result->tm_params.tp_gmt_offset = zone_offset * 60;
michael@0 1628 result->tm_params.tp_dst_offset = dst_offset * 60;
michael@0 1629
michael@0 1630 return PR_SUCCESS;
michael@0 1631 }
michael@0 1632
michael@0 1633 PR_IMPLEMENT(PRStatus)
michael@0 1634 PR_ParseTimeString(
michael@0 1635 const char *string,
michael@0 1636 PRBool default_to_gmt,
michael@0 1637 PRTime *result)
michael@0 1638 {
michael@0 1639 PRExplodedTime tm;
michael@0 1640 PRStatus rv;
michael@0 1641
michael@0 1642 rv = PR_ParseTimeStringToExplodedTime(string,
michael@0 1643 default_to_gmt,
michael@0 1644 &tm);
michael@0 1645 if (rv != PR_SUCCESS)
michael@0 1646 return rv;
michael@0 1647
michael@0 1648 *result = PR_ImplodeTime(&tm);
michael@0 1649
michael@0 1650 return PR_SUCCESS;
michael@0 1651 }
michael@0 1652
michael@0 1653 /*
michael@0 1654 *******************************************************************
michael@0 1655 *******************************************************************
michael@0 1656 **
michael@0 1657 ** OLD COMPATIBILITY FUNCTIONS
michael@0 1658 **
michael@0 1659 *******************************************************************
michael@0 1660 *******************************************************************
michael@0 1661 */
michael@0 1662
michael@0 1663
michael@0 1664 /*
michael@0 1665 *-----------------------------------------------------------------------
michael@0 1666 *
michael@0 1667 * PR_FormatTime --
michael@0 1668 *
michael@0 1669 * Format a time value into a buffer. Same semantics as strftime().
michael@0 1670 *
michael@0 1671 *-----------------------------------------------------------------------
michael@0 1672 */
michael@0 1673
michael@0 1674 PR_IMPLEMENT(PRUint32)
michael@0 1675 PR_FormatTime(char *buf, int buflen, const char *fmt, const PRExplodedTime *tm)
michael@0 1676 {
michael@0 1677 size_t rv;
michael@0 1678 struct tm a;
michael@0 1679 struct tm *ap;
michael@0 1680
michael@0 1681 if (tm) {
michael@0 1682 ap = &a;
michael@0 1683 a.tm_sec = tm->tm_sec;
michael@0 1684 a.tm_min = tm->tm_min;
michael@0 1685 a.tm_hour = tm->tm_hour;
michael@0 1686 a.tm_mday = tm->tm_mday;
michael@0 1687 a.tm_mon = tm->tm_month;
michael@0 1688 a.tm_wday = tm->tm_wday;
michael@0 1689 a.tm_year = tm->tm_year - 1900;
michael@0 1690 a.tm_yday = tm->tm_yday;
michael@0 1691 a.tm_isdst = tm->tm_params.tp_dst_offset ? 1 : 0;
michael@0 1692
michael@0 1693 /*
michael@0 1694 * On some platforms, for example SunOS 4, struct tm has two
michael@0 1695 * additional fields: tm_zone and tm_gmtoff.
michael@0 1696 */
michael@0 1697
michael@0 1698 #if (__GLIBC__ >= 2) || defined(XP_BEOS) \
michael@0 1699 || defined(NETBSD) || defined(OPENBSD) || defined(FREEBSD) \
michael@0 1700 || defined(DARWIN) || defined(SYMBIAN) || defined(ANDROID)
michael@0 1701 a.tm_zone = NULL;
michael@0 1702 a.tm_gmtoff = tm->tm_params.tp_gmt_offset +
michael@0 1703 tm->tm_params.tp_dst_offset;
michael@0 1704 #endif
michael@0 1705 } else {
michael@0 1706 ap = NULL;
michael@0 1707 }
michael@0 1708
michael@0 1709 rv = strftime(buf, buflen, fmt, ap);
michael@0 1710 if (!rv && buf && buflen > 0) {
michael@0 1711 /*
michael@0 1712 * When strftime fails, the contents of buf are indeterminate.
michael@0 1713 * Some callers don't check the return value from this function,
michael@0 1714 * so store an empty string in buf in case they try to print it.
michael@0 1715 */
michael@0 1716 buf[0] = '\0';
michael@0 1717 }
michael@0 1718 return rv;
michael@0 1719 }
michael@0 1720
michael@0 1721
michael@0 1722 /*
michael@0 1723 * The following string arrays and macros are used by PR_FormatTimeUSEnglish().
michael@0 1724 */
michael@0 1725
michael@0 1726 static const char* abbrevDays[] =
michael@0 1727 {
michael@0 1728 "Sun","Mon","Tue","Wed","Thu","Fri","Sat"
michael@0 1729 };
michael@0 1730
michael@0 1731 static const char* days[] =
michael@0 1732 {
michael@0 1733 "Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"
michael@0 1734 };
michael@0 1735
michael@0 1736 static const char* abbrevMonths[] =
michael@0 1737 {
michael@0 1738 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
michael@0 1739 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
michael@0 1740 };
michael@0 1741
michael@0 1742 static const char* months[] =
michael@0 1743 {
michael@0 1744 "January", "February", "March", "April", "May", "June",
michael@0 1745 "July", "August", "September", "October", "November", "December"
michael@0 1746 };
michael@0 1747
michael@0 1748
michael@0 1749 /*
michael@0 1750 * Add a single character to the given buffer, incrementing the buffer pointer
michael@0 1751 * and decrementing the buffer size. Return 0 on error.
michael@0 1752 */
michael@0 1753 #define ADDCHAR( buf, bufSize, ch ) \
michael@0 1754 do \
michael@0 1755 { \
michael@0 1756 if( bufSize < 1 ) \
michael@0 1757 { \
michael@0 1758 *(--buf) = '\0'; \
michael@0 1759 return 0; \
michael@0 1760 } \
michael@0 1761 *buf++ = ch; \
michael@0 1762 bufSize--; \
michael@0 1763 } \
michael@0 1764 while(0)
michael@0 1765
michael@0 1766
michael@0 1767 /*
michael@0 1768 * Add a string to the given buffer, incrementing the buffer pointer
michael@0 1769 * and decrementing the buffer size appropriately. Return 0 on error.
michael@0 1770 */
michael@0 1771 #define ADDSTR( buf, bufSize, str ) \
michael@0 1772 do \
michael@0 1773 { \
michael@0 1774 PRUint32 strSize = strlen( str ); \
michael@0 1775 if( strSize > bufSize ) \
michael@0 1776 { \
michael@0 1777 if( bufSize==0 ) \
michael@0 1778 *(--buf) = '\0'; \
michael@0 1779 else \
michael@0 1780 *buf = '\0'; \
michael@0 1781 return 0; \
michael@0 1782 } \
michael@0 1783 memcpy(buf, str, strSize); \
michael@0 1784 buf += strSize; \
michael@0 1785 bufSize -= strSize; \
michael@0 1786 } \
michael@0 1787 while(0)
michael@0 1788
michael@0 1789 /* Needed by PR_FormatTimeUSEnglish() */
michael@0 1790 static unsigned int pr_WeekOfYear(const PRExplodedTime* time,
michael@0 1791 unsigned int firstDayOfWeek);
michael@0 1792
michael@0 1793
michael@0 1794 /***********************************************************************************
michael@0 1795 *
michael@0 1796 * Description:
michael@0 1797 * This is a dumbed down version of strftime that will format the date in US
michael@0 1798 * English regardless of the setting of the global locale. This functionality is
michael@0 1799 * needed to write things like MIME headers which must always be in US English.
michael@0 1800 *
michael@0 1801 **********************************************************************************/
michael@0 1802
michael@0 1803 PR_IMPLEMENT(PRUint32)
michael@0 1804 PR_FormatTimeUSEnglish( char* buf, PRUint32 bufSize,
michael@0 1805 const char* format, const PRExplodedTime* time )
michael@0 1806 {
michael@0 1807 char* bufPtr = buf;
michael@0 1808 const char* fmtPtr;
michael@0 1809 char tmpBuf[ 40 ];
michael@0 1810 const int tmpBufSize = sizeof( tmpBuf );
michael@0 1811
michael@0 1812
michael@0 1813 for( fmtPtr=format; *fmtPtr != '\0'; fmtPtr++ )
michael@0 1814 {
michael@0 1815 if( *fmtPtr != '%' )
michael@0 1816 {
michael@0 1817 ADDCHAR( bufPtr, bufSize, *fmtPtr );
michael@0 1818 }
michael@0 1819 else
michael@0 1820 {
michael@0 1821 switch( *(++fmtPtr) )
michael@0 1822 {
michael@0 1823 case '%':
michael@0 1824 /* escaped '%' character */
michael@0 1825 ADDCHAR( bufPtr, bufSize, '%' );
michael@0 1826 break;
michael@0 1827
michael@0 1828 case 'a':
michael@0 1829 /* abbreviated weekday name */
michael@0 1830 ADDSTR( bufPtr, bufSize, abbrevDays[ time->tm_wday ] );
michael@0 1831 break;
michael@0 1832
michael@0 1833 case 'A':
michael@0 1834 /* full weekday name */
michael@0 1835 ADDSTR( bufPtr, bufSize, days[ time->tm_wday ] );
michael@0 1836 break;
michael@0 1837
michael@0 1838 case 'b':
michael@0 1839 /* abbreviated month name */
michael@0 1840 ADDSTR( bufPtr, bufSize, abbrevMonths[ time->tm_month ] );
michael@0 1841 break;
michael@0 1842
michael@0 1843 case 'B':
michael@0 1844 /* full month name */
michael@0 1845 ADDSTR(bufPtr, bufSize, months[ time->tm_month ] );
michael@0 1846 break;
michael@0 1847
michael@0 1848 case 'c':
michael@0 1849 /* Date and time. */
michael@0 1850 PR_FormatTimeUSEnglish( tmpBuf, tmpBufSize, "%a %b %d %H:%M:%S %Y", time );
michael@0 1851 ADDSTR( bufPtr, bufSize, tmpBuf );
michael@0 1852 break;
michael@0 1853
michael@0 1854 case 'd':
michael@0 1855 /* day of month ( 01 - 31 ) */
michael@0 1856 PR_snprintf(tmpBuf,tmpBufSize,"%.2ld",time->tm_mday );
michael@0 1857 ADDSTR( bufPtr, bufSize, tmpBuf );
michael@0 1858 break;
michael@0 1859
michael@0 1860 case 'H':
michael@0 1861 /* hour ( 00 - 23 ) */
michael@0 1862 PR_snprintf(tmpBuf,tmpBufSize,"%.2ld",time->tm_hour );
michael@0 1863 ADDSTR( bufPtr, bufSize, tmpBuf );
michael@0 1864 break;
michael@0 1865
michael@0 1866 case 'I':
michael@0 1867 /* hour ( 01 - 12 ) */
michael@0 1868 PR_snprintf(tmpBuf,tmpBufSize,"%.2ld",
michael@0 1869 (time->tm_hour%12) ? time->tm_hour%12 : (PRInt32) 12 );
michael@0 1870 ADDSTR( bufPtr, bufSize, tmpBuf );
michael@0 1871 break;
michael@0 1872
michael@0 1873 case 'j':
michael@0 1874 /* day number of year ( 001 - 366 ) */
michael@0 1875 PR_snprintf(tmpBuf,tmpBufSize,"%.3d",time->tm_yday + 1);
michael@0 1876 ADDSTR( bufPtr, bufSize, tmpBuf );
michael@0 1877 break;
michael@0 1878
michael@0 1879 case 'm':
michael@0 1880 /* month number ( 01 - 12 ) */
michael@0 1881 PR_snprintf(tmpBuf,tmpBufSize,"%.2ld",time->tm_month+1);
michael@0 1882 ADDSTR( bufPtr, bufSize, tmpBuf );
michael@0 1883 break;
michael@0 1884
michael@0 1885 case 'M':
michael@0 1886 /* minute ( 00 - 59 ) */
michael@0 1887 PR_snprintf(tmpBuf,tmpBufSize,"%.2ld",time->tm_min );
michael@0 1888 ADDSTR( bufPtr, bufSize, tmpBuf );
michael@0 1889 break;
michael@0 1890
michael@0 1891 case 'p':
michael@0 1892 /* locale's equivalent of either AM or PM */
michael@0 1893 ADDSTR( bufPtr, bufSize, (time->tm_hour<12)?"AM":"PM" );
michael@0 1894 break;
michael@0 1895
michael@0 1896 case 'S':
michael@0 1897 /* seconds ( 00 - 61 ), allows for leap seconds */
michael@0 1898 PR_snprintf(tmpBuf,tmpBufSize,"%.2ld",time->tm_sec );
michael@0 1899 ADDSTR( bufPtr, bufSize, tmpBuf );
michael@0 1900 break;
michael@0 1901
michael@0 1902 case 'U':
michael@0 1903 /* week number of year ( 00 - 53 ), Sunday is the first day of week 1 */
michael@0 1904 PR_snprintf(tmpBuf,tmpBufSize,"%.2d", pr_WeekOfYear( time, 0 ) );
michael@0 1905 ADDSTR( bufPtr, bufSize, tmpBuf );
michael@0 1906 break;
michael@0 1907
michael@0 1908 case 'w':
michael@0 1909 /* weekday number ( 0 - 6 ), Sunday = 0 */
michael@0 1910 PR_snprintf(tmpBuf,tmpBufSize,"%d",time->tm_wday );
michael@0 1911 ADDSTR( bufPtr, bufSize, tmpBuf );
michael@0 1912 break;
michael@0 1913
michael@0 1914 case 'W':
michael@0 1915 /* Week number of year ( 00 - 53 ), Monday is the first day of week 1 */
michael@0 1916 PR_snprintf(tmpBuf,tmpBufSize,"%.2d", pr_WeekOfYear( time, 1 ) );
michael@0 1917 ADDSTR( bufPtr, bufSize, tmpBuf );
michael@0 1918 break;
michael@0 1919
michael@0 1920 case 'x':
michael@0 1921 /* Date representation */
michael@0 1922 PR_FormatTimeUSEnglish( tmpBuf, tmpBufSize, "%m/%d/%y", time );
michael@0 1923 ADDSTR( bufPtr, bufSize, tmpBuf );
michael@0 1924 break;
michael@0 1925
michael@0 1926 case 'X':
michael@0 1927 /* Time representation. */
michael@0 1928 PR_FormatTimeUSEnglish( tmpBuf, tmpBufSize, "%H:%M:%S", time );
michael@0 1929 ADDSTR( bufPtr, bufSize, tmpBuf );
michael@0 1930 break;
michael@0 1931
michael@0 1932 case 'y':
michael@0 1933 /* year within century ( 00 - 99 ) */
michael@0 1934 PR_snprintf(tmpBuf,tmpBufSize,"%.2d",time->tm_year % 100 );
michael@0 1935 ADDSTR( bufPtr, bufSize, tmpBuf );
michael@0 1936 break;
michael@0 1937
michael@0 1938 case 'Y':
michael@0 1939 /* year as ccyy ( for example 1986 ) */
michael@0 1940 PR_snprintf(tmpBuf,tmpBufSize,"%.4d",time->tm_year );
michael@0 1941 ADDSTR( bufPtr, bufSize, tmpBuf );
michael@0 1942 break;
michael@0 1943
michael@0 1944 case 'Z':
michael@0 1945 /* Time zone name or no characters if no time zone exists.
michael@0 1946 * Since time zone name is supposed to be independant of locale, we
michael@0 1947 * defer to PR_FormatTime() for this option.
michael@0 1948 */
michael@0 1949 PR_FormatTime( tmpBuf, tmpBufSize, "%Z", time );
michael@0 1950 ADDSTR( bufPtr, bufSize, tmpBuf );
michael@0 1951 break;
michael@0 1952
michael@0 1953 default:
michael@0 1954 /* Unknown format. Simply copy format into output buffer. */
michael@0 1955 ADDCHAR( bufPtr, bufSize, '%' );
michael@0 1956 ADDCHAR( bufPtr, bufSize, *fmtPtr );
michael@0 1957 break;
michael@0 1958
michael@0 1959 }
michael@0 1960 }
michael@0 1961 }
michael@0 1962
michael@0 1963 ADDCHAR( bufPtr, bufSize, '\0' );
michael@0 1964 return (PRUint32)(bufPtr - buf - 1);
michael@0 1965 }
michael@0 1966
michael@0 1967
michael@0 1968
michael@0 1969 /***********************************************************************************
michael@0 1970 *
michael@0 1971 * Description:
michael@0 1972 * Returns the week number of the year (0-53) for the given time. firstDayOfWeek
michael@0 1973 * is the day on which the week is considered to start (0=Sun, 1=Mon, ...).
michael@0 1974 * Week 1 starts the first time firstDayOfWeek occurs in the year. In other words,
michael@0 1975 * a partial week at the start of the year is considered week 0.
michael@0 1976 *
michael@0 1977 **********************************************************************************/
michael@0 1978
michael@0 1979 static unsigned int
michael@0 1980 pr_WeekOfYear(const PRExplodedTime* time, unsigned int firstDayOfWeek)
michael@0 1981 {
michael@0 1982 int dayOfWeek;
michael@0 1983 int dayOfYear;
michael@0 1984
michael@0 1985 /* Get the day of the year for the given time then adjust it to represent the
michael@0 1986 * first day of the week containing the given time.
michael@0 1987 */
michael@0 1988 dayOfWeek = time->tm_wday - firstDayOfWeek;
michael@0 1989 if (dayOfWeek < 0)
michael@0 1990 dayOfWeek += 7;
michael@0 1991
michael@0 1992 dayOfYear = time->tm_yday - dayOfWeek;
michael@0 1993
michael@0 1994
michael@0 1995 if( dayOfYear <= 0 )
michael@0 1996 {
michael@0 1997 /* If dayOfYear is <= 0, it is in the first partial week of the year. */
michael@0 1998 return 0;
michael@0 1999 }
michael@0 2000 else
michael@0 2001 {
michael@0 2002 /* Count the number of full weeks ( dayOfYear / 7 ) then add a week if there
michael@0 2003 * are any days left over ( dayOfYear % 7 ). Because we are only counting to
michael@0 2004 * the first day of the week containing the given time, rather than to the
michael@0 2005 * actual day representing the given time, any days in week 0 will be "absorbed"
michael@0 2006 * as extra days in the given week.
michael@0 2007 */
michael@0 2008 return (dayOfYear / 7) + ( (dayOfYear % 7) == 0 ? 0 : 1 );
michael@0 2009 }
michael@0 2010 }
michael@0 2011

mercurial