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,v 1.3 2006/11/18 11:51:44 hsivonen Exp $
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    }