bug-cvs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: Patch for timezone handling in cvs log


From: Bart Robinson
Subject: Re: Patch for timezone handling in cvs log
Date: Thu, 29 Apr 2004 16:28:27 -0700 (PDT)

Here is the new patch, which uses the MT thing and does all
conversion on the client side.

OVERVIEW
--------

These changes add to log/rlog a -z option, which specifies the
timezone to use for output.  This is very similar to the -z option to
RCS's rlog command.  It works for remote repositories as well.

A special timezone "LT" is used to specify the local time at the
client.

The way this works is that the server side sends the date in the
rlog output as an MT response, tagged with "date".  An aware
client notices these and does the conversion if necessary.
Unaware clients just print them as-is (in GMT).

Example output looks like this (the -0700 part).  Notice that
the DST change on Apr 4 is accounted for.

  ----------------------------
  revision 1.291
  date: 2004/04/05 10:52:16-0700;  author: lomew;  state: Exp;  lines: +2 -1
  ...
  ----------------------------
  revision 1.290
  date: 2004/04/01 15:48:00-0800;  author: lomew;  state: Exp;  lines: +3 -3
  ...
  ----------------------------

If no -z option is given, the default output is like current CVS:

  ----------------------------
  date: 2004/04/05 17:52:16;  author: lomew;  state: Exp;  lines: +2 -1
  ----------------------------

etc.

I will gladly update docs, ChangeLogs, etc if this approved.

ISSUES
------

1. The way "cvs history" handles the special LT timezone is
   inconsistent with how these changes do it for "cvs log".  cvs
   history uses it to mean local time at the server.  I found this to
   be a less useful interpretation of LT, so I went with LT meaning
   client time.  It is possible to fix "history" to do this but I didn't
   want to change current behavior.

2. RCS rlog formats the timezone offset somewhat differently.  I.e.,

        offset  RCS     CVS
        -0700   -07     -0700
        +0530   +05:30  +0530

   rlog also allows either -z+0530 or -z+05:30 as an argument.  I was
   going to adopt this format for some sort of consistency but found
   that CVS's getdate.y doesn't parse ones with a colon in the middle,
   and I wasn't comfortable "fixing" that, plus I prefer the
   mail-style format without the colon.

PATCH NOTES
-----------

Each patch is preceded by a verbal summary and explanation.
The patches are against the HEAD of ccvs as of today (Apr 29, 2004)


###=====================================================================
### history.c: Changed to use the new tz_offset_east and
### tz_local globals as well as tz_offset_east, which was pulled
### out and put in main.c.
###
### As mentioned earlier, when the special LT timezone is used, it is
### passed to the server, resulting in localtime on the server.
###=====================================================================
Index: src/history.c
===================================================================
RCS file: /cvsroot/ccvs/src/history.c,v
retrieving revision 1.72
diff -u -p -r1.72 history.c
--- src/history.c       28 Apr 2004 04:20:33 -0000      1.72
+++ src/history.c       29 Apr 2004 23:18:25 -0000
@@ -232,8 +232,6 @@ static short repos_sort;
 static short file_sort;
 static short module_sort;
 
-static short tz_local;
-static time_t tz_seconds_east_of_GMT;
 static char *tz_name = "+0000";
 
 char *logHistory = ALL_HISTORY_REC_TYPES;
@@ -487,35 +485,15 @@ history (int argc, char **argv)
                rec_types = xstrdup (optarg);
                break;
            case 'z':
-               tz_local = 
-                   (optarg[0] == 'l' || optarg[0] == 'L')
-                   && (optarg[1] == 't' || optarg[1] == 'T')
-                   && !optarg[2];
-               if (tz_local)
-                   tz_name = optarg;
+               tz_name = optarg;
+               if (strcasecmp (optarg, "LT") == 0)
+               {
+                   tz_local = 1;
+               }
                else
                {
-                   /*
-                    * Convert a known time with the given timezone to time_t.
-                    * Use the epoch + 23 hours, so timezones east of GMT work.
-                    */
-                   size_t length;
-                   char *buf = asnprintf (NULL, &length, "1/1/1970 23:00 %s",
-                                           optarg);
-                   struct timespec t;
-                   if (!get_date (&t, buf, NULL))
+                   if (tz_offset_east (optarg, &tz_seconds_east_of_GMT) != 0)
                        error (0, 0, "%s is not a known time zone", optarg);
-                   else
-                   {
-                       /*
-                        * Convert to seconds east of GMT, removing the
-                        * 23-hour offset mentioned above.
-                        */
-                       tz_seconds_east_of_GMT = (time_t)23 * 60 * 60
-                                                 - t.tv_sec;
-                       tz_name = optarg;
-                   }
-                   free (buf);
                }
                break;
            case '?':

###=====================================================================
### log.c: Added the -z option.  Dates in the version log are
### tagged with "date" to be handled on the other side in
### handle_mt.  Had to do extensive s/cvs_output/cvs_output_tagged/
### since you can't just tag something in the middle of the
### line.  So basically, the first couple lines in a version log
### are with MT, and the log message is done with normal M
### (since it can contain embedded newlines, which MT doesn't handle).
###=====================================================================
Index: src/log.c
===================================================================
RCS file: /cvsroot/ccvs/src/log.c,v
retrieving revision 1.94
diff -u -p -r1.94 log.c
--- src/log.c   2 Apr 2004 19:56:22 -0000       1.94
+++ src/log.c   29 Apr 2004 23:18:25 -0000
@@ -168,6 +168,7 @@ static const char *const log_usage[] =
     "\t        \t(D1<D2 for range, D for latest before).\n",
     "\t-s states\tOnly list revisions with specified states.\n",
     "\t-w[logins]\tOnly list revisions checked in by specified logins.\n",
+    "\t-z[tz]\tOutput for specific time zone (e.g. -z-0700, -zLT, -zEDT)\n",
     "(Specify the --help global option for a list of other help options)\n",
     NULL
 };
@@ -228,7 +229,7 @@ cvslog (int argc, char **argv)
     prl = &log_data.revlist;
 
     optind = 0;
-    while ((c = getopt (argc, argv, "+bd:hlNSRr::s:tw::")) != -1)
+    while ((c = getopt (argc, argv, "+bd:hlNSRr::s:tw::z:")) != -1)
     {
        switch (c)
        {
@@ -269,6 +270,17 @@ cvslog (int argc, char **argv)
                else
                    log_parse_list (&log_data.authorlist, "@@MYSELF");
                break;
+           case 'z':
+               if (strcasecmp (optarg, "LT") == 0)
+               {
+                   tz_local = 1;
+               }
+               else
+               {
+                   if (tz_offset_east (optarg, &tz_seconds_east_of_GMT) != 0)
+                       error (0, 0, "%s is not a known time zone", optarg);
+               }
+               break;
            case '?':
            default:
                usage (log_usage);
@@ -1556,32 +1568,36 @@ log_version (struct log_data *log_data, 
     if (! log_version_requested (log_data, revlist, rcs, ver))
        return;
 
-    cvs_output ("----------------------------\nrevision ", 0);
-    cvs_output (ver->version, 0);
+    cvs_output_tagged ("text", "----------------------------");
+    cvs_output_tagged ("newline", NULL);
+    cvs_output_tagged ("text", "revision ");
+    cvs_output_tagged ("text", ver->version);
 
     p = findnode (RCS_getlocks (rcs), ver->version);
     if (p != NULL)
     {
-       cvs_output ("\tlocked by: ", 0);
-       cvs_output (p->data, 0);
-       cvs_output (";", 1);
+       cvs_output_tagged ("text", "\tlocked by: ");
+       cvs_output_tagged ("text", p->data);
+       cvs_output_tagged ("text", ";");
     }
+    cvs_output_tagged ("newline", NULL);
+
+    cvs_output_tagged ("text", "date: ");
 
-    cvs_output ("\ndate: ", 0);
     (void)sscanf (ver->date, SDATEFORM, &year, &mon, &mday, &hour, &min,
                  &sec);
     if (year < 1900)
        year += 1900;
     sprintf (buf, "%04d/%02d/%02d %02d:%02d:%02d", year, mon, mday,
             hour, min, sec);
-    cvs_output (buf, 0);
+    cvs_output_tagged ("date", buf);
 
-    cvs_output (";  author: ", 0);
-    cvs_output (ver->author, 0);
+    cvs_output_tagged ("text", ";  author: ");
+    cvs_output_tagged ("text", ver->author);
 
-    cvs_output (";  state: ", 0);
-    cvs_output (ver->state, 0);
-    cvs_output (";", 1);
+    cvs_output_tagged ("text", ";  state: ");
+    cvs_output_tagged ("text", ver->state);
+    cvs_output_tagged ("text", ";");
 
     if (! trunk)
     {
@@ -1609,19 +1625,20 @@ log_version (struct log_data *log_data, 
 
     if (padd != NULL)
     {
-       cvs_output ("  lines: +", 0);
-       cvs_output (padd->data, 0);
-       cvs_output (" -", 2);
-       cvs_output (pdel->data, 0);
+       cvs_output_tagged ("text", "  lines: +");
+       cvs_output_tagged ("text", padd->data);
+       cvs_output_tagged ("text", " -");
+       cvs_output_tagged ("text", pdel->data);
     }
 
     if (ver->branches != NULL)
     {
-       cvs_output ("\nbranches:", 0);
+       cvs_output_tagged ("newline", NULL);
+       cvs_output_tagged ("text", "branches:");
        walklist (ver->branches, log_branch, NULL);
     }
 
-    cvs_output ("\n", 1);
+    cvs_output_tagged ("newline", NULL);
 
     p = findnode (ver->other, "log");
     /* The p->date == NULL case is the normal one for an empty log
@@ -1652,9 +1669,9 @@ log_version (struct log_data *log_data, 
 static int
 log_branch (Node *p, void *closure)
 {
-    cvs_output ("  ", 2);
+    cvs_output_tagged ("text", "  ");
     if ((numdots (p->key) & 1) == 0)
-       cvs_output (p->key, 0);
+       cvs_output_tagged ("text", p->key);
     else
     {
        char *f, *cp;
@@ -1662,10 +1679,10 @@ log_branch (Node *p, void *closure)
        f = xstrdup (p->key);
        cp = strrchr (f, '.');
        *cp = '\0';
-       cvs_output (f, 0);
+       cvs_output_tagged ("text", f);
        free (f);
     }
-    cvs_output (";", 1);
+    cvs_output_tagged ("text", ";");
     return 0;
 }
 

###=====================================================================
### main.c: Support routines.  Seemed like a strange place
### for common routines, but there were already tm_to_internet and the
### like in there.
###
### tz_offset_east was mostly taken from what history.c used to
### do.
###
### tm_diff is actually in getdate.y but is static, so I put
### another copy in there...
###
### format_date_alloc is the common routine used in the
### client/server for the "date" tag.
###=====================================================================
Index: src/main.c
===================================================================
RCS file: /cvsroot/ccvs/src/main.c,v
retrieving revision 1.208
diff -u -p -r1.208 main.c
--- src/main.c  28 Apr 2004 04:20:33 -0000      1.208
+++ src/main.c  29 Apr 2004 23:18:25 -0000
@@ -44,6 +44,12 @@ int noexec = 0;
 int readonlyfs = 0;
 int logoff = 0;
 
+/*
+ * Stuff for displaying dates in a timezone other than GMT.
+ */
+int tz_local;                  /* 1 if want to display in local time */
+time_t tz_seconds_east_of_GMT; /* the offset otherwise (undef if tz_local) */
+
 /* Set if we should be writing CVSADM directories at top level.  At
    least for now we'll make the default be off (the CVS 1.9, not CVS
    1.9.2, behavior). */
@@ -1190,6 +1196,127 @@ tm_to_internet (char *dest, const struct
             source->tm_year + 1900, source->tm_hour, source->tm_min, 
source->tm_sec);
 }
 
+/*
+ * Calculates how many seconds East a zone is from GMT.
+ * Positive for East, negative for West.
+ * Returns non-zero if the TZNAME is invalid.
+ */
+int
+tz_offset_east (const char *tzname, time_t *offset)
+{
+    /*
+     * Convert a known time with the given timezone to time_t.
+     * Use the epoch + 23 hours, so timezones east of GMT work.
+     */
+    size_t length;
+    char *buf = asnprintf (NULL, &length, "1/1/1970 23:00 %s",
+                          optarg);
+    struct timespec t;
+    int good = get_date (&t, buf, NULL);
+    free (buf);
+    if (! good)
+       return -1;
+
+    /*
+     * Convert to seconds east of GMT, removing the
+     * 23-hour offset mentioned above.
+     */
+    *offset = (time_t)23 * 60 * 60 - t.tv_sec;
+    return 0;
+}
+
+/* Yield the difference between *A and *B,
+   measured in seconds, ignoring leap seconds.
+   The body of this function is taken directly from the GNU C Library;
+   see src/strftime.c.  */
+#ifndef TM_YEAR_BASE
+#define TM_YEAR_BASE 1900
+#endif
+long int
+tm_diff (struct tm const *a, struct tm const *b)
+{
+  /* Compute intervening leap days correctly even if year is negative.
+     Take care to avoid int overflow in leap day calculations.  */
+  int a4 = (a->tm_year >> 2) + (TM_YEAR_BASE >> 2) - ! (a->tm_year & 3);
+  int b4 = (b->tm_year >> 2) + (TM_YEAR_BASE >> 2) - ! (b->tm_year & 3);
+  int a100 = a4 / 25 - (a4 % 25 < 0);
+  int b100 = b4 / 25 - (b4 % 25 < 0);
+  int a400 = a100 >> 2;
+  int b400 = b100 >> 2;
+  int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400);
+  long int ayear = a->tm_year;
+  long int years = ayear - b->tm_year;
+  long int days = (365 * years + intervening_leap_days
+                  + (a->tm_yday - b->tm_yday));
+  return (60 * (60 * (24 * days + (a->tm_hour - b->tm_hour))
+               + (a->tm_min - b->tm_min))
+         + (a->tm_sec - b->tm_sec));
+}
+
+/*
+ * If the user has requested dates to be shown in a certain zone,
+ * reformat the input accordingly.
+ *
+ * Input looks like
+ *     2004/04/29 20:24:22
+ * Output looks the same or with +HHMM added to the end, like
+ *     2004/04/29 13:24:22-0700
+ */
+char *
+format_date_alloc (const char *datestr)
+{
+    if (tz_seconds_east_of_GMT || tz_local)
+    {
+       char datebuf[sizeof("YYYY/MM/DD HH:MM:SS GMT")];
+       struct timespec ts;
+       long offset;
+       struct tm xtm;
+       char *buf;
+
+       /*
+        * Get the date into a timespec so we can more easily perform
+        * arithmetic.
+        */
+       (void) sprintf (datebuf, "%s GMT", datestr);
+       if (! get_date (&ts, datebuf, NULL))
+           goto as_is;
+
+       /*
+        * Peform the offsetting and convert to a struct tm.
+        * For the tz_local case we have to calculate the offset for printing
+        * the, -HHMM part.
+        */
+       if (tz_seconds_east_of_GMT)
+       {
+           ts.tv_sec += tz_seconds_east_of_GMT;
+           xtm = *(gmtime (&ts.tv_sec));
+           offset = tz_seconds_east_of_GMT;
+       }
+       else
+       {
+           struct tm gtm;
+           xtm = *(localtime (&ts.tv_sec));
+           gtm = *(gmtime (&ts.tv_sec));
+           offset = tm_diff (&xtm, &gtm);
+       }
+
+       /*
+        * Format it and return.
+        */
+       buf = (char *) xmalloc (sizeof ("YYYY/MM/DD HH:MM:SS+HHMM"));
+       (void) sprintf (buf, "%04d/%02d/%02d %02d:%02d:%02d%c%02d%02d",
+                       1900+xtm.tm_year, xtm.tm_mon+1, xtm.tm_mday,
+                       xtm.tm_hour, xtm.tm_min, xtm.tm_sec,
+                       (offset < 0) ? '-' : '+',
+                       abs (offset) / (60*60),
+                       (abs (offset) % (60*60)) / 60);
+       return buf;
+    }
+
+ as_is:
+    return strdup (datestr);
+}
+
 void
 usage (register const char *const *cpp)
 {

###=====================================================================
### cvs.h: Add the prototypes for stuff we added to main.c.
###=====================================================================
Index: src/cvs.h
===================================================================
RCS file: /cvsroot/ccvs/src/cvs.h,v
retrieving revision 1.291
diff -u -p -r1.291 cvs.h
--- src/cvs.h   28 Apr 2004 04:20:33 -0000      1.291
+++ src/cvs.h   29 Apr 2004 23:18:24 -0000
@@ -373,6 +373,9 @@ extern int noexec;          /* Don't modify disk
 extern int readonlyfs;         /* fail on all write locks; succeed all read 
locks */
 extern int logoff;             /* Don't write history entry */
 
+extern int tz_local;
+extern time_t tz_seconds_east_of_GMT;
+
 extern int top_level_admin;
 #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
 extern int UseNewInfoFmtStrings;
@@ -431,6 +434,9 @@ char *date_from_time_t (time_t);
 void date_to_internet (char *, const char *);
 void date_to_tm (struct tm *, const char *);
 void tm_to_internet (char *, const struct tm *);
+int tz_offset_east (const char *tzname, time_t *offset);
+long int tm_diff (struct tm const *a, struct tm const *b);
+char *format_date_alloc (const char *text);
 
 char *Name_Repository (const char *dir, const char *update_dir);
 const char *Short_Repository (const char *repository);

###=====================================================================
### client.c: Fix handle_mt to handle the "date" tag.
###=====================================================================
Index: src/client.c
===================================================================
RCS file: /cvsroot/ccvs/src/client.c,v
retrieving revision 1.371
diff -u -p -r1.371 client.c
--- src/client.c        28 Apr 2004 04:20:33 -0000      1.371
+++ src/client.c        29 Apr 2004 23:18:24 -0000
@@ -2735,6 +2735,12 @@ handle_mt( char *args, int len )
            }
            else if (strcmp (tag, "newline") == 0)
                printf ("\n");
+           else if (strcmp (tag, "date") == 0)
+           {
+               char *date = format_date_alloc (text);
+               printf ("%s", date);
+               free (date);
+           }
            else if (text != NULL)
                printf ("%s", text);
     }

###=====================================================================
### server.c: Added code to handle the "date" tag in the event
### that MT is not supported or the repository is local.  Very
### similar to how "newline" is implemented.  We call cvs_output
### since that will either do printfs or convert the output to M
### commands.
###=====================================================================
Index: src/server.c
===================================================================
RCS file: /cvsroot/ccvs/src/server.c,v
retrieving revision 1.357
diff -u -p -r1.357 server.c
--- src/server.c        28 Apr 2004 04:20:33 -0000      1.357
+++ src/server.c        29 Apr 2004 23:18:26 -0000
@@ -6587,8 +6587,17 @@ cvs_output_tagged (const char *tag, cons
     else
 #endif
     {
+       /*
+        * No MT support or we are using a local repository.
+        */
        if (strcmp (tag, "newline") == 0)
            cvs_output ("\n", 1);
+       else if (strcmp (tag, "date") == 0)
+       {
+           char *date = format_date_alloc (text);
+           cvs_output (date, 0);
+           free (date);
+       }
        else if (text != NULL)
            cvs_output (text, 0);
     }




reply via email to

[Prev in Thread] Current Thread [Next in Thread]