1/*
2 * tclUnixTime.c --
3 *
4 *	Contains Unix specific versions of Tcl functions that obtain time
5 *	values from the operating system.
6 *
7 * Copyright (c) 1995 Sun Microsystems, Inc.
8 *
9 * See the file "license.terms" for information on usage and redistribution of
10 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
11 *
12 * RCS: @(#) $Id: tclUnixTime.c,v 1.33.2.1 2008/04/14 17:49:59 kennykb Exp $
13 */
14
15#include "tclInt.h"
16#include <locale.h>
17#if defined(TCL_WIDE_CLICKS) && defined(MAC_OSX_TCL)
18#include <mach/mach_time.h>
19#endif
20
21#define TM_YEAR_BASE 1900
22#define IsLeapYear(x)	(((x)%4 == 0) && ((x)%100 != 0 || (x)%400 == 0))
23
24/*
25 * TclpGetDate is coded to return a pointer to a 'struct tm'. For thread
26 * safety, this structure must be in thread-specific data. The 'tmKey'
27 * variable is the key to this buffer.
28 */
29
30static Tcl_ThreadDataKey tmKey;
31typedef struct ThreadSpecificData {
32    struct tm gmtime_buf;
33    struct tm localtime_buf;
34} ThreadSpecificData;
35
36/*
37 * If we fall back on the thread-unsafe versions of gmtime and localtime, use
38 * this mutex to try to protect them.
39 */
40
41TCL_DECLARE_MUTEX(tmMutex)
42
43static char *lastTZ = NULL;	/* Holds the last setting of the TZ
44				 * environment variable, or an empty string if
45				 * the variable was not set. */
46
47/*
48 * Static functions declared in this file.
49 */
50
51static void		SetTZIfNecessary(void);
52static void		CleanupMemory(ClientData clientData);
53static void		NativeScaleTime(Tcl_Time *timebuf,
54			    ClientData clientData);
55static void		NativeGetTime(Tcl_Time *timebuf,
56			    ClientData clientData);
57
58/*
59 * TIP #233 (Virtualized Time): Data for the time hooks, if any.
60 */
61
62Tcl_GetTimeProc *tclGetTimeProcPtr = NativeGetTime;
63Tcl_ScaleTimeProc *tclScaleTimeProcPtr = NativeScaleTime;
64ClientData tclTimeClientData = NULL;
65
66/*
67 *-----------------------------------------------------------------------------
68 *
69 * TclpGetSeconds --
70 *
71 *	This procedure returns the number of seconds from the epoch. On most
72 *	Unix systems the epoch is Midnight Jan 1, 1970 GMT.
73 *
74 * Results:
75 *	Number of seconds from the epoch.
76 *
77 * Side effects:
78 *	None.
79 *
80 *-----------------------------------------------------------------------------
81 */
82
83unsigned long
84TclpGetSeconds(void)
85{
86    return time(NULL);
87}
88
89/*
90 *-----------------------------------------------------------------------------
91 *
92 * TclpGetClicks --
93 *
94 *	This procedure returns a value that represents the highest resolution
95 *	clock available on the system. There are no garantees on what the
96 *	resolution will be. In Tcl we will call this value a "click". The
97 *	start time is also system dependant.
98 *
99 * Results:
100 *	Number of clicks from some start time.
101 *
102 * Side effects:
103 *	None.
104 *
105 *-----------------------------------------------------------------------------
106 */
107
108unsigned long
109TclpGetClicks(void)
110{
111    unsigned long now;
112
113#ifdef NO_GETTOD
114    if (tclGetTimeProcPtr != NativeGetTime) {
115	Tcl_Time time;
116
117	(*tclGetTimeProcPtr) (&time, tclTimeClientData);
118	now = time.sec*1000000 + time.usec;
119    } else {
120	/*
121	 * A semi-NativeGetTime, specialized to clicks.
122	 */
123	struct tms dummy;
124
125	now = (unsigned long) times(&dummy);
126    }
127#else
128    Tcl_Time time;
129
130    (*tclGetTimeProcPtr) (&time, tclTimeClientData);
131    now = time.sec*1000000 + time.usec;
132#endif
133
134    return now;
135}
136#ifdef TCL_WIDE_CLICKS
137
138/*
139 *-----------------------------------------------------------------------------
140 *
141 * TclpGetWideClicks --
142 *
143 *	This procedure returns a WideInt value that represents the highest
144 *	resolution clock available on the system. There are no garantees on
145 *	what the resolution will be. In Tcl we will call this value a "click".
146 *	The start time is also system dependant.
147 *
148 * Results:
149 *	Number of WideInt clicks from some start time.
150 *
151 * Side effects:
152 *	None.
153 *
154 *-----------------------------------------------------------------------------
155 */
156
157Tcl_WideInt
158TclpGetWideClicks(void)
159{
160    Tcl_WideInt now;
161
162    if (tclGetTimeProcPtr != NativeGetTime) {
163	Tcl_Time time;
164
165	(*tclGetTimeProcPtr) (&time, tclTimeClientData);
166	now = (Tcl_WideInt) (time.sec*1000000 + time.usec);
167    } else {
168#ifdef MAC_OSX_TCL
169	now = (Tcl_WideInt) (mach_absolute_time() & INT64_MAX);
170#else
171#error Wide high-resolution clicks not implemented on this platform
172#endif
173    }
174
175    return now;
176}
177
178/*
179 *-----------------------------------------------------------------------------
180 *
181 * TclpWideClicksToNanoseconds --
182 *
183 *	This procedure converts click values from the TclpGetWideClicks native
184 *	resolution to nanosecond resolution.
185 *
186 * Results:
187 *	Number of nanoseconds from some start time.
188 *
189 * Side effects:
190 *	None.
191 *
192 *-----------------------------------------------------------------------------
193 */
194
195double
196TclpWideClicksToNanoseconds(
197    Tcl_WideInt clicks)
198{
199    double nsec;
200
201    if (tclGetTimeProcPtr != NativeGetTime) {
202	nsec = clicks * 1000;
203    } else {
204#ifdef MAC_OSX_TCL
205	static mach_timebase_info_data_t tb;
206	static uint64_t maxClicksForUInt64;
207
208	if (!tb.denom) {
209	    mach_timebase_info(&tb);
210	    maxClicksForUInt64 = UINT64_MAX / tb.numer;
211	}
212	if ((uint64_t) clicks < maxClicksForUInt64) {
213	    nsec = ((uint64_t) clicks) * tb.numer / tb.denom;
214	} else {
215	    nsec = ((long double) (uint64_t) clicks) * tb.numer / tb.denom;
216	}
217#else
218#error Wide high-resolution clicks not implemented on this platform
219#endif
220    }
221
222    return nsec;
223}
224#endif /* TCL_WIDE_CLICKS */
225
226/*
227 *----------------------------------------------------------------------
228 *
229 * TclpGetTimeZone --
230 *
231 *	Determines the current timezone. The method varies wildly between
232 *	different platform implementations, so its hidden in this function.
233 *
234 * Results:
235 *	The return value is the local time zone, measured in minutes away from
236 *	GMT (-ve for east, +ve for west).
237 *
238 * Side effects:
239 *	None.
240 *
241 *----------------------------------------------------------------------
242 */
243
244int
245TclpGetTimeZone(
246    unsigned long currentTime)
247{
248    int timeZone;
249
250    /*
251     * We prefer first to use the time zone in "struct tm" if the structure
252     * contains such a member. Following that, we try to locate the external
253     * 'timezone' variable and use its value. If both of those methods fail,
254     * we attempt to convert a known time to local time and use the difference
255     * from UTC as the local time zone. In all cases, we need to undo any
256     * Daylight Saving Time adjustment.
257     */
258
259#if defined(HAVE_TM_TZADJ)
260#define TCL_GOT_TIMEZONE
261    /*
262     * Struct tm contains tm_tzadj - that value may be used.
263     */
264
265    time_t curTime = (time_t) currentTime;
266    struct tm *timeDataPtr = TclpLocaltime(&curTime);
267
268    timeZone = timeDataPtr->tm_tzadj / 60;
269    if (timeDataPtr->tm_isdst) {
270	timeZone += 60;
271    }
272#endif
273
274#if defined(HAVE_TM_GMTOFF) && !defined (TCL_GOT_TIMEZONE)
275#define TCL_GOT_TIMEZONE
276    /*
277     * Struct tm contains tm_gmtoff - that value may be used.
278     */
279
280    time_t curTime = (time_t) currentTime;
281    struct tm *timeDataPtr = TclpLocaltime(&curTime);
282
283    timeZone = -(timeDataPtr->tm_gmtoff / 60);
284    if (timeDataPtr->tm_isdst) {
285	timeZone += 60;
286    }
287#endif
288
289#if defined(HAVE_TIMEZONE_VAR) && !defined(TCL_GOT_TIMEZONE) && !defined(USE_DELTA_FOR_TZ)
290#define TCL_GOT_TIMEZONE
291    /*
292     * The 'timezone' external var is present and may be used.
293     */
294
295    SetTZIfNecessary();
296
297    /*
298     * Note: this is not a typo in "timezone" below! See tzset documentation
299     * for details.
300     */
301
302    timeZone = timezone / 60;
303#endif
304
305#if !defined(TCL_GOT_TIMEZONE)
306#define TCL_GOT_TIMEZONE
307    /*
308     * Fallback - determine time zone with a known reference time.
309     */
310
311    time_t tt;
312    struct tm *stm;
313
314    tt = 849268800L;		/* 1996-11-29 12:00:00  GMT */
315    stm = TclpLocaltime(&tt);	/* eg 1996-11-29  6:00:00  CST6CDT */
316
317    /*
318     * The calculation below assumes a max of +12 or -12 hours from GMT.
319     */
320
321    timeZone = (12 - stm->tm_hour)*60 + (0 - stm->tm_min);
322    if (stm->tm_isdst) {
323	timeZone += 60;
324    }
325
326    /*
327     * Now have offset for our known reference time, eg +360 for CST6CDT.
328     */
329#endif
330
331#ifndef TCL_GOT_TIMEZONE
332    /*
333     * Cause fatal compile error, we don't know how to get timezone.
334     */
335
336#error autoconf did not figure out how to determine the timezone.
337#endif
338
339    return timeZone;
340}
341
342/*
343 *----------------------------------------------------------------------
344 *
345 * Tcl_GetTime --
346 *
347 *	Gets the current system time in seconds and microseconds since the
348 *	beginning of the epoch: 00:00 UCT, January 1, 1970.
349 *
350 *	This function is hooked, allowing users to specify their own virtual
351 *	system time.
352 *
353 * Results:
354 *	Returns the current time in timePtr.
355 *
356 * Side effects:
357 *	None.
358 *
359 *----------------------------------------------------------------------
360 */
361
362void
363Tcl_GetTime(
364    Tcl_Time *timePtr)		/* Location to store time information. */
365{
366    (*tclGetTimeProcPtr) (timePtr, tclTimeClientData);
367}
368
369/*
370 *----------------------------------------------------------------------
371 *
372 * TclpGetDate --
373 *
374 *	This function converts between seconds and struct tm. If useGMT is
375 *	true, then the returned date will be in Greenwich Mean Time (GMT).
376 *	Otherwise, it will be in the local time zone.
377 *
378 * Results:
379 *	Returns a static tm structure.
380 *
381 * Side effects:
382 *	None.
383 *
384 *----------------------------------------------------------------------
385 */
386
387struct tm *
388TclpGetDate(
389    CONST time_t *time,
390    int useGMT)
391{
392    if (useGMT) {
393	return TclpGmtime(time);
394    } else {
395	return TclpLocaltime(time);
396    }
397}
398
399/*
400 *----------------------------------------------------------------------
401 *
402 * TclpGmtime --
403 *
404 *	Wrapper around the 'gmtime' library function to make it thread safe.
405 *
406 * Results:
407 *	Returns a pointer to a 'struct tm' in thread-specific data.
408 *
409 * Side effects:
410 *	Invokes gmtime or gmtime_r as appropriate.
411 *
412 *----------------------------------------------------------------------
413 */
414
415struct tm *
416TclpGmtime(
417    CONST time_t *timePtr)	/* Pointer to the number of seconds since the
418				 * local system's epoch */
419{
420    /*
421     * Get a thread-local buffer to hold the returned time.
422     */
423
424    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&tmKey);
425
426#ifdef HAVE_GMTIME_R
427    gmtime_r(timePtr, &(tsdPtr->gmtime_buf));
428#else
429    Tcl_MutexLock(&tmMutex);
430    memcpy(&(tsdPtr->gmtime_buf), gmtime(timePtr), sizeof(struct tm));
431    Tcl_MutexUnlock(&tmMutex);
432#endif
433
434    return &(tsdPtr->gmtime_buf);
435}
436
437/*
438 * Forwarder for obsolete item in Stubs
439 */
440
441struct tm *
442TclpGmtime_unix(
443    CONST time_t *timePtr)
444{
445    return TclpGmtime(timePtr);
446}
447
448/*
449 *----------------------------------------------------------------------
450 *
451 * TclpLocaltime --
452 *
453 *	Wrapper around the 'localtime' library function to make it thread
454 *	safe.
455 *
456 * Results:
457 *	Returns a pointer to a 'struct tm' in thread-specific data.
458 *
459 * Side effects:
460 *	Invokes localtime or localtime_r as appropriate.
461 *
462 *----------------------------------------------------------------------
463 */
464
465struct tm *
466TclpLocaltime(
467    CONST time_t *timePtr)	/* Pointer to the number of seconds since the
468				 * local system's epoch */
469{
470    /*
471     * Get a thread-local buffer to hold the returned time.
472     */
473
474    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&tmKey);
475
476    SetTZIfNecessary();
477#ifdef HAVE_LOCALTIME_R
478    localtime_r(timePtr, &(tsdPtr->localtime_buf));
479#else
480    Tcl_MutexLock(&tmMutex);
481    memcpy(&(tsdPtr->localtime_buf), localtime(timePtr), sizeof(struct tm));
482    Tcl_MutexUnlock(&tmMutex);
483#endif
484
485    return &(tsdPtr->localtime_buf);
486}
487/*
488 * Forwarder for obsolete item in Stubs
489 */
490struct tm*
491TclpLocaltime_unix(
492    CONST time_t *timePtr)
493{
494    return TclpLocaltime(timePtr);
495}
496
497/*
498 *----------------------------------------------------------------------
499 *
500 * Tcl_SetTimeProc --
501 *
502 *	TIP #233 (Virtualized Time): Registers two handlers for the
503 *	virtualization of Tcl's access to time information.
504 *
505 * Results:
506 *	None.
507 *
508 * Side effects:
509 *	Remembers the handlers, alters core behaviour.
510 *
511 *----------------------------------------------------------------------
512 */
513
514void
515Tcl_SetTimeProc(
516    Tcl_GetTimeProc *getProc,
517    Tcl_ScaleTimeProc *scaleProc,
518    ClientData clientData)
519{
520    tclGetTimeProcPtr = getProc;
521    tclScaleTimeProcPtr = scaleProc;
522    tclTimeClientData = clientData;
523}
524
525/*
526 *----------------------------------------------------------------------
527 *
528 * Tcl_QueryTimeProc --
529 *
530 *	TIP #233 (Virtualized Time): Query which time handlers are registered.
531 *
532 * Results:
533 *	None.
534 *
535 * Side effects:
536 *	None.
537 *
538 *----------------------------------------------------------------------
539 */
540
541void
542Tcl_QueryTimeProc(
543    Tcl_GetTimeProc **getProc,
544    Tcl_ScaleTimeProc **scaleProc,
545    ClientData *clientData)
546{
547    if (getProc) {
548	*getProc = tclGetTimeProcPtr;
549    }
550    if (scaleProc) {
551	*scaleProc = tclScaleTimeProcPtr;
552    }
553    if (clientData) {
554	*clientData = tclTimeClientData;
555    }
556}
557
558/*
559 *----------------------------------------------------------------------
560 *
561 * NativeScaleTime --
562 *
563 *	TIP #233: Scale from virtual time to the real-time. For native scaling
564 *	the relationship is 1:1 and nothing has to be done.
565 *
566 * Results:
567 *	Scales the time in timePtr.
568 *
569 * Side effects:
570 *	See above.
571 *
572 *----------------------------------------------------------------------
573 */
574
575static void
576NativeScaleTime(
577    Tcl_Time *timePtr,
578    ClientData clientData)
579{
580    /* Native scale is 1:1. Nothing is done */
581}
582
583/*
584 *----------------------------------------------------------------------
585 *
586 * NativeGetTime --
587 *
588 *	TIP #233: Gets the current system time in seconds and microseconds
589 *	since the beginning of the epoch: 00:00 UCT, January 1, 1970.
590 *
591 * Results:
592 *	Returns the current time in timePtr.
593 *
594 * Side effects:
595 *	None.
596 *
597 *----------------------------------------------------------------------
598 */
599
600static void
601NativeGetTime(
602    Tcl_Time *timePtr,
603    ClientData clientData)
604{
605    struct timeval tv;
606
607    (void) gettimeofday(&tv, NULL);
608    timePtr->sec = tv.tv_sec;
609    timePtr->usec = tv.tv_usec;
610}
611/*
612 *----------------------------------------------------------------------
613 *
614 * SetTZIfNecessary --
615 *
616 *	Determines whether a call to 'tzset' is needed prior to the next call
617 *	to 'localtime' or examination of the 'timezone' variable.
618 *
619 * Results:
620 *	None.
621 *
622 * Side effects:
623 *	If 'tzset' has never been called in the current process, or if the
624 *	value of the environment variable TZ has changed since the last call
625 *	to 'tzset', then 'tzset' is called again.
626 *
627 *----------------------------------------------------------------------
628 */
629
630static void
631SetTZIfNecessary(void)
632{
633    CONST char *newTZ = getenv("TZ");
634
635    Tcl_MutexLock(&tmMutex);
636    if (newTZ == NULL) {
637	newTZ = "";
638    }
639    if (lastTZ == NULL || strcmp(lastTZ, newTZ)) {
640	tzset();
641	if (lastTZ == NULL) {
642	    Tcl_CreateExitHandler(CleanupMemory, (ClientData) NULL);
643	} else {
644	    Tcl_Free(lastTZ);
645	}
646	lastTZ = ckalloc(strlen(newTZ) + 1);
647	strcpy(lastTZ, newTZ);
648    }
649    Tcl_MutexUnlock(&tmMutex);
650}
651
652/*
653 *----------------------------------------------------------------------
654 *
655 * CleanupMemory --
656 *
657 *	Releases the private copy of the TZ environment variable upon exit
658 *	from Tcl.
659 *
660 * Results:
661 *	None.
662 *
663 * Side effects:
664 *	Frees allocated memory.
665 *
666 *----------------------------------------------------------------------
667 */
668
669static void
670CleanupMemory(
671    ClientData ignored)
672{
673    ckfree(lastTZ);
674}
675
676/*
677 * Local Variables:
678 * mode: c
679 * c-basic-offset: 4
680 * fill-column: 78
681 * End:
682 */
683