1251881Speter/*
2251881Speter * time.c:  time/date utilities
3251881Speter *
4251881Speter * ====================================================================
5251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
6251881Speter *    or more contributor license agreements.  See the NOTICE file
7251881Speter *    distributed with this work for additional information
8251881Speter *    regarding copyright ownership.  The ASF licenses this file
9251881Speter *    to you under the Apache License, Version 2.0 (the
10251881Speter *    "License"); you may not use this file except in compliance
11251881Speter *    with the License.  You may obtain a copy of the License at
12251881Speter *
13251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
14251881Speter *
15251881Speter *    Unless required by applicable law or agreed to in writing,
16251881Speter *    software distributed under the License is distributed on an
17251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18251881Speter *    KIND, either express or implied.  See the License for the
19251881Speter *    specific language governing permissions and limitations
20251881Speter *    under the License.
21251881Speter * ====================================================================
22251881Speter */
23251881Speter
24251881Speter
25251881Speter
26251881Speter#include <string.h>
27251881Speter#include <stdlib.h>
28251881Speter#include <apr_pools.h>
29251881Speter#include <apr_time.h>
30251881Speter#include <apr_strings.h>
31251881Speter#include "svn_io.h"
32251881Speter#include "svn_time.h"
33251881Speter#include "svn_utf.h"
34251881Speter#include "svn_error.h"
35251881Speter#include "svn_private_config.h"
36251881Speter
37299742Sdim#include "private/svn_string_private.h"
38251881Speter
39299742Sdim
40251881Speter
41251881Speter/*** Code. ***/
42251881Speter
43251881Speter/* Our timestamp strings look like this:
44251881Speter *
45251881Speter *    "2002-05-07Thh:mm:ss.uuuuuuZ"
46251881Speter *
47251881Speter * The format is conformant with ISO-8601 and the date format required
48251881Speter * by RFC2518 for creationdate. It is a direct conversion between
49251881Speter * apr_time_t and a string, so converting to string and back retains
50251881Speter * the exact value.
51251881Speter */
52251881Speter#define TIMESTAMP_FORMAT "%04d-%02d-%02dT%02d:%02d:%02d.%06dZ"
53251881Speter
54251881Speter/* Our old timestamp strings looked like this:
55251881Speter *
56251881Speter *    "Tue 3 Oct 2000 HH:MM:SS.UUU (day 277, dst 1, gmt_off -18000)"
57251881Speter *
58251881Speter * The idea is that they are conventionally human-readable for the
59251881Speter * first part, and then in parentheses comes everything else required
60251881Speter * to completely fill in an apr_time_exp_t: tm_yday, tm_isdst,
61251881Speter * and tm_gmtoff.
62251881Speter *
63251881Speter * This format is still recognized on input, for backward
64251881Speter * compatibility, but no longer generated.
65251881Speter */
66251881Speter#define OLD_TIMESTAMP_FORMAT \
67251881Speter        "%3s %d %3s %d %02d:%02d:%02d.%06d (day %03d, dst %d, gmt_off %06d)"
68251881Speter
69251881Speter/* Our human representation of dates looks like this:
70251881Speter *
71251881Speter *    "2002-06-23 11:13:02 +0300 (Sun, 23 Jun 2002)"
72251881Speter *
73251881Speter * This format is used whenever time is shown to the user. It consists
74251881Speter * of a machine parseable, almost ISO-8601, part in the beginning -
75251881Speter * and a human explanatory part at the end. The machine parseable part
76251881Speter * is generated strictly by APR and our code, with a apr_snprintf. The
77251881Speter * human explanatory part is generated by apr_strftime, which means
78251881Speter * that its generation can be affected by locale, it can fail and it
79251881Speter * doesn't need to be constant in size. In other words, perfect to be
80251881Speter * converted to a configuration option later on.
81251881Speter */
82251881Speter/* Maximum length for the date string. */
83251881Speter#define SVN_TIME__MAX_LENGTH 80
84251881Speter/* Machine parseable part, generated by apr_snprintf. */
85251881Speter#define HUMAN_TIMESTAMP_FORMAT "%.4d-%.2d-%.2d %.2d:%.2d:%.2d %+.2d%.2d"
86251881Speter/* Human explanatory part, generated by apr_strftime as "Sat, 01 Jan 2000" */
87299742Sdim#define HUMAN_TIMESTAMP_FORMAT_SUFFIX _(" (%a, %d %b %Y)")
88251881Speter
89251881Speterconst char *
90251881Spetersvn_time_to_cstring(apr_time_t when, apr_pool_t *pool)
91251881Speter{
92251881Speter  apr_time_exp_t exploded_time;
93251881Speter
94251881Speter  /* We toss apr_status_t return value here -- for one thing, caller
95251881Speter     should pass in good information.  But also, where APR's own code
96251881Speter     calls these functions it tosses the return values, and
97251881Speter     furthermore their current implementations can only return success
98251881Speter     anyway. */
99251881Speter
100251881Speter  /* We get the date in GMT now -- and expect the tm_gmtoff and
101251881Speter     tm_isdst to be not set. We also ignore the weekday and yearday,
102251881Speter     since those are not needed. */
103251881Speter
104251881Speter  apr_time_exp_gmt(&exploded_time, when);
105251881Speter
106251881Speter  /* It would be nice to use apr_strftime(), but APR doesn't give a
107251881Speter     way to convert back, so we wouldn't be able to share the format
108251881Speter     string between the writer and reader. */
109251881Speter  return apr_psprintf(pool,
110251881Speter                      TIMESTAMP_FORMAT,
111251881Speter                      exploded_time.tm_year + 1900,
112251881Speter                      exploded_time.tm_mon + 1,
113251881Speter                      exploded_time.tm_mday,
114251881Speter                      exploded_time.tm_hour,
115251881Speter                      exploded_time.tm_min,
116251881Speter                      exploded_time.tm_sec,
117251881Speter                      exploded_time.tm_usec);
118251881Speter}
119251881Speter
120251881Speter
121251881Speterstatic apr_int32_t
122251881Speterfind_matching_string(char *str, apr_size_t size, const char strings[][4])
123251881Speter{
124251881Speter  apr_size_t i;
125251881Speter
126251881Speter  for (i = 0; i < size; i++)
127251881Speter    if (strings[i] && (strcmp(str, strings[i]) == 0))
128251881Speter      return (apr_int32_t) i;
129251881Speter
130251881Speter  return -1;
131251881Speter}
132251881Speter
133251881Speter
134251881Spetersvn_error_t *
135251881Spetersvn_time_from_cstring(apr_time_t *when, const char *data, apr_pool_t *pool)
136251881Speter{
137251881Speter  apr_time_exp_t exploded_time;
138251881Speter  apr_status_t apr_err;
139251881Speter  char wday[4], month[4];
140299742Sdim  const char *c;
141251881Speter
142251881Speter  /* Open-code parsing of the new timestamp format, as this
143251881Speter     is a hot path for reading the entries file.  This format looks
144251881Speter     like:  "2001-08-31T04:24:14.966996Z"  */
145299742Sdim  exploded_time.tm_year = (apr_int32_t) svn__strtoul(data, &c);
146251881Speter  if (*c++ != '-') goto fail;
147299742Sdim  exploded_time.tm_mon = (apr_int32_t) svn__strtoul(c, &c);
148251881Speter  if (*c++ != '-') goto fail;
149299742Sdim  exploded_time.tm_mday = (apr_int32_t) svn__strtoul(c, &c);
150251881Speter  if (*c++ != 'T') goto fail;
151299742Sdim  exploded_time.tm_hour = (apr_int32_t) svn__strtoul(c, &c);
152251881Speter  if (*c++ != ':') goto fail;
153299742Sdim  exploded_time.tm_min = (apr_int32_t) svn__strtoul(c, &c);
154251881Speter  if (*c++ != ':') goto fail;
155299742Sdim  exploded_time.tm_sec = (apr_int32_t) svn__strtoul(c, &c);
156251881Speter  if (*c++ != '.') goto fail;
157299742Sdim  exploded_time.tm_usec = (apr_int32_t) svn__strtoul(c, &c);
158251881Speter  if (*c++ != 'Z') goto fail;
159251881Speter
160251881Speter  exploded_time.tm_year  -= 1900;
161251881Speter  exploded_time.tm_mon   -= 1;
162251881Speter  exploded_time.tm_wday   = 0;
163251881Speter  exploded_time.tm_yday   = 0;
164251881Speter  exploded_time.tm_isdst  = 0;
165251881Speter  exploded_time.tm_gmtoff = 0;
166251881Speter
167251881Speter  apr_err = apr_time_exp_gmt_get(when, &exploded_time);
168251881Speter  if (apr_err == APR_SUCCESS)
169251881Speter    return SVN_NO_ERROR;
170251881Speter
171251881Speter  return svn_error_create(SVN_ERR_BAD_DATE, NULL, NULL);
172251881Speter
173251881Speter fail:
174251881Speter  /* Try the compatibility option.  This does not need to be fast,
175251881Speter     as this format is no longer generated and the client will convert
176251881Speter     an old-format entries file the first time it reads it.  */
177251881Speter  if (sscanf(data,
178251881Speter             OLD_TIMESTAMP_FORMAT,
179251881Speter             wday,
180251881Speter             &exploded_time.tm_mday,
181251881Speter             month,
182251881Speter             &exploded_time.tm_year,
183251881Speter             &exploded_time.tm_hour,
184251881Speter             &exploded_time.tm_min,
185251881Speter             &exploded_time.tm_sec,
186251881Speter             &exploded_time.tm_usec,
187251881Speter             &exploded_time.tm_yday,
188251881Speter             &exploded_time.tm_isdst,
189251881Speter             &exploded_time.tm_gmtoff) == 11)
190251881Speter    {
191251881Speter      exploded_time.tm_year -= 1900;
192251881Speter      exploded_time.tm_yday -= 1;
193251881Speter      /* Using hard coded limits for the arrays - they are going away
194251881Speter         soon in any case. */
195251881Speter      exploded_time.tm_wday = find_matching_string(wday, 7, apr_day_snames);
196251881Speter      exploded_time.tm_mon = find_matching_string(month, 12, apr_month_snames);
197251881Speter
198251881Speter      apr_err = apr_time_exp_gmt_get(when, &exploded_time);
199251881Speter      if (apr_err != APR_SUCCESS)
200251881Speter        return svn_error_create(SVN_ERR_BAD_DATE, NULL, NULL);
201251881Speter
202251881Speter      return SVN_NO_ERROR;
203251881Speter    }
204251881Speter  /* Timestamp is something we do not recognize. */
205251881Speter  else
206251881Speter    return svn_error_create(SVN_ERR_BAD_DATE, NULL, NULL);
207251881Speter}
208251881Speter
209251881Speter
210251881Speterconst char *
211251881Spetersvn_time_to_human_cstring(apr_time_t when, apr_pool_t *pool)
212251881Speter{
213251881Speter  apr_time_exp_t exploded_time;
214251881Speter  apr_size_t len, retlen;
215251881Speter  apr_status_t ret;
216251881Speter  char *datestr, *curptr, human_datestr[SVN_TIME__MAX_LENGTH];
217251881Speter
218251881Speter  /* Get the time into parts */
219251881Speter  ret = apr_time_exp_lt(&exploded_time, when);
220251881Speter  if (ret)
221251881Speter    return NULL;
222251881Speter
223251881Speter  /* Make room for datestring */
224251881Speter  datestr = apr_palloc(pool, SVN_TIME__MAX_LENGTH);
225251881Speter
226251881Speter  /* Put in machine parseable part */
227251881Speter  len = apr_snprintf(datestr,
228251881Speter                     SVN_TIME__MAX_LENGTH,
229251881Speter                     HUMAN_TIMESTAMP_FORMAT,
230251881Speter                     exploded_time.tm_year + 1900,
231251881Speter                     exploded_time.tm_mon + 1,
232251881Speter                     exploded_time.tm_mday,
233251881Speter                     exploded_time.tm_hour,
234251881Speter                     exploded_time.tm_min,
235251881Speter                     exploded_time.tm_sec,
236251881Speter                     exploded_time.tm_gmtoff / (60 * 60),
237251881Speter                     (abs(exploded_time.tm_gmtoff) / 60) % 60);
238251881Speter
239251881Speter  /* If we overfilled the buffer, just return what we got. */
240251881Speter  if (len >= SVN_TIME__MAX_LENGTH)
241251881Speter    return datestr;
242251881Speter
243251881Speter  /* Calculate offset to the end of the machine parseable part. */
244251881Speter  curptr = datestr + len;
245251881Speter
246251881Speter  /* Put in human explanatory part */
247251881Speter  ret = apr_strftime(human_datestr,
248251881Speter                     &retlen,
249251881Speter                     SVN_TIME__MAX_LENGTH - len,
250299742Sdim                     HUMAN_TIMESTAMP_FORMAT_SUFFIX,
251251881Speter                     &exploded_time);
252251881Speter
253251881Speter  /* If there was an error, ensure that the string is zero-terminated. */
254251881Speter  if (ret || retlen == 0)
255251881Speter    *curptr = '\0';
256251881Speter  else
257251881Speter    {
258251881Speter      const char *utf8_string;
259251881Speter      svn_error_t *err;
260251881Speter
261251881Speter      err = svn_utf_cstring_to_utf8(&utf8_string, human_datestr, pool);
262251881Speter      if (err)
263251881Speter        {
264251881Speter          *curptr = '\0';
265251881Speter          svn_error_clear(err);
266251881Speter        }
267251881Speter      else
268251881Speter        apr_cpystrn(curptr, utf8_string, SVN_TIME__MAX_LENGTH - len);
269251881Speter    }
270251881Speter
271251881Speter  return datestr;
272251881Speter}
273251881Speter
274251881Speter
275251881Spetervoid
276251881Spetersvn_sleep_for_timestamps(void)
277251881Speter{
278251881Speter  svn_io_sleep_for_timestamps(NULL, NULL);
279251881Speter}
280