tclClock.cGo to the documentation of this file.00001 /* 00002 * tclClock.c -- 00003 * 00004 * Contains the time and date related commands. This code is derived from 00005 * the time and date facilities of TclX, by Mark Diekhans and Karl 00006 * Lehenbauer. 00007 * 00008 * Copyright 1991-1995 Karl Lehenbauer and Mark Diekhans. 00009 * Copyright (c) 1995 Sun Microsystems, Inc. 00010 * Copyright (c) 2004 by Kevin B. Kenny. All rights reserved. 00011 * 00012 * See the file "license.terms" for information on usage and redistribution of 00013 * this file, and for a DISCLAIMER OF ALL WARRANTIES. 00014 * 00015 * RCS: @(#) $Id: tclClock.c,v 1.63 2007/12/13 15:23:15 dgp Exp $ 00016 */ 00017 00018 #include "tclInt.h" 00019 00020 /* 00021 * Windows has mktime. The configurators do not check. 00022 */ 00023 00024 #ifdef __WIN32__ 00025 #define HAVE_MKTIME 1 00026 #endif 00027 00028 /* 00029 * Constants 00030 */ 00031 00032 #define JULIAN_DAY_POSIX_EPOCH 2440588 00033 #define SECONDS_PER_DAY 86400 00034 #define JULIAN_SEC_POSIX_EPOCH (((Tcl_WideInt) JULIAN_DAY_POSIX_EPOCH) \ 00035 * SECONDS_PER_DAY) 00036 #define FOUR_CENTURIES 146097 /* days */ 00037 #define JDAY_1_JAN_1_CE_JULIAN 1721424 00038 #define JDAY_1_JAN_1_CE_GREGORIAN 1721426 00039 #define ONE_CENTURY_GREGORIAN 36524 /* days */ 00040 #define FOUR_YEARS 1461 /* days */ 00041 #define ONE_YEAR 365 /* days */ 00042 00043 /* 00044 * Table of the days in each month, leap and common years 00045 */ 00046 00047 static const int hath[2][12] = { 00048 {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, 00049 {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31} 00050 }; 00051 static const int daysInPriorMonths[2][13] = { 00052 {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365}, 00053 {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366} 00054 }; 00055 00056 /* 00057 * Enumeration of the string literals used in [clock] 00058 */ 00059 00060 typedef enum ClockLiteral { 00061 LIT_BCE, LIT_CE, 00062 LIT_DAYOFMONTH, LIT_DAYOFWEEK, LIT_DAYOFYEAR, 00063 LIT_ERA, LIT_GREGORIAN, 00064 LIT_ISO8601WEEK, LIT_ISO8601YEAR, 00065 LIT_JULIANDAY, LIT_LOCALSECONDS, 00066 LIT_MONTH, 00067 LIT_SECONDS, LIT_TZNAME, LIT_TZOFFSET, 00068 LIT_YEAR, 00069 LIT__END 00070 } ClockLiteral; 00071 static const char *const literals[] = { 00072 "BCE", "CE", 00073 "dayOfMonth", "dayOfWeek", "dayOfYear", 00074 "era", "gregorian", 00075 "iso8601Week", "iso8601Year", 00076 "julianDay", "localSeconds", 00077 "month", 00078 "seconds", "tzName", "tzOffset", 00079 "year" 00080 }; 00081 00082 /* 00083 * Structure containing the client data for [clock] 00084 */ 00085 00086 typedef struct ClockClientData { 00087 int refCount; /* Number of live references */ 00088 Tcl_Obj** literals; /* Pool of object literals */ 00089 } ClockClientData; 00090 00091 /* 00092 * Structure containing the fields used in [clock format] and [clock scan] 00093 */ 00094 00095 typedef struct TclDateFields { 00096 Tcl_WideInt seconds; /* Time expressed in seconds from the Posix 00097 * epoch */ 00098 Tcl_WideInt localSeconds; /* Local time expressed in nominal seconds 00099 * from the Posix epoch */ 00100 int tzOffset; /* Time zone offset in seconds east of 00101 * Greenwich */ 00102 Tcl_Obj* tzName; /* Time zone name */ 00103 int julianDay; /* Julian Day Number in local time zone */ 00104 enum {BCE=1, CE=0} era; /* Era */ 00105 int gregorian; /* Flag == 1 if the date is Gregorian */ 00106 int year; /* Year of the era */ 00107 int dayOfYear; /* Day of the year (1 January == 1) */ 00108 int month; /* Month number */ 00109 int dayOfMonth; /* Day of the month */ 00110 int iso8601Year; /* ISO8601 week-based year */ 00111 int iso8601Week; /* ISO8601 week number */ 00112 int dayOfWeek; /* Day of the week */ 00113 } TclDateFields; 00114 static const char* eras[] = { "CE", "BCE", NULL }; 00115 00116 /* 00117 * Thread specific data block holding a 'struct tm' for the 'gmtime' and 00118 * 'localtime' library calls. 00119 */ 00120 00121 static Tcl_ThreadDataKey tmKey; 00122 00123 /* 00124 * Mutex protecting 'gmtime', 'localtime' and 'mktime' calls and the statics 00125 * in the date parsing code. 00126 */ 00127 00128 TCL_DECLARE_MUTEX(clockMutex) 00129 00130 /* 00131 * Function prototypes for local procedures in this file: 00132 */ 00133 00134 static int ConvertUTCToLocal(Tcl_Interp*, 00135 TclDateFields*, Tcl_Obj*, int); 00136 static int ConvertUTCToLocalUsingTable(Tcl_Interp*, 00137 TclDateFields*, int, Tcl_Obj *const[]); 00138 static int ConvertUTCToLocalUsingC(Tcl_Interp*, 00139 TclDateFields*, int); 00140 static int ConvertLocalToUTC(Tcl_Interp*, 00141 TclDateFields*, Tcl_Obj*, int); 00142 static int ConvertLocalToUTCUsingTable(Tcl_Interp*, 00143 TclDateFields*, int, Tcl_Obj *const[]); 00144 static int ConvertLocalToUTCUsingC(Tcl_Interp*, 00145 TclDateFields*, int); 00146 static Tcl_Obj* LookupLastTransition(Tcl_Interp*, Tcl_WideInt, 00147 int, Tcl_Obj *const *); 00148 static void GetYearWeekDay(TclDateFields*, int); 00149 static void GetGregorianEraYearDay(TclDateFields*, int); 00150 static void GetMonthDay(TclDateFields*); 00151 static void GetJulianDayFromEraYearWeekDay(TclDateFields*, int); 00152 static void GetJulianDayFromEraYearMonthDay(TclDateFields*, int); 00153 static int IsGregorianLeapYear(TclDateFields*); 00154 static int WeekdayOnOrBefore(int, int); 00155 static int ClockClicksObjCmd( 00156 ClientData clientData, Tcl_Interp *interp, 00157 int objc, Tcl_Obj *const objv[]); 00158 static int ClockConvertlocaltoutcObjCmd( 00159 ClientData clientData, Tcl_Interp *interp, 00160 int objc, Tcl_Obj *const objv[]); 00161 static int ClockGetdatefieldsObjCmd( 00162 ClientData clientData, Tcl_Interp *interp, 00163 int objc, Tcl_Obj *const objv[]); 00164 static int ClockGetjuliandayfromerayearmonthdayObjCmd( 00165 ClientData clientData, Tcl_Interp *interp, 00166 int objc, Tcl_Obj *const objv[]); 00167 static int ClockGetjuliandayfromerayearweekdayObjCmd( 00168 ClientData clientData, Tcl_Interp *interp, 00169 int objc, Tcl_Obj *const objv[]); 00170 static int ClockGetenvObjCmd( 00171 ClientData clientData, Tcl_Interp *interp, 00172 int objc, Tcl_Obj *const objv[]); 00173 static int ClockMicrosecondsObjCmd( 00174 ClientData clientData, Tcl_Interp *interp, 00175 int objc, Tcl_Obj *const objv[]); 00176 static int ClockMillisecondsObjCmd( 00177 ClientData clientData, Tcl_Interp *interp, 00178 int objc, Tcl_Obj *const objv[]); 00179 static int ClockSecondsObjCmd( 00180 ClientData clientData, Tcl_Interp *interp, 00181 int objc, Tcl_Obj *const objv[]); 00182 static struct tm * ThreadSafeLocalTime(const time_t *); 00183 static void TzsetIfNecessary(void); 00184 static void ClockDeleteCmdProc(ClientData); 00185 00186 /* 00187 * Structure containing description of "native" clock commands to create. 00188 */ 00189 00190 struct ClockCommand { 00191 const char *name; /* The tail of the command name. The full name 00192 * is "::tcl::clock::<name>". When NULL marks 00193 * the end of the table. */ 00194 Tcl_ObjCmdProc *objCmdProc; /* Function that implements the command. This 00195 * will always have the ClockClientData sent 00196 * to it, but may well ignore this data. */ 00197 }; 00198 00199 static const struct ClockCommand clockCommands[] = { 00200 { "clicks", ClockClicksObjCmd }, 00201 { "getenv", ClockGetenvObjCmd }, 00202 { "microseconds", ClockMicrosecondsObjCmd }, 00203 { "milliseconds", ClockMillisecondsObjCmd }, 00204 { "seconds", ClockSecondsObjCmd }, 00205 { "Oldscan", TclClockOldscanObjCmd }, 00206 { "ConvertLocalToUTC", ClockConvertlocaltoutcObjCmd }, 00207 { "GetDateFields", ClockGetdatefieldsObjCmd }, 00208 { "GetJulianDayFromEraYearMonthDay", 00209 ClockGetjuliandayfromerayearmonthdayObjCmd }, 00210 { "GetJulianDayFromEraYearWeekDay", 00211 ClockGetjuliandayfromerayearweekdayObjCmd }, 00212 { NULL, NULL } 00213 }; 00214 00215 /* 00216 *---------------------------------------------------------------------- 00217 * 00218 * TclClockInit -- 00219 * 00220 * Registers the 'clock' subcommands with the Tcl interpreter and 00221 * initializes its client data (which consists mostly of constant 00222 * Tcl_Obj's that it is too much trouble to keep recreating). 00223 * 00224 * Results: 00225 * None. 00226 * 00227 * Side effects: 00228 * Installs the commands and creates the client data 00229 * 00230 *---------------------------------------------------------------------- 00231 */ 00232 00233 void 00234 TclClockInit( 00235 Tcl_Interp *interp) /* Tcl interpreter */ 00236 { 00237 const struct ClockCommand *clockCmdPtr; 00238 char cmdName[50]; /* Buffer large enough to hold the string 00239 *::tcl::clock::GetJulianDayFromEraYearMonthDay 00240 * plus a terminating NULL. */ 00241 ClockClientData *data; 00242 int i; 00243 00244 /* 00245 * Create the client data, which is a refcounted literal pool. 00246 */ 00247 00248 data = (ClockClientData *) ckalloc(sizeof(ClockClientData)); 00249 data->refCount = 0; 00250 data->literals = (Tcl_Obj**) ckalloc(LIT__END * sizeof(Tcl_Obj*)); 00251 for (i = 0; i < LIT__END; ++i) { 00252 data->literals[i] = Tcl_NewStringObj(literals[i], -1); 00253 Tcl_IncrRefCount(data->literals[i]); 00254 } 00255 00256 /* 00257 * Install the commands. 00258 */ 00259 00260 strcpy(cmdName, "::tcl::clock::"); 00261 #define TCL_CLOCK_PREFIX_LEN 14 /* == strlen("::tcl::clock::") */ 00262 for (clockCmdPtr=clockCommands ; clockCmdPtr->name!=NULL ; clockCmdPtr++) { 00263 strcpy(cmdName + TCL_CLOCK_PREFIX_LEN, clockCmdPtr->name); 00264 data->refCount++; 00265 Tcl_CreateObjCommand(interp, cmdName, clockCmdPtr->objCmdProc, data, 00266 ClockDeleteCmdProc); 00267 } 00268 } 00269 00270 /* 00271 *---------------------------------------------------------------------- 00272 * 00273 * ClockConvertlocaltoutcObjCmd -- 00274 * 00275 * Tcl command that converts a UTC time to a local time by whatever means 00276 * is available. 00277 * 00278 * Usage: 00279 * ::tcl::clock::ConvertUTCToLocal dictionary tzdata changeover 00280 * 00281 * Parameters: 00282 * dict - Dictionary containing a 'localSeconds' entry. 00283 * tzdata - Time zone data 00284 * changeover - Julian Day of the adoption of the Gregorian calendar. 00285 * 00286 * Results: 00287 * Returns a standard Tcl result. 00288 * 00289 * Side effects: 00290 * On success, sets the interpreter result to the given dictionary 00291 * augmented with a 'seconds' field giving the UTC time. On failure, 00292 * leaves an error message in the interpreter result. 00293 * 00294 *---------------------------------------------------------------------- 00295 */ 00296 00297 static int 00298 ClockConvertlocaltoutcObjCmd( 00299 ClientData clientData, /* Client data */ 00300 Tcl_Interp* interp, /* Tcl interpreter */ 00301 int objc, /* Parameter count */ 00302 Tcl_Obj *const *objv) /* Parameter vector */ 00303 { 00304 ClockClientData* data = (ClockClientData*) clientData; 00305 Tcl_Obj* const * literals = data->literals; 00306 Tcl_Obj* secondsObj; 00307 Tcl_Obj* dict; 00308 int changeover; 00309 TclDateFields fields; 00310 int created = 0; 00311 int status; 00312 00313 /* 00314 * Check params and convert time. 00315 */ 00316 00317 if (objc != 4) { 00318 Tcl_WrongNumArgs(interp, 1, objv, "dict tzdata changeover"); 00319 return TCL_ERROR; 00320 } 00321 dict = objv[1]; 00322 if ((Tcl_DictObjGet(interp, dict, literals[LIT_LOCALSECONDS], 00323 &secondsObj) != TCL_OK) 00324 || (Tcl_GetWideIntFromObj(interp, secondsObj, 00325 &(fields.localSeconds)) != TCL_OK) 00326 || (TclGetIntFromObj(interp, objv[3], &changeover) != TCL_OK) 00327 || ConvertLocalToUTC(interp, &fields, objv[2], changeover)) { 00328 return TCL_ERROR; 00329 } 00330 00331 /* 00332 * Copy-on-write; set the 'seconds' field in the dictionary and place the 00333 * modified dictionary in the interpreter result. 00334 */ 00335 00336 if (Tcl_IsShared(dict)) { 00337 dict = Tcl_DuplicateObj(dict); 00338 created = 1; 00339 Tcl_IncrRefCount(dict); 00340 } 00341 status = Tcl_DictObjPut(interp, dict, literals[LIT_SECONDS], 00342 Tcl_NewWideIntObj(fields.seconds)); 00343 if (status == TCL_OK) { 00344 Tcl_SetObjResult(interp, dict); 00345 } 00346 if (created) { 00347 Tcl_DecrRefCount(dict); 00348 } 00349 return status; 00350 } 00351 00352 /* 00353 *---------------------------------------------------------------------- 00354 * 00355 * ClockGetdatefieldsObjCmd -- 00356 * 00357 * Tcl command that determines the values that [clock format] will use in 00358 * formatting a date, and populates a dictionary with them. 00359 * 00360 * Usage: 00361 * ::tcl::clock::GetDateFields seconds tzdata changeover 00362 * 00363 * Parameters: 00364 * seconds - Time expressed in seconds from the Posix epoch. 00365 * tzdata - Time zone data of the time zone in which time is to 00366 * be expressed. 00367 * changeover - Julian Day Number at which the current locale adopted 00368 * the Gregorian calendar 00369 * 00370 * Results: 00371 * Returns a dictonary populated with the fields: 00372 * seconds - Seconds from the Posix epoch 00373 * localSeconds - Nominal seconds from the Posix epoch in 00374 * the local time zone. 00375 * tzOffset - Time zone offset in seconds east of Greenwich 00376 * tzName - Time zone name 00377 * julianDay - Julian Day Number in the local time zone 00378 * 00379 *---------------------------------------------------------------------- 00380 */ 00381 00382 int 00383 ClockGetdatefieldsObjCmd( 00384 ClientData clientData, /* Opaque pointer to literal pool, etc. */ 00385 Tcl_Interp* interp, /* Tcl interpreter */ 00386 int objc, /* Parameter count */ 00387 Tcl_Obj *const *objv) /* Parameter vector */ 00388 { 00389 TclDateFields fields; 00390 Tcl_Obj* dict; 00391 ClockClientData* data = (ClockClientData*) clientData; 00392 Tcl_Obj* const * literals = data->literals; 00393 int changeover; 00394 00395 /* 00396 * Check params. 00397 */ 00398 00399 if (objc != 4) { 00400 Tcl_WrongNumArgs(interp, 1, objv, "seconds tzdata changeover"); 00401 return TCL_ERROR; 00402 } 00403 if (Tcl_GetWideIntFromObj(interp, objv[1], &(fields.seconds)) != TCL_OK 00404 || TclGetIntFromObj(interp, objv[3], &changeover) != TCL_OK) { 00405 return TCL_ERROR; 00406 } 00407 00408 /* 00409 * Convert UTC time to local. 00410 */ 00411 00412 if (ConvertUTCToLocal(interp, &fields, objv[2], changeover) != TCL_OK) { 00413 return TCL_ERROR; 00414 } 00415 00416 /* 00417 * Extract Julian day. 00418 */ 00419 00420 fields.julianDay = (int) ((fields.localSeconds + JULIAN_SEC_POSIX_EPOCH) 00421 / SECONDS_PER_DAY); 00422 00423 /* 00424 * Convert to Julian or Gregorian calendar. 00425 */ 00426 00427 GetGregorianEraYearDay(&fields, changeover); 00428 GetMonthDay(&fields); 00429 GetYearWeekDay(&fields, changeover); 00430 00431 dict = Tcl_NewDictObj(); 00432 Tcl_DictObjPut(NULL, dict, literals[LIT_LOCALSECONDS], 00433 Tcl_NewWideIntObj(fields.localSeconds)); 00434 Tcl_DictObjPut(NULL, dict, literals[LIT_SECONDS], 00435 Tcl_NewWideIntObj(fields.seconds)); 00436 Tcl_DictObjPut(NULL, dict, literals[LIT_TZNAME], fields.tzName); 00437 Tcl_DecrRefCount(fields.tzName); 00438 Tcl_DictObjPut(NULL, dict, literals[LIT_TZOFFSET], 00439 Tcl_NewIntObj(fields.tzOffset)); 00440 Tcl_DictObjPut(NULL, dict, literals[LIT_JULIANDAY], 00441 Tcl_NewIntObj(fields.julianDay)); 00442 Tcl_DictObjPut(NULL, dict, literals[LIT_GREGORIAN], 00443 Tcl_NewIntObj(fields.gregorian)); 00444 Tcl_DictObjPut(NULL, dict, literals[LIT_ERA], 00445 literals[fields.era ? LIT_BCE : LIT_CE]); 00446 Tcl_DictObjPut(NULL, dict, literals[LIT_YEAR], 00447 Tcl_NewIntObj(fields.year)); 00448 Tcl_DictObjPut(NULL, dict, literals[LIT_DAYOFYEAR], 00449 Tcl_NewIntObj(fields.dayOfYear)); 00450 Tcl_DictObjPut(NULL, dict, literals[LIT_MONTH], 00451 Tcl_NewIntObj(fields.month)); 00452 Tcl_DictObjPut(NULL, dict, literals[LIT_DAYOFMONTH], 00453 Tcl_NewIntObj(fields.dayOfMonth)); 00454 Tcl_DictObjPut(NULL, dict, literals[LIT_ISO8601YEAR], 00455 Tcl_NewIntObj(fields.iso8601Year)); 00456 Tcl_DictObjPut(NULL, dict, literals[LIT_ISO8601WEEK], 00457 Tcl_NewIntObj(fields.iso8601Week)); 00458 Tcl_DictObjPut(NULL, dict, literals[LIT_DAYOFWEEK], 00459 Tcl_NewIntObj(fields.dayOfWeek)); 00460 Tcl_SetObjResult(interp, dict); 00461 00462 return TCL_OK; 00463 } 00464 00465 /* 00466 *---------------------------------------------------------------------- 00467 * 00468 * ClockGetjuliandayfromerayearmonthdayObjCmd -- 00469 * 00470 * Tcl command that converts a time from era-year-month-day to a Julian 00471 * Day Number. 00472 * 00473 * Parameters: 00474 * dict - Dictionary that contains 'era', 'year', 'month' and 00475 * 'dayOfMonth' keys. 00476 * changeover - Julian Day of changeover to the Gregorian calendar 00477 * 00478 * Results: 00479 * Result is either TCL_OK, with the interpreter result being the 00480 * dictionary augmented with a 'julianDay' key, or TCL_ERROR, 00481 * with the result being an error message. 00482 * 00483 *---------------------------------------------------------------------- 00484 */ 00485 00486 static int 00487 ClockGetjuliandayfromerayearmonthdayObjCmd ( 00488 ClientData clientData, /* Opaque pointer to literal pool, etc. */ 00489 Tcl_Interp* interp, /* Tcl interpreter */ 00490 int objc, /* Parameter count */ 00491 Tcl_Obj *const *objv) /* Parameter vector */ 00492 { 00493 TclDateFields fields; 00494 Tcl_Obj* dict; 00495 ClockClientData* data = (ClockClientData*) clientData; 00496 Tcl_Obj* const * literals = data->literals; 00497 Tcl_Obj* fieldPtr; 00498 int changeover; 00499 int copied = 0; 00500 int status; 00501 int era = 0; 00502 00503 /* 00504 * Check params. 00505 */ 00506 00507 if (objc != 3) { 00508 Tcl_WrongNumArgs(interp, 1, objv, "dict changeover"); 00509 return TCL_ERROR; 00510 } 00511 dict = objv[1]; 00512 if (Tcl_DictObjGet(interp, dict, literals[LIT_ERA], &fieldPtr) != TCL_OK 00513 || Tcl_GetIndexFromObj(interp, fieldPtr, eras, "era", TCL_EXACT, 00514 &era) != TCL_OK 00515 || Tcl_DictObjGet(interp, dict, literals[LIT_YEAR], 00516 &fieldPtr) != TCL_OK 00517 || TclGetIntFromObj(interp, fieldPtr, &(fields.year)) != TCL_OK 00518 || Tcl_DictObjGet(interp, dict, literals[LIT_MONTH], 00519 &fieldPtr) != TCL_OK 00520 || TclGetIntFromObj(interp, fieldPtr, &(fields.month)) != TCL_OK 00521 || Tcl_DictObjGet(interp, dict, literals[LIT_DAYOFMONTH], 00522 &fieldPtr) != TCL_OK 00523 || TclGetIntFromObj(interp, fieldPtr, 00524 &(fields.dayOfMonth)) != TCL_OK 00525 || TclGetIntFromObj(interp, objv[2], &changeover) != TCL_OK) { 00526 return TCL_ERROR; 00527 } 00528 fields.era = era; 00529 00530 /* 00531 * Get Julian day. 00532 */ 00533 00534 GetJulianDayFromEraYearMonthDay(&fields, changeover); 00535 00536 /* 00537 * Store Julian day in the dictionary - copy on write. 00538 */ 00539 00540 if (Tcl_IsShared(dict)) { 00541 dict = Tcl_DuplicateObj(dict); 00542 Tcl_IncrRefCount(dict); 00543 copied = 1; 00544 } 00545 status = Tcl_DictObjPut(interp, dict, literals[LIT_JULIANDAY], 00546 Tcl_NewIntObj(fields.julianDay)); 00547 if (status == TCL_OK) { 00548 Tcl_SetObjResult(interp, dict); 00549 } 00550 if (copied) { 00551 Tcl_DecrRefCount(dict); 00552 } 00553 return status; 00554 } 00555 00556 /* 00557 *---------------------------------------------------------------------- 00558 * 00559 * ClockGetjuliandayfromerayearweekdayObjCmd -- 00560 * 00561 * Tcl command that converts a time from the ISO calendar to a Julian Day 00562 * Number. 00563 * 00564 * Parameters: 00565 * dict - Dictionary that contains 'era', 'iso8601Year', 'iso8601Week' 00566 * and 'dayOfWeek' keys. 00567 * changeover - Julian Day of changeover to the Gregorian calendar 00568 * 00569 * Results: 00570 * Result is either TCL_OK, with the interpreter result being the 00571 * dictionary augmented with a 'julianDay' key, or TCL_ERROR, with the 00572 * result being an error message. 00573 * 00574 *---------------------------------------------------------------------- 00575 */ 00576 00577 static int 00578 ClockGetjuliandayfromerayearweekdayObjCmd ( 00579 ClientData clientData, /* Opaque pointer to literal pool, etc. */ 00580 Tcl_Interp* interp, /* Tcl interpreter */ 00581 int objc, /* Parameter count */ 00582 Tcl_Obj *const *objv) /* Parameter vector */ 00583 { 00584 TclDateFields fields; 00585 Tcl_Obj* dict; 00586 ClockClientData* data = (ClockClientData*) clientData; 00587 Tcl_Obj* const * literals = data->literals; 00588 Tcl_Obj* fieldPtr; 00589 int changeover; 00590 int copied = 0; 00591 int status; 00592 int era = 0; 00593 00594 /* 00595 * Check params. 00596 */ 00597 00598 if (objc != 3) { 00599 Tcl_WrongNumArgs(interp, 1, objv, "dict changeover"); 00600 return TCL_ERROR; 00601 } 00602 dict = objv[1]; 00603 if (Tcl_DictObjGet(interp, dict, literals[LIT_ERA], &fieldPtr) != TCL_OK 00604 || Tcl_GetIndexFromObj(interp, fieldPtr, eras, "era", TCL_EXACT, 00605 &era) != TCL_OK 00606 || Tcl_DictObjGet(interp, dict, literals[LIT_ISO8601YEAR], 00607 &fieldPtr) != TCL_OK 00608 || TclGetIntFromObj(interp, fieldPtr, 00609 &(fields.iso8601Year)) != TCL_OK 00610 || Tcl_DictObjGet(interp, dict, literals[LIT_ISO8601WEEK], 00611 &fieldPtr) != TCL_OK 00612 || TclGetIntFromObj(interp, fieldPtr, 00613 &(fields.iso8601Week)) != TCL_OK 00614 || Tcl_DictObjGet(interp, dict, literals[LIT_DAYOFWEEK], 00615 &fieldPtr) != TCL_OK 00616 || TclGetIntFromObj(interp, fieldPtr, 00617 &(fields.dayOfWeek)) != TCL_OK 00618 || TclGetIntFromObj(interp, objv[2], &changeover) != TCL_OK) { 00619 return TCL_ERROR; 00620 } 00621 fields.era = era; 00622 00623 /* 00624 * Get Julian day. 00625 */ 00626 00627 GetJulianDayFromEraYearWeekDay(&fields, changeover); 00628 00629 /* 00630 * Store Julian day in the dictionary - copy on write. 00631 */ 00632 00633 if (Tcl_IsShared(dict)) { 00634 dict = Tcl_DuplicateObj(dict); 00635 Tcl_IncrRefCount(dict); 00636 copied = 1; 00637 } 00638 status = Tcl_DictObjPut(interp, dict, literals[LIT_JULIANDAY], 00639 Tcl_NewIntObj(fields.julianDay)); 00640 if (status == TCL_OK) { 00641 Tcl_SetObjResult(interp, dict); 00642 } 00643 if (copied) { 00644 Tcl_DecrRefCount(dict); 00645 } 00646 return status; 00647 } 00648 00649 /* 00650 *---------------------------------------------------------------------- 00651 * 00652 * ConvertLocalToUTC -- 00653 * 00654 * Converts a time (in a TclDateFields structure) from the local wall 00655 * clock to UTC. 00656 * 00657 * Results: 00658 * Returns a standard Tcl result. 00659 * 00660 * Side effects: 00661 * Populates the 'seconds' field if successful; stores an error message 00662 * in the interpreter result on failure. 00663 * 00664 *---------------------------------------------------------------------- 00665 */ 00666 00667 static int 00668 ConvertLocalToUTC( 00669 Tcl_Interp* interp, /* Tcl interpreter */ 00670 TclDateFields* fields, /* Fields of the time */ 00671 Tcl_Obj* tzdata, /* Time zone data */ 00672 int changeover) /* Julian Day of the Gregorian transition */ 00673 { 00674 int rowc; /* Number of rows in tzdata */ 00675 Tcl_Obj** rowv; /* Pointers to the rows */ 00676 00677 /* 00678 * Unpack the tz data. 00679 */ 00680 00681 if (TclListObjGetElements(interp, tzdata, &rowc, &rowv) != TCL_OK) { 00682 return TCL_ERROR; 00683 } 00684 00685 /* 00686 * Special case: If the time zone is :localtime, the tzdata will be empty. 00687 * Use 'mktime' to convert the time to local 00688 */ 00689 00690 if (rowc == 0) { 00691 return ConvertLocalToUTCUsingC(interp, fields, changeover); 00692 } else { 00693 return ConvertLocalToUTCUsingTable(interp, fields, rowc, rowv); 00694 } 00695 } 00696 00697 /* 00698 *---------------------------------------------------------------------- 00699 * 00700 * ConvertLocalToUTCUsingTable -- 00701 * 00702 * Converts a time (in a TclDateFields structure) from local time in a 00703 * given time zone to UTC. 00704 * 00705 * Results: 00706 * Returns a standard Tcl result. 00707 * 00708 * Side effects: 00709 * Stores an error message in the interpreter if an error occurs; if 00710 * successful, stores the 'seconds' field in 'fields. 00711 * 00712 *---------------------------------------------------------------------- 00713 */ 00714 00715 static int 00716 ConvertLocalToUTCUsingTable( 00717 Tcl_Interp* interp, /* Tcl interpreter */ 00718 TclDateFields* fields, /* Time to convert, with 'seconds' filled in */ 00719 int rowc, /* Number of points at which time changes */ 00720 Tcl_Obj *const rowv[]) /* Points at which time changes */ 00721 { 00722 Tcl_Obj* row; 00723 int cellc; 00724 Tcl_Obj** cellv; 00725 int have[8]; 00726 int nHave = 0; 00727 int i; 00728 int found; 00729 00730 /* 00731 * Perform an initial lookup assuming that local == UTC, and locate the 00732 * last time conversion prior to that time. Get the offset from that row, 00733 * and look up again. Continue until we find an offset that we found 00734 * before. This definition, rather than "the same offset" ensures that we 00735 * don't enter an endless loop, as would otherwise happen when trying to 00736 * convert a non-existent time such as 02:30 during the US Spring Daylight 00737 * Saving Time transition. 00738 */ 00739 00740 found = 0; 00741 fields->tzOffset = 0; 00742 fields->seconds = fields->localSeconds; 00743 while (!found) { 00744 row = LookupLastTransition(interp, fields->seconds, rowc, rowv); 00745 if ((row == NULL) 00746 || TclListObjGetElements(interp, row, &cellc, 00747 &cellv) != TCL_OK 00748 || TclGetIntFromObj(interp, cellv[1], 00749 &(fields->tzOffset)) != TCL_OK) { 00750 return TCL_ERROR; 00751 } 00752 found = 0; 00753 for (i = 0; !found && i < nHave; ++i) { 00754 if (have[i] == fields->tzOffset) { 00755 found = 1; 00756 break; 00757 } 00758 } 00759 if (!found) { 00760 if (nHave == 8) { 00761 Tcl_Panic("loop in ConvertLocalToUTCUsingTable"); 00762 } 00763 have[nHave] = fields->tzOffset; 00764 ++nHave; 00765 } 00766 fields->seconds = fields->localSeconds - fields->tzOffset; 00767 } 00768 fields->tzOffset = have[i]; 00769 fields->seconds = fields->localSeconds - fields->tzOffset; 00770 return TCL_OK; 00771 } 00772 00773 /* 00774 *---------------------------------------------------------------------- 00775 * 00776 * ConvertLocalToUTCUsingC -- 00777 * 00778 * Converts a time from local wall clock to UTC when the local time zone 00779 * cannot be determined. Uses 'mktime' to do the job. 00780 * 00781 * Results: 00782 * Returns a standard Tcl result. 00783 * 00784 * Side effects: 00785 * Stores an error message in the interpreter if an error occurs; if 00786 * successful, stores the 'seconds' field in 'fields. 00787 * 00788 *---------------------------------------------------------------------- 00789 */ 00790 00791 static int 00792 ConvertLocalToUTCUsingC( 00793 Tcl_Interp* interp, /* Tcl interpreter */ 00794 TclDateFields* fields, /* Time to convert, with 'seconds' filled in */ 00795 int changeover) /* Julian Day of the Gregorian transition */ 00796 { 00797 struct tm timeVal; 00798 int localErrno; 00799 int secondOfDay; 00800 Tcl_WideInt jsec; 00801 00802 /* 00803 * Convert the given time to a date. 00804 */ 00805 00806 jsec = fields->localSeconds + JULIAN_SEC_POSIX_EPOCH; 00807 fields->julianDay = (int) (jsec / SECONDS_PER_DAY); 00808 secondOfDay = (int)(jsec % SECONDS_PER_DAY); 00809 if (secondOfDay < 0) { 00810 secondOfDay += SECONDS_PER_DAY; 00811 --fields->julianDay; 00812 } 00813 GetGregorianEraYearDay(fields, changeover); 00814 GetMonthDay(fields); 00815 00816 /* 00817 * Convert the date/time to a 'struct tm'. 00818 */ 00819 00820 timeVal.tm_year = fields->year - 1900; 00821 timeVal.tm_mon = fields->month - 1; 00822 timeVal.tm_mday = fields->dayOfMonth; 00823 timeVal.tm_hour = (secondOfDay / 3600) % 24; 00824 timeVal.tm_min = (secondOfDay / 60) % 60; 00825 timeVal.tm_sec = secondOfDay % 60; 00826 timeVal.tm_isdst = -1; 00827 timeVal.tm_wday = -1; 00828 timeVal.tm_yday = -1; 00829 00830 /* 00831 * Get local time. It is rumored that mktime is not thread safe on some 00832 * platforms, so seize a mutex before attempting this. 00833 */ 00834 00835 TzsetIfNecessary(); 00836 Tcl_MutexLock(&clockMutex); 00837 errno = 0; 00838 fields->seconds = (Tcl_WideInt) mktime(&timeVal); 00839 localErrno = errno; 00840 Tcl_MutexUnlock(&clockMutex); 00841 00842 /* 00843 * If conversion fails, report an error. 00844 */ 00845 00846 if (localErrno != 0 00847 || (fields->seconds == -1 && timeVal.tm_yday == -1)) { 00848 Tcl_SetResult(interp, "time value too large/small to represent", 00849 TCL_STATIC); 00850 return TCL_ERROR; 00851 } 00852 return TCL_OK; 00853 } 00854 00855 /* 00856 *---------------------------------------------------------------------- 00857 * 00858 * ConvertUTCToLocal -- 00859 * 00860 * Converts a time (in a TclDateFields structure) from UTC to local time. 00861 * 00862 * Results: 00863 * Returns a standard Tcl result. 00864 * 00865 * Side effects: 00866 * Populates the 'tzName' and 'tzOffset' fields. 00867 * 00868 *---------------------------------------------------------------------- 00869 */ 00870 00871 static int 00872 ConvertUTCToLocal( 00873 Tcl_Interp* interp, /* Tcl interpreter */ 00874 TclDateFields* fields, /* Fields of the time */ 00875 Tcl_Obj* tzdata, /* Time zone data */ 00876 int changeover) /* Julian Day of the Gregorian transition */ 00877 { 00878 int rowc; /* Number of rows in tzdata */ 00879 Tcl_Obj** rowv; /* Pointers to the rows */ 00880 00881 /* 00882 * Unpack the tz data. 00883 */ 00884 00885 if (TclListObjGetElements(interp, tzdata, &rowc, &rowv) != TCL_OK) { 00886 return TCL_ERROR; 00887 } 00888 00889 /* 00890 * Special case: If the time zone is :localtime, the tzdata will be empty. 00891 * Use 'localtime' to convert the time to local 00892 */ 00893 00894 if (rowc == 0) { 00895 return ConvertUTCToLocalUsingC(interp, fields, changeover); 00896 } else { 00897 return ConvertUTCToLocalUsingTable(interp, fields, rowc, rowv); 00898 } 00899 } 00900 00901 /* 00902 *---------------------------------------------------------------------- 00903 * 00904 * ConvertUTCToLocalUsingTable -- 00905 * 00906 * Converts UTC to local time, given a table of transition points 00907 * 00908 * Results: 00909 * Returns a standard Tcl result 00910 * 00911 * Side effects: 00912 * On success, fills fields->tzName, fields->tzOffset and 00913 * fields->localSeconds. On failure, places an error message in the 00914 * interpreter result. 00915 * 00916 *---------------------------------------------------------------------- 00917 */ 00918 00919 static int 00920 ConvertUTCToLocalUsingTable( 00921 Tcl_Interp* interp, /* Tcl interpreter */ 00922 TclDateFields* fields, /* Fields of the date */ 00923 int rowc, /* Number of rows in the conversion table 00924 * (>= 1) */ 00925 Tcl_Obj *const rowv[]) /* Rows of the conversion table */ 00926 { 00927 Tcl_Obj* row; /* Row containing the current information */ 00928 int cellc; /* Count of cells in the row (must be 4) */ 00929 Tcl_Obj** cellv; /* Pointers to the cells */ 00930 00931 /* 00932 * Look up the nearest transition time. 00933 */ 00934 00935 row = LookupLastTransition(interp, fields->seconds, rowc, rowv); 00936 if (row == NULL || 00937 TclListObjGetElements(interp, row, &cellc, &cellv) != TCL_OK || 00938 TclGetIntFromObj(interp,cellv[1],&(fields->tzOffset)) != TCL_OK) { 00939 return TCL_ERROR; 00940 } 00941 00942 /* 00943 * Convert the time. 00944 */ 00945 00946 fields->tzName = cellv[3]; 00947 Tcl_IncrRefCount(fields->tzName); 00948 fields->localSeconds = fields->seconds + fields->tzOffset; 00949 return TCL_OK; 00950 } 00951 00952 /* 00953 *---------------------------------------------------------------------- 00954 * 00955 * ConvertUTCToLocalUsingC -- 00956 * 00957 * Converts UTC to localtime in cases where the local time zone is not 00958 * determinable, using the C 'localtime' function to do it. 00959 * 00960 * Results: 00961 * Returns a standard Tcl result. 00962 * 00963 * Side effects: 00964 * On success, fills fields->tzName, fields->tzOffset and 00965 * fields->localSeconds. On failure, places an error message in the 00966 * interpreter result. 00967 * 00968 *---------------------------------------------------------------------- 00969 */ 00970 00971 static int 00972 ConvertUTCToLocalUsingC( 00973 Tcl_Interp* interp, /* Tcl interpreter */ 00974 TclDateFields* fields, /* Time to convert, with 'seconds' filled in */ 00975 int changeover) /* Julian Day of the Gregorian transition */ 00976 { 00977 time_t tock; 00978 struct tm* timeVal; /* Time after conversion */ 00979 int diff; /* Time zone diff local-Greenwich */ 00980 char buffer[8]; /* Buffer for time zone name */ 00981 00982 /* 00983 * Use 'localtime' to determine local year, month, day, time of day. 00984 */ 00985 00986 tock = (time_t) fields->seconds; 00987 if ((Tcl_WideInt) tock != fields->seconds) { 00988 Tcl_AppendResult(interp, 00989 "number too large to represent as a Posix time", NULL); 00990 Tcl_SetErrorCode(interp, "CLOCK", "argTooLarge", NULL); 00991 return TCL_ERROR; 00992 } 00993 TzsetIfNecessary(); 00994 timeVal = ThreadSafeLocalTime(&tock); 00995 if (timeVal == NULL) { 00996 Tcl_AppendResult(interp, 00997 "localtime failed (clock value may be too " 00998 "large/small to represent)", NULL); 00999 Tcl_SetErrorCode(interp, "CLOCK", "localtimeFailed", NULL); 01000 return TCL_ERROR; 01001 } 01002 01003 /* 01004 * Fill in the date in 'fields' and use it to derive Julian Day. 01005 */ 01006 01007 fields->era = CE; 01008 fields->year = timeVal->tm_year + 1900; 01009 fields->month = timeVal->tm_mon + 1; 01010 fields->dayOfMonth = timeVal->tm_mday; 01011 GetJulianDayFromEraYearMonthDay(fields, changeover); 01012 01013 /* 01014 * Convert that value to seconds. 01015 */ 01016 01017 fields->localSeconds = (((fields->julianDay * (Tcl_WideInt) 24 01018 + timeVal->tm_hour) * 60 + timeVal->tm_min) * 60 01019 + timeVal->tm_sec) - JULIAN_SEC_POSIX_EPOCH; 01020 01021 /* 01022 * Determine a time zone offset and name; just use +hhmm for the name. 01023 */ 01024 01025 diff = (int) (fields->localSeconds - fields->seconds); 01026 fields->tzOffset = diff; 01027 if (diff < 0) { 01028 *buffer = '-'; 01029 diff = -diff; 01030 } else { 01031 *buffer = '+'; 01032 } 01033 sprintf(buffer+1, "%02d", diff / 3600); 01034 diff %= 3600; 01035 sprintf(buffer+3, "%02d", diff / 60); 01036 diff %= 60; 01037 if (diff > 0) { 01038 sprintf(buffer+5, "%02d", diff); 01039 } 01040 fields->tzName = Tcl_NewStringObj(buffer, -1); 01041 Tcl_IncrRefCount(fields->tzName); 01042 return TCL_OK; 01043 } 01044 01045 /* 01046 *---------------------------------------------------------------------- 01047 * 01048 * LookupLastTransition -- 01049 * 01050 * Given a UTC time and a tzdata array, looks up the last transition on 01051 * or before the given time. 01052 * 01053 * Results: 01054 * Returns a pointer to the row, or NULL if an error occurs. 01055 * 01056 *---------------------------------------------------------------------- 01057 */ 01058 01059 static Tcl_Obj* 01060 LookupLastTransition( 01061 Tcl_Interp* interp, /* Interpreter for error messages */ 01062 Tcl_WideInt tick, /* Time from the epoch */ 01063 int rowc, /* Number of rows of tzdata */ 01064 Tcl_Obj *const *rowv) /* Rows in tzdata */ 01065 { 01066 int l; 01067 int u; 01068 Tcl_Obj* compObj; 01069 Tcl_WideInt compVal; 01070 01071 /* 01072 * Examine the first row to make sure we're in bounds. 01073 */ 01074 01075 if (Tcl_ListObjIndex(interp, rowv[0], 0, &compObj) != TCL_OK 01076 || Tcl_GetWideIntFromObj(interp, compObj, &compVal) != TCL_OK) { 01077 return NULL; 01078 } 01079 01080 /* 01081 * Bizarre case - first row doesn't begin at MIN_WIDE_INT. Return it 01082 * anyway. 01083 */ 01084 01085 if (tick < compVal) { 01086 return rowv[0]; 01087 } 01088 01089 /* 01090 * Binary-search to find the transition. 01091 */ 01092 01093 l = 0; 01094 u = rowc-1; 01095 while (l < u) { 01096 int m = (l + u + 1) / 2; 01097 01098 if (Tcl_ListObjIndex(interp, rowv[m], 0, &compObj) != TCL_OK || 01099 Tcl_GetWideIntFromObj(interp, compObj, &compVal) != TCL_OK) { 01100 return NULL; 01101 } 01102 if (tick >= compVal) { 01103 l = m; 01104 } else { 01105 u = m-1; 01106 } 01107 } 01108 return rowv[l]; 01109 } 01110 01111 /* 01112 *---------------------------------------------------------------------- 01113 * 01114 * GetYearWeekDay -- 01115 * 01116 * Given a date with Julian Calendar Day, compute the year, week, and day 01117 * in the ISO8601 calendar. 01118 * 01119 * Results: 01120 * None. 01121 * 01122 * Side effects: 01123 * Stores 'iso8601Year', 'iso8601Week' and 'dayOfWeek' in the date 01124 * fields. 01125 * 01126 *---------------------------------------------------------------------- 01127 */ 01128 01129 static void 01130 GetYearWeekDay( 01131 TclDateFields* fields, /* Date to convert, must have 'julianDay' */ 01132 int changeover) /* Julian Day Number of the Gregorian 01133 * transition */ 01134 { 01135 TclDateFields temp; 01136 int dayOfFiscalYear; 01137 01138 /* 01139 * Find the given date, minus three days, plus one year. That date's 01140 * iso8601 year is an upper bound on the ISO8601 year of the given date. 01141 */ 01142 01143 temp.julianDay = fields->julianDay - 3; 01144 GetGregorianEraYearDay(&temp, changeover); 01145 if (temp.era == BCE) { 01146 temp.iso8601Year = temp.year - 1; 01147 } else { 01148 temp.iso8601Year = temp.year + 1; 01149 } 01150 temp.iso8601Week = 1; 01151 temp.dayOfWeek = 1; 01152 GetJulianDayFromEraYearWeekDay(&temp, changeover); 01153 01154 /* 01155 * temp.julianDay is now the start of an ISO8601 year, either the one 01156 * corresponding to the given date, or the one after. If we guessed high, 01157 * move one year earlier 01158 */ 01159 01160 if (fields->julianDay < temp.julianDay) { 01161 if (temp.era == BCE) { 01162 temp.iso8601Year += 1; 01163 } else { 01164 temp.iso8601Year -= 1; 01165 } 01166 GetJulianDayFromEraYearWeekDay(&temp, changeover); 01167 } 01168 01169 fields->iso8601Year = temp.iso8601Year; 01170 dayOfFiscalYear = fields->julianDay - temp.julianDay; 01171 fields->iso8601Week = (dayOfFiscalYear / 7) + 1; 01172 fields->dayOfWeek = (dayOfFiscalYear + 1) % 7; 01173 if (fields->dayOfWeek < 1) { 01174 fields->dayOfWeek += 7; 01175 } 01176 } 01177 01178 /* 01179 *---------------------------------------------------------------------- 01180 * 01181 * GetGregorianEraYearDay -- 01182 * 01183 * Given a Julian Day Number, extracts the year and day of the year and 01184 * puts them into TclDateFields, along with the era (BCE or CE) and a 01185 * flag indicating whether the date is Gregorian or Julian. 01186 * 01187 * Results: 01188 * None. 01189 * 01190 * Side effects: 01191 * Stores 'era', 'gregorian', 'year', and 'dayOfYear'. 01192 * 01193 *---------------------------------------------------------------------- 01194 */ 01195 01196 static void 01197 GetGregorianEraYearDay( 01198 TclDateFields* fields, /* Date fields containing 'julianDay' */ 01199 int changeover) /* Gregorian transition date */ 01200 { 01201 int jday = fields->julianDay; 01202 int day; 01203 int year; 01204 int n; 01205 01206 if (jday >= changeover) { 01207 /* 01208 * Gregorian calendar. 01209 */ 01210 01211 fields->gregorian = 1; 01212 year = 1; 01213 01214 /* 01215 * n = Number of 400-year cycles since 1 January, 1 CE in the 01216 * proleptic Gregorian calendar. day = remaining days. 01217 */ 01218 01219 day = jday - JDAY_1_JAN_1_CE_GREGORIAN; 01220 n = day / FOUR_CENTURIES; 01221 day %= FOUR_CENTURIES; 01222 if (day < 0) { 01223 day += FOUR_CENTURIES; 01224 --n; 01225 } 01226 year += 400 * n; 01227 01228 /* 01229 * n = number of centuries since the start of (year); 01230 * day = remaining days 01231 */ 01232 01233 n = day / ONE_CENTURY_GREGORIAN; 01234 day %= ONE_CENTURY_GREGORIAN; 01235 if (n > 3) { 01236 /* 01237 * 31 December in the last year of a 400-year cycle. 01238 */ 01239 01240 n = 3; 01241 day += ONE_CENTURY_GREGORIAN; 01242 } 01243 year += 100 * n; 01244 01245 } else { 01246 /* 01247 * Julian calendar. 01248 */ 01249 01250 fields->gregorian = 0; 01251 year = 1; 01252 day = jday - JDAY_1_JAN_1_CE_JULIAN; 01253 01254 } 01255 01256 /* 01257 * n = number of 4-year cycles; days = remaining days. 01258 */ 01259 01260 n = day / FOUR_YEARS; 01261 day %= FOUR_YEARS; 01262 if (day < 0) { 01263 day += FOUR_YEARS; 01264 --n; 01265 } 01266 year += 4 * n; 01267 01268 /* 01269 * n = number of years; days = remaining days. 01270 */ 01271 01272 n = day / ONE_YEAR; 01273 day %= ONE_YEAR; 01274 if (n > 3) { 01275 /* 01276 * 31 December of a leap year. 01277 */ 01278 01279 n = 3; 01280 day += 365; 01281 } 01282 year += n; 01283 01284 /* 01285 * store era/year/day back into fields. 01286 */ 01287 01288 if (year <= 0) { 01289 fields->era = BCE; 01290 fields->year = 1 - year; 01291 } else { 01292 fields->era = CE; 01293 fields->year = year; 01294 } 01295 fields->dayOfYear = day + 1; 01296 } 01297 01298 /* 01299 *---------------------------------------------------------------------- 01300 * 01301 * GetMonthDay -- 01302 * 01303 * Given a date as year and day-of-year, find month and day. 01304 * 01305 * Results: 01306 * None. 01307 * 01308 * Side effects: 01309 * Stores 'month' and 'dayOfMonth' in the 'fields' structure. 01310 * 01311 *---------------------------------------------------------------------- 01312 */ 01313 01314 static void 01315 GetMonthDay( 01316 TclDateFields* fields) /* Date to convert */ 01317 { 01318 int day = fields->dayOfYear; 01319 int month; 01320 const int* h = hath[IsGregorianLeapYear(fields)]; 01321 01322 for (month = 0; month < 12 && day > h[month]; ++month) { 01323 day -= h[month]; 01324 } 01325 fields->month = month+1; 01326 fields->dayOfMonth = day; 01327 } 01328 01329 /* 01330 *---------------------------------------------------------------------- 01331 * 01332 * GetJulianDayFromEraYearWeekDay -- 01333 * 01334 * Given a TclDateFields structure containing era, ISO8601 year, ISO8601 01335 * week, and day of week, computes the Julian Day Number. 01336 * 01337 * Results: 01338 * None. 01339 * 01340 * Side effects: 01341 * Stores 'julianDay' in the fields. 01342 * 01343 *---------------------------------------------------------------------- 01344 */ 01345 01346 static void 01347 GetJulianDayFromEraYearWeekDay( 01348 TclDateFields* fields, /* Date to convert */ 01349 int changeover) /* Julian Day Number of the Gregorian 01350 * transition */ 01351 { 01352 int firstMonday; /* Julian day number of week 1, day 1 in the 01353 * given year */ 01354 01355 /* 01356 * Find January 4 in the ISO8601 year, which will always be in week 1. 01357 */ 01358 01359 TclDateFields firstWeek; 01360 firstWeek.era = fields->era; 01361 firstWeek.year = fields->iso8601Year; 01362 firstWeek.month = 1; 01363 firstWeek.dayOfMonth = 4; 01364 GetJulianDayFromEraYearMonthDay(&firstWeek, changeover); 01365 01366 /* 01367 * Find Monday of week 1. 01368 */ 01369 01370 firstMonday = WeekdayOnOrBefore(1, firstWeek.julianDay); 01371 01372 /* 01373 * Advance to the given week and day. 01374 */ 01375 01376 fields->julianDay = firstMonday + 7 * (fields->iso8601Week - 1) 01377 + fields->dayOfWeek - 1; 01378 } 01379 01380 /* 01381 *---------------------------------------------------------------------- 01382 * 01383 * GetJulianDayFromEraYearMonthDay -- 01384 * 01385 * Given era, year, month, and dayOfMonth (in TclDateFields), and the 01386 * Gregorian transition date, computes the Julian Day Number. 01387 * 01388 * Results: 01389 * None. 01390 * 01391 * Side effects: 01392 * Stores day number in 'julianDay' 01393 * 01394 *---------------------------------------------------------------------- 01395 */ 01396 01397 static void 01398 GetJulianDayFromEraYearMonthDay( 01399 TclDateFields* fields, /* Date to convert */ 01400 int changeover) /* Gregorian transition date as a Julian Day */ 01401 { 01402 int year; int ym1; 01403 int month; int mm1; 01404 int q; int r; 01405 int ym1o4; int ym1o100; int ym1o400; 01406 01407 if (fields->era == BCE) { 01408 year = 1 - fields->year; 01409 } else { 01410 year = fields->year; 01411 } 01412 01413 /* 01414 * Reduce month modulo 12. 01415 */ 01416 01417 month = fields->month; 01418 mm1 = month - 1; 01419 q = mm1 / 12; 01420 r = (mm1 % 12); 01421 if (r < 0) { 01422 r += 12; 01423 q -= 1; 01424 } 01425 year += q; 01426 month = r + 1; 01427 ym1 = year - 1; 01428 01429 /* 01430 * Adjust the year after reducing the month. 01431 */ 01432 01433 fields->gregorian = 1; 01434 if (year < 1) { 01435 fields->era = BCE; 01436 fields->year = 1-year; 01437 } else { 01438 fields->era = CE; 01439 fields->year = year; 01440 } 01441 01442 /* 01443 * Try an initial conversion in the Gregorian calendar. 01444 */ 01445 01446 ym1o4 = ym1 / 4; 01447 if (ym1 % 4 < 0) { 01448 --ym1o4; 01449 } 01450 ym1o100 = ym1 / 100; 01451 if (ym1 % 100 < 0) { 01452 --ym1o100; 01453 } 01454 ym1o400 = ym1 / 400; 01455 if (ym1 % 400 < 0) { 01456 --ym1o400; 01457 } 01458 fields->julianDay = JDAY_1_JAN_1_CE_GREGORIAN - 1 01459 + fields->dayOfMonth 01460 + daysInPriorMonths[IsGregorianLeapYear(fields)][month - 1] 01461 + (ONE_YEAR * ym1) 01462 + ym1o4 01463 - ym1o100 01464 + ym1o400; 01465 01466 /* 01467 * If the resulting date is before the Gregorian changeover, convert in 01468 * the Julian calendar instead. 01469 */ 01470 01471 if (fields->julianDay < changeover) { 01472 fields->gregorian = 0; 01473 fields->julianDay = JDAY_1_JAN_1_CE_JULIAN - 1 01474 + fields->dayOfMonth 01475 + daysInPriorMonths[year%4 == 0][month - 1] 01476 + (365 * ym1) 01477 + ym1o4; 01478 } 01479 } 01480 01481 /* 01482 *---------------------------------------------------------------------- 01483 * 01484 * IsGregorianLeapYear -- 01485 * 01486 * Tests whether a given year is a leap year, in either Julian or 01487 * Gregorian calendar. 01488 * 01489 * Results: 01490 * Returns 1 for a leap year, 0 otherwise. 01491 * 01492 *---------------------------------------------------------------------- 01493 */ 01494 01495 static int 01496 IsGregorianLeapYear( 01497 TclDateFields* fields) /* Date to test */ 01498 { 01499 int year; 01500 01501 if (fields->era == BCE) { 01502 year = 1 - fields->year; 01503 } else { 01504 year = fields->year; 01505 } 01506 if (year%4 != 0) { 01507 return 0; 01508 } else if (!(fields->gregorian)) { 01509 return 1; 01510 } else if (year%400 == 0) { 01511 return 1; 01512 } else if (year%100 == 0) { 01513 return 0; 01514 } else { 01515 return 1; 01516 } 01517 } 01518 01519 /* 01520 *---------------------------------------------------------------------- 01521 * 01522 * WeekdayOnOrBefore -- 01523 * 01524 * Finds the Julian Day Number of a given day of the week that falls on 01525 * or before a given date, expressed as Julian Day Number. 01526 * 01527 * Results: 01528 * Returns the Julian Day Number 01529 * 01530 *---------------------------------------------------------------------- 01531 */ 01532 01533 static int 01534 WeekdayOnOrBefore( 01535 int dayOfWeek, /* Day of week; Sunday == 0 or 7 */ 01536 int julianDay) /* Reference date */ 01537 { 01538 int k = (dayOfWeek + 6) % 7; 01539 if (k < 0) { 01540 k += 7; 01541 } 01542 return julianDay - ((julianDay - k) % 7); 01543 } 01544 01545 /* 01546 *---------------------------------------------------------------------- 01547 * 01548 * ClockGetenvObjCmd -- 01549 * 01550 * Tcl command that reads an environment variable from the system 01551 * 01552 * Usage: 01553 * ::tcl::clock::getEnv NAME 01554 * 01555 * Parameters: 01556 * NAME - Name of the environment variable desired 01557 * 01558 * Results: 01559 * Returns a standard Tcl result. Returns an error if the variable does 01560 * not exist, with a message left in the interpreter. Returns TCL_OK and 01561 * the value of the variable if the variable does exist, 01562 * 01563 *---------------------------------------------------------------------- 01564 */ 01565 01566 int 01567 ClockGetenvObjCmd( 01568 ClientData clientData, 01569 Tcl_Interp* interp, 01570 int objc, 01571 Tcl_Obj *const objv[]) 01572 { 01573 const char* varName; 01574 const char* varValue; 01575 01576 if (objc != 2) { 01577 Tcl_WrongNumArgs(interp, 1, objv, "name"); 01578 return TCL_ERROR; 01579 } 01580 varName = TclGetString(objv[1]); 01581 varValue = getenv(varName); 01582 if (varValue == NULL) { 01583 varValue = ""; 01584 } 01585 Tcl_SetObjResult(interp, Tcl_NewStringObj(varValue, -1)); 01586 return TCL_OK; 01587 } 01588 01589 /* 01590 *---------------------------------------------------------------------- 01591 * 01592 * ThreadSafeLocalTime -- 01593 * 01594 * Wrapper around the 'localtime' library function to make it thread 01595 * safe. 01596 * 01597 * Results: 01598 * Returns a pointer to a 'struct tm' in thread-specific data. 01599 * 01600 * Side effects: 01601 * Invokes localtime or localtime_r as appropriate. 01602 * 01603 *---------------------------------------------------------------------- 01604 */ 01605 01606 static struct tm * 01607 ThreadSafeLocalTime( 01608 const time_t *timePtr) /* Pointer to the number of seconds since the 01609 * local system's epoch */ 01610 { 01611 /* 01612 * Get a thread-local buffer to hold the returned time. 01613 */ 01614 01615 struct tm *tmPtr = (struct tm *) 01616 Tcl_GetThreadData(&tmKey, (int) sizeof(struct tm)); 01617 #ifdef HAVE_LOCALTIME_R 01618 localtime_r(timePtr, tmPtr); 01619 #else 01620 struct tm *sysTmPtr; 01621 01622 Tcl_MutexLock(&clockMutex); 01623 sysTmPtr = localtime(timePtr); 01624 if (sysTmPtr == NULL) { 01625 Tcl_MutexUnlock(&clockMutex); 01626 return NULL; 01627 } else { 01628 memcpy((void *) tmPtr, (void *) localtime(timePtr), sizeof(struct tm)); 01629 Tcl_MutexUnlock(&clockMutex); 01630 } 01631 #endif 01632 return tmPtr; 01633 } 01634 01635 /*---------------------------------------------------------------------- 01636 * 01637 * ClockClicksObjCmd -- 01638 * 01639 * Returns a high-resolution counter. 01640 * 01641 * Results: 01642 * Returns a standard Tcl result. 01643 * 01644 * Side effects: 01645 * None. 01646 * 01647 * This function implements the 'clock clicks' Tcl command. Refer to the user 01648 * documentation for details on what it does. 01649 * 01650 *---------------------------------------------------------------------- 01651 */ 01652 01653 int 01654 ClockClicksObjCmd( 01655 ClientData clientData, /* Client data is unused */ 01656 Tcl_Interp* interp, /* Tcl interpreter */ 01657 int objc, /* Parameter count */ 01658 Tcl_Obj* const* objv) /* Parameter values */ 01659 { 01660 static const char *clicksSwitches[] = { 01661 "-milliseconds", "-microseconds", NULL 01662 }; 01663 enum ClicksSwitch { 01664 CLICKS_MILLIS, CLICKS_MICROS, CLICKS_NATIVE 01665 }; 01666 int index = CLICKS_NATIVE; 01667 Tcl_Time now; 01668 01669 switch (objc) { 01670 case 1: 01671 break; 01672 case 2: 01673 if (Tcl_GetIndexFromObj(interp, objv[1], clicksSwitches, "option", 0, 01674 &index) != TCL_OK) { 01675 return TCL_ERROR; 01676 } 01677 break; 01678 default: 01679 Tcl_WrongNumArgs(interp, 1, objv, "?option?"); 01680 return TCL_ERROR; 01681 } 01682 01683 switch (index) { 01684 case CLICKS_MILLIS: 01685 Tcl_GetTime(&now); 01686 Tcl_SetObjResult(interp, Tcl_NewWideIntObj((Tcl_WideInt) 01687 now.sec * 1000 + now.usec / 1000)); 01688 break; 01689 case CLICKS_NATIVE: { 01690 #ifndef TCL_WIDE_CLICKS 01691 unsigned long clicks = TclpGetClicks(); 01692 #else 01693 Tcl_WideInt clicks = TclpGetWideClicks(); 01694 #endif 01695 Tcl_SetObjResult(interp, Tcl_NewWideIntObj((Tcl_WideInt) clicks)); 01696 break; 01697 } 01698 case CLICKS_MICROS: 01699 Tcl_GetTime(&now); 01700 Tcl_SetObjResult(interp, Tcl_NewWideIntObj( 01701 ((Tcl_WideInt) now.sec * 1000000) + now.usec)); 01702 break; 01703 } 01704 01705 return TCL_OK; 01706 } 01707 01708 /*---------------------------------------------------------------------- 01709 * 01710 * ClockMillisecondsObjCmd - 01711 * 01712 * Returns a count of milliseconds since the epoch. 01713 * 01714 * Results: 01715 * Returns a standard Tcl result. 01716 * 01717 * Side effects: 01718 * None. 01719 * 01720 * This function implements the 'clock milliseconds' Tcl command. Refer to the 01721 * user documentation for details on what it does. 01722 * 01723 *---------------------------------------------------------------------- 01724 */ 01725 01726 int 01727 ClockMillisecondsObjCmd( 01728 ClientData clientData, /* Client data is unused */ 01729 Tcl_Interp* interp, /* Tcl interpreter */ 01730 int objc, /* Parameter count */ 01731 Tcl_Obj* const* objv) /* Parameter values */ 01732 { 01733 Tcl_Time now; 01734 01735 if (objc != 1) { 01736 Tcl_WrongNumArgs(interp, 1, objv, NULL); 01737 return TCL_ERROR; 01738 } 01739 Tcl_GetTime(&now); 01740 Tcl_SetObjResult(interp, Tcl_NewWideIntObj( (Tcl_WideInt) 01741 now.sec * 1000 + now.usec / 1000)); 01742 return TCL_OK; 01743 } 01744 01745 /*---------------------------------------------------------------------- 01746 * 01747 * ClockMicrosecondsObjCmd - 01748 * 01749 * Returns a count of microseconds since the epoch. 01750 * 01751 * Results: 01752 * Returns a standard Tcl result. 01753 * 01754 * Side effects: 01755 * None. 01756 * 01757 * This function implements the 'clock microseconds' Tcl command. Refer to the 01758 * user documentation for details on what it does. 01759 * 01760 *---------------------------------------------------------------------- 01761 */ 01762 01763 int 01764 ClockMicrosecondsObjCmd( 01765 ClientData clientData, /* Client data is unused */ 01766 Tcl_Interp* interp, /* Tcl interpreter */ 01767 int objc, /* Parameter count */ 01768 Tcl_Obj* const* objv) /* Parameter values */ 01769 { 01770 Tcl_Time now; 01771 01772 if (objc != 1) { 01773 Tcl_WrongNumArgs(interp, 1, objv, NULL); 01774 return TCL_ERROR; 01775 } 01776 Tcl_GetTime(&now); 01777 Tcl_SetObjResult(interp, Tcl_NewWideIntObj( 01778 ((Tcl_WideInt) now.sec * 1000000) + now.usec)); 01779 return TCL_OK; 01780 } 01781 01782 /*---------------------------------------------------------------------- 01783 * 01784 * ClockSecondsObjCmd - 01785 * 01786 * Returns a count of microseconds since the epoch. 01787 * 01788 * Results: 01789 * Returns a standard Tcl result. 01790 * 01791 * Side effects: 01792 * None. 01793 * 01794 * This function implements the 'clock seconds' Tcl command. Refer to the user 01795 * documentation for details on what it does. 01796 * 01797 *---------------------------------------------------------------------- 01798 */ 01799 01800 int 01801 ClockSecondsObjCmd( 01802 ClientData clientData, /* Client data is unused */ 01803 Tcl_Interp* interp, /* Tcl interpreter */ 01804 int objc, /* Parameter count */ 01805 Tcl_Obj* const* objv) /* Parameter values */ 01806 { 01807 Tcl_Time now; 01808 01809 if (objc != 1) { 01810 Tcl_WrongNumArgs(interp, 1, objv, NULL); 01811 return TCL_ERROR; 01812 } 01813 Tcl_GetTime(&now); 01814 Tcl_SetObjResult(interp, Tcl_NewWideIntObj((Tcl_WideInt) now.sec)); 01815 return TCL_OK; 01816 } 01817 01818 /* 01819 *---------------------------------------------------------------------- 01820 * 01821 * TzsetIfNecessary -- 01822 * 01823 * Calls the tzset() library function if the contents of the TZ 01824 * environment variable has changed. 01825 * 01826 * Results: 01827 * None. 01828 * 01829 * Side effects: 01830 * Calls tzset. 01831 * 01832 *---------------------------------------------------------------------- 01833 */ 01834 01835 static void 01836 TzsetIfNecessary(void) 01837 { 01838 static char* tzWas = NULL; /* Previous value of TZ, protected by 01839 * clockMutex. */ 01840 const char* tzIsNow; /* Current value of TZ */ 01841 01842 Tcl_MutexLock(&clockMutex); 01843 tzIsNow = getenv("TZ"); 01844 if (tzIsNow != NULL && (tzWas == NULL || strcmp(tzIsNow, tzWas) != 0)) { 01845 tzset(); 01846 if (tzWas != NULL) { 01847 ckfree(tzWas); 01848 } 01849 tzWas = ckalloc(strlen(tzIsNow) + 1); 01850 strcpy(tzWas, tzIsNow); 01851 } else if (tzIsNow == NULL && tzWas != NULL) { 01852 tzset(); 01853 ckfree(tzWas); 01854 tzWas = NULL; 01855 } 01856 Tcl_MutexUnlock(&clockMutex); 01857 } 01858 01859 /* 01860 *---------------------------------------------------------------------- 01861 * 01862 * ClockDeleteCmdProc -- 01863 * 01864 * Remove a reference to the clock client data, and clean up memory 01865 * when it's all gone. 01866 * 01867 * Results: 01868 * None. 01869 * 01870 *---------------------------------------------------------------------- 01871 */ 01872 01873 static void 01874 ClockDeleteCmdProc( 01875 ClientData clientData) /* Opaque pointer to the client data */ 01876 { 01877 ClockClientData *data = (ClockClientData*) clientData; 01878 int i; 01879 01880 --(data->refCount); 01881 if (data->refCount == 0) { 01882 for (i = 0; i < LIT__END; ++i) { 01883 Tcl_DecrRefCount(data->literals[i]); 01884 } 01885 ckfree((char*) (data->literals)); 01886 ckfree((char*) data); 01887 } 01888 } 01889 01890 /* 01891 * Local Variables: 01892 * mode: c 01893 * c-basic-offset: 4 01894 * fill-column: 78 01895 * End: 01896 */
Generated on Wed Mar 12 12:18:12 2008 by 1.5.1 |