Add timegm POSIX call

Message ID CAEFwyTMui0EcsXp0oYmibWSneuCUJANnyrUO90w5WjYOOe_rxQ@mail.gmail.com
State New
Headers show
Series
  • Add timegm POSIX call
Related show

Commit Message

Howland, Craig D. - US via newlib Aug. 14, 2018, 1:17 a.m.
From e182faa79c35984b667029ef7b6e4a8ce7329897 Mon Sep 17 00:00:00 2001
From: Andrew Russell <ahrussell@google.com>

Date: Fri, 10 Aug 2018 12:14:18 -0700
Subject: [PATCH 1/4] Start of mktime.c copy to timegm.c

I am proposing to add the timegm POSIX call to
Newlib. Part of this refactors some of the code in libc/time/local.h and
libc/time/mktime.c, per this discussion:

https://sourceware.org/ml/newlib/2018/msg00186.html

Thanks,
Andrew

---
 newlib/libc/time/{mktime.c => timegm.c} | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename newlib/libc/time/{mktime.c => timegm.c} (100%)

 #include <stdlib.h>
@@ -58,11 +60,8 @@ static const int DAYS_IN_MONTH[12] =
 static const int _DAYS_BEFORE_MONTH[12] =
 {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334};

-#define _ISLEAP(y) (((y) % 4) == 0 && (((y) % 100) != 0 ||
(((y)+1900) % 400) == 0))
-#define _DAYS_IN_YEAR(year) (_ISLEAP(year) ? 366 : 365)
-
-static void
-validate_structure (struct tm *tim_p)
+static void
+__validate_tm_structure (struct tm *tim_p)
 {
   div_t res;
   int days_in_feb = 28;
@@ -112,7 +111,7 @@ validate_structure (struct tm *tim_p)
         }
     }

-  if (_DAYS_IN_YEAR (tim_p->tm_year) == 366)
+  if (__days_in_year(tim_p->tm_year) == 366)
     days_in_feb = 29;

   if (tim_p->tm_mday <= 0)
@@ -124,7 +123,7 @@ validate_structure (struct tm *tim_p)
        tim_p->tm_year--;
        tim_p->tm_mon = 11;
        days_in_feb =
- ((_DAYS_IN_YEAR (tim_p->tm_year) == 366) ?
+ ((__days_in_year(tim_p->tm_year) == 366) ?
  29 : 28);
      }
    tim_p->tm_mday += _DAYS_IN_MONTH (tim_p->tm_mon);
@@ -140,15 +139,36 @@ validate_structure (struct tm *tim_p)
        tim_p->tm_year++;
        tim_p->tm_mon = 0;
        days_in_feb =
- ((_DAYS_IN_YEAR (tim_p->tm_year) == 366) ?
+ ((__days_in_year(tim_p->tm_year) == 366) ?
  29 : 28);
      }
  }
     }
 }

-time_t
-mktime (struct tm *tim_p)
+void
+__set_tm_wday (long days, struct tm *tim_p)
+{
+  if ((tim_p->tm_wday = (days + 4) % 7) < 0)
+    tim_p->tm_wday += 7;
+}
+
+/* returns either 0 or 1 */
+static
+int
+__is_leap_year (int year)
+{
+  return (year % 4) == 0 && ((year % 100) != 0 || (((year / 100) & 3)
== (-(YEAR_BASE / 100)  & 3)));
+}
+
+int
+__days_in_year (int year)
+{
+  return __is_leap_year(year) ? 366 : 365;
+}
+
+time_t
+__timegm_internal (struct tm *tim_p)
 {
   time_t tim = 0;
   long days = 0;
@@ -156,7 +176,7 @@ mktime (struct tm *tim_p)
   __tzinfo_type *tz = __gettzinfo ();

   /* validate structure */
-  validate_structure (tim_p);
+  __validate_tm_structure (tim_p);

   /* compute hours, minutes, seconds */
   tim += tim_p->tm_sec + (tim_p->tm_min * _SEC_IN_MINUTE) +
@@ -165,7 +185,7 @@ mktime (struct tm *tim_p)
   /* compute days in year */
   days += tim_p->tm_mday - 1;
   days += _DAYS_BEFORE_MONTH[tim_p->tm_mon];
-  if (tim_p->tm_mon > 1 && _DAYS_IN_YEAR (tim_p->tm_year) == 366)
+  if (tim_p->tm_mon > 1 && __days_in_year(tim_p->tm_year) == 366)
     days++;

   /* compute day of the year */
@@ -178,105 +198,33 @@ mktime (struct tm *tim_p)
   if ((year = tim_p->tm_year) > 70)
     {
       for (year = 70; year < tim_p->tm_year; year++)
- days += _DAYS_IN_YEAR (year);
+ days += __days_in_year(year);
     }
   else if (year < 70)
     {
       for (year = 69; year > tim_p->tm_year; year--)
- days -= _DAYS_IN_YEAR (year);
-      days -= _DAYS_IN_YEAR (year);
+ days -= __days_in_year(year);
+      days -= __days_in_year(year);
     }

   /* compute total seconds */
   tim += (time_t)days * _SEC_IN_DAY;

-  TZ_LOCK;
-
-  _tzset_unlocked ();
-
-  if (_daylight)
-    {
-      int tm_isdst;
-      int y = tim_p->tm_year + YEAR_BASE;
-      /* Convert user positive into 1 */
-      tm_isdst = tim_p->tm_isdst > 0  ?  1 : tim_p->tm_isdst;
-      isdst = tm_isdst;
-
-      if (y == tz->__tzyear || __tzcalc_limits (y))
- {
-   /* calculate start of dst in dst local time and
-      start of std in both std local time and dst local time */
-          time_t startdst_dst = tz->__tzrule[0].change
-     - (time_t) tz->__tzrule[1].offset;
-   time_t startstd_dst = tz->__tzrule[1].change
-     - (time_t) tz->__tzrule[1].offset;
-   time_t startstd_std = tz->__tzrule[1].change
-     - (time_t) tz->__tzrule[0].offset;
-   /* if the time is in the overlap between dst and std local times */
-   if (tim >= startstd_std && tim < startstd_dst)
-     ; /* we let user decide or leave as -1 */
-          else
-     {
-       isdst = (tz->__tznorth
-        ? (tim >= startdst_dst && tim < startstd_std)
-        : (tim >= startdst_dst || tim < startstd_std));
-       /* if user committed and was wrong, perform correction, but not
-        * if the user has given a negative value (which
-        * asks mktime() to determine if DST is in effect or not) */
-       if (tm_isdst >= 0  &&  (isdst ^ tm_isdst) == 1)
- {
-   /* we either subtract or add the difference between
-      time zone offsets, depending on which way the user got it
-      wrong. The diff is typically one hour, or 3600 seconds,
-      and should fit in a 16-bit int, even though offset
-      is a long to accomodate 12 hours. */
-   int diff = (int) (tz->__tzrule[0].offset
-     - tz->__tzrule[1].offset);
-   if (!isdst)
-     diff = -diff;
-   tim_p->tm_sec += diff;
-   tim += diff;  /* we also need to correct our current time calculation */
-   int mday = tim_p->tm_mday;
-   validate_structure (tim_p);
-   mday = tim_p->tm_mday - mday;
-   /* roll over occurred */
-   if (mday) {
-     /* compensate for month roll overs */
-     if (mday > 1)
-   mday = -1;
-     else if (mday < -1)
-   mday = 1;
-     /* update days for wday calculation */
-     days += mday;
-     /* handle yday */
-     if ((tim_p->tm_yday += mday) < 0) {
-   --year;
-   tim_p->tm_yday = _DAYS_IN_YEAR(year) - 1;
-     } else {
-   mday = _DAYS_IN_YEAR(year);
-   if (tim_p->tm_yday > (mday - 1))
- tim_p->tm_yday -= mday;
-     }
-   }
- }
-     }
- }
-    }
-
-  /* add appropriate offset to put time in gmt format */
-  if (isdst == 1)
-    tim += (time_t) tz->__tzrule[1].offset;
-  else /* otherwise assume std time */
-    tim += (time_t) tz->__tzrule[0].offset;
+  return tim;
+}

-  TZ_UNLOCK;
+time_t
+timegm (struct tm *tim_p)
+{
+  time_t tim = __timegm_internal(tim_p);
+  long days = tim / SECSPERDAY;

-  /* reset isdst flag to what we have calculated */
-  tim_p->tm_isdst = isdst;
+  /* set isdst flag to 0 since we are in UTC */
+  tim_p->tm_isdst = 0;

   /* compute day of the week */
-  if ((tim_p->tm_wday = (days + 4) % 7) < 0)
-    tim_p->tm_wday += 7;
-
+  __set_tm_wday(days, tim_p);
+
   return tim;
 }
+
-- 
2.18.0.597.ga71716f1ad-goog

Comments

Corinna Vinschen Aug. 14, 2018, 3:42 p.m. | #1
On Aug 13 18:17, Andrew Russell via newlib wrote:
> >From e182faa79c35984b667029ef7b6e4a8ce7329897 Mon Sep 17 00:00:00 2001

> From: Andrew Russell <ahrussell@google.com>

> Date: Fri, 10 Aug 2018 12:14:18 -0700

> Subject: [PATCH 1/4] Start of mktime.c copy to timegm.c

> 

> I am proposing to add the timegm POSIX call to

> Newlib. Part of this refactors some of the code in libc/time/local.h and

> libc/time/mktime.c, per this discussion:

> 

> https://sourceware.org/ml/newlib/2018/msg00186.html


I'm looking for comments from other (non-Cygwin) devs here.


Thanks,
Corinna

-- 
Corinna Vinschen
Cygwin Maintainer
Red Hat
Joel Sherrill Aug. 14, 2018, 5:49 p.m. | #2
On Tue, Aug 14, 2018 at 10:42 AM, Corinna Vinschen <vinschen@redhat.com>
wrote:

> On Aug 13 18:17, Andrew Russell via newlib wrote:

> > >From e182faa79c35984b667029ef7b6e4a8ce7329897 Mon Sep 17 00:00:00 2001

> > From: Andrew Russell <ahrussell@google.com>

> > Date: Fri, 10 Aug 2018 12:14:18 -0700

> > Subject: [PATCH 1/4] Start of mktime.c copy to timegm.c

> >

> > I am proposing to add the timegm POSIX call to

> > Newlib. Part of this refactors some of the code in libc/time/local.h and

> > libc/time/mktime.c, per this discussion:

> >

> > https://sourceware.org/ml/newlib/2018/msg00186.html

>

> I'm looking for comments from other (non-Cygwin) devs here.

>


From my perspective, I don't mind having common methods that
are not in libc or POSIX in newlib or RTEMS. Ultimately, the wider
set of methods makes packages easier to port.

I would ask that the method is documented using the newlib markup
and that its historical origin is noted. The Linux man page is clearly
discouraging:

CONFORMING TO
       These functions are nonstandard GNU extensions that are also present
on
       the BSDs.  Avoid their use; see NOTES.

But overall, improving cross-platform compatibility is good even
when it means adding extensions that have no impact when not
used.

--joel


>

> Thanks,

> Corinna

>

> --

> Corinna Vinschen

> Cygwin Maintainer

> Red Hat

>
Andreas Köpke Aug. 15, 2018, 7:52 a.m. | #3
> On Aug 13 18:17, Andrew Russell via newlib wrote:

> > >From e182faa79c35984b667029ef7b6e4a8ce7329897 Mon Sep 17 00:00:00 2001

> > 

> > From: Andrew Russell <ahrussell@google.com>

> > Date: Fri, 10 Aug 2018 12:14:18 -0700

> > Subject: [PATCH 1/4] Start of mktime.c copy to timegm.c

> > 

> > I am proposing to add the timegm POSIX call to

> > Newlib. Part of this refactors some of the code in libc/time/local.h and

> > libc/time/mktime.c, per this discussion:

> > 

> > https://sourceware.org/ml/newlib/2018/msg00186.html

> 

> I'm looking for comments from other (non-Cygwin) devs here.

> 

> 

> Thanks,

> Corinna


I did not test it yet but we need this function. cygwin has it, but uses a 
completely different codebase (localtime.cc). 

It is always needed if your default timezone is not GMT and you need to 
exchange time information with e.g. sync mechanisms  like NTP or GPS. You 
usually get broken down time information referenced to GMT and need to re-
assemble using GMT as reference. 

Using setenv/getenv to temporarily change the timezone does not work: there is 
a memory leak in the implementation. Well, at least it looks like one. 

Parsing the TZ information takes time (at least on 12MHz MCUs) and stack space 
(~ 600 bytes) which is not always available. So having a working 
implementation would be great. 

Best regards, Andreas
Corinna Vinschen Aug. 15, 2018, 8:27 a.m. | #4
On Aug 15 09:52, Andreas Köpke wrote:
> > On Aug 13 18:17, Andrew Russell via newlib wrote:

> > > >From e182faa79c35984b667029ef7b6e4a8ce7329897 Mon Sep 17 00:00:00 2001

> > > 

> > > From: Andrew Russell <ahrussell@google.com>

> > > Date: Fri, 10 Aug 2018 12:14:18 -0700

> > > Subject: [PATCH 1/4] Start of mktime.c copy to timegm.c

> > > 

> > > I am proposing to add the timegm POSIX call to

> > > Newlib. Part of this refactors some of the code in libc/time/local.h and

> > > libc/time/mktime.c, per this discussion:

> > > 

> > > https://sourceware.org/ml/newlib/2018/msg00186.html

> > 

> > I'm looking for comments from other (non-Cygwin) devs here.

> > 

> > 

> > Thanks,

> > Corinna

> 

> I did not test it yet but we need this function. cygwin has it, but uses a 

> completely different codebase (localtime.cc). 

> 

> It is always needed if your default timezone is not GMT and you need to 

> exchange time information with e.g. sync mechanisms  like NTP or GPS. You 

> usually get broken down time information referenced to GMT and need to re-

> assemble using GMT as reference. 

> 

> Using setenv/getenv to temporarily change the timezone does not work: there is 

> a memory leak in the implementation. Well, at least it looks like one. 


Patch?


Corinna

-- 
Corinna Vinschen
Cygwin Maintainer
Red Hat
Corinna Vinschen Aug. 15, 2018, 8:54 a.m. | #5
On Aug 14 12:49, Joel Sherrill wrote:
> On Tue, Aug 14, 2018 at 10:42 AM, Corinna Vinschen <vinschen@redhat.com>

> wrote:

> 

> > On Aug 13 18:17, Andrew Russell via newlib wrote:

> > > >From e182faa79c35984b667029ef7b6e4a8ce7329897 Mon Sep 17 00:00:00 2001

> > > From: Andrew Russell <ahrussell@google.com>

> > > Date: Fri, 10 Aug 2018 12:14:18 -0700

> > > Subject: [PATCH 1/4] Start of mktime.c copy to timegm.c

> > >

> > > I am proposing to add the timegm POSIX call to

> > > Newlib. Part of this refactors some of the code in libc/time/local.h and

> > > libc/time/mktime.c, per this discussion:

> > >

> > > https://sourceware.org/ml/newlib/2018/msg00186.html

> >

> > I'm looking for comments from other (non-Cygwin) devs here.

> >

> 

> >From my perspective, I don't mind having common methods that

> are not in libc or POSIX in newlib or RTEMS. Ultimately, the wider

> set of methods makes packages easier to port.

> 

> I would ask that the method is documented using the newlib markup

> and that its historical origin is noted. The Linux man page is clearly

> discouraging:

> 

> CONFORMING TO

>        These functions are nonstandard GNU extensions that are also present

> on

>        the BSDs.  Avoid their use; see NOTES.

> 

> But overall, improving cross-platform compatibility is good even

> when it means adding extensions that have no impact when not

> used.

> 

> --joel


The new functions are ok to go in, I was more interested in people
testing the code.  Sorry if I was unclear!


Corinna

-- 
Corinna Vinschen
Cygwin Maintainer
Red Hat
Craig Howland Aug. 15, 2018, 3:01 p.m. | #6
On 08/13/2018 09:17 PM, Andrew Russell via newlib wrote:
> From: Andrew Russell <ahrussell@google.com>

> Date: Fri, 10 Aug 2018 12:14:18 -0700

> Subject: [PATCH 1/4] Start of mktime.c copy to timegm.c

>

> I am proposing to add the timegm POSIX call to

> Newlib. Part of this refactors some of the code in libc/time/local.h and

> libc/time/mktime.c, per this discussion:

>

> https://sourceware.org/ml/newlib/2018/msg00186.html

>

> Thanks,

> Andrew

>

> ...

>

> diff --git a/newlib/libc/include/time.h b/newlib/libc/include/time.h

> index a2efcc15e..8440ecb3c 100644

> --- a/newlib/libc/include/time.h

> +++ b/newlib/libc/include/time.h

> @@ -56,6 +56,7 @@ struct tm

>   clock_t    clock (void);

>   double    difftime (time_t _time2, time_t _time1);

>   time_t    mktime (struct tm *_timeptr);

> +time_t    timegm (struct tm *_timeptr);

This prototype should be gated by the appropriate-version GNU extension #if.  
(According to the Linux timegm() man page the gate is _BSD_SOURCE || 
_SVID_SOURCE (although the Newlib in-header version might be different).  But 
given it is called a GNU extension, you'd think _GNU_SOURCE would also be there.)
>   time_t    time (time_t *_timer);

>   #ifndef _REENT_ONLY

>   char   *asctime (const struct tm *_tblock);

>

> ...

>

> diff --git a/newlib/libc/time/mktime.c b/newlib/libc/time/mktime.c

> index 02032599a..ab47f8614 100644

> --- a/newlib/libc/time/mktime.c

> +++ b/newlib/libc/time/mktime.c

> @@ -11,6 +11,8 @@

>    * represented, returns the value (time_t) -1.

>    *

>    * Modifications: Fixed tm_isdst usage - 27 August 2008 Craig Howland.

> + *                      Refactor code from mktime.c to shared internal

> + *                      functions. - 17 July 2018 Andrew Russell.

>    */

>

>   /*

> @@ -39,157 +41,22 @@ result is the time, converted to a <<time_t>> value.

>   PORTABILITY

>   ANSI C requires <<mktime>>.

>

> -<<mktime>> requires no supporting OS subroutines.

> +<<timegm>> requires no supporting OS subroutines.

Spurious change, as this file is still mktime.
>   ...

> diff --git a/newlib/libc/time/timegm.c b/newlib/libc/time/timegm.c

(This diff as presented does not make sense from the point of view that this is 
a new file.)
> index 02032599a..3299ce228 100644

> --- a/newlib/libc/time/timegm.c

> +++ b/newlib/libc/time/timegm.c

> @@ -1,8 +1,8 @@

>   /*

> - * mktime.c

> + * timegm.c

>    * Original Author: G. Haley

>    *

> - * Converts the broken-down time, expressed as local time, in the structure

> + * Converts the broken-down time, expressed as UTC time, in the structure

>    * pointed to by tim_p into a calendar time value. The original values of the

>    * tm_wday and tm_yday fields of the structure are ignored, and the original

>    * values of the other fields have no restrictions. On successful completion

> @@ -11,25 +11,27 @@

>    * represented, returns the value (time_t) -1.

>    *

>    * Modifications: Fixed tm_isdst usage - 27 August 2008 Craig Howland.

Can get rid of this fix tm_isdst comment, as not in timegm, but in mktime.
The comments that make the man page here should probably include the same "These 
functions are nonstandard GNU extensions that are also present on the BSDs.  
Avoid their use; see NOTES." statement that appears in the timegm/timelocal 
Linux man page (with a small tweak for being only 1 function instead of two).
> ...

>

>   #include <stdlib.h>

> @@ -58,11 +60,8 @@ static const int DAYS_IN_MONTH[12] =

>   static const int _DAYS_BEFORE_MONTH[12] =

>   {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334};

Someone had suggested in the thread that DAYS_IN_MONTH be changed to char to 
save space.  How about int_least16_t to (possibly) save space?
> ...

>

> -time_t

> -mktime (struct tm *tim_p)

> +void

> +__set_tm_wday (long days, struct tm *tim_p)

> +{

> +  if ((tim_p->tm_wday = (days + 4) % 7) < 0)

> +    tim_p->tm_wday += 7;

> +}

> +

> +/* returns either 0 or 1 */

> +static

> +int

> +__is_leap_year (int year)

> +{

> +  return (year % 4) == 0 && ((year % 100) != 0 || (((year / 100) & 3)

> == (-(YEAR_BASE / 100)  & 3)));

How about "(year & 3) == 0" instead of the % to save time? (Should not matter 
with a good optimizer, but it is not necessarily on.)
> ...

> +time_t

> +timegm (struct tm *tim_p)

> +{

> +  time_t tim = __timegm_internal(tim_p);

> +  long days = tim / SECSPERDAY;

>

> -  /* reset isdst flag to what we have calculated */

> -  tim_p->tm_isdst = isdst;

> +  /* set isdst flag to 0 since we are in UTC */

> +  tim_p->tm_isdst = 0;

>

>     /* compute day of the week */

> -  if ((tim_p->tm_wday = (days + 4) % 7) < 0)

> -    tim_p->tm_wday += 7;

> -

> +  __set_tm_wday(days, tim_p);

> +

>     return tim;

>   }

> +

> -- 

> 2.18.0.597.ga71716f1ad-goog

>


I have not been able to try testing it, yet, but there are a few thoughts based 
on examination.  See inline above as well as some here.

I suggest that the new timegm.c file should contain only the new timegm() 
function and that the supporting functions all belong in mktime.c.  This comes 
closest to preserving the same size on existing implementations, which otherwise 
would then require the timegm object to be linked; the new timegm object will 
only get linked for applications which call it.  (Not that the timegm() 
function, itself, is large, but it is the new extension.)

Craig
Joel Sherrill Aug. 15, 2018, 3:27 p.m. | #7
On Wed, Aug 15, 2018 at 10:01 AM, Craig Howland <howland@lgsinnovations.com>
wrote:

> On 08/13/2018 09:17 PM, Andrew Russell via newlib wrote:

>

>> From: Andrew Russell <ahrussell@google.com>

>> Date: Fri, 10 Aug 2018 12:14:18 -0700

>> Subject: [PATCH 1/4] Start of mktime.c copy to timegm.c

>>

>> I am proposing to add the timegm POSIX call to

>> Newlib. Part of this refactors some of the code in libc/time/local.h and

>> libc/time/mktime.c, per this discussion:

>>

>> https://sourceware.org/ml/newlib/2018/msg00186.html

>>

>> Thanks,

>> Andrew

>>

>> ...

>>

>> diff --git a/newlib/libc/include/time.h b/newlib/libc/include/time.h

>> index a2efcc15e..8440ecb3c 100644

>> --- a/newlib/libc/include/time.h

>> +++ b/newlib/libc/include/time.h

>> @@ -56,6 +56,7 @@ struct tm

>>   clock_t    clock (void);

>>   double    difftime (time_t _time2, time_t _time1);

>>   time_t    mktime (struct tm *_timeptr);

>> +time_t    timegm (struct tm *_timeptr);

>>

> This prototype should be gated by the appropriate-version GNU extension

> #if.  (According to the Linux timegm() man page the gate is _BSD_SOURCE ||

> _SVID_SOURCE (although the Newlib in-header version might be different).

> But given it is called a GNU extension, you'd think _GNU_SOURCE would also

> be there.)

>

>>   time_t    time (time_t *_timer);

>>   #ifndef _REENT_ONLY

>>   char   *asctime (const struct tm *_tblock);

>>

>> ...

>>

>> diff --git a/newlib/libc/time/mktime.c b/newlib/libc/time/mktime.c

>> index 02032599a..ab47f8614 100644

>> --- a/newlib/libc/time/mktime.c

>> +++ b/newlib/libc/time/mktime.c

>> @@ -11,6 +11,8 @@

>>    * represented, returns the value (time_t) -1.

>>    *

>>    * Modifications: Fixed tm_isdst usage - 27 August 2008 Craig Howland.

>> + *                      Refactor code from mktime.c to shared internal

>> + *                      functions. - 17 July 2018 Andrew Russell.

>>    */

>>

>>   /*

>> @@ -39,157 +41,22 @@ result is the time, converted to a <<time_t>> value.

>>   PORTABILITY

>>   ANSI C requires <<mktime>>.

>>

>> -<<mktime>> requires no supporting OS subroutines.

>> +<<timegm>> requires no supporting OS subroutines.

>>

> Spurious change, as this file is still mktime.

>

>>   ...

>> diff --git a/newlib/libc/time/timegm.c b/newlib/libc/time/timegm.c

>>

> (This diff as presented does not make sense from the point of view that

> this is a new file.)

>

>> index 02032599a..3299ce228 100644

>> --- a/newlib/libc/time/timegm.c

>> +++ b/newlib/libc/time/timegm.c

>> @@ -1,8 +1,8 @@

>>   /*

>> - * mktime.c

>> + * timegm.c

>>    * Original Author: G. Haley

>>    *

>> - * Converts the broken-down time, expressed as local time, in the

>> structure

>> + * Converts the broken-down time, expressed as UTC time, in the structure

>>    * pointed to by tim_p into a calendar time value. The original values

>> of the

>>    * tm_wday and tm_yday fields of the structure are ignored, and the

>> original

>>    * values of the other fields have no restrictions. On successful

>> completion

>> @@ -11,25 +11,27 @@

>>    * represented, returns the value (time_t) -1.

>>    *

>>    * Modifications: Fixed tm_isdst usage - 27 August 2008 Craig Howland.

>>

> Can get rid of this fix tm_isdst comment, as not in timegm, but in mktime.

> The comments that make the man page here should probably include the same

> "These functions are nonstandard GNU extensions that are also present on

> the BSDs.  Avoid their use; see NOTES." statement that appears in the

> timegm/timelocal Linux man page (with a small tweak for being only 1

> function instead of two).

>


+1 Critical to have good documentation at the top.

> ...

>>

>>   #include <stdlib.h>

>> @@ -58,11 +60,8 @@ static const int DAYS_IN_MONTH[12] =

>>   static const int _DAYS_BEFORE_MONTH[12] =

>>   {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334};

>>

> Someone had suggested in the thread that DAYS_IN_MONTH be changed to char

> to save space.  How about int_least16_t to (possibly) save space?

>

>> ...

>>

>> -time_t

>> -mktime (struct tm *tim_p)

>> +void

>> +__set_tm_wday (long days, struct tm *tim_p)

>> +{

>> +  if ((tim_p->tm_wday = (days + 4) % 7) < 0)

>> +    tim_p->tm_wday += 7;

>> +}

>> +

>> +/* returns either 0 or 1 */

>> +static

>> +int

>> +__is_leap_year (int year)

>> +{

>> +  return (year % 4) == 0 && ((year % 100) != 0 || (((year / 100) & 3)

>> == (-(YEAR_BASE / 100)  & 3)));

>>

> How about "(year & 3) == 0" instead of the % to save time? (Should not

> matter with a good optimizer, but it is not necessarily on.)

>

>> ...

>> +time_t

>> +timegm (struct tm *tim_p)

>> +{

>> +  time_t tim = __timegm_internal(tim_p);

>> +  long days = tim / SECSPERDAY;

>>

>> -  /* reset isdst flag to what we have calculated */

>> -  tim_p->tm_isdst = isdst;

>> +  /* set isdst flag to 0 since we are in UTC */

>> +  tim_p->tm_isdst = 0;

>>

>>     /* compute day of the week */

>> -  if ((tim_p->tm_wday = (days + 4) % 7) < 0)

>> -    tim_p->tm_wday += 7;

>> -

>> +  __set_tm_wday(days, tim_p);

>> +

>>     return tim;

>>   }

>> +

>> --

>> 2.18.0.597.ga71716f1ad-goog

>>

>>

> I have not been able to try testing it, yet, but there are a few thoughts

> based on examination.  See inline above as well as some here.

>

> I suggest that the new timegm.c file should contain only the new timegm()

> function and that the supporting functions all belong in mktime.c.  This

> comes closest to preserving the same size on existing implementations,

> which otherwise would then require the timegm object to be linked; the new

> timegm object will only get linked for applications which call it.  (Not

> that the timegm() function, itself, is large, but it is the new extension.)

>

> Craig

>
Freddie Chopin Aug. 15, 2018, 7:18 p.m. | #8
On Wed, 2018-08-15 at 11:01 -0400, Craig Howland wrote:
> > +/* returns either 0 or 1 */

> > +static

> > +int

> > +__is_leap_year (int year)

> > +{

> > +  return (year % 4) == 0 && ((year % 100) != 0 || (((year / 100) &

> > 3)

> > == (-(YEAR_BASE / 100)  & 3)));

> 

> How about "(year & 3) == 0" instead of the % to save time? (Should

> not matter 

> with a good optimizer, but it is not necessarily on.)


On the other hand % is exactly 1:1 with the intent, while binary
operations are not.

godbolt suggests that for x86-64 "(x % 4) == 0" and "(x & 3) == 0" give
exactly the same code (binary operations) when optimization is
explicitly _disabled_ (-O0).

Also do note that year may be negative, and "& 3" is not the same as "%
4" for negative numbers. In this particular case this probably doesn't
matter anyway, but still (;

Regards,
FCh
Freddie Chopin Aug. 15, 2018, 7:27 p.m. | #9
On Wed, 2018-08-15 at 21:18 +0200, Freddie Chopin wrote:
> On Wed, 2018-08-15 at 11:01 -0400, Craig Howland wrote:

> > > +/* returns either 0 or 1 */

> > > +static

> > > +int

> > > +__is_leap_year (int year)

> > > +{

> > > +  return (year % 4) == 0 && ((year % 100) != 0 || (((year / 100)

> > > &

> > > 3)

> > > == (-(YEAR_BASE / 100)  & 3)));

> > 

> > How about "(year & 3) == 0" instead of the % to save time? (Should

> > not matter 

> > with a good optimizer, but it is not necessarily on.)

> 

> On the other hand % is exactly 1:1 with the intent, while binary

> operations are not.

> 

> godbolt suggests that for x86-64 "(x % 4) == 0" and "(x & 3) == 0"

> give

> exactly the same code (binary operations) when optimization is

> explicitly _disabled_ (-O0).


Same for arm-none-eabi (previously I could not find the right subpage
where arm-none-eabi) was available.

https://godbolt.org/g/MYFCkb

Regards,
FCh
Richard Earnshaw (lists) Aug. 16, 2018, 3:24 p.m. | #10
On 15/08/18 20:18, Freddie Chopin wrote:
> On Wed, 2018-08-15 at 11:01 -0400, Craig Howland wrote:

>>> +/* returns either 0 or 1 */

>>> +static

>>> +int

>>> +__is_leap_year (int year)

>>> +{

>>> +  return (year % 4) == 0 && ((year % 100) != 0 || (((year / 100) &

>>> 3)

>>> == (-(YEAR_BASE / 100)  & 3)));

>>

>> How about "(year & 3) == 0" instead of the % to save time? (Should

>> not matter 

>> with a good optimizer, but it is not necessarily on.)

> 

> On the other hand % is exactly 1:1 with the intent, while binary

> operations are not.

> 

> godbolt suggests that for x86-64 "(x % 4) == 0" and "(x & 3) == 0" give

> exactly the same code (binary operations) when optimization is

> explicitly _disabled_ (-O0).

> 

> Also do note that year may be negative, and "& 3" is not the same as "%

> 4" for negative numbers. In this particular case this probably doesn't

> matter anyway, but still (;

> 

> Regards,

> FCh

> 


"year" is signed, so % and & do not generate the same code - you need a
negative value correction.  So it all depends on whether you expect
negative years (BC) to be relevant for such calculations!

R.

Patch

diff --git a/newlib/libc/time/mktime.c b/newlib/libc/time/timegm.c
similarity index 100%
rename from newlib/libc/time/mktime.c
rename to newlib/libc/time/timegm.c
-- 
2.18.0.597.ga71716f1ad-goog


From e0a614c6d92ee5c14cebf8eaa69ade344998fab6 Mon Sep 17 00:00:00 2001
From: Andrew Russell <ahrussell@google.com>
Date: Fri, 10 Aug 2018 12:14:58 -0700
Subject: [PATCH 2/4] Middle of mktime.c copy to timegm.c

---
 newlib/libc/time/{mktime.c => mktime_backup.c} | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename newlib/libc/time/{mktime.c => mktime_backup.c} (100%)

diff --git a/newlib/libc/time/mktime.c b/newlib/libc/time/mktime_backup.c
similarity index 100%
rename from newlib/libc/time/mktime.c
rename to newlib/libc/time/mktime_backup.c
-- 
2.18.0.597.ga71716f1ad-goog


From 078c008672c6e3edd98f04b7998397275f466734 Mon Sep 17 00:00:00 2001
From: Andrew Russell <ahrussell@google.com>
Date: Fri, 10 Aug 2018 12:16:23 -0700
Subject: [PATCH 3/4] Final commit to copy mktime.c to timegm.c

---
 newlib/libc/time/{mktime_backup.c => mktime.c} | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename newlib/libc/time/{mktime_backup.c => mktime.c} (100%)

diff --git a/newlib/libc/time/mktime_backup.c b/newlib/libc/time/mktime.c
similarity index 100%
rename from newlib/libc/time/mktime_backup.c
rename to newlib/libc/time/mktime.c
-- 
2.18.0.597.ga71716f1ad-goog


From 2a8129248f33150c4ee0af17e34de37fcdaecb97 Mon Sep 17 00:00:00 2001
From: Andrew Russell <ahrussell@google.com>
Date: Fri, 10 Aug 2018 12:23:36 -0700
Subject: [PATCH 4/4] Refactor mktime and add the POSIX function timegm

---
 newlib/libc/include/time.h   |   1 +
 newlib/libc/saber            |   1 +
 newlib/libc/time/Makefile.am |   2 +
 newlib/libc/time/Makefile.in |  11 ++-
 newlib/libc/time/local.h     |   5 ++
 newlib/libc/time/mktime.c    | 163 ++++------------------------------
 newlib/libc/time/timegm.c    | 164 ++++++++++++-----------------------
 7 files changed, 90 insertions(+), 257 deletions(-)

diff --git a/newlib/libc/include/time.h b/newlib/libc/include/time.h
index a2efcc15e..8440ecb3c 100644
--- a/newlib/libc/include/time.h
+++ b/newlib/libc/include/time.h
@@ -56,6 +56,7 @@  struct tm
 clock_t    clock (void);
 double    difftime (time_t _time2, time_t _time1);
 time_t    mktime (struct tm *_timeptr);
+time_t    timegm (struct tm *_timeptr);
 time_t    time (time_t *_timer);
 #ifndef _REENT_ONLY
 char   *asctime (const struct tm *_tblock);
diff --git a/newlib/libc/saber b/newlib/libc/saber
index 4f16f976e..f57063f82 100644
--- a/newlib/libc/saber
+++ b/newlib/libc/saber
@@ -122,6 +122,7 @@  time/gmtime.c
 time/localtime.c
 time/mktime.c
 time/strftime.c
+time/timegm.c


 load stdio/fiprintf.c
diff --git a/newlib/libc/time/Makefile.am b/newlib/libc/time/Makefile.am
index be040baec..5c73c3a17 100644
--- a/newlib/libc/time/Makefile.am
+++ b/newlib/libc/time/Makefile.am
@@ -17,6 +17,7 @@  LIB_SOURCES = \
  lcltime.c \
  lcltime_r.c \
  mktime.c \
+ timegm.c \
  month_lengths.c \
  strftime.c  \
  strptime.c \
@@ -54,6 +55,7 @@  CHEWOUT_FILES = \
  gmtime.def \
  lcltime.def \
  mktime.def \
+ timegm.def \
  strftime.def \
  time.def \
  tzlock.def \
diff --git a/newlib/libc/time/Makefile.in b/newlib/libc/time/Makefile.in
index a32333234..2845f7438 100644
--- a/newlib/libc/time/Makefile.in
+++ b/newlib/libc/time/Makefile.in
@@ -80,6 +80,7 @@  am__objects_1 = lib_a-asctime.$(OBJEXT)
lib_a-asctime_r.$(OBJEXT) \
  lib_a-lcltime_r.$(OBJEXT) lib_a-mktime.$(OBJEXT) \
  lib_a-month_lengths.$(OBJEXT) lib_a-strftime.$(OBJEXT) \
  lib_a-strptime.$(OBJEXT) lib_a-time.$(OBJEXT) \
+ lib_a-timegm.$(OBJEXT) \
  lib_a-tzcalc_limits.$(OBJEXT) lib_a-tzlock.$(OBJEXT) \
  lib_a-tzset.$(OBJEXT) lib_a-tzset_r.$(OBJEXT) \
  lib_a-tzvars.$(OBJEXT) lib_a-wcsftime.$(OBJEXT)
@@ -90,7 +91,8 @@  libtime_la_LIBADD =
 am__objects_2 = asctime.lo asctime_r.lo clock.lo ctime.lo ctime_r.lo \
  difftime.lo gettzinfo.lo gmtime.lo gmtime_r.lo lcltime.lo \
  lcltime_r.lo mktime.lo month_lengths.lo strftime.lo \
- strptime.lo time.lo tzcalc_limits.lo tzlock.lo tzset.lo \
+ strptime.lo time.lo timegm.lo \
+ tzcalc_limits.lo tzlock.lo tzset.lo \
  tzset_r.lo tzvars.lo wcsftime.lo
 @USE_LIBTOOL_TRUE@am_libtime_la_OBJECTS = $(am__objects_2)
 libtime_la_OBJECTS = $(am_libtime_la_OBJECTS)
@@ -282,6 +284,7 @@  LIB_SOURCES = \
  strftime.c  \
  strptime.c \
  time.c \
+ timegm.c        \
  tzcalc_limits.c \
  tzlock.c \
  tzset.c \
@@ -463,6 +466,12 @@  lib_a-mktime.o: mktime.c
 lib_a-mktime.obj: mktime.c
  $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS)
$(CPPFLAGS) $(lib_a_CFLAGS) $(CFLAGS) -c -o lib_a-mktime.obj `if test
-f 'mktime.c'; then $(CYGPATH_W) 'mktime.c'; else $(CYGPATH_W)
'$(srcdir)/mktime.c'; fi`

+lib_a-timegm.o: timegm.c
+ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS)
$(CPPFLAGS) $(lib_a_CFLAGS) $(CFLAGS) -c -o lib_a-timegm.o `test -f
'timegm.c' || echo '$(srcdir)/'`timegm.c
+
+lib_a-timegm.obj: timegm.c
+ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS)
$(CPPFLAGS) $(lib_a_CFLAGS) $(CFLAGS) -c -o lib_a-timegm.obj `if test
-f 'timegm.c'; then $(CYGPATH_W) 'timegm.c'; else $(CYGPATH_W)
'$(srcdir)/timegm.c'; fi`
+
 lib_a-month_lengths.o: month_lengths.c
  $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS)
$(CPPFLAGS) $(lib_a_CFLAGS) $(CFLAGS) -c -o lib_a-month_lengths.o
`test -f 'month_lengths.c' || echo '$(srcdir)/'`month_lengths.c

diff --git a/newlib/libc/time/local.h b/newlib/libc/time/local.h
index dce51cda2..543b9fd25 100644
--- a/newlib/libc/time/local.h
+++ b/newlib/libc/time/local.h
@@ -38,3 +38,8 @@  void _tzset_unlocked (void);
 void __tz_lock (void);
 void __tz_unlock (void);

+time_t __timegm_internal (struct tm *tim_p);
+void __set_tm_wday (long days, struct tm *tim_p);
+void __validate_tm_structure (struct tm *tim_p);
+
+int __days_in_year (int year);
diff --git a/newlib/libc/time/mktime.c b/newlib/libc/time/mktime.c
index 02032599a..ab47f8614 100644
--- a/newlib/libc/time/mktime.c
+++ b/newlib/libc/time/mktime.c
@@ -11,6 +11,8 @@ 
  * represented, returns the value (time_t) -1.
  *
  * Modifications: Fixed tm_isdst usage - 27 August 2008 Craig Howland.
+ *                      Refactor code from mktime.c to shared internal
+ *                      functions. - 17 July 2018 Andrew Russell.
  */

 /*
@@ -39,157 +41,22 @@  result is the time, converted to a <<time_t>> value.
 PORTABILITY
 ANSI C requires <<mktime>>.

-<<mktime>> requires no supporting OS subroutines.
+<<timegm>> requires no supporting OS subroutines.
 */

 #include <stdlib.h>
 #include <time.h>
 #include "local.h"

-#define _SEC_IN_MINUTE 60L
-#define _SEC_IN_HOUR 3600L
-#define _SEC_IN_DAY 86400L
-
-static const int DAYS_IN_MONTH[12] =
-{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
-
-#define _DAYS_IN_MONTH(x) ((x == 1) ? days_in_feb : DAYS_IN_MONTH[x])
-
-static const int _DAYS_BEFORE_MONTH[12] =
-{0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334};
-
-#define _ISLEAP(y) (((y) % 4) == 0 && (((y) % 100) != 0 ||
(((y)+1900) % 400) == 0))
-#define _DAYS_IN_YEAR(year) (_ISLEAP(year) ? 366 : 365)
-
-static void
-validate_structure (struct tm *tim_p)
-{
-  div_t res;
-  int days_in_feb = 28;
-
-  /* calculate time & date to account for out of range values */
-  if (tim_p->tm_sec < 0 || tim_p->tm_sec > 59)
-    {
-      res = div (tim_p->tm_sec, 60);
-      tim_p->tm_min += res.quot;
-      if ((tim_p->tm_sec = res.rem) < 0)
- {
-   tim_p->tm_sec += 60;
-   --tim_p->tm_min;
- }
-    }
-
-  if (tim_p->tm_min < 0 || tim_p->tm_min > 59)
-    {
-      res = div (tim_p->tm_min, 60);
-      tim_p->tm_hour += res.quot;
-      if ((tim_p->tm_min = res.rem) < 0)
- {
-   tim_p->tm_min += 60;
-   --tim_p->tm_hour;
-        }
-    }
-
-  if (tim_p->tm_hour < 0 || tim_p->tm_hour > 23)
-    {
-      res = div (tim_p->tm_hour, 24);
-      tim_p->tm_mday += res.quot;
-      if ((tim_p->tm_hour = res.rem) < 0)
- {
-   tim_p->tm_hour += 24;
-   --tim_p->tm_mday;
-        }
-    }
-
-  if (tim_p->tm_mon < 0 || tim_p->tm_mon > 11)
-    {
-      res = div (tim_p->tm_mon, 12);
-      tim_p->tm_year += res.quot;
-      if ((tim_p->tm_mon = res.rem) < 0)
-        {
-   tim_p->tm_mon += 12;
-   --tim_p->tm_year;
-        }
-    }
-
-  if (_DAYS_IN_YEAR (tim_p->tm_year) == 366)
-    days_in_feb = 29;
-
-  if (tim_p->tm_mday <= 0)
-    {
-      while (tim_p->tm_mday <= 0)
- {
-   if (--tim_p->tm_mon == -1)
-     {
-       tim_p->tm_year--;
-       tim_p->tm_mon = 11;
-       days_in_feb =
- ((_DAYS_IN_YEAR (tim_p->tm_year) == 366) ?
- 29 : 28);
-     }
-   tim_p->tm_mday += _DAYS_IN_MONTH (tim_p->tm_mon);
- }
-    }
-  else
-    {
-      while (tim_p->tm_mday > _DAYS_IN_MONTH (tim_p->tm_mon))
- {
-   tim_p->tm_mday -= _DAYS_IN_MONTH (tim_p->tm_mon);
-   if (++tim_p->tm_mon == 12)
-     {
-       tim_p->tm_year++;
-       tim_p->tm_mon = 0;
-       days_in_feb =
- ((_DAYS_IN_YEAR (tim_p->tm_year) == 366) ?
- 29 : 28);
-     }
- }
-    }
-}
-
-time_t
+time_t
 mktime (struct tm *tim_p)
 {
-  time_t tim = 0;
-  long days = 0;
-  int year, isdst=0;
+  time_t tim = __timegm_internal(tim_p);
+  long days = tim / SECSPERDAY;
+  int year = tim_p->tm_year;
+  int isdst=0;
   __tzinfo_type *tz = __gettzinfo ();

-  /* validate structure */
-  validate_structure (tim_p);
-
-  /* compute hours, minutes, seconds */
-  tim += tim_p->tm_sec + (tim_p->tm_min * _SEC_IN_MINUTE) +
-    (tim_p->tm_hour * _SEC_IN_HOUR);
-
-  /* compute days in year */
-  days += tim_p->tm_mday - 1;
-  days += _DAYS_BEFORE_MONTH[tim_p->tm_mon];
-  if (tim_p->tm_mon > 1 && _DAYS_IN_YEAR (tim_p->tm_year) == 366)
-    days++;
-
-  /* compute day of the year */
-  tim_p->tm_yday = days;
-
-  if (tim_p->tm_year > 10000 || tim_p->tm_year < -10000)
-      return (time_t) -1;
-
-  /* compute days in other years */
-  if ((year = tim_p->tm_year) > 70)
-    {
-      for (year = 70; year < tim_p->tm_year; year++)
- days += _DAYS_IN_YEAR (year);
-    }
-  else if (year < 70)
-    {
-      for (year = 69; year > tim_p->tm_year; year--)
- days -= _DAYS_IN_YEAR (year);
-      days -= _DAYS_IN_YEAR (year);
-    }
-
-  /* compute total seconds */
-  tim += (time_t)days * _SEC_IN_DAY;
-
   TZ_LOCK;

   _tzset_unlocked ();
@@ -204,7 +71,7 @@  mktime (struct tm *tim_p)

       if (y == tz->__tzyear || __tzcalc_limits (y))
  {
-   /* calculate start of dst in dst local time and
+   /* calculate start of dst in dst local time and
       start of std in both std local time and dst local time */
           time_t startdst_dst = tz->__tzrule[0].change
      - (time_t) tz->__tzrule[1].offset;
@@ -237,7 +104,7 @@  mktime (struct tm *tim_p)
    tim_p->tm_sec += diff;
    tim += diff;  /* we also need to correct our current time calculation */
    int mday = tim_p->tm_mday;
-   validate_structure (tim_p);
+   __validate_tm_structure(tim_p);
    mday = tim_p->tm_mday - mday;
    /* roll over occurred */
    if (mday) {
@@ -251,9 +118,9 @@  mktime (struct tm *tim_p)
      /* handle yday */
      if ((tim_p->tm_yday += mday) < 0) {
    --year;
-   tim_p->tm_yday = _DAYS_IN_YEAR(year) - 1;
+   tim_p->tm_yday = __days_in_year(year) - 1;
      } else {
-   mday = _DAYS_IN_YEAR(year);
+   mday = __days_in_year(year);
    if (tim_p->tm_yday > (mday - 1))
  tim_p->tm_yday -= mday;
      }
@@ -275,8 +142,8 @@  mktime (struct tm *tim_p)
   tim_p->tm_isdst = isdst;

   /* compute day of the week */
-  if ((tim_p->tm_wday = (days + 4) % 7) < 0)
-    tim_p->tm_wday += 7;
-
+  __set_tm_wday(days, tim_p);
+
   return tim;
 }
+
diff --git a/newlib/libc/time/timegm.c b/newlib/libc/time/timegm.c
index 02032599a..3299ce228 100644
--- a/newlib/libc/time/timegm.c
+++ b/newlib/libc/time/timegm.c
@@ -1,8 +1,8 @@ 
 /*
- * mktime.c
+ * timegm.c
  * Original Author: G. Haley
  *
- * Converts the broken-down time, expressed as local time, in the structure
+ * Converts the broken-down time, expressed as UTC time, in the structure
  * pointed to by tim_p into a calendar time value. The original values of the
  * tm_wday and tm_yday fields of the structure are ignored, and the original
  * values of the other fields have no restrictions. On successful completion
@@ -11,25 +11,27 @@ 
  * represented, returns the value (time_t) -1.
  *
  * Modifications: Fixed tm_isdst usage - 27 August 2008 Craig Howland.
+ *                  Refactored mktime.c to support both mktime and timegm
+ *                       - 23 July 2018 Andrew Russell.
  */

 /*
 FUNCTION
-<<mktime>>---convert time to arithmetic representation
+<<timegm>>---convert time to arithmetic representation

 INDEX
- mktime
+ timegm

 SYNOPSIS
  #include <time.h>
- time_t mktime(struct tm *<[timp]>);
+ time_t timegm(struct tm *<[timp]>);

 DESCRIPTION
-<<mktime>> assumes the time at <[timp]> is a local time, and converts
+<<timegm>> assumes the time at <[timp]> is UTC time, and converts
 its representation from the traditional representation defined by
 <<struct tm>> into a representation suitable for arithmetic.

-<<localtime>> is the inverse of <<mktime>>.
+<<timegm>> is the inverse of <<gmtime>>.

 RETURNS
 If the contents of the structure at <[timp]> do not form a valid
@@ -37,9 +39,9 @@  calendar time representation, the result is <<-1>>.
Otherwise, the
 result is the time, converted to a <<time_t>> value.

 PORTABILITY
-ANSI C requires <<mktime>>.
+<<timegm>> is a nonstandard GNU extension to POSIX also present on BSD.

-<<mktime>> requires no supporting OS subroutines.
+<<timegm>> requires no supporting OS subroutines.
 */