brooklyn.util.time.Time.java Source code

Java tutorial

Introduction

Here is the source code for brooklyn.util.time.Time.java

Source

/*
 * 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 brooklyn.util.time;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;

import javax.annotation.Nullable;

import brooklyn.util.exceptions.Exceptions;
import brooklyn.util.text.Strings;

import com.google.common.base.Function;
import com.google.common.base.Stopwatch;

public class Time {

    public static final String DATE_FORMAT_PREFERRED = "yyyy-MM-dd HH:mm:ss.SSS";
    public static final String DATE_FORMAT_STAMP = "yyyyMMdd-HHmmssSSS";
    public static final String DATE_FORMAT_SIMPLE_STAMP = "yyyy-MM-dd-HHmm";

    public static final long MILLIS_IN_SECOND = 1000;
    public static final long MILLIS_IN_MINUTE = 60 * MILLIS_IN_SECOND;
    public static final long MILLIS_IN_HOUR = 60 * MILLIS_IN_MINUTE;
    public static final long MILLIS_IN_DAY = 24 * MILLIS_IN_HOUR;
    public static final long MILLIS_IN_YEAR = 365 * MILLIS_IN_DAY;

    /** returns the current time in {@value #DATE_FORMAT_PREFERRED} format,
     * numeric big-endian but otherwise optimized for people to read, with spaces and colons and dots */
    public static String makeDateString() {
        return makeDateString(System.currentTimeMillis());
    }

    /** returns the time in {@value #DATE_FORMAT_PREFERRED} format, given a long (e.g. returned by System.currentTimeMillis) */
    public static String makeDateString(long date) {
        return new SimpleDateFormat(DATE_FORMAT_PREFERRED).format(new Date(date));
    }

    public static Function<Long, String> toDateString() {
        return dateString;
    }

    private static Function<Long, String> dateString = new Function<Long, String>() {
        @Override
        @Nullable
        public String apply(@Nullable Long input) {
            if (input == null)
                return null;
            return Time.makeDateString(input);
        }
    };

    /** returns the current time in {@value #DATE_FORMAT_STAMP} format,
     * suitable for machines to read with only numbers and dashes and quite precise (ms) */
    public static String makeDateStampString() {
        return makeDateStampString(System.currentTimeMillis());
    }

    /** returns the time in {@value #DATE_FORMAT_STAMP} format, given a long (e.g. returned by System.currentTimeMillis);
     * cf {@link #makeDateStampString()} */
    public static String makeDateStampString(long date) {
        return new SimpleDateFormat(DATE_FORMAT_STAMP).format(new Date(date));
    }

    /** returns the current time in {@value #DATE_FORMAT_SIMPLE_STAMP} format, 
     * suitable for machines to read but easier for humans too, 
     * like {@link #makeDateStampString()} but not as precise */
    public static String makeDateSimpleStampString() {
        return makeDateSimpleStampString(System.currentTimeMillis());
    }

    /** returns the time in {@value #DATE_FORMAT_SIMPLE_STAMP} format, given a long (e.g. returned by System.currentTimeMillis);
     * cf {@link #makeDateSimpleStampString()} */
    public static String makeDateSimpleStampString(long date) {
        return new SimpleDateFormat(DATE_FORMAT_SIMPLE_STAMP).format(new Date(date));
    }

    public static Function<Long, String> toDateStampString() {
        return dateStampString;
    }

    private static Function<Long, String> dateStampString = new Function<Long, String>() {
        @Override
        @Nullable
        public String apply(@Nullable Long input) {
            if (input == null)
                return null;
            return Time.makeDateStampString(input);
        }
    };

    /** @see #makeTimeString(long, boolean) */
    public static String makeTimeStringExact(long t, TimeUnit unit) {
        long nanos = unit.toNanos(t);
        return makeTimeStringNanoExact(nanos);
    }

    /** @see #makeTimeString(long, boolean) */
    public static String makeTimeStringRounded(long t, TimeUnit unit) {
        long nanos = unit.toNanos(t);
        return makeTimeStringNanoRounded(nanos);
    }

    public static String makeTimeStringRounded(Stopwatch timer) {
        return makeTimeStringRounded(timer.elapsed(TimeUnit.MILLISECONDS), TimeUnit.MILLISECONDS);
    }

    /** @see #makeTimeString(long, boolean) */
    public static String makeTimeStringExact(long t) {
        return makeTimeString(t, false);
    }

    /** @see #makeTimeString(long, boolean) */
    public static String makeTimeStringRounded(long t) {
        return makeTimeString(t, true);
    }

    /** @see #makeTimeString(long, boolean) */
    public static String makeTimeStringRoundedSince(long utc) {
        return makeTimeString(System.currentTimeMillis() - utc, true);
    }

    /** @see #makeTimeString(long, boolean) */
    public static String makeTimeStringExact(Duration d) {
        return makeTimeStringNanoExact(d.toNanoseconds());
    }

    /** @see #makeTimeString(long, boolean) */
    public static String makeTimeStringRounded(Duration d) {
        return makeTimeStringNanoRounded(d.toNanoseconds());
    }

    /** given an elapsed time, makes it readable, eg 44d 6h, or 8s 923ms, optionally rounding */
    public static String makeTimeString(long t, boolean round) {
        return makeTimeStringNano(t * 1000000L, round);
    }

    /** @see #makeTimeString(long, boolean) */
    public static String makeTimeStringNanoExact(long tn) {
        return makeTimeStringNano(tn, false);
    }

    /** @see #makeTimeString(long, boolean) */
    public static String makeTimeStringNanoRounded(long tn) {
        return makeTimeStringNano(tn, true);
    }

    /** @see #makeTimeString(long, boolean) */
    public static String makeTimeStringNano(long tn, boolean round) {
        if (tn < 0)
            return "-" + makeTimeStringNano(-tn, round);
        // units don't matter, but since ms is the usual finest granularity let's use it
        // (previously was just "0" but that was too ambiguous in contexts like "took 0")
        if (tn == 0)
            return "0ms";

        long tnm = tn % 1000000;
        long t = tn / 1000000;
        String result = "";

        long d = t / MILLIS_IN_DAY;
        t %= MILLIS_IN_DAY;
        long h = t / MILLIS_IN_HOUR;
        t %= MILLIS_IN_HOUR;
        long m = t / MILLIS_IN_MINUTE;
        t %= MILLIS_IN_MINUTE;
        long s = t / MILLIS_IN_SECOND;
        t %= MILLIS_IN_SECOND;
        long ms = t;

        int segments = 0;
        if (d > 0) {
            result += d + "d ";
            segments++;
        }
        if (h > 0) {
            result += h + "h ";
            segments++;
        }
        if (round && segments >= 2)
            return Strings.removeAllFromEnd(result, " ");
        if (m > 0) {
            result += m + "m ";
            segments++;
        }
        if (round && (segments >= 2 || d > 0))
            return Strings.removeAllFromEnd(result, " ");
        if (s > 0) {
            if (ms == 0 && tnm == 0) {
                result += s + "s";
                segments++;
                return result;
            }
            if (round && segments > 0) {
                result += s + "s";
                segments++;
                return result;
            }
            if (round && s > 10) {
                result += toDecimal(s, ms / 1000.0, 1) + "s";
                segments++;
                return result;
            }
            if (round) {
                result += toDecimal(s, ms / 1000.0, 2) + "s";
                segments++;
                return result;
            }
            result += s + "s ";
        }
        if (round && segments > 0)
            return Strings.removeAllFromEnd(result, " ");
        if (ms > 0) {
            if (tnm == 0) {
                result += ms + "ms";
                segments++;
                return result;
            }
            if (round && ms >= 100) {
                result += toDecimal(ms, tnm / 1000000.0, 1) + "ms";
                segments++;
                return result;
            }
            if (round && ms >= 10) {
                result += toDecimal(ms, tnm / 1000000.0, 2) + "ms";
                segments++;
                return result;
            }
            if (round) {
                result += toDecimal(ms, tnm / 1000000.0, 3) + "ms";
                segments++;
                return result;
            }
            result += ms + "ms ";
        }

        long us = tnm / 1000;
        long ns = tnm % 1000;

        if (us > 0) {
            if (ns == 0) {
                result += us + "us";
                segments++;
                return result;
            }
            if (round && us >= 100) {
                result += toDecimal(us, ns / 1000.0, 1) + "us";
                segments++;
                return result;
            }
            if (round && us >= 10) {
                result += toDecimal(us, ns / 1000.0, 2) + "us";
                segments++;
                return result;
            }
            if (round) {
                result += toDecimal(us, ns / 1000.0, 3) + "us";
                segments++;
                return result;
            }
            result += us + "us ";
        }

        if (ns > 0)
            result += ns + "ns";
        return Strings.removeAllFromEnd(result, " ");
    }

    public static Function<Long, String> fromLongToTimeStringExact() {
        return LONG_TO_TIME_STRING_EXACT;
    }

    private static final Function<Long, String> LONG_TO_TIME_STRING_EXACT = new FunctionLongToTimeStringExact();

    private static final class FunctionLongToTimeStringExact implements Function<Long, String> {
        @Override
        @Nullable
        public String apply(@Nullable Long input) {
            if (input == null)
                return null;
            return Time.makeTimeStringExact(input);
        }
    }

    /** @deprecated since 0.7.0 use {@link #fromLongToTimeStringExact()} */
    @Deprecated
    public static Function<Long, String> toTimeString() {
        return timeString;
    }

    @Deprecated
    private static Function<Long, String> timeString = new Function<Long, String>() {
        @Override
        @Nullable
        public String apply(@Nullable Long input) {
            if (input == null)
                return null;
            return Time.makeTimeStringExact(input);
        }
    };

    public static Function<Long, String> fromLongToTimeStringRounded() {
        return LONG_TO_TIME_STRING_ROUNDED;
    }

    private static final Function<Long, String> LONG_TO_TIME_STRING_ROUNDED = new FunctionLongToTimeStringRounded();

    private static final class FunctionLongToTimeStringRounded implements Function<Long, String> {
        @Override
        @Nullable
        public String apply(@Nullable Long input) {
            if (input == null)
                return null;
            return Time.makeTimeStringRounded(input);
        }
    }

    /** @deprecated since 0.7.0 use {@link #fromLongToTimeStringRounded()} */
    @Deprecated
    public static Function<Long, String> toTimeStringRounded() {
        return timeStringRounded;
    }

    @Deprecated
    private static Function<Long, String> timeStringRounded = new Function<Long, String>() {
        @Override
        @Nullable
        public String apply(@Nullable Long input) {
            if (input == null)
                return null;
            return Time.makeTimeStringRounded(input);
        }
    };

    public static Function<Duration, String> fromDurationToTimeStringRounded() {
        return DURATION_TO_TIME_STRING_ROUNDED;
    }

    private static final Function<Duration, String> DURATION_TO_TIME_STRING_ROUNDED = new FunctionDurationToTimeStringRounded();

    private static final class FunctionDurationToTimeStringRounded implements Function<Duration, String> {
        @Override
        @Nullable
        public String apply(@Nullable Duration input) {
            if (input == null)
                return null;
            return Time.makeTimeStringRounded(input);
        }
    }

    private static String toDecimal(long intPart, double fracPart, int decimalPrecision) {
        long powTen = 1;
        for (int i = 0; i < decimalPrecision; i++)
            powTen *= 10;
        long fpr = Math.round(fracPart * powTen);
        if (fpr == powTen) {
            intPart++;
            fpr = 0;
        }
        return intPart + "." + Strings.makePaddedString("" + fpr, decimalPrecision, "0", "");
    }

    /** sleep which propagates Interrupted as unchecked */
    public static void sleep(long millis) {
        try {
            if (millis > 0)
                Thread.sleep(millis);
        } catch (InterruptedException e) {
            throw Exceptions.propagate(e);
        }
    }

    /** as {@link #sleep(long)} */
    public static void sleep(Duration duration) {
        Time.sleep(duration.toMillisecondsRoundingUp());
    }

    /**
     * Calculates the number of milliseconds past midnight for a given UTC time.
     */
    public static long getTimeOfDayFromUtc(long timeUtc) {
        GregorianCalendar gregorianCalendar = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
        gregorianCalendar.setTimeInMillis(timeUtc);
        int hour = gregorianCalendar.get(Calendar.HOUR_OF_DAY);
        int min = gregorianCalendar.get(Calendar.MINUTE);
        int sec = gregorianCalendar.get(Calendar.SECOND);
        int millis = gregorianCalendar.get(Calendar.MILLISECOND);
        return (((((hour * 60) + min) * 60) + sec) * 1000) + millis;
    }

    /**
     * Calculates the number of milliseconds past epoch for a given UTC time.
     */
    public static long getTimeUtc(TimeZone zone, int year, int month, int date, int hourOfDay, int minute,
            int second, int millis) {
        GregorianCalendar time = new GregorianCalendar(zone);
        time.set(year, month, date, hourOfDay, minute, second);
        time.set(Calendar.MILLISECOND, millis);
        return time.getTimeInMillis();
    }

    public static long roundFromMillis(long millis, TimeUnit units) {
        if (units.compareTo(TimeUnit.MILLISECONDS) > 0) {
            double result = ((double) millis) / units.toMillis(1);
            return Math.round(result);
        } else {
            return units.convert(millis, TimeUnit.MILLISECONDS);
        }
    }

    public static long roundFromMillis(long millis, long millisPerUnit) {
        double result = ((double) millis) / millisPerUnit;
        return Math.round(result);
    }

    /**
     * Calculates how long until maxTime has passed since the given startTime. 
     * However, maxTime==0 is a special case (e.g. could mean wait forever), so the result is guaranteed
     * to be only 0 if maxTime was 0; otherwise -1 will be returned.
     */
    public static long timeRemaining(long startTime, long maxTime) {
        if (maxTime == 0) {
            return 0;
        }
        long result = (startTime + maxTime) - System.currentTimeMillis();
        return (result == 0) ? -1 : result;
    }

    /** parses a string eg '5s' or '20m 22.123ms', returning the number of milliseconds it represents (rounded);
     * -1 on blank or "never" or "off" or "false";
     * number of millis if no units specified.
     * 
     * @throws NumberFormatException if cannot be parsed (or if null)
     */
    public static long parseTimeString(String timeString) {
        return (long) parseTimeStringAsDouble(timeString);
    }

    /** parses a string eg '5s' or '20m 22.123ms', returning the number of milliseconds it represents; -1 on blank or never or off or false;
     * number of millis if no units specified.
     * 
     * @throws NumberFormatException if cannot be parsed (or if null)
     */
    public static double parseTimeStringAsDouble(String timeString) {
        if (timeString == null)
            throw new NumberFormatException("GeneralHelper.parseTimeString cannot parse a null string");
        try {
            double d = Double.parseDouble(timeString);
            return d;
        } catch (NumberFormatException e) {
            //look for a type marker
            timeString = timeString.trim();
            String s = Strings.getLastWord(timeString).toLowerCase();
            timeString = timeString.substring(0, timeString.length() - s.length()).trim();
            int i = 0;
            while (s.length() > i) {
                char c = s.charAt(i);
                if (c == '.' || Character.isDigit(c))
                    i++;
                else
                    break;
            }
            String num = s.substring(0, i);
            if (i == 0) {
                num = Strings.getLastWord(timeString).toLowerCase();
                timeString = timeString.substring(0, timeString.length() - num.length()).trim();
            } else {
                s = s.substring(i);
            }
            long multiplier = 0;
            if (num.length() == 0) {
                //must be never or something
                if (s.equalsIgnoreCase("never") || s.equalsIgnoreCase("off") || s.equalsIgnoreCase("false"))
                    return -1;
                throw new NumberFormatException("unrecognised word  '" + s + "' in time string");
            }
            if (s.equalsIgnoreCase("ms") || s.equalsIgnoreCase("milli") || s.equalsIgnoreCase("millis")
                    || s.equalsIgnoreCase("millisec") || s.equalsIgnoreCase("millisecs")
                    || s.equalsIgnoreCase("millisecond") || s.equalsIgnoreCase("milliseconds"))
                multiplier = 1;
            else if (s.equalsIgnoreCase("s") || s.equalsIgnoreCase("sec") || s.equalsIgnoreCase("secs")
                    || s.equalsIgnoreCase("second") || s.equalsIgnoreCase("seconds"))
                multiplier = 1000;
            else if (s.equalsIgnoreCase("m") || s.equalsIgnoreCase("min") || s.equalsIgnoreCase("mins")
                    || s.equalsIgnoreCase("minute") || s.equalsIgnoreCase("minutes"))
                multiplier = 60 * 1000;
            else if (s.equalsIgnoreCase("h") || s.equalsIgnoreCase("hr") || s.equalsIgnoreCase("hrs")
                    || s.equalsIgnoreCase("hour") || s.equalsIgnoreCase("hours"))
                multiplier = 60 * 60 * 1000;
            else if (s.equalsIgnoreCase("d") || s.equalsIgnoreCase("day") || s.equalsIgnoreCase("days"))
                multiplier = 24 * 60 * 60 * 1000;
            else
                throw new NumberFormatException("unknown unit '" + s + "' in time string");
            double d = Double.parseDouble(num);
            double dd = 0;
            if (timeString.length() > 0) {
                dd = parseTimeStringAsDouble(timeString);
                if (dd == -1) {
                    throw new NumberFormatException(
                            "cannot combine '" + timeString + "' with '" + num + " " + s + "'");
                }
            }
            return d * multiplier + dd;
        }
    }

    /**
     * Parses the given date, accepting either a UTC timestamp (i.e. a long), or a formatted date.
     * @param dateString
     * @param format
     * @return
     */
    public static Date parseDateString(String dateString, DateFormat format) {
        if (dateString == null)
            throw new NumberFormatException("GeneralHelper.parseDateString cannot parse a null string");

        try {
            return format.parse(dateString);
        } catch (ParseException e) {
            // continue trying
        }

        try {
            // fix the usual ' ' => '+' nonsense when passing dates as query params
            // note this is not URL unescaping, but converting to the date format that wants "+" in the middle and not spaces
            String transformedDateString = dateString.replace(" ", "+");
            return format.parse(transformedDateString);
        } catch (ParseException e) {
            // continue trying
        }

        try {
            return new Date(Long.parseLong(dateString.trim())); // try UTC millis number
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException(
                    "Date " + dateString + " cannot be parsed as UTC millis or using format " + format);
        }
    }

    /** removes milliseconds from the date object; needed if serializing to ISO-8601 format 
     * and want to serialize back and get the same data */
    public static Date dropMilliseconds(Date date) {
        return date == null ? null
                : date.getTime() % 1000 != 0 ? new Date(date.getTime() - (date.getTime() % 1000)) : date;
    }

    /** returns the duration elapsed since the given timestamp (UTC) */
    public static Duration elapsedSince(long timestamp) {
        return Duration.millis(System.currentTimeMillis() - timestamp);
    }

    /** true iff it has been longer than the given duration since the given timestamp */
    public static boolean hasElapsedSince(long timestamp, Duration duration) {
        return elapsedSince(timestamp).compareTo(duration) > 0;
    }

    /** more readable and shorter convenience for System.currentTimeMillis() */
    public static long now() {
        return System.currentTimeMillis();
    }

}