blob: 1c30802df650a503406c32134c7269de908ae138 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.codehaus.groovy.runtime;
import groovy.lang.Closure;
import groovy.lang.GroovyRuntimeException;
import java.sql.Timestamp;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.TimeZone;
/**
* This class defines new groovy methods which appear on normal JDK
* Date and Calendar classes inside the Groovy environment.
*/
public class DateGroovyMethods extends DefaultGroovyMethodsSupport {
/**
* Support the subscript operator for a Date.
*
* @param self a Date
* @param field a Calendar field, e.g. MONTH
* @return the value for the given field, e.g. FEBRUARY
* @see java.util.Calendar
* @since 1.5.5
*/
public static int getAt(Date self, int field) {
Calendar cal = Calendar.getInstance();
cal.setTime(self);
return cal.get(field);
}
/**
* Convert a Date to a Calendar.
*
* @param self a Date
* @return a Calendar corresponding to the given Date
* @since 1.7.6
*/
public static Calendar toCalendar(Date self) {
Calendar cal = Calendar.getInstance();
cal.setTime(self);
return cal;
}
/**
* Support the subscript operator for a Calendar.
*
* @param self a Calendar
* @param field a Calendar field, e.g. MONTH
* @return the value for the given field, e.g. FEBRUARY
* @see java.util.Calendar
* @since 1.7.3
*/
public static int getAt(Calendar self, int field) {
return self.get(field);
}
/**
* Support the subscript operator for mutating a Calendar.
* Example usage:
* <pre>
* import static java.util.Calendar.*
* def cal = Calendar.instance
* cal[DAY_OF_WEEK] = MONDAY
* cal[MONTH] = MARCH
* println cal.time // A Monday in March
* </pre>
*
* @param self A Calendar
* @param field A Calendar field, e.g. MONTH
* @param value The value for the given field, e.g. FEBRUARY
* @see java.util.Calendar#set(int, int)
* @since 1.7.3
*/
public static void putAt(Calendar self, int field, int value) {
self.set(field, value);
}
/**
* Support the subscript operator for mutating a Date.
*
* @param self A Date
* @param field A Calendar field, e.g. MONTH
* @param value The value for the given field, e.g. FEBRUARY
* @see #putAt(java.util.Calendar, int, int)
* @see java.util.Calendar#set(int, int)
* @since 1.7.3
*/
public static void putAt(Date self, int field, int value) {
Calendar cal = Calendar.getInstance();
cal.setTime(self);
putAt(cal, field, value);
self.setTime(cal.getTimeInMillis());
}
/**
* Support mutating a Calendar with a Map.
* <p>
* The map values are the normal values provided as the
* second parameter to <code>java.util.Calendar#set(int, int)</code>.
* The keys can either be the normal fields values provided as
* the first parameter to that method or one of the following Strings:
* <table border="1" cellpadding="4">
* <caption>Calendar index values</caption>
* <tr><td>year</td><td>Calendar.YEAR</td></tr>
* <tr><td>month</td><td>Calendar.MONTH</td></tr>
* <tr><td>date</td><td>Calendar.DATE</td></tr>
* <tr><td>dayOfMonth</td><td>Calendar.DATE</td></tr>
* <tr><td>hourOfDay</td><td>Calendar.HOUR_OF_DAY</td></tr>
* <tr><td>minute</td><td>Calendar.MINUTE</td></tr>
* <tr><td>second</td><td>Calendar.SECOND</td></tr>
* </table>
* Example usage:
* <pre>
* import static java.util.Calendar.*
* def cal = Calendar.instance
* def m = [:]
* m[YEAR] = 2010
* m[MONTH] = DECEMBER
* m[DATE] = 25
* cal.set(m)
* println cal.time // Christmas 2010
*
* cal.set(year:2011, month:DECEMBER, date:25)
* println cal.time // Christmas 2010
* </pre>
*
* @param self A Calendar
* @param updates A Map of Calendar keys and values
* @see java.util.Calendar#set(int, int)
* @see java.util.Calendar#set(int, int, int, int, int, int)
* @since 1.7.3
*/
public static void set(Calendar self, Map<Object, Integer> updates) {
for (Map.Entry<Object, Integer> entry : updates.entrySet()) {
Object key = entry.getKey();
if (key instanceof String) key = CAL_MAP.get(key);
if (key instanceof Integer) self.set((Integer) key, entry.getValue());
}
}
/**
* Legacy alias for copyWith. Will be deprecated and removed in future versions of Groovy.
*
* @see #copyWith(java.util.Calendar, java.util.Map)
* @since 1.7.3
*/
public static Calendar updated(Calendar self, Map<Object, Integer> updates) {
Calendar result = (Calendar) self.clone();
set(result, updates);
return result;
}
/**
* Support creating a new Date having similar properties to
* an existing Date (which remains unaltered) but with
* some fields updated according to a Map of changes.
* <p>
* Example usage:
* <pre>
* import static java.util.Calendar.YEAR
* def now = Calendar.instance
* def nextYear = now[YEAR] + 1
* def oneYearFromNow = now.copyWith(year: nextYear)
* println now.time
* println oneYearFromNow.time
* </pre>
*
* @param self A Calendar
* @param updates A Map of Calendar keys and values
* @return The newly created Calendar
* @see java.util.Calendar#set(int, int)
* @see java.util.Calendar#set(int, int, int, int, int, int)
* @see #set(java.util.Calendar, java.util.Map)
* @since 2.2.0
*/
public static Calendar copyWith(Calendar self, Map<Object, Integer> updates) {
Calendar result = (Calendar) self.clone();
set(result, updates);
return result;
}
/**
* Support mutating a Date with a Map.
* <p>
* The map values are the normal values provided as the
* second parameter to <code>java.util.Calendar#set(int, int)</code>.
* The keys can either be the normal fields values provided as
* the first parameter to that method or one of the following Strings:
* <table border="1" cellpadding="4">
* <caption>Calendar index values</caption>
* <tr><td>year</td><td>Calendar.YEAR</td></tr>
* <tr><td>month</td><td>Calendar.MONTH</td></tr>
* <tr><td>date</td><td>Calendar.DATE</td></tr>
* <tr><td>dayOfMonth</td><td>Calendar.DATE</td></tr>
* <tr><td>hourOfDay</td><td>Calendar.HOUR_OF_DAY</td></tr>
* <tr><td>minute</td><td>Calendar.MINUTE</td></tr>
* <tr><td>second</td><td>Calendar.SECOND</td></tr>
* </table>
* Example usage:
* <pre>
* import static java.util.Calendar.YEAR
* def date = new Date()
* def nextYear = date[YEAR] + 1
* date.set(year: nextYear)
* println date
* </pre>
*
* @param self A Date
* @param updates A Map of Calendar keys and values
* @see java.util.Calendar#set(int, int)
* @see #set(java.util.Calendar, java.util.Map)
* @since 1.7.3
*/
public static void set(Date self, Map<Object, Integer> updates) {
Calendar cal = Calendar.getInstance();
cal.setTime(self);
set(cal, updates);
self.setTime(cal.getTimeInMillis());
}
/**
* Legacy alias for copyWith. Will be deprecated and removed in future versions of Groovy.
*
* @see #copyWith(java.util.Date, java.util.Map)
* @since 1.7.3
*/
public static Date updated(Date self, Map<Object, Integer> updates) {
Calendar cal = Calendar.getInstance();
cal.setTime(self);
set(cal, updates);
return cal.getTime();
}
/**
* Support creating a new Date having similar properties to
* an existing Date (which remains unaltered) but with
* some fields updated according to a Map of changes.
* <p>
* Example usage:
* <pre>
* import static java.util.Calendar.YEAR
* def today = new Date()
* def nextYear = today[YEAR] + 1
* def oneYearFromNow = today.copyWith(year: nextYear)
* println today
* println oneYearFromNow
* </pre>
*
* @param self A Date
* @param updates A Map of Calendar keys and values
* @return The newly created Date
* @see java.util.Calendar#set(int, int)
* @see #set(java.util.Date, java.util.Map)
* @see #copyWith(java.util.Calendar, java.util.Map)
* @since 2.2.0
*/
public static Date copyWith(Date self, Map<Object, Integer> updates) {
Calendar cal = Calendar.getInstance();
cal.setTime(self);
set(cal, updates);
return cal.getTime();
}
private static final Map<String, Integer> CAL_MAP = new HashMap<String, Integer>();
static {
CAL_MAP.put("year", Calendar.YEAR);
CAL_MAP.put("month", Calendar.MONTH);
CAL_MAP.put("date", Calendar.DATE);
CAL_MAP.put("dayOfMonth", Calendar.DATE);
CAL_MAP.put("hourOfDay", Calendar.HOUR_OF_DAY);
CAL_MAP.put("minute", Calendar.MINUTE);
CAL_MAP.put("second", Calendar.SECOND);
}
/**
* Increment a Date by one day.
*
* @param self a Date
* @return the next days date
* @since 1.0
*/
public static Date next(Date self) {
return plus(self, 1);
}
/**
* Increment a Calendar by one day.
*
* @param self a Calendar
* @return a new Calendar set to the next day
* @since 1.8.7
*/
public static Calendar next(Calendar self) {
Calendar result = (Calendar) self.clone();
result.add(Calendar.DAY_OF_YEAR, 1);
return result;
}
/**
* Decrement a Calendar by one day.
*
* @param self a Calendar
* @return a new Calendar set to the previous day
* @since 1.8.7
*/
public static Calendar previous(Calendar self) {
Calendar result = (Calendar) self.clone();
result.add(Calendar.DAY_OF_YEAR, -1);
return result;
}
/**
* Increment a java.sql.Date by one day.
*
* @param self a java.sql.Date
* @return the next days date
* @since 1.0
*/
public static java.sql.Date next(java.sql.Date self) {
return new java.sql.Date(next((Date) self).getTime());
}
/**
* Decrement a Date by one day.
*
* @param self a Date
* @return the previous days date
* @since 1.0
*/
public static Date previous(Date self) {
return minus(self, 1);
}
/**
* Decrement a java.sql.Date by one day.
*
* @param self a java.sql.Date
* @return the previous days date
* @since 1.0
*/
public static java.sql.Date previous(java.sql.Date self) {
return new java.sql.Date(previous((Date) self).getTime());
}
/**
* Add a number of days to this date and returns the new date.
*
* @param self a Date
* @param days the number of days to increase
* @return the new date
* @since 1.0
*/
public static Date plus(Date self, int days) {
Calendar calendar = (Calendar) Calendar.getInstance().clone();
calendar.setTime(self);
calendar.add(Calendar.DAY_OF_YEAR, days);
return calendar.getTime();
}
/**
* Add a number of days to this date and returns the new date.
*
* @param self a java.sql.Date
* @param days the number of days to increase
* @return the new date
* @since 1.0
*/
public static java.sql.Date plus(java.sql.Date self, int days) {
return new java.sql.Date(plus((Date) self, days).getTime());
}
/**
* Add number of days to this Timestamp and returns the new Timestamp object.
*
* @param self a Timestamp
* @param days the number of days to increase
* @return the new Timestamp
*/
public static Timestamp plus(Timestamp self, int days) {
Calendar calendar = (Calendar) Calendar.getInstance().clone();
calendar.setTime(self);
calendar.add(Calendar.DAY_OF_YEAR, days);
Timestamp ts = new Timestamp(calendar.getTime().getTime());
ts.setNanos(self.getNanos());
return ts;
}
/**
* Subtract a number of days from this date and returns the new date.
*
* @param self a Date
* @param days the number of days to subtract
* @return the new date
* @since 1.0
*/
public static Date minus(Date self, int days) {
return plus(self, -days);
}
/**
* Subtract a number of days from this date and returns the new date.
*
* @param self a java.sql.Date
* @param days the number of days to subtract
* @return the new date
* @since 1.0
*/
public static java.sql.Date minus(java.sql.Date self, int days) {
return new java.sql.Date(minus((Date) self, days).getTime());
}
/**
* Subtract a number of days from this Timestamp and returns the new Timestamp object.
*
* @param self a Timestamp
* @param days the number of days to subtract
* @return the new Timestamp
*/
public static Timestamp minus(Timestamp self, int days) {
return plus(self, -days);
}
/**
* Subtract another date from this one and return the number of days of the difference.
* <p>
* Date self = Date then + (Date self - Date then)
* <p>
* IOW, if self is before then the result is a negative value.
*
* @param self a Calendar
* @param then another Calendar
* @return number of days
* @since 1.6.0
*/
public static int minus(Calendar self, Calendar then) {
Calendar a = self;
Calendar b = then;
boolean swap = a.before(b);
if (swap) {
Calendar t = a;
a = b;
b = t;
}
int days = 0;
b = (Calendar) b.clone();
while (a.get(Calendar.YEAR) > b.get(Calendar.YEAR)) {
days += 1 + (b.getActualMaximum(Calendar.DAY_OF_YEAR) - b.get(Calendar.DAY_OF_YEAR));
b.set(Calendar.DAY_OF_YEAR, 1);
b.add(Calendar.YEAR, 1);
}
days += a.get(Calendar.DAY_OF_YEAR) - b.get(Calendar.DAY_OF_YEAR);
if (swap) days = -days;
return days;
}
/**
* Subtract another Date from this one and return the number of days of the difference.
* <p>
* Date self = Date then + (Date self - Date then)
* <p>
* IOW, if self is before then the result is a negative value.
*
* @param self a Date
* @param then another Date
* @return number of days
* @since 1.6.0
*/
public static int minus(Date self, Date then) {
Calendar a = (Calendar) Calendar.getInstance().clone();
a.setTime(self);
Calendar b = (Calendar) Calendar.getInstance().clone();
b.setTime(then);
return minus(a, b);
}
/**
* <p>Create a String representation of this date according to the given
* format pattern.
* <p>
* <p>For example, if the system timezone is GMT,
* <code>new Date(0).format('MM/dd/yy')</code> would return the string
* <code>"01/01/70"</code>. See documentation for {@link java.text.SimpleDateFormat}
* for format pattern use.
* <p>
* <p>Note that a new DateFormat instance is created for every
* invocation of this method (for thread safety).
*
* @param self a Date
* @param format the format pattern to use according to {@link java.text.SimpleDateFormat}
* @return a string representation of this date.
* @see java.text.SimpleDateFormat
* @since 1.5.7
*/
public static String format(Date self, String format) {
return new SimpleDateFormat(format).format(self);
}
/**
* <p>Create a String representation of this date according to the given
* format pattern and timezone.
* <p>
* <p>For example:
* <code>
* def d = new Date(0)
* def tz = TimeZone.getTimeZone('GMT')
* println d.format('dd/MMM/yyyy', tz)
* </code> would return the string
* <code>"01/Jan/1970"</code>. See documentation for {@link java.text.SimpleDateFormat}
* for format pattern use.
* <p>
* <p>Note that a new DateFormat instance is created for every
* invocation of this method (for thread safety).
*
* @param self a Date
* @param format the format pattern to use according to {@link java.text.SimpleDateFormat}
* @param tz the TimeZone to use
* @return a string representation of this date.
* @see java.text.SimpleDateFormat
* @since 1.8.3
*/
public static String format(Date self, String format, TimeZone tz) {
SimpleDateFormat sdf = new SimpleDateFormat(format);
sdf.setTimeZone(tz);
return sdf.format(self);
}
/**
* <p>Return a string representation of the 'day' portion of this date
* according to the locale-specific {@link java.text.DateFormat#SHORT} default format.
* For an "en_UK" system locale, this would be <code>dd/MM/yy</code>.
* <p>
* <p>Note that a new DateFormat instance is created for every
* invocation of this method (for thread safety).
*
* @param self a Date
* @return a string representation of this date
* @see java.text.DateFormat#getDateInstance(int)
* @see java.text.DateFormat#SHORT
* @since 1.5.7
*/
public static String getDateString(Date self) {
return DateFormat.getDateInstance(DateFormat.SHORT).format(self);
}
/**
* <p>Return a string representation of the time portion of this date
* according to the locale-specific {@link java.text.DateFormat#MEDIUM} default format.
* For an "en_UK" system locale, this would be <code>HH:MM:ss</code>.
* <p>
* <p>Note that a new DateFormat instance is created for every
* invocation of this method (for thread safety).
*
* @param self a Date
* @return a string representing the time portion of this date
* @see java.text.DateFormat#getTimeInstance(int)
* @see java.text.DateFormat#MEDIUM
* @since 1.5.7
*/
public static String getTimeString(Date self) {
return DateFormat.getTimeInstance(DateFormat.MEDIUM).format(self);
}
/**
* <p>Return a string representation of the date and time time portion of
* this Date instance, according to the locale-specific format used by
* {@link java.text.DateFormat}. This method uses the {@link java.text.DateFormat#SHORT}
* preset for the day portion and {@link java.text.DateFormat#MEDIUM} for the time
* portion of the output string.
* <p>
* <p>Note that a new DateFormat instance is created for every
* invocation of this method (for thread safety).
*
* @param self a Date
* @return a string representation of this date and time
* @see java.text.DateFormat#getDateTimeInstance(int, int)
* @since 1.5.7
*/
public static String getDateTimeString(Date self) {
return DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM).format(self);
}
/**
* Common code for {@link #clearTime(java.util.Calendar)} and {@link #clearTime(java.util.Date)}
* and {@link #clearTime(java.sql.Date)}
*
* @param self a Calendar to adjust
*/
private static void clearTimeCommon(final Calendar self) {
self.set(Calendar.HOUR_OF_DAY, 0);
self.clear(Calendar.MINUTE);
self.clear(Calendar.SECOND);
self.clear(Calendar.MILLISECOND);
}
/**
* Clears the time portion of this Date instance; useful utility where
* it makes sense to compare month/day/year only portions of a Date.
*
* @param self a Date
* @return the Date but with the time portion cleared
* @since 1.6.7
*/
public static Date clearTime(final Date self) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(self);
clearTimeCommon(calendar);
self.setTime(calendar.getTime().getTime());
return self;
}
/**
* Clears the time portion of this java.sql.Date instance; useful utility
* where it makes sense to compare month/day/year only portions of a Date.
*
* @param self a java.sql.Date
* @return the java.sql.Date but with the time portion cleared
* @since 1.6.7
*/
public static java.sql.Date clearTime(final java.sql.Date self) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(self);
clearTimeCommon(calendar);
self.setTime(calendar.getTime().getTime());
return self;
}
/**
* Clears the time portion of this Calendar instance; useful utility
* where it makes sense to compare month/day/year only portions of a Calendar.
*
* @param self a Calendar
* @return the Calendar but with the time portion cleared
* @since 1.6.7
*/
public static Calendar clearTime(final Calendar self) {
clearTimeCommon(self);
return self;
}
/**
* <p>Shortcut for {@link java.text.SimpleDateFormat} to output a String representation
* of this calendar instance. This method respects the Calendar's assigned
* {@link java.util.TimeZone}, whereas calling <code>cal.time.format('HH:mm:ss')</code>
* would use the system timezone.
* <p>Note that Calendar equivalents of <code>date.getDateString()</code>
* and variants do not exist because those methods are Locale-dependent.
* Although a Calendar may be assigned a {@link java.util.Locale}, that information is
* lost and therefore cannot be used to control the default date/time formats
* provided by these methods. Instead, the system Locale would always be
* used. The alternative is to simply call
* {@link java.text.DateFormat#getDateInstance(int, java.util.Locale)} and pass the same Locale
* that was used for the Calendar.
*
* @param self this calendar
* @param pattern format pattern
* @return String representation of this calendar with the given format.
* @see java.text.DateFormat#setTimeZone(java.util.TimeZone)
* @see java.text.SimpleDateFormat#format(java.util.Date)
* @see #format(java.util.Date, String)
* @since 1.6.0
*/
public static String format(Calendar self, String pattern) {
SimpleDateFormat sdf = new SimpleDateFormat(pattern);
sdf.setTimeZone(self.getTimeZone());
return sdf.format(self.getTime());
}
/**
* Iterates from this date up to the given date, inclusive,
* incrementing by one day each time.
*
* @param self a Date
* @param to another Date to go up to
* @param closure the closure to call
* @since 2.2
*/
public static void upto(Date self, Date to, Closure closure) {
if (self.compareTo(to) <= 0) {
for (Date i = (Date) self.clone(); i.compareTo(to) <= 0; i = next(i)) {
closure.call(i);
}
} else
throw new GroovyRuntimeException("The argument (" + to +
") to upto() cannot be earlier than the value (" + self + ") it's called on.");
}
/**
* Iterates from the date represented by this calendar up to the date represented
* by the given calendar, inclusive, incrementing by one day each time.
*
* @param self a Calendar
* @param to another Calendar to go up to
* @param closure the closure to call
* @since 2.2
*/
public static void upto(Calendar self, Calendar to, Closure closure) {
if (self.compareTo(to) <= 0) {
for (Calendar i = (Calendar) self.clone(); i.compareTo(to) <= 0; i = next(i)) {
closure.call(i);
}
} else
throw new GroovyRuntimeException("The argument (" + to +
") to upto() cannot be earlier than the value (" + self + ") it's called on.");
}
/**
* Iterates from this date down to the given date, inclusive,
* decrementing by one day each time.
*
* @param self a Date
* @param to another Date to go down to
* @param closure the closure to call
* @since 2.2
*/
public static void downto(Date self, Date to, Closure closure) {
if (self.compareTo(to) >= 0) {
for (Date i = (Date) self.clone(); i.compareTo(to) >= 0; i = previous(i)) {
closure.call(i);
}
} else
throw new GroovyRuntimeException("The argument (" + to +
") to downto() cannot be later than the value (" + self + ") it's called on.");
}
/**
* Iterates from the date represented by this calendar up to the date represented
* by the given calendar, inclusive, incrementing by one day each time.
*
* @param self a Calendar
* @param to another Calendar to go down to
* @param closure the closure to call
* @since 2.2
*/
public static void downto(Calendar self, Calendar to, Closure closure) {
if (self.compareTo(to) >= 0) {
for (Calendar i = (Calendar) self.clone(); i.compareTo(to) >= 0; i = previous(i)) {
closure.call(i);
}
} else
throw new GroovyRuntimeException("The argument (" + to +
") to downto() cannot be later than the value (" + self + ") it's called on.");
}
}