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