Menu

[r3207]: / trunk / Src / URFC2822Date.pas  Maximize  Restore  History

Download this file

353 lines (325 with data), 11.6 kB

  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
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
{
* This Source Code Form is subject to the terms of the Mozilla Public License,
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
* obtain one at http://mozilla.org/MPL/2.0/
*
* Copyright (C) 2010-2012, Peter Johnson (www.delphidabbler.com).
*
* $Rev$
* $Date$
*
* Provides code for handling RFC2822 format dates. This version provides one
* function to convert a RFC2822 date into a TDateTime.
}
unit URFC2822Date;
interface
uses
// Project
UExceptions;
function RFC2822DateToGMTDateTime(DateStr: string): TDateTime;
{Converts a date in RFC2822 format into the corresponding TDateTime value in
GMT (UTC).
@param DateStr [in] RFC2822 date string to be converted.
@return Converted date in GMT (UTC).
@except ERFC2822Date raised if DateStr is malformed.
}
type
{
ERFC2822Date:
Type of exception raised when a RFC2822 date passed to
RFC2822DateToGMTDateTime is malformed.
}
ERFC2822Date = class(ECodeSnip);
implementation
uses
// Delphi
SysUtils, DateUtils,
// Project
UIStringList, UStructs, UStrUtils;
function StrToWordInRange(const S: string; const Range: TRange;
const ErrMsg: string): Word;
{Converts a string representation of a number into a word value, checking it
is in an expected range.
@param S [in] String value to convert.
@param Range [in] Range of valid values for the number.
@param ErrMasg [in] Error message to use for any exception.
@return Required number.
@except ERFC2822Date raised if S does not contain a valid number or number
is not in required range.
}
var
Value: Integer; // value of S as integer
begin
if not TryStrToInt(S, Value) or not Range.Contains(Value) then
raise ERFC2822Date.CreateFmt(ErrMsg, [S]);
Result := Value;
end;
procedure ValidateDOW(const ADOW: string);
{Validates text storing a day of the week as a valid day name. Returns if day
of week is valid or raises exception if not.
@param ADOW [in] Text to be checked.
@except ERFC2822Date raised if ADOW is not a valid day name.
}
resourcestring
sErrMsg = 'Invalid day of week: "%s"'; // error message
const
// Map of day numbers to day names
cDOWs: array[1..7] of string = (
'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'
);
var
DOW: string; // reference each day of week
begin
for DOW in cDOWs do
if DOW = ADOW then
Exit;
raise ERFC2822Date.CreateFmt(sErrMsg, [ADOW]);
end;
function GetDay(const ADayStr: string): Word;
{Converts day of month from string to Word value, checking value is valid for
a day of month.
@param ADayStr [in] String containing day number.
@return Required day number.
@except ERFC2822Date raised if ADayStr is not a valid number or is out of
range for a day of month.
}
resourcestring
sErrMsg = 'Invalid day of month: "%s"'; // error message
begin
Result := StrToWordInRange(ADayStr, TRange.Create(1, 31), sErrMsg);
end;
function GetMonth(const AMonthName: string): Word;
{Converts month named in required RFC2822 format into a month number.
@param AMonthName [in] String containing month name in RFC2822 format.
@return Required month number.
@except ERFC2822Date raised if AMonthName is not a valid RFC2822 month name.
}
resourcestring
sErrMsg = 'Invalid month: "%s"'; // error message
const
// Map of month number to month names
cMonths: array[1..12] of string = (
'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'
);
var
I: 1..12; // loops through all elements of months map
begin
for I := 1 to 12 do
begin
if cMonths[I] = AMonthName then
Exit(I);
end;
raise ERFC2822Date.CreateFmt(sErrMsg, [AMonthName]);
end;
function GetYear(const AYearStr: string): Word;
{Converts year from string to Word value, adjusting 2 and 3 digit years per
RFC2822 specifications.
@param AYearStr [in] String containing year.
@return Required and adjusted, year number.
@except ERFC2822Date raised if AYearStr is not a valid number or is out of
range.
}
resourcestring
sErrMsg = 'Invalid year: "%s"'; // error message
var
Value: Integer; // value of AYearStr as integer
begin
// Check for valid Word value
if not TryStrToInt(AYearStr, Value)
or (Value < 0)
or (Value > High(Word)) then
raise ERFC2822Date.CreateFmt(sErrMsg, [AYearStr]);
// Adjust for 2 and 3 digit years
if TRange.Create(0, 49).Contains(Value) then
Inc(Value, 2000)
else if TRange.Create(50, 999).Contains(Value) then
Inc(Value, 1900);
Result := Value;
end;
function GetHour(const AHourStr: string): Word;
{Converts hour from string to Word value, checking value is valid for an hour
in the 24 hour clock.
@param AHourStr [in] String containing hour.
@return Required hour number.
@except ERFC2822Date raised if AHourStr is not a valid number or is out of
range for an hour.
}
resourcestring
sErrMsg = 'Invalid hour: "%s"'; // error message
begin
Result := StrToWordInRange(AHourStr, TRange.Create(0, 23), sErrMsg);
end;
function GetMinute(const AMinStr: string): Word;
{Converts minute from string to Word value, checking value is valid for a
minute.
@param AMinStr [in] String containing minute.
@return Required minute number.
@except ERFC2822Date raised if AMinStr is not a valid number or is out of
range for a minute.
}
resourcestring
sErrMsg = 'Invalid minute: "%s"'; // error message
begin
Result := StrToWordInRange(AMinStr, TRange.Create(0, 59), sErrMsg);
end;
function GetSecond(const ASecStr: string): Word;
{Converts second from string to Word value, checking value is valid for a
second.
@param ASecStr [in] String containing second.
@return Required second number.
@except ERFC2822Date raised if ASecStr is not a valid number or is out of
range for a second.
}
resourcestring
sErrMsg = 'Invalid second: "%s"'; // error message
begin
Result := StrToWordInRange(ASecStr, TRange.Create(0, 59), sErrMsg)
end;
function GetTime(const TimeStr: string): TTime;
{Converts a time string in format HH:MM:SS or HH:MM into a TTime value.
@param TimeStr [in] Time string to converted.
@return Converted value.
@except ERFC2822Date raised if the time string is invalid.
}
resourcestring
sErrMsg = 'Invalid time: "%s"'; // error message
var
Parts: IStringList; // constituent parts of time: hr, min sec
Hour, Min, Sec: Word; // numeric values of hr, min, sec
const
// Indices of time components in Parts
HourPart = 0; // hour part (0..23)
MinPart = 1; // minute part (0..59)
SecPart = 2; // second part (0..59)
begin
// Split time string to component parts: must be 2 or 3 parts (secs optional)
Parts := TIStringList.Create(TimeStr, ':', True, False);
if (Parts.Count < 2) or (Parts.Count > 3) then
raise ERFC2822Date.CreateFmt(sErrMsg, [TimeStr]);
// Get hour, minute and seconds as numbers
Hour := GetHour(Parts[HourPart]);
Min := GetMinute(Parts[MinPart]);
if Parts.IsValidIndex(SecPart) then
Sec := GetSecond(Parts[SecPart])
else
Sec := 0; // if seconds no specified use 0
Result := EncodeTime(Hour, Min, Sec, 0);
end;
procedure GetOffset(const AOffsetStr: string; out AHours, AMins: Integer);
{Gets offset in minutes and seconds from GMT from an RFC2822 format offset
string. Supports +9999, -9999 and text offsets. Unrecognised text offsets are
parsed as -0000 per the RFC.
@param AOffsetStr [in] The offset string to parse.
@param AHours [out] Hours component of offset as signed number.
@param AMins [out] Mins component of offset as signed number.
@except ERFC2822Date raised if a malformed numeric offset is found.
}
resourcestring
// Error message
sErrMsg = 'Invalid date offset: "%s"';
var
Sign: Integer; // sign of offset: -1 or +1
I: Integer; // loops thru elements of cObsZones table
const
// Table of supported text time offsets
cObsZones: array[1..10] of record
Zone: string; // offset time zone code
Sign: Integer; // sign of offset: -1 or +1
Hour: Integer; // offset hour: minutes always zero
end = (
(Zone: 'UT'; Sign: +1; Hour: 0),
(Zone: 'GMT'; Sign: +1; Hour: 0),
(Zone: 'EST'; Sign: -1; Hour: 5),
(Zone: 'EDT'; Sign: -1; Hour: 4),
(Zone: 'CST'; Sign: -1; Hour: 6),
(Zone: 'CDT'; Sign: -1; Hour: 5),
(Zone: 'MST'; Sign: -1; Hour: 7),
(Zone: 'MDT'; Sign: -1; Hour: 6),
(Zone: 'PST'; Sign: -1; Hour: 8),
(Zone: 'PDT'; Sign: -1; Hour: 7)
);
begin
if (Length(AOffsetStr) = 5) and (CharInSet(AOffsetStr[1], ['+', '-'])) then
begin
// Offset in standard format ("+" | "-") 9999
if AOffsetStr[1] = '-' then
Sign := -1
else
Sign := 1;
if not TryStrToInt(Copy(AOffsetStr, 2, 2), AHours)
or not TryStrToInt(Copy(AOffsetStr, 4, 2), AMins) then
raise ERFC2822Date.CreateFmt(sErrMsg, [AOffsetStr]);
end
else
begin
// Non-standard format: assume -0000 unless recognised code
Sign := -1;
AHours := 0;
AMins := 0;
for I := Low(cObsZones) to High(cObsZones) do
begin
if cObsZones[I].Zone = AOffsetStr then
begin
Sign := cObsZones[I].Sign;
AHours := cObsZones[I].Hour;
end;
end;
end;
// Adjust offsets per sign
AHours := Sign * AHours;
AMins := Sign * AMins;
end;
function RFC2822DateToGMTDateTime(DateStr: string): TDateTime;
{Converts a date in RFC2822 format into the corresponding TDateTime value in
GMT (UTC).
@param DateStr [in] RFC2822 date string to be converted.
@return Converted date in GMT (UTC).
@except ERFC2822Date raised if DateStr is malformed.
}
resourcestring
// Error messages
sBadDate = 'Bad date format';
sBadDay = 'Invalid day of month: "%s"';
var
Parts: IStringList; // component parts of date string
Day, Month, Year: Word; // day, month and year
OffsetHours, OffsetMins: Integer; // offset hours and minutes
const
// Indices of date components in Parts
DOWPart = 0; // day of week part (<day-name> ",")
DayPart = 1; // day number part
MonthPart = 2; // month number part
YearPart = 3; // year number part
TimePart = 4; // time component part (HH:MM:SS format)
OffsetPart = 5; // offset from GMT part (+9999, -9999 or some valid text)
begin
// Sun, 29 Aug 2010 13:06:03 +0000
// Newlines allowed between fields: we compress to single white spaces
DateStr := StrCompressWhiteSpace(DateStr);
// Split date into constituent parts
Parts := TIStringList.Create(DateStr, ' ', False, True);
if Parts.Count <> 6 then
raise ERFC2822Date.Create(sBadDate);
// Check day has a valid name: we don't check if it is correct for date since
// not clear whether day should be correct before or after offset is applied.
ValidateDOW(Copy(Parts[DOWPart], 1, 3));
// Extract Day, Month and Year
Day := GetDay(Parts[DayPart]);
Month := GetMonth(Parts[MonthPart]);
Year := GetYear(Parts[YearPart]);
// Check date (Day, Month, Year) for validity
if not TryEncodeDate(Year, Month, Day, Result) then
raise ERFC2822Date.Create(sBadDate);
if DaysInMonth(Result) < Day then
raise ERFC2822Date.CreateFmt(sBadDay, [Day]);
// Get time component
Result := Result + GetTime(Parts[TimePart]);
// Get any offset and adjust date accordingly
GetOffset(Parts[OffsetPart], OffsetHours, OffsetMins);
if OffsetHours <> 0 then
Result := IncHour(Result, OffsetHours);
if OffsetMins <> 0 then
Result := IncMinute(Result, OffsetMins);
end;
end.
Want the latest updates on software, tech news, and AI?
Get latest updates about software, tech news, and AI from SourceForge directly in your inbox once a month.