341 |
} |
} |
342 |
|
|
343 |
/** |
/** |
344 |
* This method returns the format symbol information used for parsing |
* This method returns a copy of the format symbol information used |
345 |
* and formatting dates. |
* for parsing and formatting dates. |
346 |
* |
* |
347 |
* @return The date format symbols. |
* @return a copy of the date format symbols. |
348 |
*/ |
*/ |
349 |
public DateFormatSymbols getDateFormatSymbols() |
public DateFormatSymbols getDateFormatSymbols() |
350 |
{ |
{ |
351 |
return formatData; |
return (DateFormatSymbols) formatData.clone(); |
352 |
} |
} |
353 |
|
|
354 |
/** |
/** |
356 |
* and formatting dates. |
* and formatting dates. |
357 |
* |
* |
358 |
* @param formatData The date format symbols. |
* @param formatData The date format symbols. |
359 |
|
* @throws NullPointerException if <code>formatData</code> is null. |
360 |
*/ |
*/ |
361 |
public void setDateFormatSymbols(DateFormatSymbols formatData) |
public void setDateFormatSymbols(DateFormatSymbols formatData) |
362 |
{ |
{ |
363 |
|
if (formatData == null) |
364 |
|
{ |
365 |
|
throw new |
366 |
|
NullPointerException("The supplied format data was null."); |
367 |
|
} |
368 |
this.formatData = formatData; |
this.formatData = formatData; |
369 |
} |
} |
370 |
|
|
620 |
boolean saw_timezone = false; |
boolean saw_timezone = false; |
621 |
int quote_start = -1; |
int quote_start = -1; |
622 |
boolean is2DigitYear = false; |
boolean is2DigitYear = false; |
623 |
for (; fmt_index < fmt_max; ++fmt_index) |
try |
624 |
{ |
{ |
625 |
char ch = pattern.charAt(fmt_index); |
for (; fmt_index < fmt_max; ++fmt_index) |
|
if (ch == '\'') |
|
626 |
{ |
{ |
627 |
int index = pos.getIndex(); |
char ch = pattern.charAt(fmt_index); |
628 |
if (fmt_index < fmt_max - 1 |
if (ch == '\'') |
629 |
&& pattern.charAt(fmt_index + 1) == '\'') |
{ |
630 |
|
int index = pos.getIndex(); |
631 |
|
if (fmt_index < fmt_max - 1 |
632 |
|
&& pattern.charAt(fmt_index + 1) == '\'') |
633 |
|
{ |
634 |
|
if (! expect (dateStr, pos, ch)) |
635 |
|
return null; |
636 |
|
++fmt_index; |
637 |
|
} |
638 |
|
else |
639 |
|
quote_start = quote_start < 0 ? fmt_index : -1; |
640 |
|
continue; |
641 |
|
} |
642 |
|
|
643 |
|
if (quote_start != -1 |
644 |
|
|| ((ch < 'a' || ch > 'z') |
645 |
|
&& (ch < 'A' || ch > 'Z'))) |
646 |
{ |
{ |
647 |
if (! expect (dateStr, pos, ch)) |
if (! expect (dateStr, pos, ch)) |
648 |
return null; |
return null; |
649 |
++fmt_index; |
continue; |
650 |
} |
} |
651 |
else |
|
652 |
quote_start = quote_start < 0 ? fmt_index : -1; |
// We've arrived at a potential pattern character in the |
653 |
continue; |
// pattern. |
654 |
} |
int fmt_count = 1; |
655 |
|
while (++fmt_index < fmt_max && pattern.charAt(fmt_index) == ch) |
|
if (quote_start != -1 |
|
|
|| ((ch < 'a' || ch > 'z') |
|
|
&& (ch < 'A' || ch > 'Z'))) |
|
|
{ |
|
|
if (! expect (dateStr, pos, ch)) |
|
|
return null; |
|
|
continue; |
|
|
} |
|
|
|
|
|
// We've arrived at a potential pattern character in the |
|
|
// pattern. |
|
|
int first = fmt_index; |
|
|
while (++fmt_index < fmt_max && pattern.charAt(fmt_index) == ch) |
|
|
; |
|
|
int fmt_count = fmt_index - first; |
|
|
|
|
|
// We might need to limit the number of digits to parse in |
|
|
// some cases. We look to the next pattern character to |
|
|
// decide. |
|
|
boolean limit_digits = false; |
|
|
if (fmt_index < fmt_max |
|
|
&& standardChars.indexOf(pattern.charAt(fmt_index)) >= 0) |
|
|
limit_digits = true; |
|
|
--fmt_index; |
|
|
|
|
|
// We can handle most fields automatically: most either are |
|
|
// numeric or are looked up in a string vector. In some cases |
|
|
// we need an offset. When numeric, `offset' is added to the |
|
|
// resulting value. When doing a string lookup, offset is the |
|
|
// initial index into the string array. |
|
|
int calendar_field; |
|
|
boolean is_numeric = true; |
|
|
String[] match = null; |
|
|
int offset = 0; |
|
|
boolean maybe2DigitYear = false; |
|
|
switch (ch) |
|
|
{ |
|
|
case 'd': |
|
|
calendar_field = Calendar.DATE; |
|
|
break; |
|
|
case 'D': |
|
|
calendar_field = Calendar.DAY_OF_YEAR; |
|
|
break; |
|
|
case 'F': |
|
|
calendar_field = Calendar.DAY_OF_WEEK_IN_MONTH; |
|
|
break; |
|
|
case 'E': |
|
|
is_numeric = false; |
|
|
offset = 1; |
|
|
calendar_field = Calendar.DAY_OF_WEEK; |
|
|
match = (fmt_count <= 3 |
|
|
? formatData.getShortWeekdays() |
|
|
: formatData.getWeekdays()); |
|
|
break; |
|
|
case 'w': |
|
|
calendar_field = Calendar.WEEK_OF_YEAR; |
|
|
break; |
|
|
case 'W': |
|
|
calendar_field = Calendar.WEEK_OF_MONTH; |
|
|
break; |
|
|
case 'M': |
|
|
calendar_field = Calendar.MONTH; |
|
|
if (fmt_count <= 2) |
|
|
offset = -1; |
|
|
else |
|
656 |
{ |
{ |
657 |
is_numeric = false; |
++fmt_count; |
|
match = (fmt_count <= 3 |
|
|
? formatData.getShortMonths() |
|
|
: formatData.getMonths()); |
|
658 |
} |
} |
659 |
break; |
|
660 |
case 'y': |
// We might need to limit the number of digits to parse in |
661 |
calendar_field = Calendar.YEAR; |
// some cases. We look to the next pattern character to |
662 |
if (fmt_count <= 2) |
// decide. |
663 |
maybe2DigitYear = true; |
boolean limit_digits = false; |
664 |
break; |
if (fmt_index < fmt_max |
665 |
case 'K': |
&& standardChars.indexOf(pattern.charAt(fmt_index)) >= 0) |
666 |
calendar_field = Calendar.HOUR; |
limit_digits = true; |
667 |
break; |
--fmt_index; |
668 |
case 'h': |
|
669 |
calendar_field = Calendar.HOUR; |
// We can handle most fields automatically: most either are |
670 |
break; |
// numeric or are looked up in a string vector. In some cases |
671 |
case 'H': |
// we need an offset. When numeric, `offset' is added to the |
672 |
calendar_field = Calendar.HOUR_OF_DAY; |
// resulting value. When doing a string lookup, offset is the |
673 |
break; |
// initial index into the string array. |
674 |
case 'k': |
int calendar_field; |
675 |
calendar_field = Calendar.HOUR_OF_DAY; |
boolean is_numeric = true; |
676 |
break; |
String[] match = null; |
677 |
case 'm': |
int offset = 0; |
678 |
calendar_field = Calendar.MINUTE; |
boolean maybe2DigitYear = false; |
679 |
break; |
switch (ch) |
|
case 's': |
|
|
calendar_field = Calendar.SECOND; |
|
|
break; |
|
|
case 'S': |
|
|
calendar_field = Calendar.MILLISECOND; |
|
|
break; |
|
|
case 'a': |
|
|
is_numeric = false; |
|
|
calendar_field = Calendar.AM_PM; |
|
|
match = formatData.getAmPmStrings(); |
|
|
break; |
|
|
case 'z': |
|
|
// We need a special case for the timezone, because it |
|
|
// uses a different data structure than the other cases. |
|
|
is_numeric = false; |
|
|
calendar_field = Calendar.DST_OFFSET; |
|
|
String[][] zoneStrings = formatData.getZoneStrings(); |
|
|
int zoneCount = zoneStrings.length; |
|
|
int index = pos.getIndex(); |
|
|
boolean found_zone = false; |
|
|
for (int j = 0; j < zoneCount; j++) |
|
680 |
{ |
{ |
681 |
String[] strings = zoneStrings[j]; |
case 'd': |
682 |
int k; |
calendar_field = Calendar.DATE; |
683 |
for (k = 1; k < strings.length; ++k) |
break; |
684 |
|
case 'D': |
685 |
|
calendar_field = Calendar.DAY_OF_YEAR; |
686 |
|
break; |
687 |
|
case 'F': |
688 |
|
calendar_field = Calendar.DAY_OF_WEEK_IN_MONTH; |
689 |
|
break; |
690 |
|
case 'E': |
691 |
|
is_numeric = false; |
692 |
|
offset = 1; |
693 |
|
calendar_field = Calendar.DAY_OF_WEEK; |
694 |
|
match = (fmt_count <= 3 |
695 |
|
? formatData.getShortWeekdays() |
696 |
|
: formatData.getWeekdays()); |
697 |
|
break; |
698 |
|
case 'w': |
699 |
|
calendar_field = Calendar.WEEK_OF_YEAR; |
700 |
|
break; |
701 |
|
case 'W': |
702 |
|
calendar_field = Calendar.WEEK_OF_MONTH; |
703 |
|
break; |
704 |
|
case 'M': |
705 |
|
calendar_field = Calendar.MONTH; |
706 |
|
if (fmt_count <= 2) |
707 |
|
offset = -1; |
708 |
|
else |
709 |
{ |
{ |
710 |
if (dateStr.startsWith(strings[k], index)) |
is_numeric = false; |
711 |
break; |
match = (fmt_count == 3 |
712 |
|
? formatData.getShortMonths() |
713 |
|
: formatData.getMonths()); |
714 |
} |
} |
715 |
if (k != strings.length) |
break; |
716 |
|
case 'y': |
717 |
|
calendar_field = Calendar.YEAR; |
718 |
|
if (fmt_count <= 2) |
719 |
|
maybe2DigitYear = true; |
720 |
|
break; |
721 |
|
case 'K': |
722 |
|
calendar_field = Calendar.HOUR; |
723 |
|
break; |
724 |
|
case 'h': |
725 |
|
calendar_field = Calendar.HOUR; |
726 |
|
break; |
727 |
|
case 'H': |
728 |
|
calendar_field = Calendar.HOUR_OF_DAY; |
729 |
|
break; |
730 |
|
case 'k': |
731 |
|
calendar_field = Calendar.HOUR_OF_DAY; |
732 |
|
break; |
733 |
|
case 'm': |
734 |
|
calendar_field = Calendar.MINUTE; |
735 |
|
break; |
736 |
|
case 's': |
737 |
|
calendar_field = Calendar.SECOND; |
738 |
|
break; |
739 |
|
case 'S': |
740 |
|
calendar_field = Calendar.MILLISECOND; |
741 |
|
break; |
742 |
|
case 'a': |
743 |
|
is_numeric = false; |
744 |
|
calendar_field = Calendar.AM_PM; |
745 |
|
match = formatData.getAmPmStrings(); |
746 |
|
break; |
747 |
|
case 'z': |
748 |
|
// We need a special case for the timezone, because it |
749 |
|
// uses a different data structure than the other cases. |
750 |
|
is_numeric = false; |
751 |
|
calendar_field = Calendar.DST_OFFSET; |
752 |
|
String[][] zoneStrings = formatData.getZoneStrings(); |
753 |
|
int zoneCount = zoneStrings.length; |
754 |
|
int index = pos.getIndex(); |
755 |
|
boolean found_zone = false; |
756 |
|
for (int j = 0; j < zoneCount; j++) |
757 |
{ |
{ |
758 |
found_zone = true; |
String[] strings = zoneStrings[j]; |
759 |
saw_timezone = true; |
int k; |
760 |
TimeZone tz = TimeZone.getTimeZone (strings[0]); |
for (k = 1; k < strings.length; ++k) |
|
calendar.set (Calendar.ZONE_OFFSET, tz.getRawOffset ()); |
|
|
offset = 0; |
|
|
if (k > 2 && tz instanceof SimpleTimeZone) |
|
761 |
{ |
{ |
762 |
SimpleTimeZone stz = (SimpleTimeZone) tz; |
if (dateStr.startsWith(strings[k], index)) |
763 |
offset = stz.getDSTSavings (); |
break; |
764 |
|
} |
765 |
|
if (k != strings.length) |
766 |
|
{ |
767 |
|
found_zone = true; |
768 |
|
saw_timezone = true; |
769 |
|
TimeZone tz = TimeZone.getTimeZone (strings[0]); |
770 |
|
calendar.set (Calendar.ZONE_OFFSET, tz.getRawOffset ()); |
771 |
|
offset = 0; |
772 |
|
if (k > 2 && tz instanceof SimpleTimeZone) |
773 |
|
{ |
774 |
|
SimpleTimeZone stz = (SimpleTimeZone) tz; |
775 |
|
offset = stz.getDSTSavings (); |
776 |
|
} |
777 |
|
pos.setIndex(index + strings[k].length()); |
778 |
|
break; |
779 |
} |
} |
|
pos.setIndex(index + strings[k].length()); |
|
|
break; |
|
780 |
} |
} |
781 |
} |
if (! found_zone) |
782 |
if (! found_zone) |
{ |
783 |
{ |
pos.setErrorIndex(pos.getIndex()); |
784 |
|
return null; |
785 |
|
} |
786 |
|
break; |
787 |
|
default: |
788 |
pos.setErrorIndex(pos.getIndex()); |
pos.setErrorIndex(pos.getIndex()); |
789 |
return null; |
return null; |
790 |
} |
} |
791 |
break; |
|
792 |
default: |
// Compute the value we should assign to the field. |
793 |
pos.setErrorIndex(pos.getIndex()); |
int value; |
794 |
return null; |
int index = -1; |
795 |
} |
if (is_numeric) |
|
|
|
|
// Compute the value we should assign to the field. |
|
|
int value; |
|
|
int index = -1; |
|
|
if (is_numeric) |
|
|
{ |
|
|
numberFormat.setMinimumIntegerDigits(fmt_count); |
|
|
if (limit_digits) |
|
|
numberFormat.setMaximumIntegerDigits(fmt_count); |
|
|
if (maybe2DigitYear) |
|
|
index = pos.getIndex(); |
|
|
Number n = numberFormat.parse(dateStr, pos); |
|
|
if (pos == null || ! (n instanceof Long)) |
|
|
return null; |
|
|
value = n.intValue() + offset; |
|
|
} |
|
|
else if (match != null) |
|
|
{ |
|
|
index = pos.getIndex(); |
|
|
int i; |
|
|
for (i = offset; i < match.length; ++i) |
|
796 |
{ |
{ |
797 |
if (dateStr.startsWith(match[i], index)) |
numberFormat.setMinimumIntegerDigits(fmt_count); |
798 |
break; |
if (limit_digits) |
799 |
|
numberFormat.setMaximumIntegerDigits(fmt_count); |
800 |
|
if (maybe2DigitYear) |
801 |
|
index = pos.getIndex(); |
802 |
|
Number n = numberFormat.parse(dateStr, pos); |
803 |
|
if (pos == null || ! (n instanceof Long)) |
804 |
|
return null; |
805 |
|
value = n.intValue() + offset; |
806 |
} |
} |
807 |
if (i == match.length) |
else if (match != null) |
808 |
{ |
{ |
809 |
pos.setErrorIndex(index); |
index = pos.getIndex(); |
810 |
return null; |
int i; |
811 |
|
for (i = offset; i < match.length; ++i) |
812 |
|
{ |
813 |
|
if (match[i] != null) |
814 |
|
if (dateStr.toUpperCase().startsWith(match[i].toUpperCase(), |
815 |
|
index)) |
816 |
|
break; |
817 |
|
} |
818 |
|
if (i == match.length) |
819 |
|
{ |
820 |
|
pos.setErrorIndex(index); |
821 |
|
return null; |
822 |
|
} |
823 |
|
pos.setIndex(index + match[i].length()); |
824 |
|
value = i; |
825 |
} |
} |
826 |
pos.setIndex(index + match[i].length()); |
else |
827 |
value = i; |
value = offset; |
|
} |
|
|
else |
|
|
value = offset; |
|
828 |
|
|
829 |
if (maybe2DigitYear) |
if (maybe2DigitYear) |
830 |
{ |
{ |
831 |
// Parse into default century if the numeric year string has |
// Parse into default century if the numeric year string has |
832 |
// exactly 2 digits. |
// exactly 2 digits. |
833 |
int digit_count = pos.getIndex() - index; |
int digit_count = pos.getIndex() - index; |
834 |
if (digit_count == 2) |
if (digit_count == 2) |
835 |
is2DigitYear = true; |
is2DigitYear = true; |
836 |
|
} |
837 |
|
|
838 |
|
// Assign the value and move on. |
839 |
|
calendar.set(calendar_field, value); |
840 |
} |
} |
|
|
|
|
// Assign the value and move on. |
|
|
calendar.set(calendar_field, value); |
|
|
} |
|
841 |
|
|
842 |
if (is2DigitYear) |
if (is2DigitYear) |
843 |
{ |
{ |
844 |
// Apply the 80-20 heuristic to dermine the full year based on |
// Apply the 80-20 heuristic to dermine the full year based on |
845 |
// defaultCenturyStart. |
// defaultCenturyStart. |
846 |
int year = defaultCentury + calendar.get(Calendar.YEAR); |
int year = defaultCentury + calendar.get(Calendar.YEAR); |
847 |
calendar.set(Calendar.YEAR, year); |
calendar.set(Calendar.YEAR, year); |
848 |
if (calendar.getTime().compareTo(defaultCenturyStart) < 0) |
if (calendar.getTime().compareTo(defaultCenturyStart) < 0) |
849 |
calendar.set(Calendar.YEAR, year + 100); |
calendar.set(Calendar.YEAR, year + 100); |
850 |
} |
} |
|
|
|
|
try |
|
|
{ |
|
851 |
if (! saw_timezone) |
if (! saw_timezone) |
852 |
{ |
{ |
853 |
// Use the real rules to determine whether or not this |
// Use the real rules to determine whether or not this |
872 |
calendar.set(Calendar.YEAR, year - 80); |
calendar.set(Calendar.YEAR, year - 80); |
873 |
set2DigitYearStart(calendar.getTime()); |
set2DigitYearStart(calendar.getTime()); |
874 |
} |
} |
875 |
|
|
876 |
|
/** |
877 |
|
* Returns a copy of this instance of |
878 |
|
* <code>SimpleDateFormat</code>. The copy contains |
879 |
|
* clones of the formatting symbols and the 2-digit |
880 |
|
* year century start date. |
881 |
|
*/ |
882 |
|
public Object clone() |
883 |
|
{ |
884 |
|
SimpleDateFormat clone = (SimpleDateFormat) super.clone(); |
885 |
|
clone.setDateFormatSymbols((DateFormatSymbols) formatData.clone()); |
886 |
|
clone.set2DigitYearStart((Date) defaultCenturyStart.clone()); |
887 |
|
return clone; |
888 |
|
} |
889 |
|
|
890 |
} |
} |