summaryrefslogtreecommitdiff
path: root/subversion/libsvn_subr/time.c
blob: 7fd6da00764a38025a0d02f3ce1953389a5a8f40 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
/*
 * time.c:  time/date utilities
 *
 * ====================================================================
 *    Licensed to the Apache Software Foundation (ASF) under one
 *    or more contributor license agreements.  See the NOTICE file
 *    distributed with this work for additional information
 *    regarding copyright ownership.  The ASF licenses this file
 *    to you under the Apache License, Version 2.0 (the
 *    "License"); you may not use this file except in compliance
 *    with the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing,
 *    software distributed under the License is distributed on an
 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *    KIND, either express or implied.  See the License for the
 *    specific language governing permissions and limitations
 *    under the License.
 * ====================================================================
 */



#include <string.h>
#include <stdlib.h>
#include <apr_pools.h>
#include <apr_time.h>
#include <apr_strings.h>
#include "svn_io.h"
#include "svn_time.h"
#include "svn_utf.h"
#include "svn_error.h"
#include "svn_private_config.h"

#include "private/svn_string_private.h"



/*** Code. ***/

/* Our timestamp strings look like this:
 *
 *    "2002-05-07Thh:mm:ss.uuuuuuZ"
 *
 * The format is conformant with ISO-8601 and the date format required
 * by RFC2518 for creationdate. It is a direct conversion between
 * apr_time_t and a string, so converting to string and back retains
 * the exact value.
 */
#define TIMESTAMP_FORMAT "%04d-%02d-%02dT%02d:%02d:%02d.%06dZ"

/* Our old timestamp strings looked like this:
 *
 *    "Tue 3 Oct 2000 HH:MM:SS.UUU (day 277, dst 1, gmt_off -18000)"
 *
 * The idea is that they are conventionally human-readable for the
 * first part, and then in parentheses comes everything else required
 * to completely fill in an apr_time_exp_t: tm_yday, tm_isdst,
 * and tm_gmtoff.
 *
 * This format is still recognized on input, for backward
 * compatibility, but no longer generated.
 */
#define OLD_TIMESTAMP_FORMAT \
        "%3s %d %3s %d %02d:%02d:%02d.%06d (day %03d, dst %d, gmt_off %06d)"

/* Our human representation of dates looks like this:
 *
 *    "2002-06-23 11:13:02 +0300 (Sun, 23 Jun 2002)"
 *
 * This format is used whenever time is shown to the user. It consists
 * of a machine parseable, almost ISO-8601, part in the beginning -
 * and a human explanatory part at the end. The machine parseable part
 * is generated strictly by APR and our code, with a apr_snprintf. The
 * human explanatory part is generated by apr_strftime, which means
 * that its generation can be affected by locale, it can fail and it
 * doesn't need to be constant in size. In other words, perfect to be
 * converted to a configuration option later on.
 */
/* Maximum length for the date string. */
#define SVN_TIME__MAX_LENGTH 80
/* Machine parseable part, generated by apr_snprintf. */
#define HUMAN_TIMESTAMP_FORMAT "%.4d-%.2d-%.2d %.2d:%.2d:%.2d %+.2d%.2d"
/* Human explanatory part, generated by apr_strftime as "Sat, 01 Jan 2000" */
#define HUMAN_TIMESTAMP_FORMAT_SUFFIX _(" (%a, %d %b %Y)")

const char *
svn_time_to_cstring(apr_time_t when, apr_pool_t *pool)
{
  apr_time_exp_t exploded_time;

  /* We toss apr_status_t return value here -- for one thing, caller
     should pass in good information.  But also, where APR's own code
     calls these functions it tosses the return values, and
     furthermore their current implementations can only return success
     anyway. */

  /* We get the date in GMT now -- and expect the tm_gmtoff and
     tm_isdst to be not set. We also ignore the weekday and yearday,
     since those are not needed. */

  apr_time_exp_gmt(&exploded_time, when);

  /* It would be nice to use apr_strftime(), but APR doesn't give a
     way to convert back, so we wouldn't be able to share the format
     string between the writer and reader. */
  return apr_psprintf(pool,
                      TIMESTAMP_FORMAT,
                      exploded_time.tm_year + 1900,
                      exploded_time.tm_mon + 1,
                      exploded_time.tm_mday,
                      exploded_time.tm_hour,
                      exploded_time.tm_min,
                      exploded_time.tm_sec,
                      exploded_time.tm_usec);
}


static apr_int32_t
find_matching_string(char *str, apr_size_t size, const char strings[][4])
{
  apr_size_t i;

  for (i = 0; i < size; i++)
    if (strings[i] && (strcmp(str, strings[i]) == 0))
      return (apr_int32_t) i;

  return -1;
}


svn_error_t *
svn_time_from_cstring(apr_time_t *when, const char *data, apr_pool_t *pool)
{
  apr_time_exp_t exploded_time;
  apr_status_t apr_err;
  char wday[4], month[4];
  const char *c;

  /* Open-code parsing of the new timestamp format, as this
     is a hot path for reading the entries file.  This format looks
     like:  "2001-08-31T04:24:14.966996Z"  */
  exploded_time.tm_year = (apr_int32_t) svn__strtoul(data, &c);
  if (*c++ != '-') goto fail;
  exploded_time.tm_mon = (apr_int32_t) svn__strtoul(c, &c);
  if (*c++ != '-') goto fail;
  exploded_time.tm_mday = (apr_int32_t) svn__strtoul(c, &c);
  if (*c++ != 'T') goto fail;
  exploded_time.tm_hour = (apr_int32_t) svn__strtoul(c, &c);
  if (*c++ != ':') goto fail;
  exploded_time.tm_min = (apr_int32_t) svn__strtoul(c, &c);
  if (*c++ != ':') goto fail;
  exploded_time.tm_sec = (apr_int32_t) svn__strtoul(c, &c);
  if (*c++ != '.') goto fail;
  exploded_time.tm_usec = (apr_int32_t) svn__strtoul(c, &c);
  if (*c++ != 'Z') goto fail;

  exploded_time.tm_year  -= 1900;
  exploded_time.tm_mon   -= 1;
  exploded_time.tm_wday   = 0;
  exploded_time.tm_yday   = 0;
  exploded_time.tm_isdst  = 0;
  exploded_time.tm_gmtoff = 0;

  apr_err = apr_time_exp_gmt_get(when, &exploded_time);
  if (apr_err == APR_SUCCESS)
    return SVN_NO_ERROR;

  return svn_error_create(SVN_ERR_BAD_DATE, NULL, NULL);

 fail:
  /* Try the compatibility option.  This does not need to be fast,
     as this format is no longer generated and the client will convert
     an old-format entries file the first time it reads it.  */
  if (sscanf(data,
             OLD_TIMESTAMP_FORMAT,
             wday,
             &exploded_time.tm_mday,
             month,
             &exploded_time.tm_year,
             &exploded_time.tm_hour,
             &exploded_time.tm_min,
             &exploded_time.tm_sec,
             &exploded_time.tm_usec,
             &exploded_time.tm_yday,
             &exploded_time.tm_isdst,
             &exploded_time.tm_gmtoff) == 11)
    {
      exploded_time.tm_year -= 1900;
      exploded_time.tm_yday -= 1;
      /* Using hard coded limits for the arrays - they are going away
         soon in any case. */
      exploded_time.tm_wday = find_matching_string(wday, 7, apr_day_snames);
      exploded_time.tm_mon = find_matching_string(month, 12, apr_month_snames);

      apr_err = apr_time_exp_gmt_get(when, &exploded_time);
      if (apr_err != APR_SUCCESS)
        return svn_error_create(SVN_ERR_BAD_DATE, NULL, NULL);

      return SVN_NO_ERROR;
    }
  /* Timestamp is something we do not recognize. */
  else
    return svn_error_create(SVN_ERR_BAD_DATE, NULL, NULL);
}


const char *
svn_time_to_human_cstring(apr_time_t when, apr_pool_t *pool)
{
  apr_time_exp_t exploded_time;
  apr_size_t len, retlen;
  apr_status_t ret;
  char *datestr, *curptr, human_datestr[SVN_TIME__MAX_LENGTH];

  /* Get the time into parts */
  ret = apr_time_exp_lt(&exploded_time, when);
  if (ret)
    return NULL;

  /* Make room for datestring */
  datestr = apr_palloc(pool, SVN_TIME__MAX_LENGTH);

  /* Put in machine parseable part */
  len = apr_snprintf(datestr,
                     SVN_TIME__MAX_LENGTH,
                     HUMAN_TIMESTAMP_FORMAT,
                     exploded_time.tm_year + 1900,
                     exploded_time.tm_mon + 1,
                     exploded_time.tm_mday,
                     exploded_time.tm_hour,
                     exploded_time.tm_min,
                     exploded_time.tm_sec,
                     exploded_time.tm_gmtoff / (60 * 60),
                     (abs(exploded_time.tm_gmtoff) / 60) % 60);

  /* If we overfilled the buffer, just return what we got. */
  if (len >= SVN_TIME__MAX_LENGTH)
    return datestr;

  /* Calculate offset to the end of the machine parseable part. */
  curptr = datestr + len;

  /* Put in human explanatory part */
  ret = apr_strftime(human_datestr,
                     &retlen,
                     SVN_TIME__MAX_LENGTH - len,
                     HUMAN_TIMESTAMP_FORMAT_SUFFIX,
                     &exploded_time);

  /* If there was an error, ensure that the string is zero-terminated. */
  if (ret || retlen == 0)
    *curptr = '\0';
  else
    {
      const char *utf8_string;
      svn_error_t *err;

      err = svn_utf_cstring_to_utf8(&utf8_string, human_datestr, pool);
      if (err)
        {
          *curptr = '\0';
          svn_error_clear(err);
        }
      else
        apr_cpystrn(curptr, utf8_string, SVN_TIME__MAX_LENGTH - len);
    }

  return datestr;
}


void
svn_sleep_for_timestamps(void)
{
  svn_io_sleep_for_timestamps(NULL, NULL);
}