/*
 * Copyright 2011-2021 B2i Healthcare Pte Ltd, http://b2i.sg
 * 
 * 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 com.b2international.snowowl.core.date;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.util.Date;

import javax.annotation.Nullable;

/**
 * Effective Times should be always in GMT time. Use this class when working with effective times in any ontology, this will ensure proper parsing and formatting of them.
 * 
 * @since 3.9
 */
public abstract class EffectiveTimes {

	private EffectiveTimes() {
	}

	/**
	 * The long timestamp representing unset effective time. Value: {@value}
	 */
	public static final long UNSET_EFFECTIVE_TIME = -1L;

	/**
	 * The string label returned for unset effective time values. Value: {@value}
	 */
	public static final String UNSET_EFFECTIVE_TIME_LABEL = "Unpublished";

	/**
	 * Formats the given object representing an effective time with the {@value DateFormats#DEFAULT} format and returns the result.
	 * 
	 * @param effectiveTime
	 *            - the date to format, may be <code>null</code>
	 * @return - the formatted date as string
	 */
	public static final String format(@Nullable Object effectiveTime) {
		return format(effectiveTime, DateFormats.DEFAULT);
	}

	/**
	 * Formats the given object representing an effective time with the given datePattern and returns the result.
	 * <p>
	 * Unset effective times will be represented with the {@link #UNSET_EFFECTIVE_TIME_LABEL} constant.
	 * 
	 * @param effectiveTime
	 *            - the date to format, may be <code>null</code>
	 * @param datePattern
	 *            - the pattern to use
	 * @return - the formatted date as string
	 */
	public static String format(@Nullable Object effectiveTime, String datePattern) {
		return format(effectiveTime, datePattern, UNSET_EFFECTIVE_TIME_LABEL);
	}

	/**
	 * Formats the given object representing an effective time with the given datePattern and returns the result.
	 * <p>
	 * Unset effective time values will be represented with the third {@code String} argument.
	 * 
	 * @param effectiveTime
	 *            - the date to format, may be <code>null</code>
	 * @param datePattern
	 *            - the pattern to use
	 * @param unsetEffectiveTimeLabel
	 *            - the label to use when the effective time is not set
	 * @return - the formatted date as string
	 */
	public static String format(@Nullable Object effectiveTime, String datePattern, String unsetEffectiveTimeLabel) {
		if (effectiveTime == null) {
			return unsetEffectiveTimeLabel;
		} else if (effectiveTime instanceof Long) {
			if (((long) effectiveTime) == UNSET_EFFECTIVE_TIME) {
				return unsetEffectiveTimeLabel;
			}
			return Dates.formatByGmt(toDate((Long) effectiveTime), datePattern);
		}
		return Dates.formatByGmt(effectiveTime, datePattern);
	}

	/**
	 * Parses the given effectiveTime with the {@value DateFormats#DEFAULT} format and returns the parsed {@link Date}.
	 * 
	 * @param effectiveTime
	 *            - the effectiveTime to parse, cannot be <code>null</code>
	 * @return - the parsed date
	 * @throws IllegalArgumentException
	 *             - if a {@link ParseException} is throw during execution
	 */
	public static Date parse(String effectiveTime) {
		return parse(effectiveTime, DateFormats.DEFAULT);
	}

	/**
	 * Parses the given effectiveTime with the given datePattern and returns the parsed {@link Date}.
	 * 
	 * @param effectiveTime
	 *            - the effectiveTime to parse, cannot be <code>null</code>
	 * @param datePattern
	 *            - the pattern to use, cannot be <code>null</code>
	 * @return - the parsed date or <code>null</code> if the given effectiveTime value equals with the value {@link #UNSET_EFFECTIVE_TIME_LABEL}.
	 * @throws IllegalArgumentException
	 *             - if a {@link ParseException} is throw during execution
	 * @throws NullPointerException
	 *             - if one of the arguments was <code>null</code>
	 */
	public static Date parse(String effectiveTime, String datePattern) {
		if (UNSET_EFFECTIVE_TIME_LABEL.equals(effectiveTime)) {
			return null;
		}
		return Dates.parse(effectiveTime, datePattern);
	}

	/**
	 * Returns the milliseconds representation of an effective time specified in String format. If the effectiveTime is <code>null</code> it returns
	 * the {@value #UNSET_EFFECTIVE_TIME} value.
	 * 
	 * @param effectiveTime
	 *            - to extract time from
	 * @param datePattern
	 *            - a valid {@link SimpleDateFormat} date pattern
	 * @return the number of milliseconds from the effective time String
	 * @see DateFormats
	 * @see #getEffectiveTime(Date)
	 */
	public static final long getEffectiveTime(@Nullable String effectiveTime, String datePattern) {
		return getEffectiveTime(parse(effectiveTime, datePattern));
	}

	/**
	 * Returns the milliseconds representation of an effective time Date. If the effectiveTime is <code>null</code> it returns the
	 * {@value #UNSET_EFFECTIVE_TIME} value.
	 * 
	 * @param effectiveTime
	 *            - to extract time from
	 * @return the number of milliseconds from the date object
	 * @see #getEffectiveTime(String, String)
	 */
	public static final long getEffectiveTime(@Nullable Date effectiveTime) {
		if (null == effectiveTime) {
			return UNSET_EFFECTIVE_TIME;
		}
		return effectiveTime.toInstant()
			      .atZone(ZoneId.of("GMT"))
			      .toLocalDate()
			      .atStartOfDay()
			      .toInstant(ZoneOffset.UTC)
			      .toEpochMilli();
	}

	/**
	 * Converts the given effectiveTime timestamp to a {@link Date} if it is not equals to {@link #UNSET_EFFECTIVE_TIME}, in this case return
	 * <code>null</code>.
	 * 
	 * @param effectiveTime
	 *            - the effectiveTime to convert
	 * @return - the converted date, can be <code>null</code>
	 */
	@Nullable
	public static Date toDate(long effectiveTime) {
		return UNSET_EFFECTIVE_TIME == effectiveTime ? null : new Date(effectiveTime);
	}

	/**
	 * Returns <code>true</code> if the given Object is an unset representation of an effective time value, <code>false</code> otherwise.
	 * 
	 * @param effectiveTimeValue
	 * @return
	 */
	public static boolean isUnset(Object effectiveTimeValue) {
		if (effectiveTimeValue == null) {
			return true;
		} else if (effectiveTimeValue instanceof Long && UNSET_EFFECTIVE_TIME == (long) effectiveTimeValue) {
			return true;
		} else if (effectiveTimeValue instanceof String) {
			if (UNSET_EFFECTIVE_TIME_LABEL.compareToIgnoreCase((String) effectiveTimeValue) == 0) {
				return true;
			}
			try {
				if (UNSET_EFFECTIVE_TIME == Long.parseLong((String) effectiveTimeValue)) {
					return true;
				}
			} catch (NumberFormatException e) {
				// ignore, return false, since the given value cannot be a valid unset effective time value
			}
		}
		return false;
	}

}
@@ -5906,20 +5906,18 @@
 Zone
-Id.of(%22GMT%22)
+Offset.UTC
 )%0A%09%09
@@ -7348,8 +7348,9 @@
 e;%0A%09%7D%0A%0A%7D
+%0A

Update core/com.b2international.snowowl.core/src/com/b2international/snowowl/core/date/EffectiveTimes.java