All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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