2 * time.c: time/date utilities
4 * ====================================================================
5 * Copyright (c) 2000-2007 CollabNet. All rights reserved.
7 * This software is licensed as described in the file COPYING, which
8 * you should have received as part of this distribution. The terms
9 * are also available at http://subversion.tigris.org/license-1.html.
10 * If newer versions of this license are posted there, you may use a
11 * newer version instead, at your option.
13 * This software consists of voluntary contributions made by many
14 * individuals. For exact contribution history, see the revision
15 * history and logs, available at http://subversion.tigris.org/.
16 * ====================================================================
23 #include <apr_pools.h>
25 #include <apr_strings.h>
28 #include "svn_error.h"
29 #include "svn_private_config.h"
35 /* Our timestamp strings look like this:
37 * "2002-05-07Thh:mm:ss.uuuuuuZ"
39 * The format is conformant with ISO-8601 and the date format required
40 * by RFC2518 for creationdate. It is a direct conversion between
41 * apr_time_t and a string, so converting to string and back retains
44 static const char * const timestamp_format
=
45 "%04d-%02d-%02dT%02d:%02d:%02d.%06dZ";
47 /* Our old timestamp strings looked like this:
49 * "Tue 3 Oct 2000 HH:MM:SS.UUU (day 277, dst 1, gmt_off -18000)"
51 * The idea is that they are conventionally human-readable for the
52 * first part, and then in parentheses comes everything else required
53 * to completely fill in an apr_time_exp_t: tm_yday, tm_isdst,
56 * This format is still recognized on input, for backward
57 * compatibility, but no longer generated.
59 static const char * const old_timestamp_format
=
60 "%3s %d %3s %d %02d:%02d:%02d.%06d (day %03d, dst %d, gmt_off %06d)";
62 /* Our human representation of dates looks like this:
64 * "2002-06-23 11:13:02 +0300 (Sun, 23 Jun 2002)"
66 * This format is used whenever time is shown to the user. It consists
67 * of a machine parseable, almost ISO-8601, part in the beginning -
68 * and a human explanatory part at the end. The machine parseable part
69 * is generated strictly by APR and our code, with a apr_snprintf. The
70 * human explanatory part is generated by apr_strftime, which means
71 * that its generation can be affected by locale, it can fail and it
72 * doesn't need to be constant in size. In other words, perfect to be
73 * converted to a configuration option later on.
75 /* Maximum length for the date string. */
76 #define SVN_TIME__MAX_LENGTH 80
77 /* Machine parseable part, generated by apr_snprintf. */
78 static const char * const human_timestamp_format
=
79 "%.4d-%.2d-%.2d %.2d:%.2d:%.2d %+.2d%.2d";
80 /* Human explanatory part, generated by apr_strftime as "Sat, 01 Jan 2000" */
81 #define human_timestamp_format_suffix _(" (%a, %d %b %Y)")
83 #define SVN_SLEEP_ENV_VAR "SVN_I_LOVE_CORRUPTED_WORKING_COPIES_SO_DISABLE_SLEEP_FOR_TIMESTAMPS"
86 svn_time_to_cstring(apr_time_t when
, apr_pool_t
*pool
)
89 apr_time_exp_t exploded_time
;
91 /* We toss apr_status_t return value here -- for one thing, caller
92 should pass in good information. But also, where APR's own code
93 calls these functions it tosses the return values, and
94 furthermore their current implementations can only return success
97 /* We get the date in GMT now -- and expect the tm_gmtoff and
98 tm_isdst to be not set. We also ignore the weekday and yearday,
99 since those are not needed. */
101 apr_time_exp_gmt(&exploded_time
, when
);
103 /* It would be nice to use apr_strftime(), but APR doesn't give a
104 way to convert back, so we wouldn't be able to share the format
105 string between the writer and reader. */
106 t_cstr
= apr_psprintf(pool
,
108 exploded_time
.tm_year
+ 1900,
109 exploded_time
.tm_mon
+ 1,
110 exploded_time
.tm_mday
,
111 exploded_time
.tm_hour
,
112 exploded_time
.tm_min
,
113 exploded_time
.tm_sec
,
114 exploded_time
.tm_usec
);
116 /* ### Remove this when the old style timestamp parsing is taken
118 t_cstr = apr_psprintf (pool,
119 old_timestamp_format,
120 apr_day_snames[exploded_time.tm_wday],
121 exploded_time.tm_mday,
122 apr_month_snames[exploded_time.tm_mon],
123 exploded_time.tm_year + 1900,
124 exploded_time.tm_hour,
125 exploded_time.tm_min,
126 exploded_time.tm_sec,
127 exploded_time.tm_usec,
128 exploded_time.tm_yday + 1,
129 exploded_time.tm_isdst,
130 exploded_time.tm_gmtoff);
138 find_matching_string(char *str
, apr_size_t size
, const char strings
[][4])
142 for (i
= 0; i
< size
; i
++)
143 if (strings
[i
] && (strcmp(str
, strings
[i
]) == 0))
151 svn_time_from_cstring(apr_time_t
*when
, const char *data
, apr_pool_t
*pool
)
153 apr_time_exp_t exploded_time
;
154 apr_status_t apr_err
;
155 char wday
[4], month
[4];
158 /* Open-code parsing of the new timestamp format, as this
159 is a hot path for reading the entries file. This format looks
160 like: "2001-08-31T04:24:14.966996Z" */
161 exploded_time
.tm_year
= strtol(data
, &c
, 10);
162 if (*c
++ != '-') goto fail
;
163 exploded_time
.tm_mon
= strtol(c
, &c
, 10);
164 if (*c
++ != '-') goto fail
;
165 exploded_time
.tm_mday
= strtol(c
, &c
, 10);
166 if (*c
++ != 'T') goto fail
;
167 exploded_time
.tm_hour
= strtol(c
, &c
, 10);
168 if (*c
++ != ':') goto fail
;
169 exploded_time
.tm_min
= strtol(c
, &c
, 10);
170 if (*c
++ != ':') goto fail
;
171 exploded_time
.tm_sec
= strtol(c
, &c
, 10);
172 if (*c
++ != '.') goto fail
;
173 exploded_time
.tm_usec
= strtol(c
, &c
, 10);
174 if (*c
++ != 'Z') goto fail
;
176 exploded_time
.tm_year
-= 1900;
177 exploded_time
.tm_mon
-= 1;
178 exploded_time
.tm_wday
= 0;
179 exploded_time
.tm_yday
= 0;
180 exploded_time
.tm_isdst
= 0;
181 exploded_time
.tm_gmtoff
= 0;
183 apr_err
= apr_time_exp_gmt_get(when
, &exploded_time
);
184 if (apr_err
== APR_SUCCESS
)
187 return svn_error_create(SVN_ERR_BAD_DATE
, NULL
, NULL
);
190 /* Try the compatibility option. This does not need to be fast,
191 as this format is no longer generated and the client will convert
192 an old-format entries file the first time it reads it. */
194 old_timestamp_format
,
196 &exploded_time
.tm_mday
,
198 &exploded_time
.tm_year
,
199 &exploded_time
.tm_hour
,
200 &exploded_time
.tm_min
,
201 &exploded_time
.tm_sec
,
202 &exploded_time
.tm_usec
,
203 &exploded_time
.tm_yday
,
204 &exploded_time
.tm_isdst
,
205 &exploded_time
.tm_gmtoff
) == 11)
207 exploded_time
.tm_year
-= 1900;
208 exploded_time
.tm_yday
-= 1;
209 /* Using hard coded limits for the arrays - they are going away
211 exploded_time
.tm_wday
= find_matching_string(wday
, 7, apr_day_snames
);
212 exploded_time
.tm_mon
= find_matching_string(month
, 12, apr_month_snames
);
214 apr_err
= apr_time_exp_gmt_get(when
, &exploded_time
);
215 if (apr_err
!= APR_SUCCESS
)
216 return svn_error_create(SVN_ERR_BAD_DATE
, NULL
, NULL
);
220 /* Timestamp is something we do not recognize. */
222 return svn_error_create(SVN_ERR_BAD_DATE
, NULL
, NULL
);
227 svn_time_to_human_cstring(apr_time_t when
, apr_pool_t
*pool
)
229 apr_time_exp_t exploded_time
;
230 apr_size_t len
, retlen
;
232 char *datestr
, *curptr
, human_datestr
[SVN_TIME__MAX_LENGTH
];
234 /* Get the time into parts */
235 apr_time_exp_lt(&exploded_time
, when
);
237 /* Make room for datestring */
238 datestr
= apr_palloc(pool
, SVN_TIME__MAX_LENGTH
);
240 /* Put in machine parseable part */
241 len
= apr_snprintf(datestr
,
242 SVN_TIME__MAX_LENGTH
,
243 human_timestamp_format
,
244 exploded_time
.tm_year
+ 1900,
245 exploded_time
.tm_mon
+ 1,
246 exploded_time
.tm_mday
,
247 exploded_time
.tm_hour
,
248 exploded_time
.tm_min
,
249 exploded_time
.tm_sec
,
250 exploded_time
.tm_gmtoff
/ (60 * 60),
251 (abs(exploded_time
.tm_gmtoff
) / 60) % 60);
253 /* If we overfilled the buffer, just return what we got. */
254 if (len
>= SVN_TIME__MAX_LENGTH
)
257 /* Calculate offset to the end of the machine parseable part. */
258 curptr
= datestr
+ len
;
260 /* Put in human explanatory part */
261 ret
= apr_strftime(human_datestr
,
263 SVN_TIME__MAX_LENGTH
- len
,
264 human_timestamp_format_suffix
,
267 /* If there was an error, ensure that the string is zero-terminated. */
268 if (ret
|| retlen
== 0)
272 const char *utf8_string
;
275 err
= svn_utf_cstring_to_utf8(&utf8_string
, human_datestr
, pool
);
279 svn_error_clear(err
);
282 apr_cpystrn(curptr
, utf8_string
, SVN_TIME__MAX_LENGTH
- len
);
290 svn_sleep_for_timestamps(void)
292 apr_time_t now
, then
;
295 sleep_env_var
= getenv(SVN_SLEEP_ENV_VAR
);
297 /* Sleep until the next second tick, plus a tenth of a second for margin. */
298 if (! sleep_env_var
|| apr_strnatcasecmp(sleep_env_var
, "yes") != 0)
300 now
= apr_time_now();
301 then
= apr_time_make(apr_time_sec(now
) + 1, APR_USEC_PER_SEC
/ 10);
302 apr_sleep(then
- now
);