/*
* 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.eigenbase.util14;
import java.text.*;
import java.util.*;
/**
* Utility functions for datetime types: date, time, timestamp. Refactored from
* SqlParserUtil because they are required by the Jdbc driver. TODO: review
* methods for performance. Due to allocations required, it may be preferable to
* introduce a "formatter" with the required state.
*/
public class DateTimeUtil {
private DateTimeUtil() {}
//~ Static fields/initializers ---------------------------------------------
/** The SimpleDateFormat string for ISO dates, "yyyy-MM-dd". */
public static final String DATE_FORMAT_STRING = "yyyy-MM-dd";
/** The SimpleDateFormat string for ISO times, "HH:mm:ss". */
public static final String TIME_FORMAT_STRING = "HH:mm:ss";
/** The SimpleDateFormat string for ISO timestamps, "yyyy-MM-dd HH:mm:ss". */
public static final String TIMESTAMP_FORMAT_STRING =
DATE_FORMAT_STRING + " " + TIME_FORMAT_STRING;
/** The GMT time zone. */
public static final TimeZone GMT_ZONE = TimeZone.getTimeZone("GMT");
/** The Java default time zone. */
public static final TimeZone DEFAULT_ZONE = TimeZone.getDefault();
/**
* The number of milliseconds in a second.
*/
public static final long MILLIS_PER_SECOND = 1000L;
/**
* The number of milliseconds in a minute.
*/
public static final long MILLIS_PER_MINUTE = 60000L;
/**
* The number of milliseconds in an hour.
*/
public static final long MILLIS_PER_HOUR = 3600000L; // = 60 * 60 * 1000
/**
* The number of milliseconds in a day.
*
* <p>This is the modulo 'mask' used when converting
* TIMESTAMP values to DATE and TIME values.
*/
public static final long MILLIS_PER_DAY = 86400000; // = 24 * 60 * 60 * 1000
/**
* Calendar set to the epoch (1970-01-01 00:00:00 UTC). Useful for
* initializing other values. Calendars are not immutable, so be careful not
* to screw up this object for everyone else.
*/
public static final Calendar ZERO_CALENDAR;
static {
ZERO_CALENDAR = Calendar.getInstance(DateTimeUtil.GMT_ZONE);
ZERO_CALENDAR.setTimeInMillis(0);
}
/**
* Calendar set to local time.
*/
private static final Calendar LOCAL_CALENDAR = Calendar.getInstance();
//~ Methods ----------------------------------------------------------------
/**
* Parses a string using {@link SimpleDateFormat} and a given pattern. This
* method parses a string at the specified parse position and if successful,
* updates the parse position to the index after the last character used.
* The parsing is strict and requires months to be less than 12, days to be
* less than 31, etc.
*
* @param s string to be parsed
* @param pattern {@link SimpleDateFormat} pattern
* @param tz time zone in which to interpret string. Defaults to the Java
* default time zone
* @param pp position to start parsing from
* @return a Calendar initialized with the parsed value, or null if parsing
* failed. If returned, the Calendar is configured to the GMT time zone.
* @pre pattern != null
*/
private static Calendar parseDateFormat(
String s,
String pattern,
TimeZone tz,
ParsePosition pp) {
assert pattern != null;
SimpleDateFormat df = new SimpleDateFormat(pattern);
if (tz == null) {
tz = DEFAULT_ZONE;
}
Calendar ret = Calendar.getInstance(tz);
df.setCalendar(ret);
df.setLenient(false);
java.util.Date d = df.parse(s, pp);
if (null == d) {
return null;
}
ret.setTime(d);
ret.setTimeZone(GMT_ZONE);
return ret;
}
/**
* Parses a string using {@link SimpleDateFormat} and a given pattern. The
* entire string must match the pattern specified.
*
* @param s string to be parsed
* @param pattern {@link SimpleDateFormat} pattern
* @param tz time zone in which to interpret string. Defaults to the Java
* default time zone
* @return a Calendar initialized with the parsed value, or null if parsing
* failed. If returned, the Calendar is configured to the GMT time zone.
*/
public static Calendar parseDateFormat(
String s,
String pattern,
TimeZone tz) {
assert pattern != null;
ParsePosition pp = new ParsePosition(0);
Calendar ret = parseDateFormat(s, pattern, tz, pp);
if (pp.getIndex() != s.length()) {
// Didn't consume entire string - not good
return null;
}
return ret;
}
/**
* Parses a string using {@link SimpleDateFormat} and a given pattern, and
* if present, parses a fractional seconds component. The fractional seconds
* component must begin with a decimal point ('.') followed by numeric
* digits. The precision is rounded to a maximum of 3 digits of fractional
* seconds precision (to obtain milliseconds).
*
* @param s string to be parsed
* @param pattern {@link SimpleDateFormat} pattern
* @param tz time zone in which to interpret string. Defaults to the
* local time zone
* @return a {@link DateTimeUtil.PrecisionTime PrecisionTime} initialized
* with the parsed value, or null if parsing failed. The PrecisionTime
* contains a GMT Calendar and a precision.
*/
public static PrecisionTime parsePrecisionDateTimeLiteral(
String s,
String pattern,
TimeZone tz) {
assert pattern != null;
ParsePosition pp = new ParsePosition(0);
Calendar cal = parseDateFormat(s, pattern, tz, pp);
if (cal == null) {
return null; // Invalid date/time format
}
// Note: the Java SimpleDateFormat 'S' treats any number after
// the decimal as milliseconds. That means 12:00:00.9 has 9
// milliseconds and 12:00:00.9999 has 9999 milliseconds.
int p = 0;
if (pp.getIndex() < s.length()) {
// Check to see if rest is decimal portion
if (s.charAt(pp.getIndex()) != '.') {
return null;
}
// Skip decimal sign
pp.setIndex(pp.getIndex() + 1);
// Parse decimal portion
if (pp.getIndex() < s.length()) {
String secFraction = s.substring(pp.getIndex());
if (!secFraction.matches("\\d+")) {
return null;
}
NumberFormat nf = NumberFormat.getIntegerInstance();
Number num = nf.parse(s, pp);
if ((num == null) || (pp.getIndex() != s.length())) {
// Invalid decimal portion
return null;
}
// Determine precision - only support prec 3 or lower
// (milliseconds) Higher precisions are quietly rounded away
p = Math.min(
3,
secFraction.length());
// Calculate milliseconds
int ms =
(int) Math.round(
num.longValue()
* Math.pow(10, 3 - secFraction.length()));
cal.add(Calendar.MILLISECOND, ms);
}
}
assert pp.getIndex() == s.length();
PrecisionTime ret = new PrecisionTime(cal, p);
return ret;
}
/**
* Gets the active time zone based on a Calendar argument
*/
public static TimeZone getTimeZone(Calendar cal) {
if (cal == null) {
return DEFAULT_ZONE;
}
return cal.getTimeZone();
}
/**
* Checks if the date/time format is valid
*
* @param pattern {@link SimpleDateFormat} pattern
* @throws IllegalArgumentException if the given pattern is invalid
*/
public static void checkDateFormat(String pattern) {
new SimpleDateFormat(pattern);
}
/**
* Creates a new date formatter with Farrago specific options. Farrago
* parsing is strict and does not allow values such as day 0, month 13, etc.
*
* @param format {@link SimpleDateFormat} pattern
*/
public static SimpleDateFormat newDateFormat(String format) {
SimpleDateFormat sdf = new SimpleDateFormat(format);
sdf.setLenient(false);
return sdf;
}
//~ Inner Classes ----------------------------------------------------------
/**
* Helper class for {@link DateTimeUtil#parsePrecisionDateTimeLiteral}
*/
public static class PrecisionTime {
private final Calendar cal;
private final int precision;
public PrecisionTime(Calendar cal, int precision) {
this.cal = cal;
this.precision = precision;
}
public Calendar getCalendar() {
return cal;
}
public int getPrecision() {
return precision;
}
}
}
// End DateTimeUtil.java