Doug Bailey | 6 Jul 2012 01:52

[tz] localtime.c bugs

I've started working with tzcode2012b and I've run into a couple of issues.

The first: in testing, I found that if I set a timezone like "CST6", the tzname[] array would not be initialized.  Digging into it, I found that settzname[] depends on having transition rules for the zone, but a zone like "CST6" will only be created with a single type and no transitions.

To work around that one I've added the following code to initialize the tzname[] array from the ttis array before running through the types array:

+    for (i=0; i<sp->typecnt; i++) {
+        const struct ttinfo * const    ttisp = &sp->ttis[i];
+        tzname[ttisp->tt_isdst] = &sp->chars[ttisp->tt_abbrind];
+    }
    /*
    ** And to get the latest zone names into tzname. . .
    */
    for (i = 0; i < sp->timecnt; ++i) {
        register const struct ttinfo * const    ttisp =
                            &sp->ttis[
                                sp->types[i]];

The second has to do with our environment.  We have an unfortunate time_t definition: an unsigned 32-bit number.  The tzload code attempts to take unsigned time_t values into account, but if I'm not making a mistake understanding the way it's supposed to work, I think it has a few problems.

First, the loop over (i < sp->timecnt - 2) means that in comparing ats[i] > ats[i+1], you never compare ats[sp->timecnt-2] > ats[sp->timecnt-1].  This means that if all the transitions except the last are pre-epoch (negative number), the list doesn't get trimmed down.

Second, with that fixed, there are still problems with zones that only have pre-epoch transitions (e.g. America/Regina).  In such a case, the ats[i] > ats[i+1] case never triggers, so you're left with a list of ats[] values that aren't valid -- they're negative numbers in a system with an unsigned time_t.

This is rather uglier.  I've included my fix for what it's worth, but you can probably work out something better.  One thing to note: I try to preserve the last pre-epoch transition, to handle situations where there's a large gap in transitions between the start of the epoch the the first post-epoch transition.

        /*
        ** Out-of-sort ats should mean we're running on a
        ** signed time_t system but using a data file with
        ** unsigned values (or vice versa).
        */
#if 1
        for (i = 0; i <= sp->timecnt - 1; ++i)
            if ( (i < sp->timecnt-1 && sp->ats[i] > sp->ats[i + 1] ) ||
                    (i == sp->timecnt-1 && !TYPE_SIGNED(time_t) && sp->ats[i] > INT32_MAX) ) {
                if (TYPE_SIGNED(time_t)) {
                    /*
                    ** Ignore the end (easy).
                    */
                    sp->timecnt = i+1;
                } else {
                    /*
                    ** Ignore the beginning (harder).
                    */
                    register int    j;
                   
                    // keep the record right before the epoch boundary, but
                    // tweak it so that it starts right with the epoch
                    sp->ats[i] = 0;

                    // move everything back...
                    if (i > 0) {
                        for (j = 0; j + i < sp->timecnt; ++j) {
                            sp->ats[j] = sp->ats[j + i];
                            sp->types[j] = sp->types[j + i];
                        }
                        sp->timecnt = j;
                    }
                }
                break;
            }
#else
        for (i = 0; i < sp->timecnt - 2; ++i)
            if (sp->ats[i] > sp->ats[i + 1]) {
                ++i;
                if (TYPE_SIGNED(time_t)) {
                    /*
                    ** Ignore the end (easy).
                    */
                    sp->timecnt = i;
                } else {
                    /*
                    ** Ignore the beginning (harder).
                    */
                    register int    j;

                    for (j = 0; j + i < sp->timecnt; ++j) {
                        sp->ats[j] = sp->ats[j + i];
                        sp->types[j] = sp->types[j + i];
                    }
                    sp->timecnt = j;
                }
                break;
            }
#endif

I appreciate any assistance with this.

Doug

Marvel, Jim (CPCOE | 6 Jul 2012 17:55
Picon
Favicon

[tz] Relative to Beginning of Month or to End

Did a zdump.  How to tell if timezone DST rules indicate counting relative to beginning of month (“2nd Tuesday”) or to end of month (last Sunday for example).  Extreme newbie.  Does this info live in the source code?
 
Marvel, Jim (CPCOE | 6 Jul 2012 20:10
Picon
Favicon

Re: [tz] Relative to Beginning of Month or to End

I see it in the Rules files now, like "europe".  Thanks also for the GNU-tip.

Jim Marvel

Software Engineer IIĀ 
Honeywell CMSS
8323 Lindbergh Court
Sarasota FL 34243
Phone - 941.360.6209 (alt: 941.360.6213)
Fax - 941.355.9241
jim.marvel <at> honeywell.com

>>-----Original Message-----
>>From: Paul Eggert [mailto:eggert <at> cs.ucla.edu]
>>Sent: Friday, July 06, 2012 2:01 PM
>>To: Marvel, Jim (CPCOE)
>>Cc: tz <at> iana.org
>>Subject: Re: [tz] Relative to Beginning of Month or to End
>>
>>On 07/06/2012 08:55 AM, Marvel, Jim (CPCOE) wrote:
>>> Did a zdump.  How to tell if timezone DST rules indicate counting
>>> relative to beginning of month ("2nd Tuesday") or to end of month
>>> (last Sunday for example).
>>> Does this info live in the source code?
>>
>>That info does not live in the data given to zdump.
>>It does live in the original source for that data, i.e., the source fed
>>to zic.  But zic discards it and it is not present in zic's output
>>(i.e., in zdump's input).
>>
>>There is software that reverse-engineers the zic output and attempts to
>>infer that info.  It's not perfect, but it works fairly well in
>>practice, at least for future time stamps where there is typically only
>>one rule into the indefinite future.  See the function calendar-time-
>>zone-daylight-rules in GNU Emacs; there may be other examples but this
>>is the one I'm familiar with.

Paul Eggert | 6 Jul 2012 20:01
Favicon

Re: [tz] Relative to Beginning of Month or to End

On 07/06/2012 08:55 AM, Marvel, Jim (CPCOE) wrote:
> Did a zdump.  How to tell if timezone DST rules indicate counting
> relative to beginning of month ("2nd Tuesday")
> or to end of month (last Sunday for example).
> Does this info live in the source code?

That info does not live in the data given to zdump.
It does live in the original source for that
data, i.e., the source fed to zic.  But zic
discards it and it is not present in zic's
output (i.e., in zdump's input).

There is software that reverse-engineers the zic
output and attempts to infer that info.  It's not
perfect, but it works fairly well in practice, at
least for future time stamps where there is typically
only one rule into the indefinite future.  See
the function calendar-time-zone-daylight-rules
in GNU Emacs; there may be other examples but this
is the one I'm familiar with.

Steffen Thorsen | 9 Jul 2012 12:51
Favicon

[tz] DST was probably observed in Detroit in 1968

It looks like the database has incorrect time for Zone America/Detroit
during 1968.

In Shanks it is standard time through all of 1968 (and that is also given
in the database), but newspaper articles from that same year seem to
contradict that.

Basically, it seems that Michigan did observe DST the same dates as the
rest of the US in 1968. (Maybe with the exception of Berrien county,
I have not investigated this exception further since it is before 1970).

One source is the Daily Globe, Friday, November 1, 1968 (in Ironwood,
Michigan), it has an article written by the AP:
(page 14, "Confusion Abounds Over Simple Daylight Time Vote"):

Relevant quote from that article:
"as a result Michigan has gone on savings time this summer and last."

Longer excerpt telling more about the background:

The Federal Uniform Time
Act of 1966 gave any state permission
to exempt itself from
fast time if it acted before April
29, 1967, using its normal legislative
process to enact an exemption
law.

Michigan did that. In 1967 the
Legislature approved and Gov.
George Romney signed a bill
providing that all of Michigan
would remain year around on
Eastern Standard Time, as most
of it had since the 1940s.

The Citizens Research Council
of Michigan says Hawaii was
the only other state to pass
such an exemption, although
Kentucky and Alaska were
granted temporary exemptions.

Backers of savings time, however,
circulated referendum petitions
challenging the exemption
law and succeeded in putting
it on the ballot. Then: success
suspended the law, and as
a result Michigan has gone on
savings time this summer and
last.

Snarled up with the Daylight
Tune issue is another section of
the Uniform Time Act which put
all of Michigan's Lower Peninsula
on Eastern Time and the
entire Upper Peninsula on Central
Time.

The two eastern counties of
the U.P. have ignored the law
and observe Eastern Standard
and Eastern Daylight Time like
the Lower Peninsula.

Best regards,
Steffen Thorsen - timeanddate.com

Arthur David Olson | 10 Jul 2012 21:23
Picon

Re: [tz] localtime.c bugs

Hmmm...if a binary file has 32-bit values that are in sort and that all have their
high-order bits set, it might be because it contains signed values that are all
pre-epoch. It might also be because it contains unsigned values that are all
post-2038--that's unlikely now, but likelier as time goes by.

If we're lucky, 32-bit systems will be a thing of the past by the time it's likely.
That being so, I've attached a slightly-revised version of Doug Bailey's fixes
(they also appear below, with tabs mangled by Firefox/Gmail).

Are there any present-day systems with unsigned 64-bit time_t's?

        --ado

8.17
2110 lines
8.18
2127 lines
*** /tmp/,alocaltime.c    2012-07-10 15:14:38.372363200 -0400
--- /tmp/,blocaltime.c    2012-07-10 15:14:38.509371000 -0400
***************
*** 5,11 ****
 
  #ifndef lint
  #ifndef NOID
! static char    elsieid[] = " <at> (#)localtime.c    8.17";
  #endif /* !defined NOID */
  #endif /* !defined lint */
 
--- 5,11 ----
 
  #ifndef lint
  #ifndef NOID
! static char    elsieid[] = " <at> (#)localtime.c    8.18";
  #endif /* !defined NOID */
  #endif /* !defined lint */
 
***************
*** 277,282 ****
--- 277,287 ----
      /*
      ** And to get the latest zone names into tzname. . .
      */
+     for (i = 0; i < sp->typecnt; ++i) {
+         register const struct ttinfo * const    ttisp = &sp->ttis[i];
+
+         tzname[ttisp->tt_isdst] = &sp->chars[ttisp->tt_abbrind];
+     }   
      for (i = 0; i < sp->timecnt; ++i) {
          register const struct ttinfo * const    ttisp =
                              &sp->ttis[
***************
*** 489,508 ****
          ** signed time_t system but using a data file with
          ** unsigned values (or vice versa).
          */
!         for (i = 0; i < sp->timecnt - 2; ++i)
!             if (sp->ats[i] > sp->ats[i + 1]) {
!                 ++i;
                  if (TYPE_SIGNED(time_t)) {
                      /*
                      ** Ignore the end (easy).
                      */
!                     sp->timecnt = i;
                  } else {
                      /*
                      ** Ignore the beginning (harder).
                      */
                      register int    j;
 
                      for (j = 0; j + i < sp->timecnt; ++j) {
                          sp->ats[j] = sp->ats[j + i];
                          sp->types[j] = sp->types[j + i];
--- 494,525 ----
          ** signed time_t system but using a data file with
          ** unsigned values (or vice versa).
          */
!         for (i = 0; i < sp->timecnt; ++i)
!             if ((i < sp->timecnt - 1 &&
!                 sp->ats[i] > sp->ats[i + 1]) ||
!                 (i == sp->timecnt - 1 &&
!                 !TYPE_SIGNED(time_t) &&
!                 stored == 4 &&
!                 sp->ats[i] > INT32_MAX)) {
                  if (TYPE_SIGNED(time_t)) {
                      /*
                      ** Ignore the end (easy).
                      */
!                     sp->timecnt = i + 1;
                  } else {
                      /*
                      ** Ignore the beginning (harder).
                      */
                      register int    j;
 
+                     /*
+                     ** Keep the record right before the
+                     ** epoch boundary,
+                     ** but tweak it so that it starts
+                     ** right with the epoch
+                     ** (thanks to Doug Bailey).
+                     */
+                     sp->ats[i] = 0;
                      for (j = 0; j + i < sp->timecnt; ++j) {
                          sp->ats[j] = sp->ats[j + i];
                          sp->types[j] = sp->types[j + i];

8.17
2110 lines
8.18
2127 lines
*** /tmp/,alocaltime.c	2012-07-10 15:14:38.372363200 -0400
--- /tmp/,blocaltime.c	2012-07-10 15:14:38.509371000 -0400
***************
*** 5,11 ****

  #ifndef lint
  #ifndef NOID
! static char	elsieid[] = " <at> (#)localtime.c	8.17";
  #endif /* !defined NOID */
  #endif /* !defined lint */

--- 5,11 ----

  #ifndef lint
  #ifndef NOID
! static char	elsieid[] = " <at> (#)localtime.c	8.18";
  #endif /* !defined NOID */
  #endif /* !defined lint */

***************
*** 277,282 ****
--- 277,287 ----
  	/*
  	** And to get the latest zone names into tzname. . .
  	*/
+ 	for (i = 0; i < sp->typecnt; ++i) {
+ 		register const struct ttinfo * const	ttisp = &sp->ttis[i];
+ 
+ 		tzname[ttisp->tt_isdst] = &sp->chars[ttisp->tt_abbrind];
+ 	}	
  	for (i = 0; i < sp->timecnt; ++i) {
  		register const struct ttinfo * const	ttisp =
  							&sp->ttis[
***************
*** 489,508 ****
  		** signed time_t system but using a data file with
  		** unsigned values (or vice versa).
  		*/
! 		for (i = 0; i < sp->timecnt - 2; ++i)
! 			if (sp->ats[i] > sp->ats[i + 1]) {
! 				++i;
  				if (TYPE_SIGNED(time_t)) {
  					/*
  					** Ignore the end (easy).
  					*/
! 					sp->timecnt = i;
  				} else {
  					/*
  					** Ignore the beginning (harder).
  					*/
  					register int	j;

  					for (j = 0; j + i < sp->timecnt; ++j) {
  						sp->ats[j] = sp->ats[j + i];
  						sp->types[j] = sp->types[j + i];
--- 494,525 ----
  		** signed time_t system but using a data file with
  		** unsigned values (or vice versa).
  		*/
! 		for (i = 0; i < sp->timecnt; ++i)
! 			if ((i < sp->timecnt - 1 &&
! 			    sp->ats[i] > sp->ats[i + 1]) ||
! 			    (i == sp->timecnt - 1 &&
! 			    !TYPE_SIGNED(time_t) &&
! 			    stored == 4 &&
! 			    sp->ats[i] > INT32_MAX)) {
  				if (TYPE_SIGNED(time_t)) {
  					/*
  					** Ignore the end (easy).
  					*/
! 					sp->timecnt = i + 1;
  				} else {
  					/*
  					** Ignore the beginning (harder).
  					*/
  					register int	j;

+ 					/*
+ 					** Keep the record right before the
+ 					** epoch boundary,
+ 					** but tweak it so that it starts
+ 					** right with the epoch
+ 					** (thanks to Doug Bailey).
+ 					*/
+ 					sp->ats[i] = 0;
  					for (j = 0; j + i < sp->timecnt; ++j) {
  						sp->ats[j] = sp->ats[j + i];
  						sp->types[j] = sp->types[j + i];
Paul Eggert | 10 Jul 2012 23:16
Favicon

Re: [tz] localtime.c bugs

On 07/10/2012 12:23 PM, Arthur David Olson wrote:
> Are there any present-day systems with unsigned 64-bit time_t's?

Nothing major that I know of.  However, NewOS
does it that way and might be considered to be present-day.
See:

http://git.newos.org/?p=newos.git;a=blob;f=include/time.h

Ian Abbott | 11 Jul 2012 11:30
Picon
Favicon
Gravatar

Re: [tz] localtime.c bugs

On 2012/07/10 10:16 PM, Paul Eggert wrote:
> On 07/10/2012 12:23 PM, Arthur David Olson wrote:
>> Are there any present-day systems with unsigned 64-bit time_t's?
> 
> Nothing major that I know of.  However, NewOS
> does it that way and might be considered to be present-day.
> See:
> 
> http://git.newos.org/?p=newos.git;a=blob;f=include/time.h

That supposedly has time_t values representing microseconds since Jan 1,
1 AD (using the Gregorian rules extrapolated back to 1 AD), although the
time calculations in its libc look a little buggy, see
_getLastDayOfYear() in:

http://git.newos.org/?p=newos.git;a=blob;f=lib/libc/time/time.c

--

-- 
-=( Ian Abbott  <at>  MEV Ltd.    E-mail: <abbotti <at> mev.co.uk>             )=-
-=( Tel: +44 (0)161 477 1898   FAX: +44 (0)161 718 3587              )=-

Arthur David Olson | 11 Jul 2012 21:59
Picon

Re: [tz] localtime.c bugs

> > Are there any present-day systems with unsigned 64-bit time_t's?

 
> Nothing major that I know of.
> However, NewOS does it that way and might be considered to be present-day.

That being true, I've attached a change that caters to 64-bit unsigned systems (with the usual mangled copy below);
these changes are relative to the current ftpable version of localtime.c

        --ado

8.17
2110 lines
8.19
2126 lines
*** /tmp/,alocaltime.c    2012-07-11 15:55:34.439147200 -0400
--- /tmp/,blocaltime.c    2012-07-11 15:55:34.641947500 -0400
***************
*** 5,11 ****
 
  #ifndef lint
  #ifndef NOID
! static char    elsieid[] = " <at> (#)localtime.c    8.17";
  #endif /* !defined NOID */
  #endif /* !defined lint */
 
--- 5,11 ----
 
  #ifndef lint
  #ifndef NOID
! static char    elsieid[] = " <at> (#)localtime.c    8.19";
  #endif /* !defined NOID */
  #endif /* !defined lint */
 
***************
*** 277,282 ****
--- 277,287 ----
      /*
      ** And to get the latest zone names into tzname. . .
      */
+     for (i = 0; i < sp->typecnt; ++i) {
+         register const struct ttinfo * const    ttisp = &sp->ttis[i];
+
+         tzname[ttisp->tt_isdst] = &sp->chars[ttisp->tt_abbrind];
+     }   
      for (i = 0; i < sp->timecnt; ++i) {
          register const struct ttinfo * const    ttisp =
                              &sp->ttis[
***************
*** 489,508 ****
          ** signed time_t system but using a data file with
          ** unsigned values (or vice versa).
          */
!         for (i = 0; i < sp->timecnt - 2; ++i)
!             if (sp->ats[i] > sp->ats[i + 1]) {
!                 ++i;
                  if (TYPE_SIGNED(time_t)) {
                      /*
                      ** Ignore the end (easy).
                      */
!                     sp->timecnt = i;
                  } else {
                      /*
                      ** Ignore the beginning (harder).
                      */
                      register int    j;
 
                      for (j = 0; j + i < sp->timecnt; ++j) {
                          sp->ats[j] = sp->ats[j + i];
                          sp->types[j] = sp->types[j + i];
--- 494,524 ----
          ** signed time_t system but using a data file with
          ** unsigned values (or vice versa).
          */
!         for (i = 0; i < sp->timecnt; ++i)
!             if ((i < sp->timecnt - 1 &&
!                 sp->ats[i] > sp->ats[i + 1]) ||
!                 (i == sp->timecnt - 1 && !TYPE_SIGNED(time_t) &&
!                 sp->ats[i] >
!                 ((stored == 4) ? INT32_MAX : INT64_MAX))) {
                  if (TYPE_SIGNED(time_t)) {
                      /*
                      ** Ignore the end (easy).
                      */
!                     sp->timecnt = i + 1;
                  } else {
                      /*
                      ** Ignore the beginning (harder).
                      */
                      register int    j;
 
+                     /*
+                     ** Keep the record right before the
+                     ** epoch boundary,
+                     ** but tweak it so that it starts
+                     ** right with the epoch
+                     ** (thanks to Doug Bailey).
+                     */
+                     sp->ats[i] = 0;
                      for (j = 0; j + i < sp->timecnt; ++j) {
                          sp->ats[j] = sp->ats[j + i];
                          sp->types[j] = sp->types[j + i];

8.17
2110 lines
8.19
2126 lines
*** /tmp/,alocaltime.c	2012-07-11 15:55:34.439147200 -0400
--- /tmp/,blocaltime.c	2012-07-11 15:55:34.641947500 -0400
***************
*** 5,11 ****

  #ifndef lint
  #ifndef NOID
! static char	elsieid[] = " <at> (#)localtime.c	8.17";
  #endif /* !defined NOID */
  #endif /* !defined lint */

--- 5,11 ----

  #ifndef lint
  #ifndef NOID
! static char	elsieid[] = " <at> (#)localtime.c	8.19";
  #endif /* !defined NOID */
  #endif /* !defined lint */

***************
*** 277,282 ****
--- 277,287 ----
  	/*
  	** And to get the latest zone names into tzname. . .
  	*/
+ 	for (i = 0; i < sp->typecnt; ++i) {
+ 		register const struct ttinfo * const	ttisp = &sp->ttis[i];
+ 
+ 		tzname[ttisp->tt_isdst] = &sp->chars[ttisp->tt_abbrind];
+ 	}	
  	for (i = 0; i < sp->timecnt; ++i) {
  		register const struct ttinfo * const	ttisp =
  							&sp->ttis[
***************
*** 489,508 ****
  		** signed time_t system but using a data file with
  		** unsigned values (or vice versa).
  		*/
! 		for (i = 0; i < sp->timecnt - 2; ++i)
! 			if (sp->ats[i] > sp->ats[i + 1]) {
! 				++i;
  				if (TYPE_SIGNED(time_t)) {
  					/*
  					** Ignore the end (easy).
  					*/
! 					sp->timecnt = i;
  				} else {
  					/*
  					** Ignore the beginning (harder).
  					*/
  					register int	j;

  					for (j = 0; j + i < sp->timecnt; ++j) {
  						sp->ats[j] = sp->ats[j + i];
  						sp->types[j] = sp->types[j + i];
--- 494,524 ----
  		** signed time_t system but using a data file with
  		** unsigned values (or vice versa).
  		*/
! 		for (i = 0; i < sp->timecnt; ++i)
! 			if ((i < sp->timecnt - 1 &&
! 			    sp->ats[i] > sp->ats[i + 1]) ||
! 			    (i == sp->timecnt - 1 && !TYPE_SIGNED(time_t) &&
! 			    sp->ats[i] >
! 			    ((stored == 4) ? INT32_MAX : INT64_MAX))) {
  				if (TYPE_SIGNED(time_t)) {
  					/*
  					** Ignore the end (easy).
  					*/
! 					sp->timecnt = i + 1;
  				} else {
  					/*
  					** Ignore the beginning (harder).
  					*/
  					register int	j;

+ 					/*
+ 					** Keep the record right before the
+ 					** epoch boundary,
+ 					** but tweak it so that it starts
+ 					** right with the epoch
+ 					** (thanks to Doug Bailey).
+ 					*/
+ 					sp->ats[i] = 0;
  					for (j = 0; j + i < sp->timecnt; ++j) {
  						sp->ats[j] = sp->ats[j + i];
  						sp->types[j] = sp->types[j + i];
Larry Leung | 11 Jul 2012 19:46
Picon
Favicon

[tz] Zone America/Argentina/San_Luis possible rule inconsistency

Hi,
    with respect to 2012c south america. San Luis appears to have a zone/rule inconsistency. The latest zone indicates using rule SanLuis, but the rule is used only until 2009. For dates past 2009 is there consistency? 

# San Luis (SL)

Rule    SanLuis 2008    2009    -       Mar     Sun>=8  0:00    0       -
Rule    SanLuis 2007    2009    -       Oct     Sun>=8  0:00    1:00    S

Zone America/Argentina/San_Luis -4:25:24 - LMT  1894 Oct 31
                        .
                        .
                        .
                        -3:00   Arg     AR%sT   2008 Jan 21
                        -4:00   SanLuis WAR%sT

Thanks,

Larry.


Gmane