/* * Copyright (c) 1998-2011 Caucho Technology -- all rights reserved * * This file is part of Resin(R) Open Source * * Each copy or derived work must preserve the copyright notice and this * notice unmodified. * * Resin Open Source is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Resin Open Source is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty * of NON-INFRINGEMENT. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with Resin Open Source; if not, write to the * * Free Software Foundation, Inc. * 59 Temple Place, Suite 330 * Boston, MA 02111-1307 USA * * @author Scott Ferguson */ package com.caucho.util; import com.caucho.vfs.WriteStream; import java.io.IOException; import java.text.DateFormat; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; import java.util.TimeZone; import java.util.logging.Level; import java.util.logging.Logger; /** * Resin Date object */ public class QDate { private static final Logger log = Logger.getLogger(QDate.class.getName()); static final public int YEAR = 0; static final public int MONTH = YEAR + 1; static final public int DAY_OF_MONTH = MONTH + 1; static final public int DAY = DAY_OF_MONTH + 1; static final public int DAY_OF_WEEK = DAY + 1; static final public int HOUR = DAY_OF_WEEK + 1; static final public int MINUTE = HOUR + 1; static final public int SECOND = MINUTE + 1; static final public int MILLISECOND = SECOND + 1; static final public int TIME = MILLISECOND + 1; static final public int TIME_ZONE = TIME + 1; static final long MS_PER_DAY = 24 * 60 * 60 * 1000L; static final long MS_PER_EON = MS_PER_DAY * (365 * 400 + 100 - 3); static final int []DAYS_IN_MONTH = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; static final String []DAY_NAMES = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; static final String []MONTH_NAMES = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; private static final String []SHORT_WEEKDAY = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; private static final String []LONG_WEEKDAY = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" }; private static final String []SHORT_MONTH = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", }; private static final String []LONG_MONTH = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", }; private static TimeZone _localTimeZone = TimeZone.getDefault(); private static TimeZone _gmtTimeZone = TimeZone.getTimeZone("GMT"); private static String _localDstName = _localTimeZone.getDisplayName(true, TimeZone.SHORT); private static String _localStdName = _localTimeZone.getDisplayName(false, TimeZone.SHORT); private static String _gmtDstName = _gmtTimeZone.getDisplayName(true, TimeZone.SHORT); private static String _gmtStdName = _gmtTimeZone.getDisplayName(false, TimeZone.SHORT); // static dates for the static formatting private static QDate _gmtDate = new QDate(false); private static QDate _localDate = new QDate(true); private static final FreeList<QDate> _freeLocalDate = new FreeList<QDate>(8); private static final FreeList<QDate> _freeGmtDate = new FreeList<QDate>(8); private TimeZone _timeZone; private Calendar _calendar; private String _dstName; private String _stdName; private DateFormat _dateFormat; private DateFormat _shortDateFormat; private DateFormat _shortTimeFormat; private Date _date = new Date(); // All times are local private long _localTimeOfEpoch; private long _dayOfEpoch; private long _year; private int _dayOfYear; private long _month; private long _dayOfMonth; private long _hour; private long _minute; private long _second; private long _ms; private boolean _isLeapYear; private long _timeOfDay; private boolean _isDaylightTime; private long _zoneOffset; private String _zoneName; private long _lastTime; private String _lastDate; /** * Creates the date for GMT. */ public QDate() { this(_gmtTimeZone); } /** * Creates the date for GMT. */ public QDate(long time) { this(_localTimeZone); setGMTTime(time); } /** * Creates the date form local or GMT. */ public QDate(boolean isLocal) { this(isLocal ? _localTimeZone : _gmtTimeZone); } /** * Creates the date from local or GMT. */ public QDate(TimeZone zone) { _timeZone = zone; if (zone == _gmtTimeZone) { _stdName = _gmtStdName; _dstName = _gmtDstName; } else if (zone == _localTimeZone) { _stdName = _localStdName; _dstName = _localDstName; } else { _stdName = _timeZone.getDisplayName(false, TimeZone.SHORT); _dstName = _timeZone.getDisplayName(true, TimeZone.SHORT); } _calendar = new GregorianCalendar(_timeZone); setLocalTime(Alarm.getCurrentTime()); } /** * Creates the date from local or GMT. */ public QDate(TimeZone zone, long now) { _timeZone = zone; if (zone == _gmtTimeZone) { _stdName = _gmtStdName; _dstName = _gmtDstName; } else if (zone == _localTimeZone) { _stdName = _localStdName; _dstName = _localDstName; } else { _stdName = _timeZone.getDisplayName(false, TimeZone.SHORT); _dstName = _timeZone.getDisplayName(true, TimeZone.SHORT); } _calendar = new GregorianCalendar(_timeZone); if (zone == _gmtTimeZone) setGMTTime(now); else setLocalTime(now); } /** * Creates the date for the local time zone. * * @see #setDate(long, long, long) */ public QDate(long year, long month, long dayOfMonth) { this(_localTimeZone); setDate(year, month, dayOfMonth); } /** * Creates a local calendar. */ public static QDate createLocal() { return new QDate(true); } public static QDate allocateLocalDate() { QDate date = _freeLocalDate.allocate(); if (date == null) date = new QDate(true); return date; } public static void freeLocalDate(QDate date) { _freeLocalDate.free(date); } public static QDate allocateGmtDate() { QDate date = _freeGmtDate.allocate(); if (date == null) date = new QDate(false); return date; } public static void freeGmtDate(QDate date) { _freeGmtDate.free(date); } /** * Sets the time in milliseconds since the epoch and calculate * the internal variables. */ public void setLocalTime(long time) { // If this is a local time zone date, just set the time if (_timeZone != _gmtTimeZone) { calculateSplit(time); } // If this is a GMT date, convert from local to GMT else { calculateSplit(time - _localTimeZone.getRawOffset()); try { long offset = _localTimeZone.getOffset(GregorianCalendar.AD, (int) _year, (int) _month, (int) _dayOfMonth + 1, getDayOfWeek(), (int) _timeOfDay); calculateSplit(time - offset); } catch (Exception e) { log.log(Level.FINE, e.toString(), e); } } } /** * Returns the time in milliseconds since the epoch. */ public long getLocalTime() { // If this is a local time zone date, just set the time if (_timeZone != _gmtTimeZone) { return _localTimeOfEpoch; } // If this is a GMT date, convert from local to GMT else { long offset = _localTimeZone.getOffset(GregorianCalendar.AD, (int) _year, (int) _month, (int) _dayOfMonth + 1, getDayOfWeek(), (int) _timeOfDay); return _localTimeOfEpoch + offset; } } /** * Return the current time as a java.util.Calendar. **/ public Calendar getCalendar() { return _calendar; } /** * Sets the time in milliseconds since the epoch and calculate * the internal variables. */ public void setGMTTime(long time) { calculateSplit(time + _timeZone.getOffset(time)); } /** * Returns the time in milliseconds since the epoch. */ public long getGMTTime() { return _localTimeOfEpoch - _zoneOffset; } /** * Returns the milliseconds since the beginning of the day. */ public long getTimeOfDay() { return _timeOfDay; } /** * Returns the year. */ public int getYear() { return (int) _year; } /** * Sets the year, recalculating the time since epoch. */ public void setYear(int year) { _year = year; calculateJoin(); calculateSplit(_localTimeOfEpoch); } /** * Returns the month in the year. */ public int getMonth() { return (int) _month; } /** * Sets the month in the year. */ public void setMonth(int month) { if (month < 0 || DAYS_IN_MONTH.length <= month) return; _month = month; if (DAYS_IN_MONTH[month] <= _dayOfMonth) _dayOfMonth = DAYS_IN_MONTH[month] - 1; calculateJoin(); calculateSplit(_localTimeOfEpoch); } /** * Returns the day of the month, based on 1 for the first of the month. */ public int getDayOfMonth() { return (int) _dayOfMonth + 1; } /** * sets the day of the month based on 1 for the first of the month. */ public void setDayOfMonth(int day) { _dayOfMonth = day - 1; calculateJoin(); calculateSplit(_localTimeOfEpoch); } /** * Returns the day of the month, based on 1 for the first of the month. */ public int getDaysInMonth() { if (_month == 1) return _isLeapYear ? 29 : 28; else return DAYS_IN_MONTH[(int) _month]; } /** * Returns the day of the week. */ public int getDayOfWeek() { return (int) ((_dayOfEpoch % 7) + 11) % 7 + 1; } /** * Returns the day of the year, based on 0 for January 1. */ public int getDayOfYear() { return (int) _dayOfYear; } /** * Returns the hour. */ public int getHour() { return (int) _hour; } /** * Sets the hour, recalculating the localTimeOfEpoch. */ public void setHour(int hour) { _hour = hour; calculateJoin(); calculateSplit(_localTimeOfEpoch); } /** * Returns the minute. */ public int getMinute() { return (int) _minute; } /** * Sets the minute, recalculating the localTimeOfEpoch. */ public void setMinute(int minute) { _minute = minute; calculateJoin(); calculateSplit(_localTimeOfEpoch); } /** * Returns the second. */ public int getSecond() { return (int) _second; } /** * Sets the second, recalculating the localTimeOfEpoch. */ public void setSecond(int second) { _second = second; calculateJoin(); calculateSplit(_localTimeOfEpoch); } /** * Returns the millisecond. */ public long getMillisecond() { return _ms; } /** * Sets the millisecond, recalculating the localTimeOfEpoch. */ public void setMillisecond(long millisecond) { _ms = millisecond; calculateJoin(); calculateSplit(_localTimeOfEpoch); } /** * Returns the time zone offset for that particular day. */ public long getZoneOffset() { return _zoneOffset; } /** * Returns the name of the timezone */ public String getZoneName() { return _zoneName; } /** * Returns true for DST */ public boolean isDST() { return _isDaylightTime; } /** * Returns the current time zone. */ public TimeZone getLocalTimeZone() { return _timeZone; } /** * Returns the week in the year. */ public int getWeek() { int dow4th = (int) ((_dayOfEpoch - _dayOfYear + 3) % 7 + 10) % 7; int ww1monday = 3 - dow4th; if (_dayOfYear < ww1monday) return 53; int week = (_dayOfYear - ww1monday) / 7 + 1; if (_dayOfYear >= 360) { int days = 365 + (_isLeapYear ? 1 : 0); long nextNewYear = (_dayOfEpoch - _dayOfYear + days); int dowNext4th = (int) ((nextNewYear + 3) % 7 + 10) % 7; int nextWw1Monday = 3 - dowNext4th; if (days <= _dayOfYear - nextWw1Monday) return 1; } return week; } /** * Gets values based on a field. */ public long get(int field) { switch (field) { case TIME: return getLocalTime(); case YEAR: return getYear(); case MONTH: return getMonth(); case DAY_OF_MONTH: return getDayOfMonth(); case DAY: return getDayOfWeek(); case DAY_OF_WEEK: return getDayOfWeek(); case HOUR: return getHour(); case MINUTE: return getMinute(); case SECOND: return getSecond(); case MILLISECOND: return getMillisecond(); case TIME_ZONE: return getZoneOffset() / 1000; default: return Long.MAX_VALUE; } } /** * Sets values based on a field. */ public long set(int field, long value) { switch (field) { case YEAR: setYear((int) value); break; case MONTH: setMonth((int) value); break; case DAY_OF_MONTH: setDayOfMonth((int) value); break; case HOUR: setHour((int) value); break; case MINUTE: setMinute((int) value); break; case SECOND: setSecond((int) value); break; case MILLISECOND: setMillisecond(value); break; default: throw new RuntimeException(); } return _localTimeOfEpoch; } /* * Mon, 17 Jan 1994 11:14:55 -0500 (EST) */ public String printDate() { if (_lastDate != null && _lastTime == _localTimeOfEpoch) return _lastDate; CharBuffer cb = new CharBuffer(); printDate(cb); _lastDate = cb.toString(); _lastTime = _localTimeOfEpoch; return _lastDate; } /* * Mon, 17 Jan 1994 11:14:55 -0500 (EST) */ public void printDate(CharBuffer cb) { cb.append(DAY_NAMES[(int) (_dayOfEpoch % 7 + 11) % 7]); cb.append(", "); cb.append((_dayOfMonth + 1) / 10); cb.append((_dayOfMonth + 1) % 10); cb.append(" "); cb.append(MONTH_NAMES[(int) _month]); cb.append(" "); cb.append(_year); cb.append(" "); cb.append((_timeOfDay / 36000000L) % 10); cb.append((_timeOfDay / 3600000L) % 10); cb.append(":"); cb.append((_timeOfDay / 600000L) % 6); cb.append((_timeOfDay / 60000L) % 10); cb.append(":"); cb.append((_timeOfDay / 10000L) % 6); cb.append((_timeOfDay / 1000L) % 10); if (_zoneName == null || _zoneName.equals("GMT")) { cb.append(" GMT"); return; } long offset = _zoneOffset; if (offset < 0) { cb.append(" -"); offset = - offset; } else cb.append(" +"); cb.append((offset / 36000000) % 10); cb.append((offset / 3600000) % 10); cb.append((offset / 600000) % 6); cb.append((offset / 60000) % 10); cb.append(" ("); cb.append(_zoneName); cb.append(")"); } /** * Prints the date to a stream. */ public void printDate(WriteStream os) throws IOException { os.print(DAY_NAMES[(int) (_dayOfEpoch % 7 + 11) % 7]); os.write(','); os.write(' '); os.print((_dayOfMonth + 1) / 10); os.print((_dayOfMonth + 1) % 10); os.write(' '); os.print(MONTH_NAMES[(int) _month]); os.write(' '); os.print(_year); os.write(' '); os.print((_timeOfDay / 36000000) % 10); os.print((_timeOfDay / 3600000) % 10); os.write(':'); os.print((_timeOfDay / 600000) % 6); os.print((_timeOfDay / 60000) % 10); os.write(':'); os.print((_timeOfDay / 10000) % 6); os.print((_timeOfDay / 1000) % 10); if (_zoneName == null) { os.print(" GMT"); return; } long offset = _zoneOffset; if (offset < 0) { os.write(' '); os.write('-'); offset = - offset; } else { os.write(' '); os.write('+'); } os.print((offset / 36000000) % 10); os.print((offset / 3600000) % 10); os.print((offset / 600000) % 6); os.print((offset / 60000) % 10); os.write(' '); os.write('('); os.print(_zoneName); os.write(')'); } /* * Mon, 17 Jan 1994 11:14:55 -0500 */ public void printRFC2822(CharBuffer cb) { cb.append(DAY_NAMES[(int) (_dayOfEpoch % 7 + 11) % 7]); cb.append(", "); cb.append((_dayOfMonth + 1) / 10); cb.append((_dayOfMonth + 1) % 10); cb.append(" "); cb.append(MONTH_NAMES[(int) _month]); cb.append(" "); cb.append(_year); cb.append(" "); cb.append((_timeOfDay / 36000000L) % 10); cb.append((_timeOfDay / 3600000L) % 10); cb.append(":"); cb.append((_timeOfDay / 600000L) % 6); cb.append((_timeOfDay / 60000L) % 10); cb.append(":"); cb.append((_timeOfDay / 10000L) % 6); cb.append((_timeOfDay / 1000L) % 10); long offset = _zoneOffset; if (offset < 0) { cb.append(" -"); offset = - offset; } else cb.append(" +"); cb.append((offset / 36000000) % 10); cb.append((offset / 3600000) % 10); cb.append((offset / 600000) % 6); cb.append((offset / 60000) % 10); } /** * Prints the time in ISO 8601 */ public String printISO8601() { StringBuilder sb = new StringBuilder(); if (_year > 0) { sb.append((_year / 1000) % 10); sb.append((_year / 100) % 10); sb.append((_year / 10) % 10); sb.append(_year % 10); sb.append('-'); sb.append(((_month + 1) / 10) % 10); sb.append((_month + 1) % 10); sb.append('-'); sb.append(((_dayOfMonth + 1) / 10) % 10); sb.append((_dayOfMonth + 1) % 10); } long time = _timeOfDay / 1000; long ms = _timeOfDay % 1000; sb.append('T'); sb.append((time / 36000) % 10); sb.append((time / 3600) % 10); sb.append(':'); sb.append((time / 600) % 6); sb.append((time / 60) % 10); sb.append(':'); sb.append((time / 10) % 6); sb.append((time / 1) % 10); if (ms != 0) { sb.append('.'); sb.append((ms / 100) % 10); sb.append((ms / 10) % 10); sb.append(ms % 10); } if (_zoneName == null) { sb.append("Z"); return sb.toString(); } // server/1471 - XXX: was commented out long offset = _zoneOffset; if (offset < 0) { sb.append("-"); offset = - offset; } else sb.append("+"); sb.append((offset / 36000000) % 10); sb.append((offset / 3600000) % 10); sb.append(':'); sb.append((offset / 600000) % 6); sb.append((offset / 60000) % 10); return sb.toString(); } /** * Prints just the date component of ISO 8601 */ public String printISO8601Date() { CharBuffer cb = new CharBuffer(); if (_year > 0) { cb.append((_year / 1000) % 10); cb.append((_year / 100) % 10); cb.append((_year / 10) % 10); cb.append(_year % 10); cb.append('-'); cb.append(((_month + 1) / 10) % 10); cb.append((_month + 1) % 10); cb.append('-'); cb.append(((_dayOfMonth + 1) / 10) % 10); cb.append((_dayOfMonth + 1) % 10); } return cb.toString(); } /** * Formats a date. * * @param time the time to format * @param format the format string */ public synchronized static String formatGMT(long gmtTime, String format) { _gmtDate.setGMTTime(gmtTime); return _gmtDate.format(new CharBuffer(), format).toString(); } /** * Formats a date, using the default time format. * * @param time the time to format */ public synchronized static String formatGMT(long gmtTime) { _gmtDate.setGMTTime(gmtTime); return _gmtDate.printDate(); } /** * Formats a time in the local time zone. * * @param time in milliseconds, GMT, from the epoch. * @param format formatting string. */ public synchronized static String formatLocal(long gmtTime, String format) { _localDate.setGMTTime(gmtTime); return _localDate.format(new CharBuffer(), format).toString(); } /** * Formats a time in the local time zone, using the default format. * * @param time in milliseconds, GMT, from the epoch. */ public synchronized static String formatLocal(long gmtTime) { _localDate.setGMTTime(gmtTime); return _localDate.printDate(); } /** * Formats a time in the local time zone. * * @param time in milliseconds, GMT, from the epoch. * @param format formatting string. */ public synchronized static CharBuffer formatLocal(CharBuffer cb, long gmtTime, String format) { _localDate.setGMTTime(gmtTime); return _localDate.format(cb, format); } public synchronized static String formatISO8601(long gmtTime) { if (_gmtDate == null) _gmtDate = new QDate(); _gmtDate.setGMTTime(gmtTime); return _gmtDate.printISO8601(); } /** * Global date must be synchronized before you can do anything on it. */ public static QDate getGlobalDate() { return _localDate; } /** * Formats the current date. */ public String format(String format) { CharBuffer cb = new CharBuffer(); return format(cb, format).close(); } /** * Format the date using % escapes: * * <table> * <tr><td>%a<td>day of week (short) * <tr><td>%A<td>day of week (verbose) * <tr><td>%b<td>day of month (short) * <tr><td>%B<td>day of month (verbose) * <tr><td>%c<td>Java locale date * <tr><td>%d<td>day of month (two-digit) * <tr><td>%H<td>24-hour (two-digit) * <tr><td>%I<td>12-hour (two-digit) * <tr><td>%j<td>day of year (three-digit) * <tr><td>%m<td>month (two-digit) * <tr><td>%M<td>minutes * <tr><td>%p<td>am/pm * <tr><td>%S<td>seconds * <tr><td>%s<td>milliseconds * <tr><td>%x<td>Java locale short date * <tr><td>%X<td>Java locale short time * <tr><td>%W<td>week in year (three-digit) * <tr><td>%w<td>day of week (one-digit) * <tr><td>%y<td>year (two-digit) * <tr><td>%Y<td>year (four-digit) * <tr><td>%Z<td>time zone (name) * <tr><td>%z<td>time zone (+/-0800) * </table> */ public CharBuffer format(CharBuffer cb, String format) { int length = format.length(); for (int i = 0; i < length; i++) { char ch = format.charAt(i); if (ch != '%') { cb.append(ch); continue; } switch (format.charAt(++i)) { case 'a': cb.append(SHORT_WEEKDAY[getDayOfWeek() - 1]); break; case 'A': cb.append(LONG_WEEKDAY[getDayOfWeek() - 1]); break; case 'h': case 'b': cb.append(SHORT_MONTH[(int) _month]); break; case 'B': cb.append(LONG_MONTH[(int) _month]); break; case 'c': cb.append(printLocaleDate()); break; case 'd': cb.append((_dayOfMonth + 1) / 10); cb.append((_dayOfMonth + 1) % 10); break; case 'D': cb.append((_month + 1) / 10); cb.append((_month + 1) % 10); cb.append('/'); cb.append((_dayOfMonth + 1) / 10); cb.append((_dayOfMonth + 1) % 10); cb.append('/'); cb.append(_year / 10 % 10); cb.append(_year % 10); break; case 'e': if ((_dayOfMonth + 1) / 10 == 0) cb.append(' '); else cb.append((_dayOfMonth + 1) / 10); cb.append((_dayOfMonth + 1) % 10); break; // ISO year case 'H': { int hour = (int) (_timeOfDay / 3600000) % 24; cb.append(hour / 10); cb.append(hour % 10); break; } case 'I': { int hour = (int) (_timeOfDay / 3600000) % 12; if (hour == 0) hour = 12; cb.append(hour / 10); cb.append(hour % 10); break; } case 'j': cb.append((_dayOfYear + 1) / 100); cb.append((_dayOfYear + 1) / 10 % 10); cb.append((_dayOfYear + 1) % 10); break; case 'm': cb.append((_month + 1) / 10); cb.append((_month + 1) % 10); break; case 'M': cb.append((_timeOfDay / 600000) % 6); cb.append((_timeOfDay / 60000) % 10); break; case 'p': { int hour = (int) (_timeOfDay / 3600000) % 24; if (hour < 12) cb.append("am"); else cb.append("pm"); break; } case 'S': cb.append((_timeOfDay / 10000) % 6); cb.append((_timeOfDay / 1000) % 10); break; case 's': cb.append((_timeOfDay / 100) % 10); cb.append((_timeOfDay / 10) % 10); cb.append(_timeOfDay % 10); break; case 'T': { int hour = (int) (_timeOfDay / 3600000) % 24; cb.append(hour / 10); cb.append(hour % 10); cb.append(':'); cb.append((_timeOfDay / 600000) % 6); cb.append((_timeOfDay / 60000) % 10); cb.append(':'); cb.append((_timeOfDay / 10000) % 6); cb.append((_timeOfDay / 1000) % 10); break; } case 'W': int week = getWeek(); cb.append((week + 1) / 10); cb.append((week + 1) % 10); break; case 'w': cb.append(getDayOfWeek() - 1); break; case 'x': cb.append(printShortLocaleDate()); break; case 'X': cb.append(printShortLocaleTime()); break; case 'y': cb.append(_year / 10 % 10); cb.append(_year % 10); break; case 'Y': cb.append(_year / 1000 % 10); cb.append(_year / 100 % 10); cb.append(_year / 10 % 10); cb.append(_year % 10); break; case 'Z': if (_zoneName == null) cb.append("GMT"); else cb.append(_zoneName); break; case 'z': long offset = _zoneOffset; if (offset < 0) { cb.append("-"); offset = - offset; } else cb.append("+"); cb.append((offset / 36000000) % 10); cb.append((offset / 3600000) % 10); cb.append((offset / 600000) % 6); cb.append((offset / 60000) % 10); break; case '%': cb.append('%'); break; default: cb.append(format.charAt(i)); } } return cb; } /* * XXX: buggy (Because cal is buggy), may have to implement the sdf */ public String printLocaleDate() { _date.setTime(_localTimeOfEpoch); // SimpleDateFormat sdf = new SimpleDateFormat(); // System.out.println("" + sdf.toPattern()); if (_dateFormat == null) _dateFormat = DateFormat.getInstance(); return _dateFormat.format(_date); } /** * Returns a date in M/dd/yy format (i.e. 11/30/69 in US locale). */ public String printShortLocaleDate() { _date.setTime(_localTimeOfEpoch); if (_shortDateFormat == null) _shortDateFormat = DateFormat.getDateInstance(DateFormat.SHORT); return _shortDateFormat.format(_date); } /** * Returns a date in H:mm:ss PM format. */ public String printShortLocaleTime() { _date.setTime(_localTimeOfEpoch); if (_shortTimeFormat == null) _shortTimeFormat = DateFormat.getTimeInstance(DateFormat.SHORT); return _shortTimeFormat.format(_date); } /* * XXX: okay, this is vile. * Mon, 17 Jan 1994 11:14:55 -0500 (EST) * * In local time */ public long parseLocalDate(String string) throws Exception { long time = parseDate(string); synchronized (this) { setLocalTime(time); return getGMTTime(); } } /* * XXX: okay, this is vile. * Mon, 17 Jan 1994 11:14:55 -0500 (EST) * * In GMT time */ public long parseDate(String string) throws Exception { try { int strlen = string.length(); if (strlen == 0) return 0; int i = skipWhitespace(string, strlen, 0); int ch = string.charAt(i); if (ch >= '0' && ch <= '9' || (ch == 'T' && i + 1 < strlen && string.charAt(i + 1) >= '0' && string.charAt(i + 1) <= '9')) return parseISO8601Date(string, i); CharBuffer cb = new CharBuffer(); i = scan(string, 0, cb, true); if (cb.length() == 0 || ! Character.isDigit(cb.charAt(0))) i = scan(string, i, cb, true); int dayOfMonth = parseInt(cb); i = scan(string, i, cb, true); String smonth = cb.toString(); int month; for (month = 0; month < 12; month++) { if (MONTH_NAMES[(int) month].equalsIgnoreCase(smonth)) break; } if (month == 12) throw new Exception("Unexpected month: " + month); i = scan(string, i, cb, true); int year = parseInt(cb); if (cb.length() < 3 && year < 50) year += 2000; else if (cb.length() < 3 && year < 100) year += 1900; i = scan(string, i, cb, false); long timeOfDay = parseInt(cb) * 3600000; i = scan(string, i, cb, false); timeOfDay += parseInt(cb) * 60000; i = scan(string, i, cb, false); timeOfDay += parseInt(cb) * 1000; // XXX: gross hack if (year <= 1600) dayOfMonth--; long time = (MS_PER_DAY * (yearToDayOfEpoch(year) + monthToDayOfYear(month, isLeapYear(year)) + dayOfMonth - 1) + timeOfDay); try { i = scan(string, i, cb, false); for (int j = 0; j < cb.length(); j++) { if ((ch = cb.charAt(j)) == ';' || ch == ' ') cb.setLength(j); } ch = cb.length() > 0 ? cb.charAt(0) : 0; if (ch == '-' || ch == '+' || ch >= '0' && ch <= '9') { long zoneOffset; zoneOffset = parseInt(cb); zoneOffset = 60000 * (60 * (zoneOffset / 100) + zoneOffset % 100); time -= zoneOffset; setGMTTime(time); } else if (cb.equalsIgnoreCase("gmt") || cb.equalsIgnoreCase("utc")) { setGMTTime(time); } else { setLocalTime(time); } } catch (Exception e) { log.log(Level.FINER, e.toString(), e); } return _localTimeOfEpoch - _zoneOffset; } catch (Exception e) { log.log(Level.FINER, e.toString(), e); return Long.MAX_VALUE; } } private long parseISO8601Date(String string, int pos) throws Exception { int strlen = string.length(); int year = 0; char ch = string.charAt(pos); if ('0' <= ch && ch <= '9') { year = scanISOInt(string, pos, strlen, 4); pos += 4; } if (pos < strlen && string.charAt(pos) == '-') pos++; int month = 0; if (pos < strlen && '0' <= (ch = string.charAt(pos)) && ch <= '9') { month = scanISOInt(string, pos, strlen, 2); month--; pos += 2; } else if (ch == 'W') return Long.MAX_VALUE; if (pos < strlen && string.charAt(pos) == '-') pos++; int day = 0; if (pos < strlen && '0' <= (ch = string.charAt(pos)) && ch <= '9') { day = scanISOInt(string, pos, strlen, 2); day--; pos += 2; } int hour = 0; int minute = 0; int second = 0; int millisecond = 0; if (pos < strlen && string.charAt(pos) == 'T') { pos++; if (pos < strlen && '0' <= (ch = string.charAt(pos)) && ch <= '9') { hour = scanISOInt(string, pos, strlen, 2); pos += 2; } // XXX: fractions can technically be used anywhere by using a // , or . instead of a : // e.g. 14:30,5 == 14:30:30 if (pos < strlen && string.charAt(pos) == ':') pos++; if (pos < strlen && '0' <= (ch = string.charAt(pos)) && ch <= '9') { minute = scanISOInt(string, pos, strlen, 2); pos += 2; } if (pos < strlen && string.charAt(pos) == ':') pos++; if (pos < strlen && '0' <= (ch = string.charAt(pos)) && ch <= '9') { second = scanISOInt(string, pos, strlen, 2); pos += 2; } if (pos < strlen && (string.charAt(pos) == '.' || string.charAt(pos) == ',')) { pos++; // XXX: fractions can be any strlen, not just 3 millisecond = scanISOInt(string, pos, strlen, 3); pos += 3; } } long timeOfDay = millisecond + 1000 * (second + 60 * (minute + 60 * hour)); // XXX: gross hack if (year <= 1600) day--; long time = (MS_PER_DAY * (yearToDayOfEpoch(year) + monthToDayOfYear(month, isLeapYear(year)) + day) + timeOfDay); if (strlen <= pos) { setLocalTime(time); return _localTimeOfEpoch; } if (string.charAt(pos) == 'Z') { pos++; } else if (string.charAt(pos) == '-' || string.charAt(pos) == '+') { int sign = -1; if (string.charAt(pos) == '-') sign = 1; pos++; int tzHour = scanISOInt(string, pos, strlen, 2); pos += 2; int tzMinute = 0; if (pos < strlen && string.charAt(pos) == ':') pos++; if (pos < strlen && '0' <= (ch = string.charAt(pos)) && ch <= '9') { tzMinute = scanISOInt(string, pos, strlen, 2); pos += 2; } time += sign * 1000 * (60 * (tzMinute + 60 * tzHour)); } else { setLocalTime(time); return _localTimeOfEpoch; } pos = skipWhitespace(string, strlen, pos); if (pos < strlen) throw new Exception("extra junk at end of ISO date"); setGMTTime(time); return _localTimeOfEpoch; } /** * Based on the year, return the number of days since the epoch. */ private long yearToDayOfEpoch(long year) { if (year > 0) { year -= 1601; return (365 * year + year / 4 - year / 100 + year / 400 - ((1970 - 1601) * 365 + (1970 - 1601) / 4 - 3)); } else { year = 2000 - year; return ((2000 - 1970) * 365 + (2000 - 1970) / 4 - (365 * year + year / 4 - year / 100 + year / 400)); } } /** * Calculates the day of the year for the beginning of the month. */ private long monthToDayOfYear(long month, boolean isLeapYear) { long day = 0; for (int i = 0; i < month && i < 12; i++) { day += DAYS_IN_MONTH[i]; if (i == 1 && isLeapYear) day++; } return day; } /** * Returns true if the given year is a leap year. */ private boolean isLeapYear(long year) { return ! ((year % 4) != 0 || (year % 100) == 0 && (year % 400) != 0); } private int scanISOInt(String string, int pos, int length, int digits) throws Exception { int value = 0; for (int i = 0; i < digits; i++) { if (pos >= length) throw new Exception("expected ISO8601 digit"); char ch = string.charAt(pos++); if ('0' <= ch && ch <= '9') value = 10 * value + ch - '0'; else throw new Exception("expected ISO8601 digit"); } return value; } private int skipWhitespace(String string, int strlen, int i) { char ch; for (; i < strlen && ((ch = string.charAt(i)) == ' ' || ch == '\t' || ch == '\n' || ch == '\r'); i++) { } return i; } /* * Scan to whitespace or ':' */ private int scan(String string, int i, CharBuffer cb, boolean dash) throws Exception { char ch; cb.setLength(0); int strlen = string.length(); for (; i < strlen; i++) { if (! Character.isWhitespace(ch = string.charAt(i)) && (ch != ':' && (! dash || ch != '-'))) break; } for (; i < strlen; i++) { if (! Character.isWhitespace(ch = string.charAt(i)) && (ch != ':' && (! dash || ch != '-'))) cb.append((char) ch); else break; } if (cb.length() == 0) throw new Exception(); return i; } private int parseInt(CharBuffer cb) throws Exception { int value = 0; int sign = 1; for (int i = 0; i < cb.length(); i++) { int ch = cb.charAt(i); if (i == 0 && ch == '-') sign = -1; else if (i == 0 && ch == '+') { } else if (ch >= '0' && ch <= '9') value = 10 * value + ch - '0'; else throw new Exception(); } return sign * value; } /** * Sets date in the local time. * * @param year * @param month where January = 0 * @param day day of month where the 1st = 1 */ public long setDate(long year, long month, long day) { year += (long) Math.floor(month / 12.0); month -= (long) 12 * Math.floor(month / 12.0); _year = year; _month = month; _dayOfMonth = day - 1; calculateJoin(); calculateSplit(_localTimeOfEpoch); return _localTimeOfEpoch; } public long setTime(long hour, long minute, long second, long ms) { _hour = hour; _minute = minute; _second = second; _ms = ms; calculateJoin(); calculateSplit(_localTimeOfEpoch); return _localTimeOfEpoch; } /** * Calculate and set the calendar components based on the given time. * * @param localTime local time in milliseconds since the epoch */ private void calculateSplit(long localTime) { _localTimeOfEpoch = localTime; _dayOfEpoch = divFloor(_localTimeOfEpoch, MS_PER_DAY); _timeOfDay = _localTimeOfEpoch - MS_PER_DAY * _dayOfEpoch; calculateYear(); calculateMonth(); _hour = _timeOfDay / 3600000; _minute = _timeOfDay / 60000 % 60; _second = _timeOfDay / 1000 % 60; _ms = _timeOfDay % 1000; if (_timeZone == _gmtTimeZone) { _isDaylightTime = false; _zoneName = _stdName; _zoneOffset = 0; } else { // server/1470 long tempOffset = _timeZone.getOffset(_localTimeOfEpoch); _zoneOffset = _timeZone.getOffset(_localTimeOfEpoch - tempOffset); if (_zoneOffset == _timeZone.getRawOffset()) { _isDaylightTime = false; _zoneName = _stdName; } else { _isDaylightTime = true; _zoneName = _dstName; } } _calendar.setTimeInMillis(_localTimeOfEpoch); } /** * Calculates the year, the dayOfYear and whether this is a leap year * from the current days since the epoch. */ private void calculateYear() { long days = _dayOfEpoch; // shift to using 1601 as a base days += (1970 - 1601) * 365 + (1970 - 1601) / 4 - 3; long n400 = divFloor(days, 400 * 365 + 100 - 3); days -= n400 * (400 * 365 + 100 - 3); long n100 = divFloor(days, 100 * 365 + 25 - 1); if (n100 == 4) n100 = 3; days -= n100 * (100 * 365 + 25 - 1); long n4 = divFloor(days, 4 * 365 + 1); if (n4 == 25) n4 = 24; days -= n4 * (4 * 365 + 1); long n1 = divFloor(days, 365); if (n1 == 4) n1 = 3; _year = 400 * n400 + 100 * n100 + 4 * n4 + n1 + 1601; _dayOfYear = (int) (days - 365 * n1); _isLeapYear = isLeapYear(_year); } public boolean isLeapYear() { return _isLeapYear; } /** * Calculates the month based on the day of the year. */ private void calculateMonth() { _dayOfMonth = _dayOfYear; for (_month = 0; _month < 12; _month++) { if (_month == 1 && _isLeapYear) { if (_dayOfMonth < 29) return; else _dayOfMonth -= 29; } else if (_dayOfMonth < DAYS_IN_MONTH[(int) _month]) return; else _dayOfMonth -= DAYS_IN_MONTH[(int) _month]; } } /** * Based on the current data, calculate the time since the epoch. * * @return time since the epoch, given the calendar components */ private long calculateJoin() { _year += divFloor(_month, 12); _month -= 12 * divFloor(_month, 12); _localTimeOfEpoch = MS_PER_DAY * (yearToDayOfEpoch(_year) + monthToDayOfYear(_month, isLeapYear(_year)) + _dayOfMonth); _localTimeOfEpoch += _ms + 1000 * (_second + 60 * (_minute + 60 * _hour)); return _localTimeOfEpoch; } private long divFloor(long n, long d) { if (n > 0) return n / d; else return (n - d + 1) / d; } @Override public Object clone() { QDate newObj = new QDate(_timeZone); newObj.calculateSplit(_localTimeOfEpoch); return newObj; } @Override public String toString() { return "QDate[" + printDate() + "]"; } }