| /* |
| * Copyright 2008 Google Inc. |
| * |
| * 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 com.google.gwt.i18n.shared.impl; |
| |
| import java.util.Date; |
| |
| /** |
| * Implementation detail of DateTimeFormat -- not a public API and subject to |
| * change. |
| * |
| * DateRecord class exposes almost the same set of interface as Date class with |
| * only a few exceptions. The main purpose is the record all the information |
| * during parsing phase and resolve them in a later time when all information |
| * can be processed together. |
| */ |
| @SuppressWarnings("deprecation") |
| public class DateRecord extends Date { |
| |
| /* |
| * The serial version UID is only defined because this class is implicitly |
| * serializable, causing warnings due to its absence. It will generally not be |
| * used. |
| */ |
| private static final long serialVersionUID = -1278816193740448162L; |
| |
| public static final int AM = 0; |
| public static final int PM = 1; |
| |
| private static final int JS_START_YEAR = 1900; |
| |
| private int era; |
| private int year; |
| private int month; |
| private int dayOfMonth; |
| private int ampm; |
| private boolean midnightIs24; |
| private int hours; |
| private int minutes; |
| private int seconds; |
| private int milliseconds; |
| |
| private int tzOffset; |
| private int dayOfWeek; |
| private boolean ambiguousYear; |
| |
| /** |
| * Initialize DateExt object with default value. Here we use -1 for most of |
| * the field to indicate that field is not set. |
| */ |
| public DateRecord() { |
| era = -1; |
| ambiguousYear = false; |
| year = Integer.MIN_VALUE; |
| month = -1; |
| dayOfMonth = -1; |
| ampm = -1; |
| midnightIs24 = false; |
| hours = -1; |
| minutes = -1; |
| seconds = -1; |
| milliseconds = -1; |
| dayOfWeek = -1; |
| tzOffset = Integer.MIN_VALUE; |
| } |
| |
| /** |
| * calcDate uses all the field available so far to fill a Date object. For |
| * those information that is not provided, the existing value in 'date' will |
| * be kept. Ambiguous year will be resolved after the date/time values are |
| * resolved. |
| * |
| * If the strict option is set to true, calcDate will calculate certain |
| * invalid dates by wrapping around as needed. For example, February 30 will |
| * wrap to March 2. |
| * |
| * @param date The Date object being filled. Its value should be set to an |
| * acceptable default before pass in to this method |
| * @param strict true to be strict when parsing |
| * @return true if successful, otherwise false. |
| */ |
| public boolean calcDate(Date date, boolean strict) { |
| // Year 0 is 1 BC, and so on. |
| if (this.era == 0 && this.year > 0) { |
| this.year = -(this.year - 1); |
| } |
| |
| if (this.year > Integer.MIN_VALUE) { |
| date.setYear(this.year - JS_START_YEAR); |
| } |
| |
| // "setMonth" and "setDate" is a little bit tricky. Suppose content in |
| // date is 11/30, switch month to 02 will lead to 03/02 since 02/30 does |
| // not exist. And you certain won't like 02/12 turn out to be 03/12. So |
| // here to set date to a smaller number before month, and later setMonth. |
| // Real date is set after, and that might cause month switch. However, |
| // that's desired. |
| int orgDayOfMonth = date.getDate(); |
| date.setDate(1); |
| |
| if (this.month >= 0) { |
| date.setMonth(this.month); |
| } |
| |
| if (this.dayOfMonth >= 0) { |
| date.setDate(this.dayOfMonth); |
| } else if (this.month >= 0) { |
| // If the month was parsed but dayOfMonth was not, then the current day of |
| // the month shouldn't affect the parsed month. For example, if "Feb2006" |
| // is parse on January 31, the resulting date should be in February, not |
| // March. So, we limit the day of the month to the maximum day within the |
| // parsed month. |
| Date tmp = new Date(date.getYear(), date.getMonth(), 35); |
| int daysInCurrentMonth = 35 - tmp.getDate(); |
| date.setDate(Math.min(daysInCurrentMonth, orgDayOfMonth)); |
| } else { |
| date.setDate(orgDayOfMonth); |
| } |
| |
| // adjust ampm |
| if (this.hours < 0) { |
| this.hours = date.getHours(); |
| } |
| |
| if (this.ampm > 0) { |
| if (this.hours < 12) { |
| this.hours += 12; |
| } |
| } |
| date.setHours(this.hours == 24 && this.midnightIs24 ? 0 : this.hours); |
| |
| if (this.minutes >= 0) { |
| date.setMinutes(this.minutes); |
| } |
| |
| if (this.seconds >= 0) { |
| date.setSeconds(this.seconds); |
| } |
| |
| if (this.milliseconds >= 0) { |
| date.setTime(date.getTime() / 1000 * 1000 + this.milliseconds); |
| } |
| |
| // If strict, verify that the original date fields match the calculated date |
| // fields. We do this before we set the timezone offset, which will skew all |
| // of the dates. |
| // |
| // We don't need to check the day of week as it is guaranteed to be correct |
| // or return false below. |
| if (strict) { |
| if ((this.year > Integer.MIN_VALUE) |
| && ((this.year - JS_START_YEAR) != date.getYear())) { |
| return false; |
| } |
| if ((this.month >= 0) && (this.month != date.getMonth())) { |
| return false; |
| } |
| if ((this.dayOfMonth >= 0) && (this.dayOfMonth != date.getDate())) { |
| return false; |
| } |
| // Times have well defined maximums |
| if (this.hours == 24 && this.midnightIs24) { |
| if (this.ampm > 0) { |
| return false; |
| } |
| } else if (this.hours >= 24) { |
| return false; |
| } else if (this.hours == 0 && this.midnightIs24) { |
| return false; |
| } |
| if (this.minutes >= 60) { |
| return false; |
| } |
| if (this.seconds >= 60) { |
| return false; |
| } |
| if (this.milliseconds >= 1000) { |
| return false; |
| } |
| } |
| |
| // Resolve ambiguous year if needed. |
| if (this.ambiguousYear) { // the two-digit year == the default start year |
| Date defaultCenturyStart = new Date(); |
| defaultCenturyStart.setYear(defaultCenturyStart.getYear() - 80); |
| if (date.before(defaultCenturyStart)) { |
| date.setYear(defaultCenturyStart.getYear() + 100); |
| } |
| } |
| |
| // Date is resolved to the nearest dayOfWeek if date is not explicitly |
| // specified. There is one exception, if the nearest dayOfWeek falls |
| // into a different month, the 2nd nearest dayOfWeek, which is on the |
| // other direction, will be used. |
| if (this.dayOfWeek >= 0) { |
| if (this.dayOfMonth == -1) { |
| // Adjust to the nearest day of the week. |
| int adjustment = (7 + this.dayOfWeek - date.getDay()) % 7; |
| if (adjustment > 3) { |
| adjustment -= 7; |
| } |
| int orgMonth = date.getMonth(); |
| date.setDate(date.getDate() + adjustment); |
| |
| // If the nearest weekday fall into a different month, we will use the |
| // 2nd nearest weekday, which will be on the other direction, and is |
| // sure fall into the same month. |
| if (date.getMonth() != orgMonth) { |
| date.setDate(date.getDate() + (adjustment > 0 ? -7 : 7)); |
| } |
| } else { |
| if (date.getDay() != this.dayOfWeek) { |
| return false; |
| } |
| } |
| } |
| |
| // Adjust time zone. |
| if (this.tzOffset > Integer.MIN_VALUE) { |
| int offset = date.getTimezoneOffset(); |
| date.setTime(date.getTime() + (this.tzOffset - offset) * 60 * 1000); |
| // HBJ date.setTime(date.getTime() + this.tzOffset * 60 * 1000); |
| } |
| |
| return true; |
| } |
| |
| /** |
| * Set ambiguous year field. This flag indicates that a 2 digit years's |
| * century need to be determined by its date/time value. This can only be |
| * resolved after its date/time is known. |
| * |
| * @param ambiguousYear true if it is ambiguous year. |
| */ |
| public void setAmbiguousYear(boolean ambiguousYear) { |
| this.ambiguousYear = ambiguousYear; |
| } |
| |
| /** |
| * Set morning/afternoon field. |
| * |
| * @param ampm ampm value. |
| */ |
| public void setAmpm(int ampm) { |
| this.ampm = ampm; |
| } |
| |
| /** |
| * Set dayOfMonth field. |
| * |
| * @param day dayOfMonth value |
| */ |
| public void setDayOfMonth(int day) { |
| this.dayOfMonth = day; |
| } |
| |
| /** |
| * Set dayOfWeek field. |
| * |
| * @param dayOfWeek day of the week. |
| */ |
| public void setDayOfWeek(int dayOfWeek) { |
| this.dayOfWeek = dayOfWeek; |
| } |
| |
| /** |
| * Set Era field. |
| * |
| * @param era era value being set. |
| */ |
| public void setEra(int era) { |
| this.era = era; |
| } |
| |
| /** |
| * Set hour field. |
| * |
| * @param hours hour value. |
| */ |
| @Override |
| public void setHours(int hours) { |
| this.hours = hours; |
| } |
| |
| /** |
| * Set midnightIs24 field. |
| * |
| * @param midnightIs24 whether an hour value of 24 signifies midnight. |
| */ |
| public void setMidnightIs24(boolean midnightIs24) { |
| this.midnightIs24 = midnightIs24; |
| } |
| |
| /** |
| * Set milliseconds field. |
| * |
| * @param milliseconds milliseconds value. |
| */ |
| public void setMilliseconds(int milliseconds) { |
| this.milliseconds = milliseconds; |
| } |
| |
| /** |
| * Set minute field. |
| * |
| * @param minutes minute value. |
| */ |
| @Override |
| public void setMinutes(int minutes) { |
| this.minutes = minutes; |
| } |
| |
| /** |
| * Set month field. |
| * |
| * @param month month value. |
| */ |
| @Override |
| public void setMonth(int month) { |
| this.month = month; |
| } |
| |
| /** |
| * Set seconds field. |
| * |
| * @param seconds second value. |
| */ |
| @Override |
| public void setSeconds(int seconds) { |
| this.seconds = seconds; |
| } |
| |
| /** |
| * Set timezone offset, in minutes. |
| * |
| * @param tzOffset timezone offset. |
| */ |
| public void setTzOffset(int tzOffset) { |
| this.tzOffset = tzOffset; |
| } |
| |
| /** |
| * Set year field. |
| * |
| * @param value year value. |
| */ |
| @Override |
| public void setYear(int value) { |
| this.year = value; |
| } |
| } |