ucar.nc2.time.CalendarDateFormatter Maven / Gradle / Ivy
The newest version!
/*
* Copyright (c) 1998-2018 John Caron and University Corporation for Atmospheric Research/Unidata
* See LICENSE for license information.
*/
package ucar.nc2.time;
import com.google.re2j.Matcher;
import com.google.re2j.Pattern;
import org.joda.time.Chronology;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import ucar.nc2.units.DateFormatter;
import javax.annotation.concurrent.ThreadSafe;
import java.util.Date;
import java.util.StringTokenizer;
/**
* Threadsafe static routines for date formatting.
* Replacement for ucar.nc2.units.DateFormatter
*
* @author John
* @since 7/9/11
*/
@ThreadSafe
public class CalendarDateFormatter {
// these are thread-safe (yeah!)
private static DateTimeFormatter isof = DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss'Z'").withZoneUTC();
private static DateTimeFormatter isof_with_millis_of_second =
DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'").withZoneUTC();
private static DateTimeFormatter dtf = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss'Z'").withZoneUTC();
private static DateTimeFormatter dtf_with_millis_of_second =
DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss.SSS'Z'").withZoneUTC();
private static DateTimeFormatter df = DateTimeFormat.forPattern("yyyy-MM-dd").withZoneUTC();
private static DateTimeFormatter df_units = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss.SSS 'UTC'").withZoneUTC(); // udunits
public static String toDateTimeStringISO(CalendarDate cd) {
if (cd.getDateTime().getMillisOfSecond() == 0)
return isof.print(cd.getDateTime());
else
return isof_with_millis_of_second.print(cd.getDateTime());
}
public static String toDateTimeStringISO(Date d) {
return toDateTimeStringISO(CalendarDate.of(d));
}
public static String toDateTimeStringISO(long millisecs) {
return toDateTimeStringISO(CalendarDate.of(millisecs));
}
public static String toDateTimeString(CalendarDate cd) {
if (cd.getDateTime().getMillisOfSecond() == 0)
return dtf.print(cd.getDateTime());
else
return dtf_with_millis_of_second.print(cd.getDateTime());
}
public static String toDateString(Date date) {
return toDateString(CalendarDate.of(date));
}
public static String toDateTimeString(Date date) {
return toDateTimeString(CalendarDate.of(date));
}
public static String toDateTimeStringPresent() {
return dtf.print(new DateTime());
}
public static String toDateString(CalendarDate cd) {
return df.print(cd.getDateTime());
}
public static String toDateStringPresent() {
return df.print(new DateTime());
}
/**
* udunits formatting
*
* @param cd the calendar date
* @return udunits formated date
*/
public static String toTimeUnits(CalendarDate cd) {
return df_units.print(cd.getDateTime());
}
public static String toTimeUnits(Date date) {
return df_units.print(date.getTime());
}
public static CalendarDateFormatter factory(CalendarPeriod period) {
switch (period.getField()) {
case Year:
return new CalendarDateFormatter("yyyy");
case Month:
return new CalendarDateFormatter("yyyy-MM");
case Day:
return new CalendarDateFormatter("yyyy-MM-dd");
case Hour:
return new CalendarDateFormatter("yyyy-MM-ddTHH");
default:
return new CalendarDateFormatter("yyyy-MM-ddTHH:mm:ss");
}
}
/////////////////////////////////////////////////////////////////////////////
// reading an ISO formatted date
/**
* Old version using DateFormatter
*
* @param iso ISO 8601 date String
* @return equivilent Date
*
* @deprecated As of 4.3.10 use {@link #isoStringToDate(String)} instead
*
*/
@Deprecated
public static Date parseISODate(String iso) {
DateFormatter df = new DateFormatter();
return df.getISODate(iso);
}
/**
* Convert an ISO formatted String to a CalendarDate.
*
* @param calt calendar, may be null for default calendar (Calendar.getDefault())
* @param iso ISO 8601 date String
*
*
* possible forms for W3C profile of ISO 8601
Year:
YYYY (eg 1997)
Year and month:
YYYY-MM (eg 1997-07)
Complete date:
YYYY-MM-DD (eg 1997-07-16)
Complete date plus hours and minutes:
YYYY-MM-DDThh:mmTZD (eg 1997-07-16T19:20+01:00)
Complete date plus hours, minutes and seconds:
YYYY-MM-DDThh:mm:ssTZD (eg 1997-07-16T19:20:30+01:00)
Complete date plus hours, minutes, seconds and a decimal fraction of a second
YYYY-MM-DDThh:mm:ss.sTZD (eg 1997-07-16T19:20:30.45+01:00)
where:
YYYY = four-digit year
MM = two-digit month (01=January, etc.)
DD = two-digit day of month (01 through 31)
hh = two digits of hour (00 through 23) (am/pm NOT allowed)
mm = two digits of minute (00 through 59)
ss = two digits of second (00 through 59)
s = one or more digits representing a decimal fraction of a second
TZD = time zone designator (Z or +hh:mm or -hh:mm)
except:
You may use a space instead of the 'T'
The year may be preceeded by a '+' (ignored) or a '-' (makes the date BCE)
The date part uses a '-' delimiter instead of a fixed number of digits for each field
The time part uses a ':' delimiter instead of a fixed number of digits for each field
*
*
* @return CalendarDate using given calendar
* @throws IllegalArgumentException if the String is not a valid ISO 8601 date
* @see "http://www.w3.org/TR/NOTE-datetime"
*/
public static CalendarDate isoStringToCalendarDate(Calendar calt, String iso) throws IllegalArgumentException {
DateTime dt = parseIsoTimeString(calt, iso);
Calendar useCal = Calendar.of(dt.getChronology());
return new CalendarDate(useCal, dt);
}
/**
* Does not handle non-standard Calendars
*
* @param iso iso formatted string
* @return Date
* @deprecated use isoStringToCalendarDate
*/
public static Date isoStringToDate(String iso) throws IllegalArgumentException {
CalendarDate dt = isoStringToCalendarDate(null, iso);
return dt.toDate();
}
// 1 2 3
public static final String isodatePatternString = "([\\+\\-?\\d]+)([ t]([\\.\\:?\\d]*)([ \\+\\-]\\S*)?z?)?$"; // public
// for
// testing
// private static final String isodatePatternString = "([\\+\\-\\d]+)[ Tt]([\\.\\:\\d]*)([ \\+\\-]\\S*)?z?)?$";
private static final Pattern isodatePattern = Pattern.compile(isodatePatternString);
private static DateTime parseIsoTimeString(Calendar calt, String iso) {
iso = iso.trim();
iso = iso.toLowerCase();
Matcher m = isodatePattern.matcher(iso);
if (!m.matches()) {
throw new IllegalArgumentException(iso + " does not match regexp " + isodatePatternString);
}
String dateString = m.group(1);
String timeString = m.group(3);
String zoneString = m.group(4);
// Set the defaults for any values that are not specified
int year = 0;
int month = 1;
int day = 1;
int hour = 0;
int minute = 0;
double second = 0.0;
try {
boolean isMinus = false;
if (dateString.startsWith("-")) {
isMinus = true;
dateString = dateString.substring(1);
} else if (dateString.startsWith("+")) {
dateString = dateString.substring(1);
}
if (dateString.contains("-")) {
StringTokenizer dateTokenizer = new StringTokenizer(dateString, "-");
if (dateTokenizer.hasMoreTokens())
year = Integer.parseInt(dateTokenizer.nextToken());
if (dateTokenizer.hasMoreTokens())
month = Integer.parseInt(dateTokenizer.nextToken());
if (dateTokenizer.hasMoreTokens())
day = Integer.parseInt(dateTokenizer.nextToken());
} else {
int dateLength = dateString.length();
if (dateLength % 2 != 0) {
throw new IllegalArgumentException(dateString + " is ambiguous. Cannot parse uneven "
+ "length date strings when no date delimiter is used.");
} else {
// dateString length must be even - only four digit year, two digit month, and
// two digit day values are allowed when there is no date delimiter.
if (dateLength > 3)
year = Integer.parseInt(dateString.substring(0, 4));
if (dateLength > 5)
month = Integer.parseInt(dateString.substring(4, 6));
if (dateLength > 7)
day = Integer.parseInt(dateString.substring(6, 8));
}
}
// Parse the time if present
if (timeString != null && !timeString.isEmpty()) {
if (timeString.contains(":")) {
StringTokenizer timeTokenizer = new StringTokenizer(timeString, ":");
if (timeTokenizer.hasMoreTokens())
hour = Integer.parseInt(timeTokenizer.nextToken());
if (timeTokenizer.hasMoreTokens())
minute = Integer.parseInt(timeTokenizer.nextToken());
if (timeTokenizer.hasMoreTokens())
second = Double.parseDouble(timeTokenizer.nextToken());
} else {
int timeLengthNoSubseconds = timeString.length();
// possible this contains a seconds value with subseconds (i.e. 25.125 seconds)
// since the seconds value can be a Double, let's check check the length of the
// time, without the subseconds (if they exist)
if (timeString.contains(".")) {
timeLengthNoSubseconds = timeString.split("\\.")[0].length();
}
// Ok, so this is a little tricky.
// First: A udunit date of 1992-10-8t7 is valid. We want to make sure this still work, so
// there is a special case of timeString.length() == 1;
//
// Second: We have the length of the timeString, without
// any subseconds. Given that the values for hour, minute, and second must be two
// digit numbers, the length of the timeString, without subseconds, should be even.
// However, if someone has encoded time as hhms, there is no way we will be able to tell.
// So, this is the best we can do to ensure we are parsing the time properly.
// Known failure here: hhms will be interpreted as hhmm, but hhms is not following iso, and
// a bad idea to use anyway.
if (timeLengthNoSubseconds == 1) {
hour = Integer.parseInt(timeString);
} else if (timeLengthNoSubseconds % 2 != 0) {
throw new IllegalArgumentException(timeString + " is ambiguous. Cannot parse uneven "
+ "length time strings (ignoring subseconds) when no time delimiter is used.");
} else {
// dateString length must be even - only four digit year, two digit month, and
// two digit day values are allowed when there is no date delimiter.
if (timeString.length() > 1)
hour = Integer.parseInt(timeString.substring(0, 2));
if (timeString.length() > 3)
minute = Integer.parseInt(timeString.substring(2, 4));
if (timeString.length() > 5)
second = Double.parseDouble(timeString.substring(4));
}
}
}
if (isMinus)
year = -year;
// kludge to deal with legacy files using year 0. // 10/10/2013 jcaron
if ((year == 0) && (calt == Calendar.gregorian)) {
calt = Calendar.proleptic_gregorian;
}
// if (year < -292275054 || year > 292278993)
// throw new IllegalArgumentException(" incorrect date specification = " + iso);
// Get a DateTime object in this Chronology
Chronology cron = Calendar.getChronology(calt);
cron = cron.withUTC(); // default is UTC
DateTime dt = new DateTime(year, month, day, hour, minute, 0, 0, cron);
// Add the seconds
dt = dt.plus((long) (1000 * second));
// Parse the time zone if present
if (zoneString != null) {
zoneString = zoneString.trim();
if (!zoneString.isEmpty() && !zoneString.equalsIgnoreCase("Z") && !zoneString.equalsIgnoreCase("UTC")
&& !zoneString.equalsIgnoreCase("GMT")) {
isMinus = false;
if (zoneString.startsWith("-")) {
isMinus = true;
zoneString = zoneString.substring(1);
} else if (zoneString.startsWith("+")) {
zoneString = zoneString.substring(1);
}
// allow 01:00, 1:00, 01 or 0100
int hourOffset;
int minuteOffset = 0;
int posColon = zoneString.indexOf(':');
if (posColon > 0) {
String hourS = zoneString.substring(0, posColon);
String minS = zoneString.substring(posColon + 1);
hourOffset = Integer.parseInt(hourS);
minuteOffset = Integer.parseInt(minS);
} else { // no colon - assume 2 digit hour, optional minutes
if (zoneString.length() > 2) {
String hourS = zoneString.substring(0, 2);
String minS = zoneString.substring(2);
hourOffset = Integer.parseInt(hourS);
minuteOffset = Integer.parseInt(minS);
} else {
hourOffset = Integer.parseInt(zoneString);
}
}
if (isMinus) {
// DateTimeZone.forOffsetHoursMinutes: "If constructed with the values (-2, 30), the resulting zone is
// '-02:30'.
// so i guess dont make minuteOffset negetive
hourOffset = -hourOffset;
}
DateTimeZone dtz = DateTimeZone.forOffsetHoursMinutes(hourOffset, minuteOffset);
// Apply the time zone offset, retaining the field values. This
// manipulates the millisecond instance.
dt = dt.withZoneRetainFields(dtz);
// Now convert to the UTC time zone, retaining the millisecond instant
dt = dt.withZone(DateTimeZone.UTC);
} // else {
// dt = dt.withZone(DateTimeZone.UTC); // default UTC
// }
// } else {
// dt = dt.withZone(DateTimeZone.UTC); // default UTC
}
return dt;
} catch (Throwable e) { // catch random joda exceptions
throw new IllegalArgumentException("Illegal base time specification: '" + dateString + "' " + e.getMessage());
}
}
/////////////////////////////////////////////
private final DateTimeFormatter dflocal;
/**
* Date formatter with specified pattern.
* NOTE: we are using jodatime patterns right now, but may switch to jsr-310 when thats available in java 8.
* Not sure whether these patterns will still work then, so use this formatter at the risk of having to
* change it eventually. OTOH, its likely that the same functionality will be present in jsr-310.
*
* The pattern syntax is mostly compatible with java.text.SimpleDateFormat -
* time zone names cannot be parsed and a few more symbols are supported.
* All ASCII letters are reserved as pattern letters, which are defined as follows:
*
*
*
* Symbol Meaning Presentation Examples
* ------ ------- ------------ -------
* G era text AD
* C century of era (>=0) number 20
* Y year of era (>=0) year 1996
*
* x weekyear year 1996
* w week of weekyear number 27
* e day of week number 2
* E day of week text Tuesday; Tue
*
* y year year 1996
* D day of year number 189
* M month of year month July; Jul; 07
* d day of month number 10
*
* a halfday of day text PM
* K hour of halfday (0~11) number 0
* h clockhour of halfday (1~12) number 12
*
* H hour of day (0~23) number 0
* k clockhour of day (1~24) number 24
* m minute of hour number 30
* s second of minute number 55
* S fraction of second number 978
*
* z time zone text Pacific Standard Time; PST
* Z time zone offset/id zone -0800; -08:00; America/Los_Angeles
*
* ' escape for text delimiter
* '' single quote literal '
*
*/
public CalendarDateFormatter(String pattern) {
dflocal = DateTimeFormat.forPattern(pattern).withZoneUTC();
}
public CalendarDateFormatter(String pattern, CalendarTimeZone tz, Calendar cal) {
Chronology chron = Calendar.getChronology(cal);
dflocal = DateTimeFormat.forPattern(pattern).withChronology(chron).withZone(tz.getJodaTimeZone());
}
public CalendarDateFormatter(String pattern, CalendarTimeZone tz) {
dflocal = DateTimeFormat.forPattern(pattern).withZone(tz.getJodaTimeZone());
}
public String toString(CalendarDate cd) {
return dflocal.print(cd.getDateTime());
}
public CalendarDate parse(String timeString) {
DateTime dt = dflocal.parseDateTime(timeString);
Calendar cal = Calendar.get(dt.getChronology().toString());
return new CalendarDate(cal, dt);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy