com.buffalokiwi.api.APIDate.java Source code

Java tutorial

Introduction

Here is the source code for com.buffalokiwi.api.APIDate.java

Source

/**
 * This file is part of the Aerodrome package, and is subject to the 
 * terms and conditions defined in file 'LICENSE', which is part 
 * of this source code package.
 *
 * Copyright (c) 2016 All Rights Reserved, John T. Quinn III,
 * <johnquinn3@gmail.com>
 *
 * THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 * KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
 * PARTICULAR PURPOSE.
 */

package com.buffalokiwi.api;

import java.sql.Timestamp;
import java.time.DateTimeException;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.format.DateTimeParseException;
import java.time.temporal.ChronoField;
import java.time.temporal.TemporalAccessor;
import java.util.Date;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * A wrapper for all of the various date/time formats used within some API.
 * 
 * A set of base formats have been included in the base configuration.
 * 
 * Formats:
 * yyyy-MM-dd'T'HH:mm:ss
 * yyyy-MM-dd'T'HH:mm:ss'Z'
 * yyyy-MM-dd'T'HH:mm:ss.SSSSSSS'Z'
 * yyyy-MM-dd'T'HH:mm:ss.SSSSSSSXXX
 * 
 * @author John Quinn
 */
public class APIDate implements IAPIDate {
    /**
     * Standard local date format without the T 
     */
    public static final String FMT_STD = "yyyy-MM-dd HH:mm:ss";

    /**
     * Local time without timezone.
     * If not specified, this will assume the offset to be ZoneId.systemDefault()
     */
    public static final String FMT_LOCAL = "yyyy-MM-dd'T'HH:mm:ss";

    /**
     * UTC 
     */
    public static final String FMT_ZULU = "yyyy-MM-dd'T'HH:mm:ss'Z'";

    /**
     * UTC with microseconds
     */
    public static final String FMT_ZULU_MICRO = "yyyy-MM-dd'T'HH:mm:ss.SSSSSSS'Z'";

    /**
     * UTC with forced zeroed microseconds
     */
    public static final String FMT_ZULU_ZERO = "yyyy-MM-dd'T'HH:mm:ss'.0000000'Z";

    /**
     * Local date/time with microseconds and timezone offset 
     */
    public static final String FMT_LOCAL_MICRO = "yyyy-MM-dd'T'HH:mm:ss.SSSSSSSXXX";

    /**
     * Available formats for the parser to try
     */
    protected static final List<DateTimeFormatter> FORMATS = new CopyOnWriteArrayList<>();

    /**
     * Logger 
     */
    protected static final Log LOG = LogFactory.getLog(APIDate.class);

    /**
     * The zoned date and time to UTC
     */
    protected final ZonedDateTime date;

    /**
     * The zone offset 
     */
    protected final ZoneOffset offset;

    /**
     * Initialize the date/time formats used by Jet 
     */
    static {
        FORMATS.add(DateTimeFormatter.ISO_ZONED_DATE_TIME);
        FORMATS.add(DateTimeFormatter.ISO_OFFSET_DATE_TIME);
        FORMATS.add(DateTimeFormatter.ISO_DATE_TIME);
        FORMATS.add(new DateTimeFormatterBuilder().appendPattern(FMT_LOCAL)
                .appendFraction(ChronoField.MICRO_OF_SECOND, 0, 9, true).appendOffset("+HH:MM", "Z").toFormatter());
        FORMATS.add(new DateTimeFormatterBuilder().appendPattern(FMT_LOCAL)
                .appendFraction(ChronoField.MICRO_OF_SECOND, 0, 9, true).appendOffset("+HHMM", "+0000")
                .toFormatter());
        FORMATS.add(new DateTimeFormatterBuilder().appendPattern(FMT_LOCAL).toFormatter());
        FORMATS.add(new DateTimeFormatterBuilder().appendPattern(FMT_STD).toFormatter());
    }

    /**
     * Add a custom date/time format to the list 
     * @param format Format to add
     */
    public void addFormat(final DateTimeFormatter format) {
        if (format == null)
            throw new IllegalArgumentException("format can't be null");

        if (!FORMATS.contains(format))
            FORMATS.add(format);
    }

    /**
     * Attempt to take some value and turn it into a valid APIDate.
     * If it isn't valid, then this returns null.
     * 
     * @param value Jet value 
     * @return date or null
     */
    public static APIDate fromStringOrNull(String value) {
        if (value == null || value.isEmpty())
            return null;

        for (final DateTimeFormatter fmt : FORMATS) {
            try {
                final TemporalAccessor t = fmt.parse(value);

                try {
                    return new APIDate(ZonedDateTime.from(t));
                } catch (DateTimeException e) {
                    APILog.warn(LOG, e, "Failed to determine timezone.  Defaulting to local offset");
                    final LocalDateTime local = LocalDateTime.from(t);
                    final ZoneOffset offset = ZoneId.systemDefault().getRules().getOffset(Instant.now());
                    return new APIDate(ZonedDateTime.of(local, offset));
                }
            } catch (DateTimeParseException e) {
                //..do nothing, yet.
            }
        }

        //..Not found.  Log it and return null
        APILog.error(LOG, "Failed to parse date string:", value);
        return null;
    }

    /**
     * Create a new APIDate instance from ZonedDateTime 
     * @param zdt 
     */
    public APIDate(final ZonedDateTime zdt) {
        if (zdt == null)
            throw new IllegalArgumentException("zdt can't be null");

        date = zdt;

        this.offset = zdt.getOffset();
    }

    /**
     * Create a new APIDate set to now.
     * This will use the local system offset
     */
    public APIDate() {
        this(Instant.now().atZone(ZoneId.systemDefault()));
    }

    /**
     * Create a new APIDate using the local system offset 
     * @param date date to use 
     */
    public APIDate(final Date date) {
        if (date == null)
            throw new IllegalArgumentException("date can't be null");

        this.date = date.toInstant().atZone(ZoneId.systemDefault());
        offset = ZoneId.systemDefault().getRules().getOffset(date.toInstant());
    }

    /**
     * Retrieve the date in the local time zone
     * @return zoned time
     */
    @Override
    public ZonedDateTime getLocalDate() {
        return date.withZoneSameInstant(ZoneId.systemDefault());
    }

    /**
     * Retrieve the exact string retrieved from the api response that 
     * represents a date.
     * @param pattern DateTimeFormatter pattern to use for ofPattern()
     * @return date string 
     */
    @Override
    public String getDateString(final String pattern) {
        return date.format(DateTimeFormatter.ofPattern(pattern));
    }

    /**
     * Retrieve the local date as a string.
     * This does NOT include zone information
     * @return date/time
     */
    @Override
    public String getLocalDateString() {
        return date.withZoneSameInstant(offset).format(DateTimeFormatter.ofPattern(FMT_LOCAL));
    }

    /**
     * Retrieve the date in UTC.
     * @return  Date
     */
    @Override
    public Date getDate() {
        return Date.from(date.toInstant());
    }

    /**
     * Convert whatever date this is to the system zone
     * @return system date/time
     */
    @Override
    public Date toLocalDate() {
        return Date.from(date.withZoneSameInstant(ZoneId.systemDefault()).toInstant());
    }

    /**
     * Converts the internal UTC date/time to a timestamp.
     * The returned Timestamp will always be UTC.
     * @return timestamp
     */
    @Override
    public Timestamp toSqlTimestamp() {
        return new Timestamp(getDate().getTime());
    }

    @Override
    public String toString() {
        return getDateString(FMT_ZULU_MICRO);
    }
}