001 /*
002 * Copyright (c) 2006 Henri Sivonen
003 *
004 * Permission is hereby granted, free of charge, to any person obtaining a
005 * copy of this software and associated documentation files (the "Software"),
006 * to deal in the Software without restriction, including without limitation
007 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
008 * and/or sell copies of the Software, and to permit persons to whom the
009 * Software is furnished to do so, subject to the following conditions:
010 *
011 * The above copyright notice and this permission notice shall be included in
012 * all copies or substantial portions of the Software.
013 *
014 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
015 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
016 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
017 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
018 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
019 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
020 * DEALINGS IN THE SOFTWARE.
021 */
022
023 package org.whattf.datatype;
024
025 import java.util.Date;
026 import java.util.Locale;
027 import java.util.regex.Matcher;
028 import java.util.regex.Pattern;
029
030 import org.relaxng.datatype.DatatypeException;
031
032 import com.ibm.icu.util.Calendar;
033 import com.ibm.icu.util.GregorianCalendar;
034 import com.ibm.icu.util.TimeZone;
035
036 /**
037 * This datatype shall accept strings that conform to the format specified for
038 * <a href='http://whatwg.org/specs/web-forms/current-work/#week'><code>week</code></a>
039 * inputs in Web Forms 2.0.
040 * <p>This datatype must not accept the empty string.
041 *
042 * @version $Id: Week.java 121 2006-11-18 11:51:44Z hsivonen $
043 * @author hsivonen
044 */
045 public final class Week extends AbstractDatatype {
046
047 /**
048 * The singleton instance.
049 */
050 public static final Week THE_INSTANCE = new Week();
051
052 /**
053 * The rexexp for this datatype.
054 */
055 private static final Pattern THE_PATTERN = Pattern.compile("^([0-9]{4,})-W([0-9]{2})$");
056
057 /**
058 * Constructor.
059 */
060 private Week() {
061 super();
062 }
063
064 private void checkWeek(String year, String week)
065 throws DatatypeException {
066 checkWeek(Integer.parseInt(year), Integer.parseInt(week));
067 }
068
069 private void checkWeek(int year, int week)
070 throws DatatypeException {
071 if (year < 1) {
072 throw new DatatypeException("Year cannot be less than 1.");
073 }
074 if (week < 1) {
075 throw new DatatypeException("Week cannot be less than 1.");
076 }
077 if (week == 53) {
078 // TODO still in doubt about the concurrency contract
079 // not using instance variables just in case
080
081 // Can this year have ISO week #53?
082 // Using ICU4J to find out, because the calculation is
083 // non-trivial.
084 TimeZone tz = TimeZone.getTimeZone("GMT");
085 tz.setRawOffset(0);
086 // Using fixed time zone and locale to make possible
087 // bugs more tractable.
088 GregorianCalendar gc = new GregorianCalendar(tz, Locale.FRANCE);
089 // Say no to the Julian calendar
090 gc.setGregorianChange(new Date(Long.MIN_VALUE));
091 gc.setLenient(false);
092 gc.setFirstDayOfWeek(Calendar.MONDAY); // ISO week start
093 gc.setMinimalDaysInFirstWeek(4); // ISO week rule
094 gc.set(year, 6, 1);
095 if (gc.getActualMaximum(Calendar.WEEK_OF_YEAR) != 53) {
096 throw new DatatypeException("Week out of range.");
097 }
098 } else if (week > 53) {
099 throw new DatatypeException("Week out of range.");
100 }
101 }
102
103 public final void checkValid(CharSequence literal)
104 throws DatatypeException {
105 Matcher m = THE_PATTERN.matcher(literal);
106 if (m.matches()) {
107 checkWeek(m.group(1), m.group(2));
108 } else {
109 throw new DatatypeException(
110 "The literal did not satisfy the format for week.");
111 }
112 }
113
114 }