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

org.pageseeder.flint.lucene.util.Dates Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2015 Allette Systems (Australia)
 * http://www.allette.com.au
 *
 * Licensed 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.pageseeder.flint.lucene.util;

import org.apache.lucene.document.DateTools;
import org.apache.lucene.document.DateTools.Resolution;
import org.pageseeder.xmlwriter.XMLWriter;

import java.io.IOException;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.OffsetDateTime;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.TimeZone;

/**
 * A collection of utility methods to deal with dates.
 *
 * 

By default, Flint uses ISO8601. * * @author Christophe Lauret * @version 10 September 2010 */ public final class Dates { /** * The ISO 8601 Date and time format (required for resolution > day only) * * @see ISO: Numeric representation of Dates and Time */ private static final String ISO8601_DATETIME = "yyyy-MM-dd'T'HH:mm:ssZ"; /** * The maximum length for a field to expand. */ private static final int ONE_SECOND_IN_MS = 1000; /** * One minute in milliseconds. */ private static final int ONE_MINUTE_IN_MS = 60000; /** * One hour in milliseconds. */ private static final int ONE_HOUR_IN_MS = 3600000; /** * The ISO 8601 date and time formatter to use (when resolution is greater than day). */ private static final DateFormat ISO_DATETIME = new SimpleDateFormat(ISO8601_DATETIME); /** Utility class. */ private Dates() { } /** * Format the value as an ISO8601 date time. * * @param value the value from the index * @param offset the timezone offset (adjust for the specified offset) * * @return the corresponding value. * * @throws ParseException if the value is not a parseable date. */ public static synchronized String toISODateTime(String value, int offset) throws ParseException { int x = findNonDigitCharacter(value); if (x != -1) throw new ParseException("Value is not a valid Lucene date", x); Date date = DateTools.stringToDate(value); // Only adjust for daylight savings... TimeZone tz = TimeZone.getDefault(); int rawOffset = tz.inDaylightTime(date)? offset - ONE_HOUR_IN_MS : offset; tz.setRawOffset(rawOffset); ISO_DATETIME.setTimeZone(tz); String formatted = ISO_DATETIME.format(date); // the Java timezone does not include the required ':' return formatted.substring(0, formatted.length() - 2) + ":" + formatted.substring(formatted.length() - 2); } /** * Returns an ISO 8601 calendar date from a Lucene Index value. * * @param value the value from the index * * @return the corresponding value. * * @throws ParseException if the value is not a parseable date. */ public static String toISODate(String value) throws ParseException { int x = findNonDigitCharacter(value); if (x != -1) throw new ParseException("Value is not a valid Lucene date", x); // Odd case when it is zero?? if ("0".equals(value)) return ""; final int length = value.length(); switch (length) { case 4: // Resolution.YEAR (stored in Lucene as 'yyyy') return value; case 6: // Resolution.MONTH (stored in Lucene as 'yyyymm') return value.substring(0, 4)+'-'+value.substring(4); case 8: // Resolution.DAY (stored in Lucene as 'yyyymmdd') return value.substring(0, 4)+'-'+value.substring(4, 6)+'-'+value.substring(6); default: throw new ParseException("Value is not a valid Lucene date", 0); } } /** * Indicates whether the specified value may be a date value in Lucene. * * @param value The value to check * * @return true if the specified value may be parseable as a date; * false otherwise. */ public static boolean isLuceneDate(String value) { if (value == null || value.isEmpty()) return false; final int length = value.length(); switch (length) { case 4: // yyyy (Resolution.YEAR) case 6: // yyyymm (Resolution.MONTH) case 8: // yyyymmdd (Resolution.DAY) case 10: // yyyymmddhh (Resolution.HOUR) case 12: // yyyymmddhhmm (Resolution.MINUTE) case 14: // yyyymmddhhmmss (Resolution.SECOND) case 17: // yyyymmddhhmmssxxx (Resolution.MILLISECOND) return findNonDigitCharacter(value) == -1; default: } return false; } /** * Formats the specified date as a String with the given resolution using ISO8601. * *

Formats based on the resolution using the ISO8601 extended format: *

    *
  • Year [YYYY] *
  • Month [YYYY]-[MM] *
  • Day [YYYY]-[MM]-[DD] *
  • Hour [YYYY]-[MM]-[DD]T[hh] *
  • Minute [YYYY]-[MM]-[DD]T[hh]:[mm] *
  • Second [YYYY]-[MM]-[DD]T[hh]:[mm]:[ss] *
  • MilliSecond [YYYY]-[MM]-[DD]T[hh]:[mm]:[ss].[sss] *
* *

Note: dates returned in local time. * * @param date The date to format * @param resolution The resolution for the formatting * * @return the formatted date as ISO8601 or null. */ public static String format(Date date, Resolution resolution) { if (date == null) return null; return format(date.getTime(), resolution); } /** * Formats the specified date as a String with the given resolution using ISO8601. * *

Formats based on the resolution using the ISO8601 extended format: *

    *
  • Year [YYYY] *
  • Month [YYYY]-[MM] *
  • Day [YYYY]-[MM]-[DD] *
  • Hour [YYYY]-[MM]-[DD]T[hh] *
  • Minute [YYYY]-[MM]-[DD]T[hh]:[mm] *
  • Second [YYYY]-[MM]-[DD]T[hh]:[mm]:[ss] *
  • MilliSecond [YYYY]-[MM]-[DD]T[hh]:[mm]:[ss].[sss] *
* *

Note: dates returned in local time. * * @param date The date to format * @param resolution The resolution for the formatting * * @return the formatted date as ISO8601 or null. */ public static String format(OffsetDateTime date, Resolution resolution) { if (date == null) return null; return format(date.toInstant().toEpochMilli(), resolution); } /** * Return the string value used by Lucene 3 for dates. * * @param date The date to turn to a string * @param resolution The resolution for the formatting * * @return The string value for use by Lucene. */ public static String toString(Date date, Resolution resolution) { if (date == null) return null; return DateTools.timeToString(date.getTime(), resolution); } /** * Return the string value used by Lucene 3 for dates. * * @param date The date to turn to a string * @param resolution The resolution for the formatting * * @return The string value for use by Lucene. */ public static String toString(OffsetDateTime date, Resolution resolution) { if (date == null) return null; return DateTools.timeToString(date.toInstant().toEpochMilli(), resolution); } /** * Return the string value used by Lucene 3 for dates. * * @param date The date to turn to a string * @param resolution The resolution for the formatting * * @return The string value for use by Lucene. */ public static String toString(Instant date, Resolution resolution) { if (date == null) return null; return DateTools.timeToString(date.toEpochMilli(), resolution); } /** * Return the numeric value used by Lucene 3 for dates. * * @param date The date to convert * @param resolution The resolution for the formatting * * @return The numeric value for use by Lucene. */ public static Number toNumber(Date date, Resolution resolution) { if (date == null) return null; return toNumber(date.getTime(), resolution); } /** * Return the numeric value used by Lucene 3 for dates. * * @param date The date to convert * @param resolution The resolution for the formatting * * @return The numeric value for use by Lucene. */ public static Number toNumber(OffsetDateTime date, Resolution resolution) { if (date == null) return null; return toNumber(date.toInstant().toEpochMilli(), resolution); } /** * Output a range or interval as XML * @param element the tag name * @param min the min value * @param max the max value * @param withMin if the min is included in the range/interval * @param withMax if the max is included in the range/interval * @param cardinality the nb of occurrences * @param xml where to write the output * * @throws IOException if writing the output failed */ public static void dateRangeToXML(String element, String min, String max, boolean withMin, boolean withMax, int cardinality, XMLWriter xml) throws IOException { xml.openElement(element); if (min != null) xml.attribute("min", min); if (max != null) xml.attribute("max", max); if (min != null) xml.attribute("include-min", withMin ? "true" : "false"); if (max != null) xml.attribute("include-max", withMax ? "true" : "false"); xml.attribute("cardinality", cardinality); xml.closeElement(); } // private helpers ------------------------------------------------------------------------------ /** * Formats the specified date as a String with the given resolution using ISO8601. * *

Formats based on the resolution using the ISO8601 extended format: *

    *
  • Year [YYYY] *
  • Month [YYYY]-[MM] *
  • Day [YYYY]-[MM]-[DD] *
  • Hour [YYYY]-[MM]-[DD]T[hh] *
  • Minute [YYYY]-[MM]-[DD]T[hh]:[mm] *
  • Second [YYYY]-[MM]-[DD]T[hh]:[mm]:[ss] *
  • MilliSecond [YYYY]-[MM]-[DD]T[hh]:[mm]:[ss].[sss] *
* *

Note: dates returned in local time. * * @param timems The time in mile seconds. * @param resolution The resolution for the formatting * * @return the formatted date as ISO8601 or null. */ public static String format(long timems, Resolution resolution) { Calendar calendar = GregorianCalendar.getInstance(); calendar.setTimeInMillis(timems); // Year [YYYY] String iso = leftZeroPad4(calendar.get(GregorianCalendar.YEAR)); if (resolution == Resolution.YEAR) return iso; // Month [YYYY]-[MM] iso += '-' + leftZeroPad2(calendar.get(GregorianCalendar.MONTH) + 1); if (resolution == Resolution.MONTH) return iso; // Day [YYYY]-[MM]-[DD] iso += '-' + leftZeroPad2(calendar.get(GregorianCalendar.DAY_OF_MONTH)); if (resolution == Resolution.DAY) return iso; // TODO: Times require the time zone String z = toTimeZone(calendar.getTimeZone().getOffset(timems)); // Hour [YYYY]-[MM]-[DD]T[hh] iso += 'T' + leftZeroPad2(calendar.get(GregorianCalendar.HOUR_OF_DAY)); if (resolution == Resolution.HOUR) return iso + z; // Minute [YYYY]-[MM]-[DD]T[hh]:[mm] iso += ':' + leftZeroPad2(calendar.get(GregorianCalendar.MINUTE)); if (resolution == Resolution.MINUTE) return iso + z; // Second [YYYY]-[MM]-[DD]T[hh]:[mm]:[ss] iso += ':' + leftZeroPad2(calendar.get(GregorianCalendar.SECOND)); if (resolution == Resolution.SECOND) return iso + z; // MilliSecond [YYYY]-[MM]-[DD]T[hh]:[mm]:[ss].[sss] iso += '.' + leftZeroPad3(calendar.get(GregorianCalendar.MILLISECOND)); return iso + z; } private static Number toNumber(long timems, Resolution resolution) { // Resolution higher than Day -> Long if (resolution == Resolution.MILLISECOND) return timems; else if (resolution == Resolution.SECOND) return timems / ONE_SECOND_IN_MS; else if (resolution == Resolution.MINUTE) return timems / ONE_MINUTE_IN_MS; else if (resolution == Resolution.HOUR) return timems / ONE_HOUR_IN_MS; // Resolution lower than Day -> Integer Calendar c = GregorianCalendar.getInstance(); c.setTimeInMillis(timems); if (resolution == Resolution.DAY) return c.get(GregorianCalendar.YEAR) * 10000 + (c.get(GregorianCalendar.MONTH) + 1) * 100 + c.get(GregorianCalendar.DAY_OF_MONTH); if (resolution == Resolution.MONTH) return c.get(GregorianCalendar.YEAR) * 100 + c.get(GregorianCalendar.MONTH) + 1; if (resolution == Resolution.YEAR) return c.get(GregorianCalendar.YEAR); return null; } /** * Pads the given numbers with zeros to the left. * * @param value to pad (eg. 2) * @return The padded value (eg. "02") */ private static String leftZeroPad2(int value) { return (value < 10)? "0" + value : Integer.toString(value); } /** * Pads the given numbers with zeros to the left. * * @param value to pad (eg. 2) * @return The padded value (eg. "002") */ private static String leftZeroPad3(int value) { if (value >= 100) return Integer.toString(value); if (value >= 10) return "0" + value; return "00" + value; } /** * Pads the given numbers with zeros to the left. * * @param value to pad (eg. 2) * @return The padded value (eg. "0002") */ private static String leftZeroPad4(int value) { if (value >= 1000) return Integer.toString(value); if (value >= 100) return "0" + value; if (value >= 10) return "00" + value; else return "000" + value; } /** * Returns the timezone component of and ISO 8601 date as +[hh]:[ss], * -[hh]:[ss] or Z. * * @param offset in milliseconds. * @return the timezone component as +[hh]:[ss], -[hh]:[ss] or Z */ private static String toTimeZone(int offset) { if (offset == 0) return "Z"; int _offset = offset < 0 ? offset*-1 : offset; return (offset >= 0 ? "+" : "-") + leftZeroPad2(_offset / (1000*60*60)) + ':' + leftZeroPad2((_offset / (1000*60)) % 60); } /** * Indicates whether the specified value is made of digits only. * * @param value The value to test. * * @return The index of any character is not a digit [0-9]; * -1 otherwise. */ private static int findNonDigitCharacter(String value) { final int length = value.length(); for (int i = 0; i < length; i++) { char c = value.charAt(i); if (c < '0' || c > '9') return i; } return -1; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy