sl@0: /* sl@0: * tclMacTime.c -- sl@0: * sl@0: * Contains Macintosh specific versions of Tcl functions that sl@0: * obtain time values from the operating system. sl@0: * sl@0: * Copyright (c) 1995-1997 Sun Microsystems, Inc. sl@0: * sl@0: * See the file "license.terms" for information on usage and redistribution sl@0: * of this file, and for a DISCLAIMER OF ALL WARRANTIES. sl@0: * sl@0: * RCS: @(#) $Id: tclMacTime.c,v 1.7 2002/01/04 11:21:05 das Exp $ sl@0: */ sl@0: sl@0: #include "tclInt.h" sl@0: #include "tclPort.h" sl@0: #include "tclMacInt.h" sl@0: #include sl@0: #include sl@0: #include sl@0: sl@0: /* sl@0: * Static variables used by the Tcl_GetTime function. sl@0: */ sl@0: sl@0: static int initalized = false; sl@0: static unsigned long baseSeconds; sl@0: static UnsignedWide microOffset; sl@0: sl@0: static int gmt_initialized = false; sl@0: static long gmt_offset; sl@0: static int gmt_isdst; sl@0: TCL_DECLARE_MUTEX(gmtMutex) sl@0: sl@0: static int gmt_lastGetDateUseGMT = 0; sl@0: sl@0: typedef struct _TABLE { sl@0: char *name; sl@0: int type; sl@0: time_t value; sl@0: } TABLE; sl@0: sl@0: sl@0: #define HOUR(x) ((time_t) (3600 * x)) sl@0: sl@0: #define tZONE 0 sl@0: #define tDAYZONE 1 sl@0: sl@0: sl@0: /* sl@0: * inverse timezone table, adapted from tclDate.c by removing duplicates and sl@0: * adding some made up names for unusual daylight savings sl@0: */ sl@0: static TABLE invTimezoneTable[] = { sl@0: { "Z", -1, HOUR( 36) }, /* Unknown */ sl@0: { "GMT", tZONE, HOUR( 0) }, /* Greenwich Mean */ sl@0: { "BST", tDAYZONE, HOUR( 0) }, /* British Summer */ sl@0: { "WAT", tZONE, HOUR( 1) }, /* West Africa */ sl@0: { "WADST", tDAYZONE, HOUR( 1) }, /* West Africa Daylight*/ sl@0: { "AT", tZONE, HOUR( 2) }, /* Azores Daylight*/ sl@0: { "ADST", tDAYZONE, HOUR( 2) }, /* Azores */ sl@0: { "NFT", tZONE, HOUR( 7/2) }, /* Newfoundland */ sl@0: { "NDT", tDAYZONE, HOUR( 7/2) }, /* Newfoundland Daylight */ sl@0: { "AST", tZONE, HOUR( 4) }, /* Atlantic Standard */ sl@0: { "ADT", tDAYZONE, HOUR( 4) }, /* Atlantic Daylight */ sl@0: { "EST", tZONE, HOUR( 5) }, /* Eastern Standard */ sl@0: { "EDT", tDAYZONE, HOUR( 5) }, /* Eastern Daylight */ sl@0: { "CST", tZONE, HOUR( 6) }, /* Central Standard */ sl@0: { "CDT", tDAYZONE, HOUR( 6) }, /* Central Daylight */ sl@0: { "MST", tZONE, HOUR( 7) }, /* Mountain Standard */ sl@0: { "MDT", tDAYZONE, HOUR( 7) }, /* Mountain Daylight */ sl@0: { "PST", tZONE, HOUR( 8) }, /* Pacific Standard */ sl@0: { "PDT", tDAYZONE, HOUR( 8) }, /* Pacific Daylight */ sl@0: { "YST", tZONE, HOUR( 9) }, /* Yukon Standard */ sl@0: { "YDT", tDAYZONE, HOUR( 9) }, /* Yukon Daylight */ sl@0: { "HST", tZONE, HOUR(10) }, /* Hawaii Standard */ sl@0: { "HDT", tDAYZONE, HOUR(10) }, /* Hawaii Daylight */ sl@0: { "NT", tZONE, HOUR(11) }, /* Nome */ sl@0: { "NST", tDAYZONE, HOUR(11) }, /* Nome Daylight*/ sl@0: { "IDLW", tZONE, HOUR(12) }, /* International Date Line West */ sl@0: { "CET", tZONE, -HOUR( 1) }, /* Central European */ sl@0: { "CEST", tDAYZONE, -HOUR( 1) }, /* Central European Summer */ sl@0: { "EET", tZONE, -HOUR( 2) }, /* Eastern Europe, USSR Zone 1 */ sl@0: { "EEST", tDAYZONE, -HOUR( 2) }, /* Eastern Europe, USSR Zone 1 Daylight*/ sl@0: { "BT", tZONE, -HOUR( 3) }, /* Baghdad, USSR Zone 2 */ sl@0: { "BDST", tDAYZONE, -HOUR( 3) }, /* Baghdad, USSR Zone 2 Daylight*/ sl@0: { "IT", tZONE, -HOUR( 7/2) }, /* Iran */ sl@0: { "IDST", tDAYZONE, -HOUR( 7/2) }, /* Iran Daylight*/ sl@0: { "ZP4", tZONE, -HOUR( 4) }, /* USSR Zone 3 */ sl@0: { "ZP4S", tDAYZONE, -HOUR( 4) }, /* USSR Zone 3 */ sl@0: { "ZP5", tZONE, -HOUR( 5) }, /* USSR Zone 4 */ sl@0: { "ZP5S", tDAYZONE, -HOUR( 5) }, /* USSR Zone 4 */ sl@0: { "IST", tZONE, -HOUR(11/2) }, /* Indian Standard */ sl@0: { "ISDST", tDAYZONE, -HOUR(11/2) }, /* Indian Standard */ sl@0: { "ZP6", tZONE, -HOUR( 6) }, /* USSR Zone 5 */ sl@0: { "ZP6S", tDAYZONE, -HOUR( 6) }, /* USSR Zone 5 */ sl@0: { "WAST", tZONE, -HOUR( 7) }, /* West Australian Standard */ sl@0: { "WADT", tDAYZONE, -HOUR( 7) }, /* West Australian Daylight */ sl@0: { "JT", tZONE, -HOUR(15/2) }, /* Java (3pm in Cronusland!) */ sl@0: { "JDST", tDAYZONE, -HOUR(15/2) }, /* Java (3pm in Cronusland!) */ sl@0: { "CCT", tZONE, -HOUR( 8) }, /* China Coast, USSR Zone 7 */ sl@0: { "CCST", tDAYZONE, -HOUR( 8) }, /* China Coast, USSR Zone 7 */ sl@0: { "JST", tZONE, -HOUR( 9) }, /* Japan Standard, USSR Zone 8 */ sl@0: { "JSDST", tDAYZONE, -HOUR( 9) }, /* Japan Standard, USSR Zone 8 */ sl@0: { "CAST", tZONE, -HOUR(19/2) }, /* Central Australian Standard */ sl@0: { "CADT", tDAYZONE, -HOUR(19/2) }, /* Central Australian Daylight */ sl@0: { "EAST", tZONE, -HOUR(10) }, /* Eastern Australian Standard */ sl@0: { "EADT", tDAYZONE, -HOUR(10) }, /* Eastern Australian Daylight */ sl@0: { "NZT", tZONE, -HOUR(12) }, /* New Zealand */ sl@0: { "NZDT", tDAYZONE, -HOUR(12) }, /* New Zealand Daylight */ sl@0: { NULL } sl@0: }; sl@0: sl@0: /* sl@0: * Prototypes for procedures that are private to this file: sl@0: */ sl@0: sl@0: static void SubtractUnsignedWide _ANSI_ARGS_((UnsignedWide *x, sl@0: UnsignedWide *y, UnsignedWide *result)); sl@0: sl@0: /* sl@0: *----------------------------------------------------------------------------- sl@0: * sl@0: * TclpGetGMTOffset -- sl@0: * sl@0: * This procedure gets the offset seconds that needs to be _added_ to tcl time sl@0: * in seconds (i.e. GMT time) to get local time needed as input to various sl@0: * Mac OS APIs, to convert Mac OS API output to tcl time, _subtract_ this value. sl@0: * sl@0: * Results: sl@0: * Number of seconds separating GMT time and mac. sl@0: * sl@0: * Side effects: sl@0: * None. sl@0: * sl@0: *----------------------------------------------------------------------------- sl@0: */ sl@0: sl@0: long sl@0: TclpGetGMTOffset() sl@0: { sl@0: if (gmt_initialized == false) { sl@0: MachineLocation loc; sl@0: sl@0: Tcl_MutexLock(&gmtMutex); sl@0: ReadLocation(&loc); sl@0: gmt_offset = loc.u.gmtDelta & 0x00ffffff; sl@0: if (gmt_offset & 0x00800000) { sl@0: gmt_offset = gmt_offset | 0xff000000; sl@0: } sl@0: gmt_isdst=(loc.u.dlsDelta < 0); sl@0: gmt_initialized = true; sl@0: Tcl_MutexUnlock(&gmtMutex); sl@0: } sl@0: return (gmt_offset); sl@0: } sl@0: sl@0: /* sl@0: *----------------------------------------------------------------------------- sl@0: * sl@0: * TclpGetSeconds -- sl@0: * sl@0: * This procedure returns the number of seconds from the epoch. On sl@0: * the Macintosh the epoch is Midnight Jan 1, 1904. Unfortunatly, sl@0: * the Macintosh doesn't tie the epoch to a particular time zone. For sl@0: * Tcl we tie the epoch to GMT. This makes the time zone date parsing sl@0: * code work. The epoch for Mac-Tcl is: Midnight Jan 1, 1904 GMT. sl@0: * sl@0: * Results: sl@0: * Number of seconds from the epoch in GMT. sl@0: * sl@0: * Side effects: sl@0: * None. sl@0: * sl@0: *----------------------------------------------------------------------------- sl@0: */ sl@0: sl@0: unsigned long sl@0: TclpGetSeconds() sl@0: { sl@0: unsigned long seconds; sl@0: sl@0: GetDateTime(&seconds); sl@0: return (seconds - TclpGetGMTOffset() + tcl_mac_epoch_offset); sl@0: } sl@0: sl@0: /* sl@0: *----------------------------------------------------------------------------- sl@0: * sl@0: * TclpGetClicks -- sl@0: * sl@0: * This procedure returns a value that represents the highest resolution sl@0: * clock available on the system. There are no garantees on what the sl@0: * resolution will be. In Tcl we will call this value a "click". The sl@0: * start time is also system dependant. sl@0: * sl@0: * Results: sl@0: * Number of clicks from some start time. sl@0: * sl@0: * Side effects: sl@0: * None. sl@0: * sl@0: *----------------------------------------------------------------------------- sl@0: */ sl@0: sl@0: unsigned long sl@0: TclpGetClicks() sl@0: { sl@0: UnsignedWide micros; sl@0: sl@0: Microseconds(µs); sl@0: return micros.lo; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * TclpGetTimeZone -- sl@0: * sl@0: * Get the current time zone. sl@0: * sl@0: * Results: sl@0: * The return value is the local time zone, measured in sl@0: * minutes away from GMT (-ve for east, +ve for west). sl@0: * sl@0: * Side effects: sl@0: * None. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: int sl@0: TclpGetTimeZone ( sl@0: unsigned long currentTime) /* Ignored on Mac. */ sl@0: { sl@0: long offset; sl@0: sl@0: /* sl@0: * Convert the Mac offset from seconds to minutes and sl@0: * add an hour if we have daylight savings time. sl@0: */ sl@0: offset = -TclpGetGMTOffset(); sl@0: offset /= 60; sl@0: if (gmt_isdst) { sl@0: offset += 60; sl@0: } sl@0: sl@0: return offset; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * Tcl_GetTime -- sl@0: * sl@0: * Gets the current system time in seconds and microseconds sl@0: * since the beginning of the epoch: 00:00 UCT, January 1, 1970. sl@0: * sl@0: * Results: sl@0: * Returns the current time (in the local timezone) in timePtr. sl@0: * sl@0: * Side effects: sl@0: * None. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: void sl@0: Tcl_GetTime( sl@0: Tcl_Time *timePtr) /* Location to store time information. */ sl@0: { sl@0: UnsignedWide micro; sl@0: #ifndef NO_LONG_LONG sl@0: long long *microPtr; sl@0: #endif sl@0: sl@0: if (initalized == false) { sl@0: GetDateTime(&baseSeconds); sl@0: /* sl@0: * Remove the local offset that ReadDateTime() adds. sl@0: */ sl@0: baseSeconds -= TclpGetGMTOffset() - tcl_mac_epoch_offset; sl@0: Microseconds(µOffset); sl@0: initalized = true; sl@0: } sl@0: sl@0: Microseconds(µ); sl@0: sl@0: #ifndef NO_LONG_LONG sl@0: microPtr = (long long *) µ sl@0: *microPtr -= *((long long *) µOffset); sl@0: timePtr->sec = baseSeconds + (*microPtr / 1000000); sl@0: timePtr->usec = *microPtr % 1000000; sl@0: #else sl@0: SubtractUnsignedWide(µ, µOffset, µ); sl@0: sl@0: /* sl@0: * This lovely computation is equal to: base + (micro / 1000000) sl@0: * For the .hi part the ratio of 0x100000000 / 1000000 has been sl@0: * reduced to avoid overflow. This computation certainly has sl@0: * problems as the .hi part gets large. However, your application sl@0: * would have to run for a long time to make that happen. sl@0: */ sl@0: sl@0: timePtr->sec = baseSeconds + (micro.lo / 1000000) + sl@0: (long) (micro.hi * ((double) 33554432.0 / 15625.0)); sl@0: timePtr->usec = micro.lo % 1000000; sl@0: #endif sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * TclpGetDate -- sl@0: * sl@0: * Converts raw seconds to a struct tm data structure. The sl@0: * returned time will be for Greenwich Mean Time if the useGMT flag sl@0: * is set. Otherwise, the returned time will be for the local sl@0: * time zone. This function is meant to be used as a replacement sl@0: * for localtime and gmtime which is broken on most ANSI libs sl@0: * on the Macintosh. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * The passed in struct tm data structure is modified. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: struct tm * sl@0: TclpGetDate( sl@0: TclpTime_t time, /* Time struct to fill. */ sl@0: int useGMT) /* True if date should reflect GNT time. */ sl@0: { sl@0: const time_t *tp = (const time_t *)time; sl@0: DateTimeRec dtr; sl@0: unsigned long offset=0L; sl@0: static struct tm statictime; sl@0: static const short monthday[12] = sl@0: {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}; sl@0: sl@0: if(useGMT) sl@0: SecondsToDate(*tp - tcl_mac_epoch_offset, &dtr); sl@0: else sl@0: SecondsToDate(*tp + TclpGetGMTOffset() - tcl_mac_epoch_offset, &dtr); sl@0: sl@0: statictime.tm_sec = dtr.second; sl@0: statictime.tm_min = dtr.minute; sl@0: statictime.tm_hour = dtr.hour; sl@0: statictime.tm_mday = dtr.day; sl@0: statictime.tm_mon = dtr.month - 1; sl@0: statictime.tm_year = dtr.year - 1900; sl@0: statictime.tm_wday = dtr.dayOfWeek - 1; sl@0: statictime.tm_yday = monthday[statictime.tm_mon] sl@0: + statictime.tm_mday - 1; sl@0: if (1 < statictime.tm_mon && !(statictime.tm_year & 3)) { sl@0: ++statictime.tm_yday; sl@0: } sl@0: if(useGMT) sl@0: statictime.tm_isdst = 0; sl@0: else sl@0: statictime.tm_isdst = gmt_isdst; sl@0: gmt_lastGetDateUseGMT=useGMT; /* hack to make TclpGetTZName below work */ sl@0: return(&statictime); sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * TclpGetTZName -- sl@0: * sl@0: * Gets the current timezone string. sl@0: * sl@0: * Results: sl@0: * Returns a pointer to a static string, or NULL on failure. sl@0: * sl@0: * Side effects: sl@0: * None. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: char * sl@0: TclpGetTZName(int dst) sl@0: { sl@0: register TABLE *tp; sl@0: long zonevalue=-TclpGetGMTOffset(); sl@0: sl@0: if (gmt_isdst) sl@0: zonevalue += HOUR(1); sl@0: sl@0: if(gmt_lastGetDateUseGMT) /* hack: if last TclpGetDate was called */ sl@0: zonevalue=0; /* with useGMT==1 then we're using GMT */ sl@0: sl@0: for (tp = invTimezoneTable; tp->name; tp++) { sl@0: if ((tp->value == zonevalue) && (tp->type == dst)) break; sl@0: } sl@0: if(!tp->name) sl@0: tp = invTimezoneTable; /* default to unknown */ sl@0: sl@0: return tp->name; sl@0: } sl@0: sl@0: #ifdef NO_LONG_LONG sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * SubtractUnsignedWide -- sl@0: * sl@0: * Subtracts one UnsignedWide value from another. sl@0: * sl@0: * Results: sl@0: * The subtracted value. sl@0: * sl@0: * Side effects: sl@0: * None. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static void sl@0: SubtractUnsignedWide( sl@0: UnsignedWide *x, /* Ptr to wide int. */ sl@0: UnsignedWide *y, /* Ptr to wide int. */ sl@0: UnsignedWide *result) /* Ptr to result. */ sl@0: { sl@0: result->hi = x->hi - y->hi; sl@0: if (x->lo < y->lo) { sl@0: result->hi--; sl@0: } sl@0: result->lo = x->lo - y->lo; sl@0: } sl@0: #endif