
ca.uhn.fhir.util.DateUtils Maven / Gradle / Ivy
/*
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2024 Smile CDR, Inc.
* %%
* 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 ca.uhn.fhir.util;
import ca.uhn.fhir.i18n.Msg;
import com.google.common.base.Preconditions;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import java.lang.ref.SoftReference;
import java.text.ParseException;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.Month;
import java.time.YearMonth;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoField;
import java.time.temporal.TemporalAccessor;
import java.time.temporal.TemporalField;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.TimeZone;
/**
* A utility class for parsing and formatting HTTP dates as used in cookies and
* other headers. This class handles dates as defined by RFC 2616 section
* 3.3.1 as well as some other common non-standard formats.
*
* This class is basically intended to be a high-performance workaround
* for the fact that Java SimpleDateFormat is kind of expensive to
* create and yet isn't thread safe.
*
*
* This class was adapted from the class with the same name from the Jetty
* project, licensed under the terms of the Apache Software License 2.0.
*
*/
public final class DateUtils {
/**
* GMT TimeZone
*/
public static final TimeZone GMT = TimeZone.getTimeZone("GMT");
/**
* Date format pattern used to parse HTTP date headers in RFC 1123 format.
*/
@SuppressWarnings("WeakerAccess")
public static final String PATTERN_RFC1123 = "EEE, dd MMM yyyy HH:mm:ss zzz";
/**
* Date format pattern used to parse HTTP date headers in RFC 1036 format.
*/
@SuppressWarnings("WeakerAccess")
public static final String PATTERN_RFC1036 = "EEE, dd-MMM-yy HH:mm:ss zzz";
/**
* Date format pattern used to parse HTTP date headers in ANSI C
* {@code asctime()} format.
*/
@SuppressWarnings("WeakerAccess")
public static final String PATTERN_ASCTIME = "EEE MMM d HH:mm:ss yyyy";
private static final String PATTERN_INTEGER_DATE = "yyyyMMdd";
private static final String[] DEFAULT_PATTERNS = new String[] {PATTERN_RFC1123, PATTERN_RFC1036, PATTERN_ASCTIME};
private static final Date DEFAULT_TWO_DIGIT_YEAR_START;
static {
final Calendar calendar = Calendar.getInstance();
calendar.setTimeZone(GMT);
calendar.set(2000, Calendar.JANUARY, 1, 0, 0, 0);
calendar.set(Calendar.MILLISECOND, 0);
DEFAULT_TWO_DIGIT_YEAR_START = calendar.getTime();
}
/**
* This class should not be instantiated.
*/
private DateUtils() {}
/**
* Calculate a LocalDateTime with any missing date/time data points defaulting to the earliest values (ex 0 for hour)
* from a TemporalAccessor or empty if it doesn't contain a year.
*
* @param theTemporalAccessor The TemporalAccessor containing date/time information
* @return A LocalDateTime or empty
*/
public static Optional extractLocalDateTimeForRangeStartOrEmpty(
TemporalAccessor theTemporalAccessor) {
if (theTemporalAccessor.isSupported(ChronoField.YEAR)) {
final int year = theTemporalAccessor.get(ChronoField.YEAR);
final Month month = Month.of(getTimeUnitIfSupported(theTemporalAccessor, ChronoField.MONTH_OF_YEAR, 1));
final int day = getTimeUnitIfSupported(theTemporalAccessor, ChronoField.DAY_OF_MONTH, 1);
final int hour = getTimeUnitIfSupported(theTemporalAccessor, ChronoField.HOUR_OF_DAY, 0);
final int minute = getTimeUnitIfSupported(theTemporalAccessor, ChronoField.MINUTE_OF_HOUR, 0);
final int seconds = getTimeUnitIfSupported(theTemporalAccessor, ChronoField.SECOND_OF_MINUTE, 0);
return Optional.of(LocalDateTime.of(year, month, day, hour, minute, seconds));
}
return Optional.empty();
}
/**
* Calculate a LocalDateTime with any missing date/time data points defaulting to the latest values (ex 23 for hour)
* from a TemporalAccessor or empty if it doesn't contain a year.
*
* @param theTemporalAccessor The TemporalAccessor containing date/time information
* @return A LocalDateTime or empty
*/
public static Optional extractLocalDateTimeForRangeEndOrEmpty(TemporalAccessor theTemporalAccessor) {
if (theTemporalAccessor.isSupported(ChronoField.YEAR)) {
final int year = theTemporalAccessor.get(ChronoField.YEAR);
final Month month = Month.of(getTimeUnitIfSupported(theTemporalAccessor, ChronoField.MONTH_OF_YEAR, 12));
final int day = getTimeUnitIfSupported(
theTemporalAccessor,
ChronoField.DAY_OF_MONTH,
YearMonth.of(year, month).atEndOfMonth().getDayOfMonth());
final int hour = getTimeUnitIfSupported(theTemporalAccessor, ChronoField.HOUR_OF_DAY, 23);
final int minute = getTimeUnitIfSupported(theTemporalAccessor, ChronoField.MINUTE_OF_HOUR, 59);
final int seconds = getTimeUnitIfSupported(theTemporalAccessor, ChronoField.SECOND_OF_MINUTE, 59);
return Optional.of(LocalDateTime.of(year, month, day, hour, minute, seconds));
}
return Optional.empty();
}
/**
* With the provided DateTimeFormatter, parse a date time String or return empty if the String doesn't correspond
* to the formatter.
*
* @param theDateTimeString A date/time String in some date format
* @param theSupportedDateTimeFormatter The DateTimeFormatter we expect corresponds to the String
* @return The parsed TemporalAccessor or empty
*/
public static Optional parseDateTimeStringIfValid(
String theDateTimeString, DateTimeFormatter theSupportedDateTimeFormatter) {
Objects.requireNonNull(theSupportedDateTimeFormatter);
Preconditions.checkArgument(StringUtils.isNotBlank(theDateTimeString));
try {
return Optional.of(theSupportedDateTimeFormatter.parse(theDateTimeString));
} catch (Exception exception) {
return Optional.empty();
}
}
private static int getTimeUnitIfSupported(
TemporalAccessor theTemporalAccessor, TemporalField theTemporalField, int theDefaultValue) {
return getTimeUnitIfSupportedOrEmpty(theTemporalAccessor, theTemporalField)
.orElse(theDefaultValue);
}
private static Optional getTimeUnitIfSupportedOrEmpty(
TemporalAccessor theTemporalAccessor, TemporalField theTemporalField) {
if (theTemporalAccessor.isSupported(theTemporalField)) {
return Optional.of(theTemporalAccessor.get(theTemporalField));
}
return Optional.empty();
}
/**
* A factory for {@link SimpleDateFormat}s. The instances are stored in a
* threadlocal way because SimpleDateFormat is not thread safe as noted in
* {@link SimpleDateFormat its javadoc}.
*/
static final class DateFormatHolder {
private static final ThreadLocal>> THREADLOCAL_FORMATS =
ThreadLocal.withInitial(() -> new SoftReference<>(new HashMap<>()));
/**
* creates a {@link SimpleDateFormat} for the requested format string.
*
* @param pattern a non-{@code null} format String according to
* {@link SimpleDateFormat}. The format is not checked against
* {@code null} since all paths go through
* {@link DateUtils}.
* @return the requested format. This simple DateFormat should not be used
* to {@link SimpleDateFormat#applyPattern(String) apply} to a
* different pattern.
*/
static SimpleDateFormat formatFor(final String pattern) {
final SoftReference
© 2015 - 2025 Weber Informatics LLC | Privacy Policy