summaryrefslogtreecommitdiffstats
path: root/simple/simple-http/src/main/java/org/simpleframework/http/parse/DateParser.java
blob: 7efea9c3eea7867f37c197ae656d9139e39947f6 (plain)
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
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
/*
 * DateParser.java February 2001
 *
 * Copyright (C) 2001, Niall Gallagher <niallg@users.sf.net>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 
 * implied. See the License for the specific language governing 
 * permissions and limitations under the License.
 */
 
package org.simpleframework.http.parse;

import static java.util.Calendar.DAY_OF_MONTH;
import static java.util.Calendar.DAY_OF_WEEK;
import static java.util.Calendar.HOUR_OF_DAY;
import static java.util.Calendar.MILLISECOND;
import static java.util.Calendar.MINUTE;
import static java.util.Calendar.MONTH;
import static java.util.Calendar.SECOND;
import static java.util.Calendar.YEAR;

import java.util.Calendar;
import java.util.TimeZone;

import org.simpleframework.common.parse.Parser;

/** 
 * This is used to create a <code>Parser</code> for the HTTP date format. 
 * This will parse the 3 formats that are acceptable for the HTTP/1.1 date. 
 * The three formats that are acceptable for the HTTP-date are  
 * <pre>
 * Sun, 06 Nov 1994 08:49:37 GMT  ; RFC 822, updated by RFC 1123
 * Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036
 * Sun Nov  6 08:49:37 1994       ; ANSI C's asctime() format
 * </pre>
 * <p>
 * This can also parse the date in ms as retrived from the <code>System</code>'s 
 * <code>System.currentTimeMillis</code> method. This has a parse method for a 
 * <code>long</code> which will do the same as the <code>parse(String)</code>. 
 * Once the date has been parsed there are two methods that allow the date 
 * to be represented, the <code>toLong</code> method converts the date to a 
 * <code>long</code> and the <code>toString</code> method will convert the date 
 * into a <code>String</code>.
 * <p>
 * This produces the same string as the <code>SimpleDateFormat.format</code> 
 * using the pattern <code>"EEE, dd MMM yyyy hh:mm:ss 'GMT'"</code>. This will
 * however do the job faster as it does not take arbitrary inputs.
 *
 * @author Niall Gallagher
 */
public class DateParser extends Parser {

   /**
    * Ensure that the time zone for dates if set to GMT. 
    */
   private static final TimeZone ZONE = TimeZone.getTimeZone("GMT");

   /** 
    * Contains the possible days of the week for RFC 1123.
    */
   private static final String WKDAYS[] = { "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" };

   /** 
    * Contains the possible days of the week for RFC 850.
    */
   private static final String WEEKDAYS[] = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" };

   /** 
    * Contains the possible months in the year for HTTP-date.
    */
   private static final String MONTHS[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
  
   /** 
    * Used as an index into the months array to get the month.
    */
   private int month;
      
   /** 
    * Represents the decimal value of the date such as 1977.
    */
   private int year;
   
   /** 
    * Represents the decimal value of the date such as 18.
    */
   private int day;
   
   /** 
    * Used as an index into the weekdays array to get the weekday.
    */
   private int weekday;
   
   /** 
    * Represents the decimal value of the hour such as 24.
    */
   private int hour;

   /** 
    * Represents the decimal value of the minute.
    */
   private int mins;
   
   /** 
    * Represents the decimal value for the second.
    */
   private int secs;

   /** 
    * The default constructor will create a parser that can parse 
    * <code>String</code>s that contain dates in the form of RFC 1123, 
    * RFC 850 or asctime. If the dates that are to be parsed are not in 
    * the form of one of these date encodings the results of this 
    * parser will be random. 
    */
   public DateParser(){
      this.init();         
   }  
 
   /** 
    * This constructor will conveniently parse the <code>long</code> argument 
    * in the constructor. This can also  be done by first calling the no-arg 
    * constructor and then using the parse method. 
    * <p>
    * This will then set this object to one that uses the RFC 1123 format 
    * for a date. 
    *
    * @param date the date to be parsed
    */ 
   public DateParser(long date){
      this();
      parse(date);
   }

   /** This constructor will conveniently parse the <code>String</code> 
   * argument in the constructor. This can also be done by first calling 
   * the no-arg constructor and then using the parse method. 
    * <p> 
    * This will then set this object to one that uses the RFC 1123 format
    * for a date.
    * 
    * @param date the date to be parsed
    */   
   public DateParser(String date)  {      
      this();
      parse(date);
   } 

   /** 
    * This is used to extract the date from a <code>long</code>. If this
    * method is given the value of the date as a <code>long</code> it will
    * construct the RFC 1123 date as required by RFC 2616 sec 3.3.
    * <p>
    * This saves time on parsing a <code>String</code> that is encoded in 
    * the HTTP-date format. The date given must be positive, if the date 
    * given is not a positive '<code>long</code>' then the results
    * of this method is random/unknown.
    * 
    * @param date the date to be parsed
    */   
   public void parse(long date){
      Calendar calendar = Calendar.getInstance(ZONE);
      calendar.setTimeInMillis(date); 
      
      weekday = calendar.get(DAY_OF_WEEK);
      year = calendar.get(YEAR);
      month = calendar.get(MONTH);
      day = calendar.get(DAY_OF_MONTH);
      hour = calendar.get(HOUR_OF_DAY);
      mins = calendar.get(MINUTE);
      secs = calendar.get(SECOND);
      month = month > 11 ? 11: month;
      weekday = (weekday+5) % 7;
   }
   
   /**
    * Convenience method used to convert the specified HTTP date in to a 
    * long representing the time. This is used when a single method is
    * required to convert a HTTP date format to a usable long value for
    * use in creating <code>Date</code> objects.
    * 
    * @param date the date specified in on of the HTTP date formats
    * 
    * @return the date value as a long value in milliseconds
    */
   public long convert(String date) {
      parse(date);
      return toLong();
      
   }
   
   /**
    * Convenience method used to convert the specified long date in to a
    * HTTP date format. This is used when a single method is required to
    * convert a long data value in milliseconds to a HTTP date value. 
    * 
    * @param date the date specified as a long of milliseconds
    * 
    * @return the date represented in the HTTP date format RFC 1123
    */
   public String convert(long date) {
      parse(date);
      return toString();
   }

   /** 
    * This is used to reset the date and the buffer variables 
    * for this <code>DateParser</code>. Every in is set to the 
    * value of 0.
    */
   protected void init() {
      month = year = day = 
      weekday = hour = mins = 
      secs = off = 0;
   }

   /** 
    * This is used to parse the contents of the <code>buf</code>. This 
    * checks the fourth char of the buffer to see what it contains. Invariably 
    * a date format belonging to RFC 1123 will have a ',' character in position 4, 
    * a date format belonging to asctime will have a ' ' character in position 4 
    * and if neither of these characters are found at position 4 then it is 
    * assumed that the date is in the RFC 850 fromat, however it may not be.
    */
   protected void parse(){
      if(buf.length<4)return;
      if(buf[3]==','){
         rfc1123();
      }else if(buf[3]==' '){
         asctime();
      }else{
         rfc850();
      } 
   }

   /** 
    * This will parse a date that is in the form of an RFC 1123 date. This 
    * date format is the date format that is to be used with all applications 
    * that are HTTP/1.1 compliant. The RFC 1123 date format is  
    * <pre>
    * rfc1123 = 'wkday "," SP date1 SP time SP GMT'. 
    * date1 = '2DIGIT SP month SP 4DIGIT' and finally
    * time = '2DIGIT ":" 2DIGIT ":" 2DIGIT'. 
    * </pre>
    */
   private void rfc1123(){
      wkday();
      off+=2;
      date1();
      off++;
      time();      
   }  
 
   /** 
    * This will parse a date that is in the form of an RFC 850 date. This date 
    * format is the date format that is to be used with some applications that 
    * are HTTP/1.0 compliant. The RFC 1123 date format is  
    * <pre>
    * rfc850 = 'weekday "," SP date2 SP time SP GMT'. 
    * date2 = '2DIGIT "-" month "-" 2DIGIT' and finally
    * time = '2DIGIT ":" 2DIGIT ":" 2DIGIT'. 
    * </pre>
    */
   private void rfc850() {
      weekday();
      off+=2;
      date2();
      off++;
      time();
   }

   /** 
    * This will parse a date that is in the form of an asctime date. This date 
    * format is the date format that is to be used with some applications that 
    * are HTTP/1.0 compliant. The RFC 1123 date format is  
    * <pre>
    * asctime = 'weekday SP date3 SP time SP 4DIGIT'. 
    * date3 = 'month SP (2DIGIT | (SP 1DIGIT))' and 
    * time = '2DIGIT ":" 2DIGIT ":" 2DIGIT'. 
    * </pre>
    */
   private void asctime(){
      wkday();
      off++;
      date3();
      off++;
      time();
      off++;
      year4();
   }

   /** 
    * This is the date1 format of a date that is used by the RFC 1123
    * date format. This date is
    * <pre>
    * date1 = '2DIGIT SP month SP 4DIGIT'.
    * example '02 Jun 1982'.
    * </pre>
    */
   private void date1(){
      day();
      off++;
      month();
      off++;
      year4();
   }

   /** 
    * This is the date2 format of a date that is used by the RFC 850 
    * date format. This date is
    * <pre>
    * date2 = '2DIGIT "-" month "-" 2DIGIT'
    * example '02-Jun-82'.
    * </pre>
    */   
   private void date2(){
      day();
      off++;
      month();
      off++;
      year2();
   }

   /** 
    * This is the date3 format of a date that is used by the asctime 
    * date format. This date is
    * <pre>
    * date3 = 'month SP (2DIGIT | (SP 1DIGIT))' 
    * example 'Jun  2'.
    * <pre>
    */
   private void date3(){
      month();
      off++;
      day();
   }

   /** 
    * This is used to parse a consecutive set of digit characters to create 
    * the day of the week. This will tolerate a space on front of the digits 
    * thiswill allow all date formats including asctime to use this to get 
    * the day. This may parse more than 2 digits, however if there are more 
    * than 2 digits the date format is incorrect anyway.
    */
   private void day(){
      if(space(buf[off])){
         off++;
      }
      while(off < count){
         if(!digit(buf[off])){
            break;
         }
         day *= 10;
         day += buf[off];
         day -= '0';
         off++;
      }    
   }

   /** 
    * This is used to get the year from a set of digit characters. This is 
    * used to parse years that are of the form of 2 digits (e.g 82) however 
    * this will assume that any dates that are in 2 digit format are dates 
    * for the 2000 th milleneum so 01 will be 2001.
    * <p>
    * This may parse more than 2 digits but if there are more than 2 digits
    *  in a row then the date format is incorrect anyway.
    */
   private void year2(){      
      int mill = 2000;  /* milleneum */
      int cent = 0;   /* century */
      while(off < count){
         if(!digit(buf[off])){            
            break;
         }
         cent *= 10;
         cent += buf[off];
         cent -= '0';
         off++;
      }  
      year= mill+cent; /* result 4 digits*/
   }

   /** 
    * This is used to get the year from a set of digit characters. This 
    * is used to parse years that are of the form of 4 digits (e.g 1982).
    * <p>
    * This may parse more than 4 digits but if there are more than 2  
    * digits in a row then the date format is incorrect anyway.
    */
   private void year4() {
      while(off < count){
         if(!digit(buf[off])){
            break;
         }
         year *= 10;
         year += buf[off];
         year -= '0';
         off++;
      }      
   }

   /** 
    * This is used to parse the time for a HTTP-date. The time for a 
    * HTTP-date is in the form <code>00:00:00</code> that is
    * <pre>
    * time = '2DIGIT ":" 2DIGIT ":" 2DIGIT' so this will
    * read only a time of that form, although this will
    * parse time = '2DIGIT CHAR 2DIGIT CHAR 2DIGIT'.
    * </pre>
    */
   private void time(){
      hours();
      off++;
      mins();
      off++;
      secs();
   }

   /** 
    * This is used to initialize the hour. This will read a consecutive 
    * sequence of digit characters and convert them into a decimal number 
    * to represent the hour that this date represents. 
    * <p>
    * This may parse more than 2 digits but if there are more than 2 
    * digits the date is already incorrect.
    */
   private void hours(){
      while(off < count){
         if(!digit(buf[off])){
            break;
         }
         hour *= 10;
         hour += buf[off];
         hour -= '0';
         off++;
      }
   }

   /** 
    * This is used to initialize the mins. This will read a consecutive 
    * sequence of digit characters and convert them into a decimal number 
    * to represent the mins that  this date represents. 
    * <p>
    * This may parse more than 2 digits but if there are more than 2 
    * digits the date is already incorrect.
    */
   private void mins(){
      while(off < count){
         if(!digit(buf[off])){
            break;
         }
         mins *= 10;
         mins += buf[off];
         mins -= '0';
         off++;
      }
   }

   /** 
    * This is used to initialize the secs. This will read a consecutive 
    * sequence of digit characters and convert them into a decimal 
    * number to represent the secs that this date represents. 
    * <p>
    * This may parse more than 2 digits but if there are more than 2 
    * digits the date is already incorrect
    */
   private void secs(){
      while(off < count){
         if(!digit(buf[off])){
            break;
         }
         secs *= 10;
         secs += buf[off];
         secs -= '0';
         off++;
      }
   }

   /** 
    * This is used to read the week day of HTTP-date. The shorthand day 
    * (e.g Mon for Monday) is used by the RFC 1123 and asctime date formats. 
    * This will simply try to read each day from the buffer, when the day
    * is read successfully then the index of that day is saved.
    */    
   private void wkday(){
      for(int i =0; i < WKDAYS.length;i++){
         if(skip(WKDAYS[i])){
            weekday = i;
            return;
         }      
      }
   }

   /** 
    * This is used to read the week day of HTTP-date. This format is used 
    * by the RFC 850 date format. This will simply try to read each day from 
    * the buffer, when the day is read successfully then the index of that 
    * day is saved.
    */ 
   private void weekday(){
      for(int i =0; i < WKDAYS.length;i++){
         if(skip(WEEKDAYS[i])){
            weekday = i;
            return;
         }      
      }     
   }

   /** 
    * This is used to read the month of HTTP-date. This will simply 
    * try to read each month from the buffer, when the month is read 
    * successfully then the index of that month is saved.
    */
   private void month(){
      for(int i =0; i < MONTHS.length;i++){
         if(skip(MONTHS[i])){
            month = i;
            return;
         }      
      } 
   }
   
   /**
    * This is used to append the date in RFC 1123 format to the given
    * string builder. This will append the date and a trailing space
    * character to the buffer. Dates like the following are appended. 
    * <pre>
    * Tue, 02 Jun 1982 
    * </pre>. 
    * For performance reasons a string builder is used. This avoids 
    * an unneeded synchronization caused by the string buffers.
    * 
    * @param builder this is the builder to append the date to
    */
   private void date(StringBuilder builder) {
      builder.append(WKDAYS[weekday]);
      builder.append(", ");
      
      if(day <= 9) {
         builder.append('0');
      }
      builder.append(day);
      builder.append(' ');
      builder.append(MONTHS[month]);
      builder.append(' ');
      builder.append(year);
      builder.append(' ');
   }
   
   /**
    * This is used to append the time in RFC 1123 format to the given
    * string builder. This will append the time and a trailing space
    * character to the buffer. Times like the following are appended. 
    * <pre>
    * 23:59:59
    * </pre>. 
    * For performance reasons a string builder is used. This avoids 
    * an unneeded synchronization caused by the string buffers. 
    *
    * @param builder this is the builder to write the time to
    */
   private void time(StringBuilder builder) {
      if(hour <= 9) {
         builder.append('0');
      }
      builder.append(hour);
      builder.append(':');
      
      if(mins <= 9) {
         builder.append('0');
      }
      builder.append(mins);
      builder.append(':');
      
      if(secs <= 9) {
         builder.append('0');
      }
      builder.append(secs);
      builder.append(' ');
   }
   
   /**
    * This is used to append the time zone to the provided appender.
    * For HTTP the dates should always be in GMT format. So this will
    * simply append the "GMT" string to the end of the builder.
    * 
    * @param builder this builder to append the time zone to
    */
   private void zone(StringBuilder builder) {
      builder.append("GMT");
   }

   /** 
    * This returns the date in as a <code>long</code>, given the exact 
    * time this will use the <code>java.util.Date</code> to parse this date 
    * into a <code>long</code>. The <code>GregorianCalendar</code> uses 
    * the method <code>getTime</code> which produces the <code>Date</code>
    * object from this the <code>getTime</code> returns the <code>long</code>
    *
    * @return the date parsed as a <code>long</code>
    */
   public long toLong() {
      Calendar calendar = Calendar.getInstance(ZONE); /* GMT*/
      calendar.set(year,month, day, hour, mins, secs);
      calendar.set(MILLISECOND, 0);

      return calendar.getTime().getTime();   
   }

   /** 
    * This prints the date in the format of a RFC 1123 date. Example
    * <pre>
    * Tue, 02 Jun 1982 23:59:59 GMT
    * </pre>. 
    * This uses a <code>StringBuffer</code> to accumulate the various 
    * <code>String</code>s/<code>int</code>s to form the resulting date 
    * value. The resulting date value is the one required by RFC 2616. 
    * <p>
    * The HTTP date must be in the form of RFC 1123. The hours, minutes 
    * and seconds are appended with the 0 character if they are less than 
    * 9 i.e. if they do not have two digits.
    *
    * @return the date in RFC 1123 format
    */
   public String toString(){
      StringBuilder builder = new StringBuilder(30);
      
      date(builder);
      time(builder);
      zone(builder);
      
      return builder.toString();
   }
}