1 //------------------------------------------------------------------------------
2 // <copyright file="_HTTPDateParse.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 //------------------------------------------------------------------------------
7 using System.Globalization;
10 internal static class HttpDateParse {
11 private const int BASE_DEC = 10; // base 10
14 // Date indicies used to figure out what each entry is.
18 private const int DATE_INDEX_DAY_OF_WEEK = 0;
20 private const int DATE_1123_INDEX_DAY = 1;
21 private const int DATE_1123_INDEX_MONTH = 2;
22 private const int DATE_1123_INDEX_YEAR = 3;
23 private const int DATE_1123_INDEX_HRS = 4;
24 private const int DATE_1123_INDEX_MINS = 5;
25 private const int DATE_1123_INDEX_SECS = 6;
27 private const int DATE_ANSI_INDEX_MONTH = 1;
28 private const int DATE_ANSI_INDEX_DAY = 2;
29 private const int DATE_ANSI_INDEX_HRS = 3;
30 private const int DATE_ANSI_INDEX_MINS = 4;
31 private const int DATE_ANSI_INDEX_SECS = 5;
32 private const int DATE_ANSI_INDEX_YEAR = 6;
34 private const int DATE_INDEX_TZ = 7;
36 private const int DATE_INDEX_LAST = DATE_INDEX_TZ;
37 private const int MAX_FIELD_DATE_ENTRIES = (DATE_INDEX_LAST+1);
40 // DATE_TOKEN's DWORD values used to determine what day/month we're on
43 private const int DATE_TOKEN_JANUARY = 1;
44 private const int DATE_TOKEN_FEBRUARY = 2;
45 private const int DATE_TOKEN_Microsoft = 3;
46 private const int DATE_TOKEN_APRIL = 4;
47 private const int DATE_TOKEN_MAY = 5;
48 private const int DATE_TOKEN_JUNE = 6;
49 private const int DATE_TOKEN_JULY = 7;
50 private const int DATE_TOKEN_AUGUST = 8;
51 private const int DATE_TOKEN_SEPTEMBER = 9;
52 private const int DATE_TOKEN_OCTOBER = 10;
53 private const int DATE_TOKEN_NOVEMBER = 11;
54 private const int DATE_TOKEN_DECEMBER = 12;
56 private const int DATE_TOKEN_LAST_MONTH = (DATE_TOKEN_DECEMBER+1);
58 private const int DATE_TOKEN_SUNDAY = 0;
59 private const int DATE_TOKEN_MONDAY = 1;
60 private const int DATE_TOKEN_TUESDAY = 2;
61 private const int DATE_TOKEN_WEDNESDAY = 3;
62 private const int DATE_TOKEN_THURSDAY = 4;
63 private const int DATE_TOKEN_FRIDAY = 5;
64 private const int DATE_TOKEN_SATURDAY = 6;
66 private const int DATE_TOKEN_LAST_DAY = (DATE_TOKEN_SATURDAY+1);
68 private const int DATE_TOKEN_GMT = -1000;
70 private const int DATE_TOKEN_LAST = DATE_TOKEN_GMT;
72 private const int DATE_TOKEN_ERROR = (DATE_TOKEN_LAST+1);
76 // MAKE_UPPER - takes an assumed lower character and bit manipulates into a upper.
77 // (make sure the character is Lower case alpha char to begin,
78 // otherwise it corrupts)
85 return(Char.ToUpper(c, CultureInfo.InvariantCulture));
92 Looks at the first three bytes of string to determine if we're looking
93 at a Day of the Week, or Month, or "GMT" string. Is inlined so that
94 the compiler can optimize this code into the caller FInternalParseHttpDate.
98 lpszDay - a string ptr to the first byte of the string in question.
103 Success - The Correct date token, 0-6 for day of the week, 1-14 for month, etc
105 Failure - DATE_TOKEN_ERROR
116 switch (MAKE_UPPER(lpszDay[index])) { // make uppercase
118 switch (MAKE_UPPER(lpszDay[index+1])) {
120 return DATE_TOKEN_APRIL;
122 return DATE_TOKEN_AUGUST;
125 return DATE_TOKEN_ERROR;
128 return DATE_TOKEN_DECEMBER;
131 switch (MAKE_UPPER(lpszDay[index+1])) {
133 return DATE_TOKEN_FRIDAY;
135 return DATE_TOKEN_FEBRUARY;
138 return DATE_TOKEN_ERROR;
141 return DATE_TOKEN_GMT;
145 switch (MAKE_UPPER(lpszDay[index+1])) {
147 return DATE_TOKEN_MONDAY;
149 switch (MAKE_UPPER(lpszDay[index+2])) {
151 return DATE_TOKEN_Microsoft;
153 return DATE_TOKEN_MAY;
156 // fall through to error
160 return DATE_TOKEN_ERROR;
163 return DATE_TOKEN_NOVEMBER;
167 switch (MAKE_UPPER(lpszDay[index+1])) {
169 return DATE_TOKEN_JANUARY;
172 switch (MAKE_UPPER(lpszDay[index+2])) {
174 return DATE_TOKEN_JUNE;
176 return DATE_TOKEN_JULY;
179 // fall through to error
183 return DATE_TOKEN_ERROR;
186 return DATE_TOKEN_OCTOBER;
190 switch (MAKE_UPPER(lpszDay[index+1])) {
192 return DATE_TOKEN_SATURDAY;
194 return DATE_TOKEN_SUNDAY;
196 return DATE_TOKEN_SEPTEMBER;
199 return DATE_TOKEN_ERROR;
203 switch (MAKE_UPPER(lpszDay[index+1])) {
205 return DATE_TOKEN_TUESDAY;
207 return DATE_TOKEN_THURSDAY;
210 return DATE_TOKEN_ERROR;
213 return DATE_TOKEN_GMT;
216 return DATE_TOKEN_WEDNESDAY;
220 return DATE_TOKEN_ERROR;
227 Parses through a ANSI, RFC850, or RFC1123 date format and covents it into
228 a FILETIME/SYSTEMTIME time format.
230 Important this a time-critical function and should only be changed
231 with the intention of optimizing or a critical need work item.
235 lpft - Ptr to FILETIME structure. Used to store converted result.
236 Must be NULL if not intended to be used !!!
238 lpSysTime - Ptr to SYSTEMTIME struture. Used to return Systime if needed.
240 lpcszDateStr - Const Date string to parse.
258 int i = 0, iLastLettered = -1;
259 bool fIsANSIDateFormat = false;
260 int [] rgdwDateParseResults = new int[MAX_FIELD_DATE_ENTRIES];
262 char [] lpInputBuffer = DateString.ToCharArray();
264 dtOut = new DateTime();
267 // Date Parsing v2 (1 more to go), and here is how it works...
268 // We take a date string and churn through it once, converting
269 // integers to integers, Month,Day, and GMT strings into integers,
270 // and all is then placed IN order in a temp array.
272 // At the completetion of the parse stage, we simple look at
273 // the data, and then map the results into the correct
274 // places in the SYSTIME structure. Simple, No allocations, and
275 // No dirting the data.
277 // The end of the function does something munging and pretting
278 // up of the results to handle the year 2000, and TZ offsets
279 // Note: do we need to fully handle TZs anymore?
282 while (index < DateString.Length && i < MAX_FIELD_DATE_ENTRIES) {
283 if (lpInputBuffer[index] >= '0' && lpInputBuffer[index] <= '9') {
285 // we have a numerical entry, scan through it and convent to DWORD
288 rgdwDateParseResults[i] = 0;
291 rgdwDateParseResults[i] *= BASE_DEC;
292 rgdwDateParseResults[i] += (lpInputBuffer[index] - '0');
294 } while (index < DateString.Length &&
295 lpInputBuffer[index] >= '0' &&
296 lpInputBuffer[index] <= '9');
300 else if ((lpInputBuffer[index] >= 'A' && lpInputBuffer[index] <= 'Z') ||
301 (lpInputBuffer[index] >= 'a' && lpInputBuffer[index] <= 'z')) {
303 // we have a string, should be a day, month, or GMT
304 // lets skim to the end of the string
307 rgdwDateParseResults[i] =
308 MapDayMonthToDword(lpInputBuffer, index);
312 // We want to ignore the possibility of a time zone such as PST or EST in a non-standard
313 // date format such as "Thu Dec 17 16:01:28 PST 1998" (Notice that the year is _after_ the time zone
314 if ((rgdwDateParseResults[i] == DATE_TOKEN_ERROR)
316 !(fIsANSIDateFormat && (i==DATE_ANSI_INDEX_YEAR))) {
322 // At this point if we have a vaild string
323 // at this index, we know for sure that we're
324 // looking at a ANSI type DATE format.
327 if (i == DATE_ANSI_INDEX_MONTH) {
328 fIsANSIDateFormat = true;
332 // Read past the end of the current set of alpha characters,
333 // as MapDayMonthToDword only peeks at a few characters
338 } while (index < DateString.Length &&
339 ( (lpInputBuffer[index] >= 'A' && lpInputBuffer[index] <= 'Z') ||
340 (lpInputBuffer[index] >= 'a' && lpInputBuffer[index] <= 'z') ));
346 // For the generic case its either a space, comma, semi-colon, etc.
347 // the point is we really don't care, nor do we need to waste time
348 // worring about it (the orginal code did). The point is we
349 // care about the actual date information, So we just advance to the
358 // We're finished parsing the string, now take the parsed tokens
359 // and turn them to the actual structured information we care about.
360 // So we build lpSysTime from the Array, using a local if none is passed in.
373 if (fIsANSIDateFormat) {
374 day = rgdwDateParseResults[DATE_ANSI_INDEX_DAY];
375 month = rgdwDateParseResults[DATE_ANSI_INDEX_MONTH];
376 hour = rgdwDateParseResults[DATE_ANSI_INDEX_HRS];
377 minute = rgdwDateParseResults[DATE_ANSI_INDEX_MINS];
378 second = rgdwDateParseResults[DATE_ANSI_INDEX_SECS];
379 if (iLastLettered != DATE_ANSI_INDEX_YEAR) {
380 year = rgdwDateParseResults[DATE_ANSI_INDEX_YEAR];
383 // This is a fix to get around toString/toGMTstring (where the timezone is
384 // appended at the end. (See above)
385 year = rgdwDateParseResults[DATE_INDEX_TZ];
389 day = rgdwDateParseResults[DATE_1123_INDEX_DAY];
390 month = rgdwDateParseResults[DATE_1123_INDEX_MONTH];
391 year = rgdwDateParseResults[DATE_1123_INDEX_YEAR];
392 hour = rgdwDateParseResults[DATE_1123_INDEX_HRS];
393 minute = rgdwDateParseResults[DATE_1123_INDEX_MINS];
394 second = rgdwDateParseResults[DATE_1123_INDEX_SECS];
398 // Normalize the year, 90 == 1990, handle the year 2000, 02 == 2002
399 // This is Year 2000 handling folks!!! We get this wrong and
404 year += ((year < 80) ? 2000 : 1900);
408 // if we got misformed time, then plug in the current time
409 // !lpszHrs || !lpszMins || !lpszSec
422 // Now do the DateTime conversion
425 dtOut = new DateTime (year, month, day, hour, minute, second, millisecond);
428 // we want the system time to be accurate. This is _suhlow_
429 // The time passed in is in the local time zone; we have to convert this into GMT.
432 if (iLastLettered==DATE_ANSI_INDEX_YEAR) {
433 // this should be an unusual case.
434 dtOut = dtOut.ToUniversalTime();
438 // If we have an Offset to another Time Zone
439 // then convert to appropriate GMT time
442 if ((i > DATE_INDEX_TZ &&
443 rgdwDateParseResults[DATE_INDEX_TZ] != DATE_TOKEN_GMT)) {
446 // if we received +/-nnnn as offset (hhmm), modify the output FILETIME
451 offset = (double) rgdwDateParseResults[DATE_INDEX_TZ];
452 dtOut.AddHours(offset);
455 // In the end, we leave it all in LocalTime
457 dtOut = dtOut.ToLocalTime();
465 } // namespace System.Net