Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1294 lines
38 KiB

  1. /***
  2. *tzset.c - set timezone information and see if we're in daylight time
  3. *
  4. * Copyright (c) 1985-2001, Microsoft Corporation. All rights reserved.
  5. *
  6. *Purpose:
  7. * defines _tzset() - set timezone and daylight saving time vars
  8. *
  9. *Revision History:
  10. * 03-??-84 RLB initial version
  11. * 03-26-86 TC added minus capability to time difference w.r.t GMT
  12. * 03-27-86 TC fixed daylight davings time calculation, off by a day
  13. * error
  14. * 12-03-86 SKS daylight savings time is different starting april 1987
  15. * Fixed off-by-1 errors when either Apr 30 or Oct 31 is
  16. * Sat. Simplified leap year check: this works for
  17. * 1970-2099 only!
  18. * 11-19-87 SKS Add __tzset() which calls tzset only the first time
  19. * Made _isindst() a near procedure
  20. * 11-25-87 WAJ Added calls to _lock and _unlock
  21. * 12-11-87 JCR Added "_LOAD_DS" to declaration
  22. * 01-27-88 SKS Made _isindst() and _dtoxtime() are no longer near (for
  23. * QC)
  24. * 05-24-88 PHG Merged DLL and normal versions
  25. * 03-20-90 GJF Made calling type _CALLTYPE1, added #include
  26. * <cruntime.h>, removed #include <register.h>, removed
  27. * some leftover 16-bit support and fixed the copyright.
  28. * Also, cleaned up the formatting a bit.
  29. * 03-23-90 GJF Made static functions _CALLTYPE4.
  30. * 07-30-90 SBM Added void to empty function arg lists to create
  31. * prototypes
  32. * 10-04-90 GJF New-style function declarators.
  33. * 01-21-91 GJF ANSI naming.
  34. * 08-10-92 PBS Posix Support (TZ stuff).
  35. * 03-30-93 GJF Ported C8-16 version to Win32.
  36. * 04-06-93 SKS Replace _CRTAPI* with __cdecl
  37. * 04-07-93 SKS Replace strdup() with ANSI conformant _strdup()
  38. * 06-28-93 GJF Limited support for system's notion of time zone
  39. * in Windows NT.
  40. * 07-15-93 GJF Resurrected __tzset().
  41. * 04-22-94 GJF Made definitions of lastTZ and first_time conditonal
  42. * on DLL_FOR_WIN32S.
  43. * 01-10-95 CFW Debug CRT allocs.
  44. * 02-13-95 GJF Appended Mac version of source file (somewhat cleaned
  45. * up), with appropriate #ifdef-s.
  46. * 04-07-95 JCF Change gmtFlags with u. Due to Changes in macos\osutils.h
  47. * 06-28-95 CFW Mac: when TZ not set, _tzname[0,1]="" not "???";
  48. * 08-30-95 GJF Complete support for Win32's notion of time zones.
  49. * 11-08-95 GJF Fixed isindst() to release lock.
  50. * 11-15-95 GJF Ensure dststart, dstend get recomputed after _tzset.
  51. * 01-18-96 GJF Ensure _tzname[] strings are null-terminated.
  52. * 03-22-96 GJF Zero out _dstbias if there is no DST. This works
  53. * around a bug in NT's GetTimeZoneInformation API.
  54. * 07-25-96 RDK Removed PMAC init ptr here to clock.c.
  55. * 10-11-96 GJF Return value of TIME_ZONE_ID_UNKNOWN from
  56. * GetTimeZoneInformation does NOT mean there is no time
  57. * zone info (it may simply mean there is no DST).
  58. * 08-28-97 GJF Fixed underflow adjustment in cvtdate. Also, deleted
  59. * some old Win32S support and detab-ed.
  60. * 01-28-98 GJF Use WideCharToMultiByte API instead of mbstowcs so
  61. * that the host's default ANSI codepage is used when we
  62. * are in the C locale.
  63. * 02-09-98 GJF Changes for Win64: removing unnecessary typing of vars
  64. * and constants as long.
  65. * 05-11-98 GJF Use more general leap determination to support time
  66. * values past 2099.
  67. * 05-11-98 GJF Merged in crt.ia64 and crt.ia64 versions.
  68. * 08-27-98 GJF Copy __lc_codepage, a global, to a local var for
  69. * multithread safety.
  70. * 09-25-98 GJF Minor bug in transition day calculation.
  71. * 04-28-99 GJF Changed dwFlags arg value to 0 in WideCharToMultiByte
  72. * calls to avoid problems with codepage 1258 on NT 5.0.
  73. * 05-17-99 PML Remove all Macintosh support.
  74. * 06-08-99 GJF Fixed handling of empty TZ environment variable.
  75. * 09-28-99 PML Fixed double free of lastTZ (ntbug#390281)
  76. *
  77. *******************************************************************************/
  78. #include <cruntime.h>
  79. #include <ctype.h>
  80. #include <ctime.h>
  81. #include <time.h>
  82. #include <stdlib.h>
  83. #include <internal.h>
  84. #ifdef _POSIX_
  85. #include <limits.h>
  86. #else
  87. #include <mtdll.h>
  88. #include <windows.h>
  89. #endif
  90. #include <setlocal.h>
  91. #include <string.h>
  92. #include <dbgint.h>
  93. #ifndef _POSIX_
  94. /*
  95. * Number of milliseconds in one day
  96. */
  97. #define DAY_MILLISEC (24 * 60 * 60 * 1000)
  98. /*
  99. * Pointer to a saved copy of the TZ value obtained in the previous call
  100. * to tzset() set (if any).
  101. */
  102. static char * lastTZ = NULL;
  103. /*
  104. * Flag indicating that time zone information came from GetTimeZoneInformation
  105. * API call.
  106. */
  107. static int tzapiused;
  108. static TIME_ZONE_INFORMATION tzinfo;
  109. /*
  110. * Structure used to represent DST transition date/times.
  111. */
  112. typedef struct {
  113. int yr; /* year of interest */
  114. int yd; /* day of year */
  115. int ms; /* milli-seconds in the day */
  116. } transitiondate;
  117. /*
  118. * DST start and end structs.
  119. */
  120. static transitiondate dststart = { -1, 0, 0 };
  121. static transitiondate dstend = { -1, 0, 0 };
  122. static int __cdecl _isindst_lk(struct tm *);
  123. #endif
  124. /***
  125. *void tzset() - sets timezone information and calc if in daylight time
  126. *
  127. *Purpose:
  128. * Sets the timezone information from the TZ environment variable
  129. * and then sets _timezone, _daylight, and _tzname. If we're in daylight
  130. * time is automatically calculated.
  131. *
  132. *Entry:
  133. * None, reads TZ environment variable.
  134. *
  135. *Exit:
  136. * sets _daylight, _timezone, and _tzname global vars, no return value
  137. *
  138. *Exceptions:
  139. *
  140. *******************************************************************************/
  141. #ifndef _POSIX_
  142. #ifdef _MT
  143. static void __cdecl _tzset_lk(void);
  144. #else
  145. #define _tzset_lk _tzset
  146. #endif
  147. void __cdecl __tzset(void)
  148. {
  149. static int first_time = 0;
  150. if ( !first_time ) {
  151. _mlock( _TIME_LOCK );
  152. if ( !first_time ) {
  153. _tzset_lk();
  154. first_time++;
  155. }
  156. _munlock(_TIME_LOCK );
  157. }
  158. }
  159. #ifdef _MT /* multi-thread; define both tzset and _tzset_lk */
  160. void __cdecl _tzset (
  161. void
  162. )
  163. {
  164. _mlock( _TIME_LOCK );
  165. _tzset_lk();
  166. _munlock( _TIME_LOCK );
  167. }
  168. static void __cdecl _tzset_lk (
  169. #else /* non multi-thread; only define tzset */
  170. void __cdecl _tzset (
  171. #endif /* rejoin common code */
  172. void
  173. )
  174. {
  175. char *TZ;
  176. int defused;
  177. int negdiff = 0;
  178. unsigned int lc_cp;
  179. _mlock(_ENV_LOCK);
  180. /*
  181. * Copy codepage to local (only really necessary for multithread case)
  182. */
  183. lc_cp = __lc_codepage;
  184. /*
  185. * Clear the flag indicated whether GetTimeZoneInformation was used.
  186. */
  187. tzapiused = 0;
  188. /*
  189. * Set year fields of dststart and dstend structures to -1 to ensure
  190. * they are recomputed as after this
  191. */
  192. dststart.yr = dstend.yr = -1;
  193. /*
  194. * Fetch the value of the TZ environment variable.
  195. */
  196. if ( ((TZ = _getenv_lk("TZ")) == NULL) || (*TZ =='\0') ) {
  197. /*
  198. * There is no TZ environment variable, try to use the time zone
  199. * information from the system.
  200. */
  201. /*
  202. * If there is a lastTZ, discard it
  203. */
  204. if ( lastTZ != NULL ) {
  205. _free_crt(lastTZ);
  206. lastTZ = NULL;
  207. }
  208. _munlock(_ENV_LOCK);
  209. if ( GetTimeZoneInformation( &tzinfo ) != 0xFFFFFFFF ) {
  210. /*
  211. * Note that the API was used.
  212. */
  213. tzapiused = 1;
  214. /*
  215. * Derive _timezone value from Bias and StandardBias fields.
  216. */
  217. _timezone = tzinfo.Bias * 60;
  218. if ( tzinfo.StandardDate.wMonth != 0 )
  219. _timezone += (tzinfo.StandardBias * 60);
  220. /*
  221. * Check to see if there is a daylight time bias. Since the
  222. * StandardBias has been added into _timezone, it must be
  223. * compensated for in the value computed for _dstbias.
  224. */
  225. if ( (tzinfo.DaylightDate.wMonth != 0) &&
  226. (tzinfo.DaylightBias != 0) )
  227. {
  228. _daylight = 1;
  229. _dstbias = (tzinfo.DaylightBias - tzinfo.StandardBias) *
  230. 60;
  231. }
  232. else {
  233. _daylight = 0;
  234. /*
  235. * Set daylight bias to 0 because GetTimeZoneInformation
  236. * may return TIME_ZONE_ID_DAYLIGHT even though there is
  237. * no DST (in NT 3.51, just turn off the automatic DST
  238. * adjust in the control panel)!
  239. */
  240. _dstbias = 0;
  241. }
  242. /*
  243. * Try to grab the name strings for both the time zone and the
  244. * daylight zone. Note the wide character strings in tzinfo
  245. * must be converted to multibyte characters strings. The
  246. * locale codepage, __lc_codepage, is used for this. Note that
  247. * if setlocale() with LC_ALL or LC_CTYPE has not been called,
  248. * then __lc_codepage will be 0 (_CLOCALECP), which is CP_ACP
  249. * (which means use the host's default ANSI codepage).
  250. */
  251. if ( (WideCharToMultiByte( lc_cp,
  252. 0,
  253. tzinfo.StandardName,
  254. -1,
  255. _tzname[0],
  256. 63,
  257. NULL,
  258. &defused ) != 0) &&
  259. (!defused) )
  260. _tzname[0][63] = '\0';
  261. else
  262. _tzname[0][0] = '\0';
  263. if ( (WideCharToMultiByte( lc_cp,
  264. 0,
  265. tzinfo.DaylightName,
  266. -1,
  267. _tzname[1],
  268. 63,
  269. NULL,
  270. &defused ) != 0) &&
  271. (!defused) )
  272. _tzname[1][63] = '\0';
  273. else
  274. _tzname[1][0] = '\0';
  275. }
  276. /*
  277. * Time zone information is unavailable, just return.
  278. */
  279. return;
  280. }
  281. if ( (lastTZ != NULL) && (strcmp(TZ, lastTZ) == 0) )
  282. {
  283. /*
  284. * TZ is unchanged from a earlier call (to this function). Just
  285. * return.
  286. */
  287. _munlock(_ENV_LOCK);
  288. return;
  289. }
  290. /*
  291. * Update lastTZ
  292. */
  293. if ( lastTZ != NULL )
  294. _free_crt(lastTZ);
  295. if ((lastTZ = _malloc_crt(strlen(TZ)+1)) == NULL)
  296. {
  297. _munlock(_ENV_LOCK);
  298. return;
  299. }
  300. strcpy(lastTZ, TZ);
  301. _munlock(_ENV_LOCK);
  302. /*
  303. * Process TZ value and update _tzname, _timezone and _daylight.
  304. */
  305. strncpy(_tzname[0], TZ, 3);
  306. _tzname[0][3] = '\0';
  307. /*
  308. * time difference is of the form:
  309. *
  310. * [+|-]hh[:mm[:ss]]
  311. *
  312. * check minus sign first.
  313. */
  314. if ( *(TZ += 3) == '-' ) {
  315. negdiff++;
  316. TZ++;
  317. }
  318. /*
  319. * process, then skip over, the hours
  320. */
  321. _timezone = atol(TZ) * 3600;
  322. while ( (*TZ == '+') || ((*TZ >= '0') && (*TZ <= '9')) ) TZ++;
  323. /*
  324. * check if minutes were specified
  325. */
  326. if ( *TZ == ':' ) {
  327. /*
  328. * process, then skip over, the minutes
  329. */
  330. _timezone += atol(++TZ) * 60;
  331. while ( (*TZ >= '0') && (*TZ <= '9') ) TZ++;
  332. /*
  333. * check if seconds were specified
  334. */
  335. if ( *TZ == ':' ) {
  336. /*
  337. * process, then skip over, the seconds
  338. */
  339. _timezone += atol(++TZ);
  340. while ( (*TZ >= '0') && (*TZ <= '9') ) TZ++;
  341. }
  342. }
  343. if ( negdiff )
  344. _timezone = -_timezone;
  345. /*
  346. * finally, check for a DST zone suffix
  347. */
  348. if ( _daylight = *TZ ) {
  349. strncpy(_tzname[1], TZ, 3);
  350. _tzname[1][3] = '\0';
  351. }
  352. else
  353. *_tzname[1] = '\0';
  354. }
  355. /***
  356. *static void cvtdate( trantype, datetype, year, month, week, dayofweek,
  357. * date, hour, min, second, millisec ) - convert
  358. * transition date format
  359. *
  360. *Purpose:
  361. * Convert the format of a transition date specification to a value of
  362. * a transitiondate structure.
  363. *
  364. *Entry:
  365. * int trantype - 1, if it is the start of DST
  366. * 0, if is the end of DST (in which case the date is
  367. * is a DST date)
  368. * int datetype - 1, if a day-in-month format is specified.
  369. * 0, if an absolute date is specified.
  370. * int year - year for which the date is being converted (70 ==
  371. * 1970)
  372. * int month - month (0 == January)
  373. * int week - week of month, if datetype == 1 (note that 5== last
  374. * week of month),
  375. * 0, otherwise.
  376. * int dayofweek - day of week (0 == Sunday), if datetype == 1.
  377. * 0, otherwise.
  378. * int date - date of month (1 - 31)
  379. * int hour - hours (0 - 23)
  380. * int min - minutes (0 - 59)
  381. * int sec - seconds (0 - 59)
  382. * int msec - milliseconds (0 - 999)
  383. *
  384. *Exit:
  385. * dststart or dstend is filled in with the converted date.
  386. *
  387. *******************************************************************************/
  388. static void __cdecl cvtdate (
  389. int trantype,
  390. int datetype,
  391. int year,
  392. int month,
  393. int week,
  394. int dayofweek,
  395. int date,
  396. int hour,
  397. int min,
  398. int sec,
  399. int msec
  400. )
  401. {
  402. int yearday;
  403. int monthdow;
  404. if ( datetype == 1 ) {
  405. /*
  406. * Transition day specified in day-in-month format.
  407. */
  408. /*
  409. * Figure the year-day of the start of the month.
  410. */
  411. yearday = 1 + (_IS_LEAP_YEAR(year) ? _lpdays[month - 1] :
  412. _days[month - 1]);
  413. /*
  414. * Figure the day of the week of the start of the month.
  415. */
  416. monthdow = (yearday + ((year - 70) * 365) +
  417. _ELAPSED_LEAP_YEARS(year) + _BASE_DOW) % 7;
  418. /*
  419. * Figure the year-day of the transition date
  420. */
  421. if ( monthdow <= dayofweek )
  422. yearday += (dayofweek - monthdow) + (week - 1) * 7;
  423. else
  424. yearday += (dayofweek - monthdow) + week * 7;
  425. /*
  426. * May have to adjust the calculation above if week == 5 (meaning
  427. * the last instance of the day in the month). Check if year falls
  428. * beyond after month and adjust accordingly.
  429. */
  430. if ( (week == 5) &&
  431. (yearday > (_IS_LEAP_YEAR(year) ? _lpdays[month] :
  432. _days[month])) )
  433. {
  434. yearday -= 7;
  435. }
  436. }
  437. else {
  438. /*
  439. * Transition day specified as an absolute day
  440. */
  441. yearday = _IS_LEAP_YEAR(year) ? _lpdays[month - 1] :
  442. _days[month - 1];
  443. yearday += date;
  444. }
  445. if ( trantype == 1 ) {
  446. /*
  447. * Converted date was for the start of DST
  448. */
  449. dststart.yd = yearday;
  450. dststart.ms = msec + (1000 * (sec + 60 * (min + 60 * hour)));
  451. /*
  452. * Set year field of dststart so that unnecessary calls to
  453. * cvtdate() may be avoided.
  454. */
  455. dststart.yr = year;
  456. }
  457. else {
  458. /*
  459. * Converted date was for the end of DST
  460. */
  461. dstend.yd = yearday;
  462. dstend.ms = msec + (1000 * (sec + 60 * (min + 60 * hour)));
  463. /*
  464. * The converted date is still a DST date. Must convert to a
  465. * standard (local) date while being careful the millisecond field
  466. * does not overflow or underflow.
  467. */
  468. if ( (dstend.ms += (_dstbias * 1000)) < 0 ) {
  469. dstend.ms += DAY_MILLISEC;
  470. dstend.yd--;
  471. }
  472. else if ( dstend.ms >= DAY_MILLISEC ) {
  473. dstend.ms -= DAY_MILLISEC;
  474. dstend.yd++;
  475. }
  476. /*
  477. * Set year field of dstend so that unnecessary calls to cvtdate()
  478. * may be avoided.
  479. */
  480. dstend.yr = year;
  481. }
  482. return;
  483. }
  484. /***
  485. *int _isindst(tb) - determine if broken-down time falls in DST
  486. *
  487. *Purpose:
  488. * Determine if the given broken-down time falls within daylight saving
  489. * time (DST). The DST rules are either obtained from Win32 (tzapiused !=
  490. * TRUE) or assumed to be USA rules, post 1986.
  491. *
  492. * If the DST rules are obtained from Win32's GetTimeZoneInformation API,
  493. * the transition dates to/from DST can be specified in either of two
  494. * formats. First, a day-in-month format, similar to the way USA rules
  495. * are specified, can be used. The transition date is given as the n-th
  496. * occurence of a specified day of the week in a specified month. Second,
  497. * an absolute date can be specified. The two cases are distinguished by
  498. * the value of wYear field in the SYSTEMTIME structure (0 denotes a
  499. * day-in-month format).
  500. *
  501. * USA rules for DST are that a time is in DST iff it is on or after
  502. * 02:00 on the first Sunday in April, and before 01:00 on the last
  503. * Sunday in October.
  504. *
  505. *Entry:
  506. * struct tm *tb - structure holding broken-down time value
  507. *
  508. *Exit:
  509. * 1, if time represented is in DST
  510. * 0, otherwise
  511. *
  512. *******************************************************************************/
  513. int __cdecl _isindst (
  514. struct tm *tb
  515. )
  516. #ifdef _MT
  517. {
  518. int retval;
  519. _mlock( _TIME_LOCK );
  520. retval = _isindst_lk( tb );
  521. _munlock( _TIME_LOCK );
  522. return retval;
  523. }
  524. static int __cdecl _isindst_lk (
  525. struct tm *tb
  526. )
  527. #endif /* _MT */
  528. {
  529. long ms;
  530. if ( _daylight == 0 )
  531. return 0;
  532. /*
  533. * Compute (recompute) the transition dates for daylight saving time
  534. * if necessary.The yr (year) fields of dststart and dstend is
  535. * compared to the year of interest to determine necessity.
  536. */
  537. if ( (tb->tm_year != dststart.yr) || (tb->tm_year != dstend.yr) ) {
  538. if ( tzapiused ) {
  539. /*
  540. * Convert the start of daylight saving time to dststart.
  541. */
  542. if ( tzinfo.DaylightDate.wYear == 0 )
  543. cvtdate( 1,
  544. 1, /* day-in-month format */
  545. tb->tm_year,
  546. tzinfo.DaylightDate.wMonth,
  547. tzinfo.DaylightDate.wDay,
  548. tzinfo.DaylightDate.wDayOfWeek,
  549. 0,
  550. tzinfo.DaylightDate.wHour,
  551. tzinfo.DaylightDate.wMinute,
  552. tzinfo.DaylightDate.wSecond,
  553. tzinfo.DaylightDate.wMilliseconds );
  554. else
  555. cvtdate( 1,
  556. 0, /* absolute date */
  557. tb->tm_year,
  558. tzinfo.DaylightDate.wMonth,
  559. 0,
  560. 0,
  561. tzinfo.DaylightDate.wDay,
  562. tzinfo.DaylightDate.wHour,
  563. tzinfo.DaylightDate.wMinute,
  564. tzinfo.DaylightDate.wSecond,
  565. tzinfo.DaylightDate.wMilliseconds );
  566. /*
  567. * Convert start of standard time to dstend.
  568. */
  569. if ( tzinfo.StandardDate.wYear == 0 )
  570. cvtdate( 0,
  571. 1, /* day-in-month format */
  572. tb->tm_year,
  573. tzinfo.StandardDate.wMonth,
  574. tzinfo.StandardDate.wDay,
  575. tzinfo.StandardDate.wDayOfWeek,
  576. 0,
  577. tzinfo.StandardDate.wHour,
  578. tzinfo.StandardDate.wMinute,
  579. tzinfo.StandardDate.wSecond,
  580. tzinfo.StandardDate.wMilliseconds );
  581. else
  582. cvtdate( 0,
  583. 0, /* absolute date */
  584. tb->tm_year,
  585. tzinfo.StandardDate.wMonth,
  586. 0,
  587. 0,
  588. tzinfo.StandardDate.wDay,
  589. tzinfo.StandardDate.wHour,
  590. tzinfo.StandardDate.wMinute,
  591. tzinfo.StandardDate.wSecond,
  592. tzinfo.StandardDate.wMilliseconds );
  593. }
  594. else {
  595. /*
  596. * GetTimeZoneInformation API was NOT used, or failed. USA
  597. * daylight saving time rules are assumed.
  598. */
  599. cvtdate( 1,
  600. 1,
  601. tb->tm_year,
  602. 4, /* April */
  603. 1, /* first... */
  604. 0, /* ...Sunday */
  605. 0,
  606. 2, /* 02:00 (2 AM) */
  607. 0,
  608. 0,
  609. 0 );
  610. cvtdate( 0,
  611. 1,
  612. tb->tm_year,
  613. 10, /* October */
  614. 5, /* last... */
  615. 0, /* ...Sunday */
  616. 0,
  617. 2, /* 02:00 (2 AM) */
  618. 0,
  619. 0,
  620. 0 );
  621. }
  622. }
  623. /*
  624. * Handle simple cases first.
  625. */
  626. if ( dststart.yd < dstend.yd ) {
  627. /*
  628. * Northern hemisphere ordering
  629. */
  630. if ( (tb->tm_yday < dststart.yd) || (tb->tm_yday > dstend.yd) )
  631. return 0;
  632. if ( (tb->tm_yday > dststart.yd) && (tb->tm_yday < dstend.yd) )
  633. return 1;
  634. }
  635. else {
  636. /*
  637. * Southern hemisphere ordering
  638. */
  639. if ( (tb->tm_yday < dstend.yd) || (tb->tm_yday > dststart.yd) )
  640. return 1;
  641. if ( (tb->tm_yday > dstend.yd) && (tb->tm_yday < dststart.yd) )
  642. return 0;
  643. }
  644. ms = 1000 * (tb->tm_sec + 60 * tb->tm_min + 3600 * tb->tm_hour);
  645. if ( tb->tm_yday == dststart.yd ) {
  646. if ( ms >= dststart.ms )
  647. return 1;
  648. else
  649. return 0;
  650. }
  651. else {
  652. /*
  653. * tb->tm_yday == dstend.yd
  654. */
  655. if ( ms < dstend.ms )
  656. return 1;
  657. else
  658. return 0;
  659. }
  660. }
  661. #else /* _POSIX_ */
  662. /*
  663. * The following is an implementation of the TZ grammar specified in the
  664. * document:
  665. *
  666. * 8.1.1 Extension to Time Functions
  667. * IEEE Std 1003.1 - 1990
  668. * Page 152 - 153
  669. *
  670. * The TZ grammar looks like:
  671. *
  672. * stdoffset[dst[offset][,start[/time],end[/time]]]
  673. *
  674. * Variables used in code:
  675. *
  676. * tzname[0] ==> std
  677. * _timezone ==> offset(the one after 'std')
  678. * tzname[1] ==> dst
  679. * _dstoffset ==> offset(the one after 'dst')
  680. * _startdate ==> start
  681. * _starttime ==> time(the one after 'start')
  682. * _enddate ==> end
  683. * _endtime ==> time(the one after 'end')
  684. *
  685. */
  686. /*
  687. * Refer to the document for the detailed description of fields of _DSTDATE.
  688. * Two of Jn, n, and Mm are -1, indicating the one(not -1) is a vaild value.
  689. */
  690. typedef struct _DSTDATE {
  691. int Jn; /* -1 or [1, 365](year day and leap day shall not be counted) */
  692. int n; /* -1 or [0, 365](year day and leap day shall be counted) */
  693. int Mm; /* -1 or [1, 12](month) */
  694. int Mn; /* [1, 5] if Mm != -1 (week) */
  695. int Md; /* [0, 6] if Mm != -1 (weekday, Sunday == 0) */
  696. } DSTDATE, *PDSTDATE;
  697. #define SEC_PER_HOUR (60 * 60)
  698. #define SEC_PER_DAY (SEC_PER_HOUR * 24)
  699. /*
  700. * The default TZ in tzset() should look like:
  701. *
  702. * TZ = "PST8PDT,M4.1.0/2:00,M10.5.0/2:00";
  703. */
  704. /* Day light saving start/end date and default vaules */
  705. static DSTDATE _startdate = { -1, -1, 4, 1, 0};
  706. static DSTDATE _enddate = {-1, -1, 10, 5, 0};
  707. /* Seconds since midnight on _startdate/_enddate with default values.
  708. * _endtime is 1am instead of 2am because the DST end time is 2am
  709. * local time, which by default is 1am standard time.
  710. */
  711. long _starttime = 7200L, _endtime = 7200L;
  712. /*
  713. * If we are only interested in years between 1901 and 2099, we could use this:
  714. *
  715. * #define IS_LEAP_YEAR(y) (y % 4 == 0)
  716. */
  717. #define IS_LEAP_YEAR(y) ((y % 4 == 0 && y % 100 != 0) || y % 400 == 0)
  718. /*
  719. * ParsePosixStdOrDst - parse the std or dst element in TZ.
  720. *
  721. * ENTRY pch - beginning of the substring in TZ.
  722. *
  723. * RETURN pointer to one position after the std or dst element parsed,
  724. * or NULL if failed.
  725. */
  726. static char * __cdecl
  727. ParsePosixStdOrDst(
  728. REG1 char *pch
  729. )
  730. {
  731. #define UNWANTED(x) (isdigit(x) || x=='\0' || x==',' || x=='-' || x=='+')
  732. int i;
  733. /*
  734. * Check against the rule.
  735. */
  736. if(*pch == ':' || UNWANTED(*pch)) {
  737. return NULL;
  738. }
  739. /*
  740. * Get a valid std or dst(i.e. 3 <= lenth_of(std | dst) <= TZNAME_MAX).
  741. */
  742. for(i=1, ++pch; (i < TZNAME_MAX) && !UNWANTED(*pch); i++, pch++) {
  743. ;
  744. }
  745. /*
  746. * pch now point to 1 position after the valid std or dst.
  747. */
  748. return (i >= 3) ? pch : NULL;
  749. }
  750. /*
  751. * ParsePosixOffset - parse the offset element in TZ. The format of time is:
  752. *
  753. * [- | +]hh[:mm[:ss]]
  754. *
  755. * ENTRY pch - beginning of the substring in TZ.
  756. *
  757. * ptime - pointer to a variable(_timezone or _dstoffset) storing the
  758. * time(in seconds) parsed.
  759. *
  760. * RETURN pointer to one position after the end of the offset element parsed.
  761. */
  762. static char * __cdecl
  763. ParsePosixOffset(
  764. REG1 char *pch,
  765. REG2 long *poffset
  766. )
  767. {
  768. int fNegative;
  769. long offset;
  770. if((fNegative = (*pch == '-')) || *pch == '+') {
  771. pch++;
  772. }
  773. offset = atol(pch)*3600L; /* hh */
  774. while(*pch && isdigit(*pch)) {
  775. pch++;
  776. }
  777. if(*pch == ':') {
  778. offset += atol(++pch)*60L; /* mm */
  779. while(*pch && isdigit(*pch)) {
  780. pch++;
  781. }
  782. if(*pch == ':') {
  783. offset += atol(++pch); /* ss */
  784. while(*pch && isdigit(*pch)) {
  785. pch++;
  786. }
  787. }
  788. }
  789. *poffset = fNegative ? -offset : offset;
  790. return pch;
  791. }
  792. /*
  793. * ParsePosixDate - parse the date element in TZ. The format of date is one
  794. * of following:
  795. *
  796. * Jn, n, and Mm.n.d
  797. *
  798. * ENTRY pch - beginning of the substring in TZ.
  799. *
  800. * pDstDate - pointer to _startdate or _enddate storing the result.
  801. *
  802. * RETURN pointer to one position after the end of the date element parsed,
  803. * or NULL if failed.
  804. */
  805. static char * __cdecl
  806. ParsePosixDate(
  807. REG1 char *pch,
  808. REG2 PDSTDATE pDstDate
  809. )
  810. {
  811. pDstDate->Jn = -1;
  812. pDstDate->n = -1;
  813. pDstDate->Mn = -1;
  814. /*
  815. * Two out of the three -1's will remain.
  816. */
  817. if(*pch == 'J') { /* Jn */
  818. pDstDate->Jn = atoi(++pch);
  819. } else if(*pch != 'M') { /* n */
  820. pDstDate->n = atoi(pch);
  821. } else { /* Mm.n.d */
  822. pDstDate->Mm = atoi(++pch);
  823. if(*++pch != '.') {
  824. pch++;
  825. }
  826. pDstDate->Mn = atoi(++pch);
  827. if(*++pch != '.') {
  828. pch++;
  829. }
  830. pDstDate->Md = atoi(++pch);
  831. }
  832. while(*pch && isdigit(*pch)) {
  833. pch++;
  834. }
  835. #define IN_RANGE(x, a, b) (x >= a && x <= b)
  836. return ((pDstDate->Jn != -1 && IN_RANGE(pDstDate->Jn, 1, 365)) ||
  837. (pDstDate->n != -1 && IN_RANGE(pDstDate->n, 0, 365)) ||
  838. (pDstDate->Mm != -1 && IN_RANGE(pDstDate->Mm, 1, 12) &&
  839. IN_RANGE(pDstDate->Mn, 1, 5) && IN_RANGE(pDstDate->Md, 0, 6)))
  840. ? pch : NULL;
  841. }
  842. /*
  843. * ParsePosixTime - parse the time element in TZ. The format of time is:
  844. *
  845. * hh[:mm[:ss]]
  846. *
  847. * ENTRY pch - beginning of the substring in TZ.
  848. *
  849. * ptime - pointer to a variable(_starttime or _endtime) storing the
  850. * time(in seconds) parsed.
  851. *
  852. * RETURN pointer to one position after the end of the time element parsed.
  853. */
  854. static char * __cdecl
  855. ParsePosixTime(
  856. REG1 char *pch,
  857. REG2 long *ptime
  858. )
  859. {
  860. long time;
  861. time = atol(pch)*SEC_PER_HOUR; /* hh */
  862. while(*pch && isdigit(*pch)) {
  863. pch++;
  864. }
  865. if(*pch == ':') {
  866. time += atol(++pch)*60L; /* mm */
  867. while(*pch && isdigit(*pch)) {
  868. pch++;
  869. }
  870. if(*pch == ':') {
  871. time += atol(++pch); /* ss */
  872. while(*pch && isdigit(*pch)) {
  873. pch++;
  874. }
  875. }
  876. }
  877. *ptime = time;
  878. return pch;
  879. }
  880. /*
  881. * tzset - sets the timezone information from the TZ environment variable.
  882. * Global tzname[], _timezone, _daylight, and _dstoffset will be
  883. * set. Static _startdate, _enddate, _starttime, and _endtime will
  884. * also be set. TZ string looks like:
  885. *
  886. * stdoffset[dst[offset][,start[/time],end[/time]]]
  887. *
  888. * In form of variables: tzname[0]_timezone[tzname[1][_dstoffset]
  889. * [,_startdate[/_starttime],_enddate[/_endtime]]]
  890. *
  891. * ENTRY none.
  892. *
  893. * RETURN none.
  894. */
  895. void __cdecl tzset(
  896. void
  897. )
  898. {
  899. /* pch points to the beginning of an element to be parsed. */
  900. REG1 char *pch;
  901. /* pchCurr points to one position after the end of last element parsed. */
  902. REG2 char *pchCurr;
  903. char *TZ;
  904. _endtime = 7200L;
  905. _starttime = 7200L;
  906. if (!(TZ = getenv("TZ")) || !*TZ) {
  907. TZ = "PST8PDT7,M4.1.0/2:00,M10.5.0/2:00"; /* use default */
  908. }
  909. if((pchCurr = ParsePosixStdOrDst(pch=TZ)) == NULL) {
  910. return;
  911. }
  912. memcpy(tzname[0], pch, (int)(pchCurr-pch));
  913. tzname[0][(int)(pchCurr-pch)] = '\0';
  914. if((pchCurr = ParsePosixOffset(pch=pchCurr, &_timezone)) == NULL) {
  915. return;
  916. }
  917. _daylight = (*pchCurr != '\0');
  918. if(!_daylight) {
  919. return;
  920. }
  921. if((pchCurr = ParsePosixStdOrDst(pch=pchCurr)) == NULL) {
  922. return;
  923. }
  924. memcpy(tzname[1], pch, (int)(pchCurr-pch));
  925. tzname[1][(int)(pchCurr-pch)] = '\0';
  926. if(isdigit(*pchCurr) || *pchCurr == '-' || *pchCurr == '+') {
  927. if((pchCurr = ParsePosixOffset(pch=pchCurr, &_dstoffset)) == NULL) {
  928. return;
  929. }
  930. } else {
  931. /* default: 1 hour ahead of standard time */
  932. _dstoffset = _timezone - SEC_PER_HOUR;
  933. }
  934. if(*pchCurr == ',') { /* ,start[/time],end[/time] */
  935. if((pchCurr = ParsePosixDate(pchCurr+1, &_startdate)) == NULL) {
  936. goto out;
  937. }
  938. if(*pchCurr == '/') {
  939. if(!(pchCurr = ParsePosixTime(pchCurr+1, &_starttime))) {
  940. goto out;
  941. }
  942. }
  943. if(*pchCurr != ',') {
  944. goto out;
  945. }
  946. if ((pchCurr = ParsePosixDate(pchCurr+1, &_enddate)) == NULL) {
  947. goto out;
  948. }
  949. if (*pchCurr == '/') {
  950. if(!(pchCurr = ParsePosixTime(pchCurr+1, &_endtime))) {
  951. goto out;
  952. }
  953. }
  954. }
  955. out:
  956. /*
  957. * Adjust the _endtime to account for the fact that
  958. * dst ends at _endtime local time, rather than
  959. * standard time.
  960. */
  961. _endtime -= (_timezone - _dstoffset);
  962. }
  963. #define DAY1 (4) /* Jan 1 1970 was a Thursday */
  964. /*
  965. * GetDstStartOrEndYearDay - Converts day info from DSTDATE into 0-based
  966. * year-day.
  967. *
  968. * ENTRY tm_year - the year concerned(tb->tm_year).
  969. *
  970. * pDstDate - pointer to either _startdate or _enddate.
  971. *
  972. * RETURN the year-day calculated.
  973. */
  974. static int __cdecl
  975. GetDstStartOrEndYearDay(
  976. REG1 int tm_year,
  977. REG2 PDSTDATE pDstDate
  978. )
  979. {
  980. REG1 int yday; /* year-day */
  981. REG2 int theyear;
  982. theyear = tm_year + 1900;
  983. if(pDstDate->Jn != -1) {
  984. /*
  985. * Jn is in [1, 365] and leap day is not counted.
  986. * Convert Jn to 0-based yday; Note: 60 is March 1.
  987. */
  988. yday = (IS_LEAP_YEAR(theyear) && (pDstDate->Jn >= 60))
  989. ? pDstDate->Jn : pDstDate->Jn - 1;
  990. } else if(pDstDate->n != -1) {
  991. /*
  992. * n is in [0, 365] and leap day is counted.
  993. */
  994. yday = pDstDate->n;
  995. } else { /* Mm.n.d */
  996. int *ptrday;
  997. int years;
  998. int wday;
  999. /*
  1000. * We first need to calculate year-day(yday) and week-day
  1001. * (wday) of 1st day of month pDstDate->Mm. We then figure
  1002. * out year-day(yday) of Md day of week Mn of month Mm.
  1003. */
  1004. ptrday = IS_LEAP_YEAR(theyear) ? _lpdays : _days;
  1005. yday = ptrday[pDstDate->Mm-1] + 1; /* ptrday[i] are all by -1 off */
  1006. years = tm_year - 70;
  1007. /*
  1008. * Here constant Day1 is the week-day of Jan 1, 1970.
  1009. * (years+1)/4 is for correcting the leap years.
  1010. */
  1011. wday = (yday + 365*years + (years+1)/4 + DAY1) % 7;
  1012. /*
  1013. * Calculate yday of Md day of week 1 of month Mm.
  1014. */
  1015. yday += pDstDate->Md - wday;
  1016. if(pDstDate->Md < wday) {
  1017. yday += 7;
  1018. }
  1019. /*
  1020. * Calculate yday of Md day of week Mn of month Mm.
  1021. */
  1022. yday += (pDstDate->Mn-1)*7;
  1023. /*
  1024. * Adjust if yday goes beyond the end of the month.
  1025. */
  1026. if(pDstDate->Md == 5 && yday >= ptrday[pDstDate->Mm] + 1) {
  1027. yday -= 7;
  1028. }
  1029. }
  1030. return yday;
  1031. }
  1032. /*
  1033. * _isindst - Tells whether Xenix-type time value falls under DST.
  1034. *
  1035. * ENTRY tb - 'time' structure holding broken-down time value.
  1036. *
  1037. * RETURN 1 if time represented is in DST, else 0.
  1038. */
  1039. int __cdecl _isindst (
  1040. REG1 struct tm *tb
  1041. )
  1042. {
  1043. int st_yday, end_yday;
  1044. int st_sec, end_sec;
  1045. int check_time;
  1046. /*
  1047. * We need start/end year-days of DST in syday/eyday which are converted
  1048. * from one of the format Jn, n, and Mm.n.d. We already have start/end
  1049. * time (in seconds) of DST in _starttime/_endtime.
  1050. */
  1051. st_yday = GetDstStartOrEndYearDay(tb->tm_year, &_startdate);
  1052. end_yday = GetDstStartOrEndYearDay(tb->tm_year, &_enddate);
  1053. st_sec = st_yday * SEC_PER_DAY + _starttime;
  1054. end_sec = end_yday * SEC_PER_DAY + _endtime;
  1055. check_time = tb->tm_yday * SEC_PER_DAY + tb->tm_hour * SEC_PER_HOUR
  1056. + tb->tm_min * 60 + tb->tm_sec;
  1057. if (check_time >= st_sec && check_time < end_sec)
  1058. return 1;
  1059. return 0;
  1060. }
  1061. /*
  1062. * _isskiptime - Tells whether the given time is skipped at the
  1063. * dst change. For instance, we set our clocks forward one
  1064. * hour at 2am to 3am... This function returns true for
  1065. * the times between 1:59:59 and 3:00:00.
  1066. *
  1067. * ENTRY tb - 'time' structure holding broken-down time value.
  1068. *
  1069. * RETURN 1 if time represented is in the skipped period, 0
  1070. * otherwise.
  1071. */
  1072. int __cdecl _isskiptime (
  1073. REG1 struct tm *tb
  1074. )
  1075. {
  1076. int st_yday;
  1077. int st_sec;
  1078. int check_time;
  1079. st_yday = GetDstStartOrEndYearDay(tb->tm_year, &_startdate);
  1080. st_sec = st_yday * SEC_PER_DAY + _starttime;
  1081. check_time = tb->tm_yday * SEC_PER_DAY + tb->tm_hour * SEC_PER_HOUR
  1082. + tb->tm_min * 60 + tb->tm_sec;
  1083. if (check_time >= st_sec && check_time < st_sec - _dstoffset) {
  1084. return 1;
  1085. }
  1086. return 0;
  1087. }
  1088. #endif /* _POSIX_ */