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

com.arakelian.core.utils.DateUtils Maven / Gradle / Ivy

Go to download

Small utility classes useful in a variety of projects. Don't reinvent the wheel. Stay small.

There is a newer version: 4.0.1
Show newest version
/*
 * 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 com.arakelian.core.utils;

import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.Month;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.chrono.IsoChronology;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.format.DateTimeParseException;
import java.time.format.ResolverStyle;
import java.time.format.SignStyle;
import java.time.temporal.ChronoField;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalQuery;
import java.util.Date;
import java.util.Random;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Preconditions;

/**
 * Generic date parsing and conversion utilities
 *
 * Note: Consider at some point removing dependency on joda-time and switching to Java 8 time.
 *
 * @see http://time4j.net/tutorial/appendix.html
 **/
public class DateUtils {
    public enum EpochUnits {
        MICROSECONDS(100000000000000L) {
            @Override
            public long toMillis(final long value) {
                return value / 1000;
            }
        },

        MILLISECONDS(100000000000L) {
            @Override
            public long toMillis(final long value) {
                return value;
            }
        },

        SECONDS(0) {
            @Override
            public long toMillis(final long value) {
                return value * 1000;
            }
        };

        public static EpochUnits valueOf(final long epoch) {
            if (MICROSECONDS.isValid(epoch)) {
                return MICROSECONDS;
            } else if (MILLISECONDS.isValid(epoch)) {
                return MILLISECONDS;
            } else {
                return SECONDS;
            }
        }

        private final long minValue;

        private EpochUnits(final long minValue) {
            this.minValue = minValue;
        }

        public Instant toInstant(final long value) {
            final long epochMillis = toMillis(value);
            return Instant.ofEpochMilli(epochMillis);
        }

        public abstract long toMillis(long value);

        private boolean isValid(final long epoch) {
            return epoch >= minValue || epoch <= -minValue;
        }
    }

    private static final Logger LOGGER = LoggerFactory.getLogger(DateUtils.class);

    /**
     * Customized ISO 8601 formatter which ensures that milliseconds use the same number of digit
     * positions, even if it is zero, or ends with zeros; the default Java 8 versions will remove or
     * trim them in those situations
     **/
    private static final DateTimeFormatter ISO_8601_NANOS = new DateTimeFormatterBuilder() //
            .appendValue(ChronoField.YEAR_OF_ERA, 4, 19, SignStyle.EXCEEDS_PAD) //
            .appendLiteral('-') //
            .appendValue(ChronoField.MONTH_OF_YEAR, 2) //
            .appendLiteral('-') //
            .appendValue(ChronoField.DAY_OF_MONTH, 2) //
            .appendLiteral('T') //
            .appendValue(ChronoField.HOUR_OF_DAY, 2) //
            .appendLiteral(':') //
            .appendValue(ChronoField.MINUTE_OF_HOUR, 2) //
            .optionalStart() //
            .appendLiteral(':') //
            .appendValue(ChronoField.SECOND_OF_MINUTE, 2) //
            .optionalStart() //
            .appendFraction(ChronoField.NANO_OF_SECOND, 9, 9, true) //
            .optionalEnd() //
            .optionalEnd() //
            .appendOffset("+HHmm", "Z") //
            .toFormatter();

    /** Number of milliseconds per day **/
    private static final long MILLIS_PER_DAY = 24 * 60 * 60 * 1000;

    /** Thread-safe date only **/
    public static final DateTimeFormatter MM_DD_YYYY = DateTimeFormatter.ofPattern("MM/dd/yyyy");

    /** Thread-safe Date with time (without seconds) **/
    public static final DateTimeFormatter MMDDYYYY_HHMM = DateTimeFormatter.ofPattern("MM/dd/yyyy hh:mm a");

    /** Thread-safe Date with time (including seconds) **/
    public static final DateTimeFormatter MMDDYYYY_HHMMSS = DateTimeFormatter
            .ofPattern("MM/dd/yyyy hh:mm:ss a");

    /** Thread-safe YYYY/MM/DD formatter */
    public static final DateTimeFormatter YYYY_MM = DateTimeFormatter.ofPattern("yyyy/MM");

    /** Thread-safe YYYY/MM/DD formatter */
    public static final DateTimeFormatter YYYY_MM_DD = DateTimeFormatter.ofPattern("yyyy/MM/dd");

    /** Thread-safe M/D/YY formatter */
    public static final DateTimeFormatter M_D_YY = DateTimeFormatter.ofPattern("M/d/yy");

    /** Thread-safe Sat M/D/YY formatter */
    public static final DateTimeFormatter EEE_M_D_YY = DateTimeFormatter.ofPattern("EEE M/d/yy");

    /** Thread-safe HH:MM am/pm */
    public static final DateTimeFormatter H_MM_ampm = DateTimeFormatter.ofPattern("h:mma");

    private static final DateTimeFormatter ZONED_DATE_TIME_PARSER = new DateTimeFormatterBuilder() //
            .parseStrict() //
            .parseCaseInsensitive() //
            .optionalStart().append(DateTimeFormatter.ISO_ZONED_DATE_TIME).optionalEnd() //
            .optionalStart().append(DateTimeFormatter.ISO_DATE).optionalEnd() //
            .optionalStart().append(DateTimeFormatter.ISO_INSTANT).optionalEnd() //
            .parseStrict() //
            .parseCaseInsensitive() //
            .appendPattern("[yyyy/M[M]/d[d]]") //
            .appendPattern("[yyyyMMdd]") //
            .appendPattern("[yyyyMMMdd]") //
            .appendPattern("[M[M]/d[d]/yyyy]") //
            .appendPattern("[M[M]-d[d]-yyyy]") //
            .appendPattern("[M[M].d[d].yyyy]") //
            .appendPattern("[MMM-d[d]-yyyy]") //
            .appendPattern("[d[d]-MMM-yyyy]") //
            .appendPattern("[MMM d[d][,] yyyy]") //
            .appendPattern("[MMMM d[d][,] yyyy]") //
            .parseDefaulting(ChronoField.HOUR_OF_DAY, 0) //
            .parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0) //
            .parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0) //
            .parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0) //
            .parseDefaulting(ChronoField.ERA, 1) // AD
            .toFormatter() //
            .withChronology(IsoChronology.INSTANCE) //
            .withResolverStyle(ResolverStyle.STRICT);

    public static final ZoneId UTC_ZONE = ZoneId.of("Z");

    private static final Random JVM_RANDOM = new Random();

    public static ZonedDateTime atStartOfDay(final ZonedDateTime date) {
        return date != null ? date.truncatedTo(ChronoUnit.DAYS) : null;
    }

    public static int compare(final Date d1, final Date d2) {
        if (d1 == null) {
            if (d2 != null) {
                return -1;
            }
        } else if (d2 == null) {
            return +1;
        } else {
            final long t1 = d1.getTime();
            final long t2 = d2.getTime();
            if (t1 < t2) {
                return -1;
            } else if (t1 > t2) {
                return -1;
            }
        }
        return 0;
    }

    public static boolean hasSameDate(final ZonedDateTime lhs, final ZonedDateTime rhs) {
        if (lhs == null) {
            return rhs == null;
        } else if (rhs == null) {
            return false;
        }
        if (lhs.getMonth() != rhs.getMonth()) {
            return false;
        }
        if (lhs.getDayOfMonth() != rhs.getDayOfMonth()) {
            return false;
        }
        if (lhs.getYear() != rhs.getYear()) {
            return false;
        }
        return true;
    }

    public static boolean hasTimeComponent(final Date date) {
        if (date != null) {
            final long epochMillis = date.getTime();
            final long timePortion = epochMillis % MILLIS_PER_DAY;
            return timePortion != 0;
        }
        return false;
    }

    public static boolean hasTimeComponent(final ZonedDateTime dateTime) {
        return dateTime != null && (dateTime.getHour() != 0 //
                || dateTime.getMinute() != 0 //
                || dateTime.getSecond() != 0 //
                || dateTime.getNano() != 0);
    }

    public static boolean isUtc(final ZonedDateTime date) {
        if (date == null) {
            return false;
        }
        return UTC_ZONE.equals(date.getZone());
    }

    public static ZonedDateTime nowWithZoneUtc() {
        return ZonedDateTime.now(ZoneOffset.UTC);
    }

    public static  T parse(
            final String text,
            final ZoneId zoneIfNotSpecified,
            final TemporalQuery query) {
        if (text == null) {
            return null;
        }
        try {
            // rely upon zone offset being specified in original
            final T date = ZONED_DATE_TIME_PARSER.parse(text, query);
            return date;
        } catch (final DateTimeParseException e1) {
            try {
                // try with system timezone
                final T date = ZONED_DATE_TIME_PARSER.withZone(zoneIfNotSpecified).parse(text, query);
                return date;
            } catch (final DateTimeParseException e2) {
                // invalid dates are treated as nulls
                LOGGER.trace("{}", e2.getMessage());
                return null;
            }
        }
    }

    public static ZonedDateTime randomZonedDateTimeUtc(
            final Random random,
            final ZonedDateTime from,
            final ZonedDateTime to) {
        final long begin = DateUtils.toEpochMillisUtc(from);
        final long end = DateUtils.toEpochMillisUtc(to);
        final long range = end - begin + 1;
        final long epoch = begin + (long) (random.nextDouble() * range);
        return DateUtils.toZonedDateTimeUtc(epoch, EpochUnits.MILLISECONDS);
    }

    public static ZonedDateTime randomZonedDateTimeUtc(final ZonedDateTime from, final ZonedDateTime to) {
        return randomZonedDateTimeUtc(JVM_RANDOM, from, to);
    }

    public static long timeBetween(
            final ZonedDateTime firstDate,
            final ZonedDateTime secondDate,
            final ChronoUnit units) {
        Preconditions.checkArgument(units != null, "units must be non-null");
        final LocalDate first = toUtc(firstDate).toLocalDate();
        final LocalDate after = toUtc(secondDate).toLocalDate();
        return Math.abs(units.between(first, after));
    }

    public static Date toDate(final Instant instant) {
        return instant != null ? Date.from(instant) : null;
    }

    public static Date toDate(final LocalDateTime date) {
        return date != null ? toDate(date.atZone(ZoneOffset.systemDefault())) : null;
    }

    public static Date toDate(final LocalDateTime date, final ZoneOffset offset) {
        return date != null ? toDate(date.toInstant(offset)) : null;
    }

    public static Date toDate(final ZonedDateTime date) {
        return date != null ? Date.from(date.toInstant()) : null;
    }

    public static long toEpochMillisUtc(final ZonedDateTime date) {
        Preconditions.checkArgument(date != null, "date must be non-null");
        return toUtc(date).toInstant().toEpochMilli();
    }

    public static LocalDateTime toLocalDateTime(final String text) {
        return parse(text, ZoneOffset.systemDefault(), LocalDateTime::from);
    }

    public static long toNanos(final int millis) {
        return TimeUnit.MILLISECONDS.toNanos(millis);
    }

    public static String toStringIsoFormat(final String date) {
        return toStringIsoFormat(toZonedDateTimeUtc(date));
    }

    /**
     * 

* Returns the given ZonedDateTime in ISO format that readable by a wide range of * clients. *

* *

* In general the format has these components: *

*
    *
  • A date: yyyy '-' MM '-' dd
  • *
  • A time: 'T' HH ':' mm ':' ss
  • *
  • Milliseconds: ('.' | ',') digit+
  • *
  • UTC offset: 'Z'
  • *
* * Example: 2017-12-29T03:21:24.564000000Z * * @param date * a zoned date/time value * @return the given ZonedDateTime in ISO format * * @see dateOptionalTimeParser * @see strict_date_optional_time */ public static String toStringIsoFormat(final ZonedDateTime date) { // we convert given date to UTC timezone and output in UTC format (ends with Z) return date != null ? date.withZoneSameInstant(ZoneOffset.UTC).format(ISO_8601_NANOS) : null; } public static ZonedDateTime toUtc(final ZonedDateTime date) { return date != null ? date.withZoneSameInstant(ZoneOffset.UTC) : null; } public static ZonedDateTime toZonedDateTimeUtc(final Date date) { return date != null ? toZonedDateTimeUtc(toInstant(date)) : null; } public static ZonedDateTime toZonedDateTimeUtc(final Instant instant) { return instant != null ? ZonedDateTime.ofInstant(instant, ZoneOffset.UTC) : null; } public static ZonedDateTime toZonedDateTimeUtc(final int year, final Month month, final int dayOfMonth) { return toZonedDateTimeUtc(LocalDate.of(year, month, dayOfMonth)); } public static ZonedDateTime toZonedDateTimeUtc(final LocalDate date) { return date != null ? date.atStartOfDay(UTC_ZONE) : null; } /** * Returns a ZonedDateTime from the given epoch value. * * Note that this implementation attempts to protect against caller providing timestamps in * different units. For example, Unix timestamps are the number of SECONDS since January 1, * 1970, while Java Dates are number of MILLISECONDS since January 1, 1970. * * @param epoch * timestamp value in seconds, milliseconds or microseconds * @return a ZonedDateTime or null if the date is not valid */ public static ZonedDateTime toZonedDateTimeUtc(final long epoch) { final EpochUnits units = EpochUnits.valueOf(epoch); return toZonedDateTimeUtc(epoch, units); } public static ZonedDateTime toZonedDateTimeUtc(final String text) { final ZonedDateTime date = parse(text, ZoneOffset.systemDefault(), ZonedDateTime::from); return date != null ? toUtc(date) : null; } private static Instant toInstant(final Date date) { final Instant instant; if (date instanceof java.sql.Date) { // SQL dates do not contain time information and they throw an exception if toInstant() // is called instant = Instant.ofEpochMilli(date.getTime()); } else { instant = date.toInstant(); } return instant; } /** * Returns a ZonedDateTime from the given epoch value. * * @param epoch * value in milliseconds * @return a ZonedDateTime or null if the date is not valid */ private static ZonedDateTime toZonedDateTimeUtc(final long epochValue, final EpochUnits units) { Preconditions.checkArgument(units != null, "units must be non-null"); final Instant instant = units.toInstant(epochValue); return ZonedDateTime.ofInstant(instant, ZoneOffset.UTC); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy