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

org.fujion.common.DateUtil Maven / Gradle / Ivy

There is a newer version: 3.1.0
Show newest version
/*
 * #%L
 * fujion
 * %%
 * Copyright (C) 2018 Fujion Framework
 * %%
 * 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.
 *
 * #L%
 */
package org.fujion.common;

import java.text.DateFormat;
import java.text.DecimalFormat;
import java.text.ParseException;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang.time.DateUtils;
import org.apache.commons.lang.time.FastDateFormat;

/**
 * Utility methods for managing dates.
 */
public class DateUtil {
    
    private static ThreadLocal decimalFormat = new ThreadLocal() {
        
        @Override
        protected DecimalFormat initialValue() {
            return new DecimalFormat("##0.##");
        }
    };
    
    private static final String HL7_DATE_ONLY_PATTERN = "yyyyMMdd";
    
    private static final String HL7_DATE_TIME_PATTERN = HL7_DATE_ONLY_PATTERN + "HHmmssz";
    
    private static final String UNKNOWN = "Unknown";
    
    /*
     * Defines a regular expression pattern representing a permissible
     * extended style date that can be converted into a traditional date.
     *
     * Such a value starts with either 't' or 'n', and then optionally plus or
     * minus a numeric value of 'd' (days), 'm' (months), or 'y' (years).
     */
    private static final Pattern PATTERN_EXT_DATE = Pattern
            .compile("^\\s*[t|n]{1}\\s*([+|-]{1}\\s*[\\d]*\\s*[s|n|h|d|m|y]?)?\\s*$");
    
    /*
     * Defines a regular expression pattern representing a value ending in one
     * of the acceptable extended style date units (d, m, or y).
     */
    private static final Pattern PATTERN_SPECIFIES_UNITS = Pattern.compile("^.*[s|n|h|d|m|y]$");
    
    /*
     * Defines a regular expression pattern for extracting a numeric prefix from a string.
     */
    private static final Pattern PATTERN_NUMERIC_PREFIX = Pattern.compile("^-?[\\d\\.]+");
    
    private static final double[] MS_FP = new double[] { 31557600000.0, 2592000000.0, 604800000.0, 86400000.0, 3600000.0,
            60000.0, 1000.0, 1.0 };
    
    private static final long[] MS_LG = new long[] { 31557600000L, 2592000000L, 604800000L, 86400000L, 3600000L, 60000L,
            1000L, 1L };
    
    /**
     * Labels for time units. TODO: externalize these for localization support.
     */
    public static String[][] TIME_UNIT = new String[][] { { "year", "years", "yr", "yrs" },
            { "month", "months", "mo", "mos" }, { "week", "weeks", "wk", "wks" }, { "day", "days", "day", "days" },
            { "hour", "hours", "hr", "hrs" }, { "minute", "minutes", "min", "mins" }, { "second", "seconds", "sec", "secs" },
            { "millisecond", "milliseconds", "ms", "ms" } };
    
    /**
     * Represents time units in order of increasing precision.
     */
    public enum TimeUnit {
        YEARS, MONTHS, WEEKS, DAYS, HOURS, MINUTES, SECONDS, MILLISECONDS
    }
    
    /**
     * Enum representing common date formats.
     */
    public enum Format {
        //@formatter:off
        WITH_TZ("dd-MMM-yyyy HH:mm zzz"),
        WITHOUT_TZ("dd-MMM-yyyy HH:mm"),
        WITHOUT_TIME("dd-MMM-yyyy"),
        HL7(HL7_DATE_TIME_PATTERN),
        HL7_WITHOUT_TIME(HL7_DATE_ONLY_PATTERN),
        JS_WITH_TZ("yyyy-MM-dd HH:mm zzz"),
        JS_WITHOUT_TZ("yyyy-MM-dd HH:mm"),
        JS_WITHOUT_TIME("yyyy-MM-dd"),
        TO_STRING("EEE MMM dd HH:mm:ss zzz yyyy");
        //@formatter:on
        
        private String pattern;
        
        private Format(String pattern) {
            this.pattern = pattern;
        }
        
        /**
         * Returns the format pattern.
         *
         * @return The format pattern.
         */
        public String getPattern() {
            return pattern;
        }
        
        /**
         * Returns a formatter for this date format.
         *
         * @return A formatter.
         */
        public FastDateFormat getFormatter() {
            boolean ignoreTime = this == WITHOUT_TIME || this == HL7_WITHOUT_TIME;
            return FastDateFormat.getInstance(pattern, ignoreTime ? TimeZone.getDefault() : getLocalTimeZone());
        }
        
        /**
         * Formats an input date.
         *
         * @param date The date to format.
         * @return The formatted date.
         */
        public String format(Date date) {
            return date == null ? "" : getFormatter().format(date);
        }
        
        /**
         * Parses an input value.
         *
         * @param value The value to parse.
         * @return The resulting date value if successful.
         * @throws ParseException Date parsing exception.
         */
        public Date parse(String value) throws ParseException {
            return parseDate(value, pattern);
        }
    }
    
    /**
     * 

* Convert a string value to a date/time. Attempts to convert using the four locale-specific * date formats (FULL, LONG, MEDIUM, SHORT). If these fail, looks to see if T+/-offset or * N+/-offset is used. *

*

* TODO: probably we can make the "Java parse" portion a bit smarter by using a better variety * of formats, maybe to catch Euro-style input as well. *

*

* TODO: probably we can add something like "t+d" or "t-y" as valid cases; in these scenarios, * the coefficient was omitted and could be defaulted to 1. *

* * @param s String containing value to be converted. * @return Date object corresponding to the input value, or null if * the parsing failed to resolve a valid Date. */ public static Date parseDate(String s) { Date result = null; if (s != null && !s.isEmpty()) { s = s.toLowerCase(); // make lc if ((PATTERN_EXT_DATE.matcher(s)).matches()) { // is an extended date? try { s = s.replaceAll("\\s+", ""); // strip space since they not // delim String _k = s.substring(1); // _k will ultimately be the multiplier value char k = 'd'; // k = s, n, h, d (default), m, or y if (1 == s.length()) { _k = "0"; } else { if ((PATTERN_SPECIFIES_UNITS.matcher(s)).matches()) { _k = s.substring(1, s.length() - 1); k = s.charAt(s.length() - 1); } } if ('+' == _k.charAt(0)) { // clip positive coefficient... _k = _k.substring(1); } int field = Calendar.DAY_OF_YEAR; int offset = Integer.parseInt(_k); Calendar c = Calendar.getInstance(); c.setLenient(false); if (s.charAt(0) == 't') { c.setTime(DateUtil.today()); } switch (k) { case 'y': // years field = Calendar.YEAR; break; case 'm': // months field = Calendar.MONTH; break; case 'h': // hours field = Calendar.HOUR_OF_DAY; break; case 'n': // minutes field = Calendar.MINUTE; break; case 's': // seconds field = Calendar.SECOND; break; } c.add(field, offset); result = c.getTime(); // format } catch (Exception e) { return null; // found unparseable date (e.g. t-y) } } else { result = tryParse(s); if (result != null) { return result; } s = s.replaceAll("[\\.|-]", "/"); // dots, dashes to slashes result = tryParse(s); if (result != null) { return result; } s = s.replaceAll("\\s", "/"); // last chance to parse: spaces to result = tryParse(s); // slashes! } } return result; } /** * Attempts to parse an input value using one of several patterns. * * @param value String to parse. * @param patterns Patterns to be tried in succession until parsing succeeds. * @return The resulting date value. * @throws ParseException Date parsing exception. */ public static Date parseDate(String value, String... patterns) throws ParseException { return DateUtils.parseDate(value, patterns); } /** * Attempts to parse a string containing a date representation using several different date * patterns. * * @param value String to parse * @return If the parsing was successful, returns the date value represented by the input value. * Otherwise, returns null. */ private static Date tryParse(String value) { for (Format format : Format.values()) { try { return format.parse(value); } catch (Exception e) {} } for (int i = 3; i >= 0; i--) { try { return DateFormat.getDateInstance(i).parse(value); } catch (Exception e) {} } return null; } /** * Clones a date. * * @param date Date to clone. * @return A clone of the original date, or null if the original date was null. */ public static Date cloneDate(Date date) { return date == null ? null : new Date(date.getTime()); } /** * Adds specified number of days to date and, optionally strips the time component. * * @param date Date value to process. * @param daysOffset # of days to add. * @param stripTime If true, strip the time component. * @return Input value the specified operations applied. */ public static Date addDays(Date date, int daysOffset, boolean stripTime) { if (date == null) { return null; } Calendar calendar = Calendar.getInstance(); calendar.setLenient(false); // Make sure the calendar will not perform // automatic correction. calendar.setTime(date); // Set the time of the calendar to the given // date. if (stripTime) { // Remove the hours, minutes, seconds and milliseconds. calendar.set(Calendar.HOUR_OF_DAY, 0); calendar.set(Calendar.MINUTE, 0); calendar.set(Calendar.SECOND, 0); calendar.set(Calendar.MILLISECOND, 0); } calendar.add(Calendar.DAY_OF_MONTH, daysOffset); return calendar.getTime(); } /** * Strips the time component from a date. * * @param date Original date. * @return Date without the time component. */ public static Date stripTime(Date date) { return addDays(date, 0, true); } /** * Returns the input date with the time set to the end of the day. * * @param date Original date. * @return Date with time set to end of day. */ public static Date endOfDay(Date date) { if (date == null) { return null; } Calendar calendar = Calendar.getInstance(); calendar.setTime(date); calendar.set(Calendar.HOUR_OF_DAY, 23); calendar.set(Calendar.MINUTE, 59); calendar.set(Calendar.SECOND, 59); calendar.set(Calendar.MILLISECOND, 999); return calendar.getTime(); } /** * Returns a date with the current time. * * @return Current date and time. */ public static Date now() { return new Date(); } /** * Returns a date with the current day (no time). * * @return Current date. */ public static Date today() { return stripTime(now()); } /** * Compares two dates. Allows nulls. * * @param date1 First date to compare. * @param date2 Second date to compare. * @return Result of comparison. */ public static int compare(Date date1, Date date2) { long diff = date1 == date2 ? 0 : date1 == null ? -1 : date2 == null ? 1 : date1.getTime() - date2.getTime(); return diff < 0 ? -1 : diff > 0 ? 1 : 0; } /** * Converts a date/time value to a string, using the format dd-mmm-yyyy hh:mm. Because we cannot * determine the absence of a time from a time of 24:00, we must assume a time of 24:00 means * that no time is present and strip that from the return value. * * @param date Date value to convert. * @return Formatted string representation of the specified date, or an empty string if date is * null. */ public static String formatDate(Date date) { return formatDate(date, false, false); } /** * Converts a date/time value to a string, using the format dd-mmm-yyyy hh:mm. Because we cannot * determine the absence of a time from a time of 24:00, we must assume a time of 24:00 means * that no time is present and strip that from the return value. * * @param date Date value to convert. * @param showTimezone If true, time zone information is also appended. * @return Formatted string representation of the specified date, or an empty string if date is * null. */ public static String formatDate(Date date, boolean showTimezone) { return formatDate(date, showTimezone, false); } /** * Converts a date/time value to a string, using the format dd-mmm-yyyy hh:mm. Because we cannot * determine the absence of a time from a time of 24:00, we must assume a time of 24:00 means * that no time is present and strip that from the return value. * * @param date Date value to convert * @param showTimezone If true, time zone information is also appended. * @param ignoreTime If true, the time component is ignored. * @return Formatted string representation of the specified date, or an empty string if date is * null. */ public static String formatDate(Date date, boolean showTimezone, boolean ignoreTime) { ignoreTime = ignoreTime || !hasTime(date); Format format = ignoreTime ? Format.WITHOUT_TIME : showTimezone ? Format.WITH_TZ : Format.WITHOUT_TZ; return format.format(date); } /** * Same as formatDate(Date, boolean) except replaces the time separator with the specified * string. * * @param date Date value to convert * @param timeSeparator String to use in place of default time separator * @return Formatted string representation of the specified date using the specified time * separator. */ public static String formatDate(Date date, String timeSeparator) { return formatDate(date).replaceFirst(" ", timeSeparator); } /** * Convert a date to HL7 format. * * @param date Date to convert. * @return The HL7-formatted date. */ public static String toHL7(Date date) { Format format = hasTime(date) ? Format.HL7_WITHOUT_TIME : Format.HL7; return format.format(date); } /** * Returns true if the date has an associated time. * * @param date Date value to check. * @return True if the date has a time component. */ public static boolean hasTime(Date date) { if (date == null) { return false; } long time1 = date.getTime(); long time2 = stripTime(date).getTime(); return time1 != time2; // Do not use "Date.equals" since date may be of type Timestamp. } /** * Return elapsed time in ms to displayable format with units. * * @param elapsed Elapsed time in ms. * @return Elapsed time in displayable format. */ public static String formatElapsed(double elapsed) { return formatElapsed(elapsed, true, false, false); } /** * Return elapsed time in ms to displayable format with units. * * @param elapsed Elapsed time in ms. * @return Elapsed time in displayable format. * @param minUnits Minimum units for return value (null = ms). */ public static String formatElapsed(double elapsed, TimeUnit minUnits) { return formatElapsed(elapsed, true, false, false, minUnits); } /** * Return elapsed time in ms to displayable format with units. * * @param elapsed Elapsed time in ms. * @param pluralize If true, pluralize units when appropriate. * @param abbreviated If true, use abbreviated form of units. * @param round If true, round result to an integer. * @return Elapsed time in displayable format. */ public static String formatElapsed(double elapsed, boolean pluralize, boolean abbreviated, boolean round) { return formatElapsed(elapsed, pluralize, abbreviated, round, null); } /** * Return elapsed time in ms to displayable format with units. * * @param elapsed Elapsed time in ms. * @param pluralize If true, pluralize units when appropriate. * @param abbreviated If true, use abbreviated form of units. * @param round If true, round result to an integer. * @param minUnits Minimum units for return value (null = ms). * @return Elapsed time in displayable format. */ public static String formatElapsed(double elapsed, boolean pluralize, boolean abbreviated, boolean round, TimeUnit minUnits) { int index = (minUnits == null ? TimeUnit.MILLISECONDS : minUnits).ordinal(); String prefix = ""; if (elapsed < 0) { elapsed = -elapsed; prefix = "-"; } for (int i = 0; i <= index; i++) { if (elapsed >= MS_FP[i] || i == index) { elapsed /= MS_FP[i]; index = i; break; } } if (round) { elapsed = Math.floor(elapsed); } return prefix + decimalFormat.get().format(elapsed) + " " + getDurationUnits(index, pluralize && elapsed != 1.0, abbreviated); } /** * Parses an elapsed time string, returning time in milliseconds. * * @param value The string value to parse. * @return The elapsed time value in milliseconds. */ public static double parseElapsed(String value) { return parseElapsed(value, TimeUnit.MILLISECONDS); } /** * Parses an elapsed time string, returning time in specified units. * * @param value The string value to parse. * @param units The units of the returned value (defaults to ms). * @return The elapsed time value in the requested units. */ public static double parseElapsed(String value, TimeUnit units) { Matcher matcher = PATTERN_NUMERIC_PREFIX.matcher(value); if (!matcher.find()) { return 0; } int i = matcher.end(); double result; try { result = Double.parseDouble(value.substring(0, i)); } catch (NumberFormatException e) { return 0; } value = value.substring(i).trim().toLowerCase(); for (TimeUnit tu : TimeUnit.values()) { for (String unit : TIME_UNIT[tu.ordinal()]) { if (unit.equals(value)) { result *= MS_FP[tu.ordinal()]; if (units != null && units != TimeUnit.MILLISECONDS) { result /= MS_FP[units.ordinal()]; } return result; } } } return 0; } /** * Formats a duration in ms. * * @param duration Duration in ms. * @return Formatted duration. */ public static String formatDuration(long duration) { return formatDuration(duration, null); } /** * Formats a duration in ms to the specified accuracy. * * @param duration Duration in ms. * @param accuracy Accuracy of output. * @return Formatted duration. */ public static String formatDuration(long duration, TimeUnit accuracy) { return formatDuration(duration, accuracy, true, false); } /** * Formats a duration in ms to the specified accuracy. * * @param duration Duration in ms. * @param accuracy Accuracy of output. * @param pluralize If true, pluralize units when appropriate. * @param abbreviated If true, use abbreviated form of units. * @return Formatted duration. */ public static String formatDuration(long duration, TimeUnit accuracy, boolean pluralize, boolean abbreviated) { StringBuilder sb = new StringBuilder(); if (duration < 0) { duration = -duration; sb.append('-'); } accuracy = accuracy == null ? TimeUnit.MILLISECONDS : accuracy; int last = accuracy.ordinal(); boolean empty = true; for (int i = 0; i <= last; i++) { long val = duration / MS_LG[i]; duration -= val * MS_LG[i]; if (val != 0 || (empty && i == last)) { if (!empty) { sb.append(' '); } else { empty = false; } sb.append(val).append(' ').append(getDurationUnits(i, pluralize && val != 1, abbreviated)); } } return sb.toString(); } private static String getDurationUnits(TimeUnit accuracy, boolean plural, boolean abbreviated) { return getDurationUnits(accuracy.ordinal(), plural, abbreviated); } private static String getDurationUnits(int index, boolean plural, boolean abbreviated) { int which = (plural ? 1 : 0) + (abbreviated ? 2 : 0); return TIME_UNIT[index][which]; } /** * Returns the user's time zone. * * @return The user's time zone. */ public static TimeZone getLocalTimeZone() { return Localizer.getTimeZone(); } /** *

* Returns age as a formatted string expressed in days, months, or years, depending on whether * person is an infant (< 2 mos), toddler (> 2 mos, < 2 yrs), or more than 2 years old. *

* * @param dob Date of person's birth * @return the age display string */ public static String formatAge(Date dob) { return formatAge(dob, true, null); } /** *

* Returns age as a formatted string expressed in days, months, or years, depending on whether * person is an infant (< 2 mos), toddler (> 2 mos, < 2 yrs), or more than 2 years old. *

*

* Allows the caller to specify an "as-of" date. The calculated age will be as-of the * provided date, rather than as-of the current date. *

*

* Allows the caller to specify whether or not to pluralize the age units in the age display * string. *

* * @param dob Date of person's birth * @param pluralize If true, pluralize the age units in the age display string. * @param refDate The date as of which to calculate the Person's age (null means today). * @return the age display string */ public static String formatAge(Date dob, boolean pluralize, Date refDate) { if (dob == null) { return UNKNOWN; } Calendar asOf = Calendar.getInstance(); asOf.setTimeInMillis(refDate == null ? System.currentTimeMillis() : refDate.getTime()); Calendar bd = Calendar.getInstance(); bd.setTime(dob); long birthDateInDays = (asOf.getTimeInMillis() - bd.getTimeInMillis()) / 1000 / 60 / 60 / 24; if (birthDateInDays < 0) { return UNKNOWN; } if (birthDateInDays <= 1) { return "newborn"; } // If person is less than 2 months old, then display age in days if (birthDateInDays <= 60) { return formatUnits(birthDateInDays, TimeUnit.DAYS, pluralize); } int birthYear = bd.get(Calendar.YEAR); int birthMonth = bd.get(Calendar.MONTH); int birthDay = bd.get(Calendar.DATE); int refYear = asOf.get(Calendar.YEAR); int refMonth = asOf.get(Calendar.MONTH); int refDay = asOf.get(Calendar.DATE); if (birthDateInDays <= 730) { // If person is more than 2 months but less than 2 years then display age in months if (refMonth >= birthMonth && refDay >= birthDay) { // If person has had a birthday already this year return formatUnits((refYear - birthYear) * 12 + refMonth - birthMonth, TimeUnit.MONTHS, pluralize); } // then age in months = # years old * 12 + months so far this year // If person has not yet had a birthday this year, subtract 1 month return formatUnits((refYear - birthYear) * 12 + refMonth - birthMonth - 1, TimeUnit.MONTHS, pluralize); } // If person is more than 2 years old then display age in years return formatUnits(getAgeInYears(birthYear, birthMonth, birthDay, refYear, refMonth, refDay), TimeUnit.YEARS, pluralize); } private static int getAgeInYears(int birthYear, int birthMonth, int birthDay, int refYear, int refMonth, int refDay) { // If person has had a birthday already this year if (refMonth > birthMonth || (refMonth == birthMonth && refDay >= birthDay)) { return refYear - birthYear; } // If person has not yet had a birthday this year, subtract 1 return refYear - birthYear - 1; } private static String formatUnits(long value, TimeUnit accuracy, boolean pluralize) { return value + " " + getDurationUnits(accuracy, pluralize && value != 1, true); } /** * Converts day, month, and year to a date. * * @param day Day of month. * @param month Month (1=January, etc.) * @param year Year (4 digit). * @return Date instance. */ public static Date toDate(int day, int month, int year) { return toDate(day, month, year, 0, 0, 0); } /** * Converts day, month, year and time parameters to a date. * * @param day Day of month. * @param month Month (1=January, etc.) * @param year Year (4 digit). * @param hr Hour of day. * @param min Minutes past the hour. * @param sec Seconds past the minute. * @return Date instance. */ public static Date toDate(int day, int month, int year, int hr, int min, int sec) { Calendar cal = Calendar.getInstance(Localizer.getTimeZone()); cal.set(year, month - 1, day, hr, min, sec); cal.set(Calendar.MILLISECOND, 0); return cal.getTime(); } /** * Enforce static class. */ private DateUtil() { } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy