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

com.helger.commons.datetime.PDTFormatter Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2014-2024 Philip Helger (www.helger.com)
 * philip[at]helger[dot]com
 *
 * 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.helger.commons.datetime;

import java.text.DateFormat;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.Locale;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import javax.annotation.concurrent.ThreadSafe;

import com.helger.commons.ValueEnforcer;
import com.helger.commons.annotation.PresentForCodeCoverage;
import com.helger.commons.cache.Cache;
import com.helger.commons.hashcode.HashCodeGenerator;
import com.helger.commons.string.StringHelper;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;

/**
 * Create common {@link DateTimeFormatter} objects used for printing and parsing
 * date and time objects.
 *
 * @author Philip Helger
 */
@Immutable
@SuppressFBWarnings ("JCIP_FIELD_ISNT_FINAL_IN_IMMUTABLE_CLASS")
public final class PDTFormatter
{
  /**
   * Internal cache key for {@link LocalizedDateFormatCache}.
   *
   * @author Philip Helger
   */
  @Immutable
  public static final class CacheKey
  {
    private final EDTType m_eDTType;
    private final Locale m_aLocale;
    private final FormatStyle m_eStyle;
    private final EDTFormatterMode m_eMode;

    CacheKey (@Nonnull final EDTType eDTType,
              @Nullable final Locale aLocale,
              @Nonnull final FormatStyle eStyle,
              @Nonnull final EDTFormatterMode eMode)
    {
      ValueEnforcer.notNull (eDTType, "DTType");
      ValueEnforcer.notNull (eStyle, "Style");
      ValueEnforcer.notNull (eMode, "Mode");
      m_eDTType = eDTType;
      // Ensure a non-null Locale is used - same as in DateFormat itself
      // Having this shortcut here meaning less cache entries that when having
      // "null" as a separate key
      m_aLocale = aLocale != null ? aLocale : Locale.getDefault (Locale.Category.FORMAT);
      m_eStyle = eStyle;
      m_eMode = eMode;
    }

    @Override
    public boolean equals (final Object o)
    {
      if (o == this)
        return true;
      if (o == null || !getClass ().equals (o.getClass ()))
        return false;
      final CacheKey rhs = (CacheKey) o;
      return m_eDTType.equals (rhs.m_eDTType) &&
             m_aLocale.equals (rhs.m_aLocale) &&
             m_eStyle.equals (rhs.m_eStyle) &&
             m_eMode == rhs.m_eMode;
    }

    @Override
    public int hashCode ()
    {
      return new HashCodeGenerator (this).append (m_eDTType)
                                         .append (m_aLocale)
                                         .append (m_eStyle)
                                         .append (m_eMode)
                                         .getHashCode ();
    }
  }

  /**
   * This class caches the compiled patterns for localized date and time
   * formatter. Using e.g.
   * {@link DateTimeFormatter#ofLocalizedDate(java.time.format.FormatStyle)} is
   * not an option because the used "year of era", corresponding to pattern "y"
   * makes problems. Instead "year" with the pattern "u" must be used for best
   * backwards compatibility.
   *
   * @author Philip Helger
   */
  @ThreadSafe
  public static final class LocalizedDateFormatCache extends Cache 
  {
    public LocalizedDateFormatCache ()
    {
      super (aCacheKey -> {
        String sPattern = getSourcePattern (aCacheKey);

        // Change "year of era" to "year"
        sPattern = StringHelper.replaceAll (sPattern, 'y', 'u');

        if (false)
          if (aCacheKey.m_eMode == EDTFormatterMode.PARSE && StringHelper.getCharCount (sPattern, 'u') == 1)
          {
            // In Java 9, if CLDR mode is active, switch from a single "u" to
            // "uuuu" (for parsing)
            sPattern = StringHelper.replaceAll (sPattern, "u", "uuuu");
          }

        if (aCacheKey.m_eMode == EDTFormatterMode.PARSE &&
            "de".equals (aCacheKey.m_aLocale.getLanguage ()) &&
            aCacheKey.m_eStyle == FormatStyle.MEDIUM)
        {
          // Change from 2 required fields to 1
          sPattern = StringHelper.replaceAll (sPattern, "dd", "d");
          sPattern = StringHelper.replaceAll (sPattern, "MM", "M");
          sPattern = StringHelper.replaceAll (sPattern, "HH", "H");
          sPattern = StringHelper.replaceAll (sPattern, "mm", "m");
          sPattern = StringHelper.replaceAll (sPattern, "ss", "s");
        }

        // And finally create the cached DateTimeFormatter
        // Default to strict - can be changed afterwards
        return DateTimeFormatterCache.getDateTimeFormatterStrict (sPattern);
      }, 1000, LocalizedDateFormatCache.class.getName ());
    }

    @Nonnull
    public static String getSourcePattern (@Nonnull final CacheKey aKey)
    {
      switch (aKey.m_eDTType)
      {
        case LOCAL_TIME:
          return PDTFormatPatterns.getPatternTime (aKey.m_eStyle, aKey.m_aLocale);
        case OFFSET_TIME:
          return PDTFormatPatterns.getPatternTime (aKey.m_eStyle, aKey.m_aLocale) + " Z";
        case LOCAL_DATE:
          return PDTFormatPatterns.getPatternDate (aKey.m_eStyle, aKey.m_aLocale);
        case OFFSET_DATE:
          return PDTFormatPatterns.getPatternDate (aKey.m_eStyle, aKey.m_aLocale) + " Z";
        case LOCAL_DATE_TIME:
          return PDTFormatPatterns.getPatternDateTime (aKey.m_eStyle, aKey.m_aLocale);
        case OFFSET_DATE_TIME:
          return PDTFormatPatterns.getPatternDateTime (aKey.m_eStyle, aKey.m_aLocale) + " Z";
        case ZONED_DATE_TIME:
          return PDTFormatPatterns.getPatternDateTime (aKey.m_eStyle, aKey.m_aLocale) + " z";
        default:
          throw new IllegalStateException ("Unsupported DTType " + aKey.m_eDTType);
      }
    }
  }

  /** By default the medium style is used */
  public static final FormatStyle DEFAULT_STYLE = FormatStyle.MEDIUM;
  private static final LocalizedDateFormatCache PARSER_CACHE = new LocalizedDateFormatCache ();

  @PresentForCodeCoverage
  private static final PDTFormatter INSTANCE = new PDTFormatter ();

  private PDTFormatter ()
  {}

  @Nonnull
  public static FormatStyle toFormatStyle (final int nStyle)
  {
    switch (nStyle)
    {
      case DateFormat.FULL:
        return FormatStyle.FULL;
      case DateFormat.LONG:
        return FormatStyle.LONG;
      case DateFormat.MEDIUM:
        return FormatStyle.MEDIUM;
      case DateFormat.SHORT:
        return FormatStyle.SHORT;
      default:
        throw new IllegalArgumentException ("Invalid style passed: " + nStyle);
    }
  }

  public static int toDateStyle (@Nonnull final FormatStyle eStyle)
  {
    switch (eStyle)
    {
      case FULL:
        return DateFormat.FULL;
      case LONG:
        return DateFormat.LONG;
      case MEDIUM:
        return DateFormat.MEDIUM;
      case SHORT:
        return DateFormat.SHORT;
      default:
        throw new IllegalArgumentException ("Unsupported style passed: " + eStyle);
    }
  }

  @Nonnull
  public static String getPattern (@Nonnull final EDTType eDTType,
                                   @Nullable final Locale aLocale,
                                   @Nonnull final FormatStyle eStyle,
                                   @Nonnull final EDTFormatterMode eMode)
  {
    return LocalizedDateFormatCache.getSourcePattern (new CacheKey (eDTType, aLocale, eStyle, eMode));
  }

  /**
   * Assign the passed display locale to the passed date time formatter.
   *
   * @param aFormatter
   *        The formatter to be modified. May not be null.
   * @param aDisplayLocale
   *        The display locale to be used. May be null.
   * @return The modified date time formatter. Never null.
   */
  @Nonnull
  public static DateTimeFormatter getWithLocale (@Nonnull final DateTimeFormatter aFormatter,
                                                 @Nullable final Locale aDisplayLocale)
  {
    DateTimeFormatter ret = aFormatter;
    if (aDisplayLocale != null)
      ret = ret.withLocale (aDisplayLocale);
    return ret;
  }

  @Nonnull
  private static DateTimeFormatter _getFormatter (@Nonnull final CacheKey aCacheKey,
                                                  @Nullable final Locale aDisplayLocale)
  {
    final DateTimeFormatter aFormatter = PARSER_CACHE.getFromCache (aCacheKey);
    return getWithLocale (aFormatter, aDisplayLocale);
  }

  /**
   * Get the date formatter for the passed locale.
   *
   * @param eStyle
   *        The format style to be used. May not be null.
   * @param aDisplayLocale
   *        The display locale to be used. May be null.
   * @param eMode
   *        Print or parse? May not be null.
   * @return The created date formatter. Never null.
   * @since 8.5.6
   */
  @Nonnull
  public static DateTimeFormatter getFormatterDate (@Nonnull final FormatStyle eStyle,
                                                    @Nullable final Locale aDisplayLocale,
                                                    @Nonnull final EDTFormatterMode eMode)
  {
    return _getFormatter (new CacheKey (EDTType.LOCAL_DATE, aDisplayLocale, eStyle, eMode), aDisplayLocale);
  }

  /**
   * Get the date formatter for the passed locale.
   *
   * @param eStyle
   *        The format style to be used. May not be null.
   * @param aDisplayLocale
   *        The display locale to be used. May be null.
   * @param eMode
   *        Print or parse? May not be null.
   * @return The created date formatter. Never null.
   * @since 10.1.2
   */
  @Nonnull
  public static DateTimeFormatter getFormatterOffsetDate (@Nonnull final FormatStyle eStyle,
                                                          @Nullable final Locale aDisplayLocale,
                                                          @Nonnull final EDTFormatterMode eMode)
  {
    return _getFormatter (new CacheKey (EDTType.OFFSET_DATE, aDisplayLocale, eStyle, eMode), aDisplayLocale);
  }

  /**
   * Get the time formatter for the passed locale.
   *
   * @param eStyle
   *        The format style to be used. May not be null.
   * @param aDisplayLocale
   *        The display locale to be used. May be null.
   * @param eMode
   *        Print or parse? May not be null.
   * @return The created time formatter. Never null.
   * @since 8.5.6
   */
  @Nonnull
  public static DateTimeFormatter getFormatterTime (@Nonnull final FormatStyle eStyle,
                                                    @Nullable final Locale aDisplayLocale,
                                                    @Nonnull final EDTFormatterMode eMode)
  {
    return _getFormatter (new CacheKey (EDTType.LOCAL_TIME, aDisplayLocale, eStyle, eMode), aDisplayLocale);
  }

  /**
   * Get the time formatter for the passed locale.
   *
   * @param eStyle
   *        The format style to be used. May not be null.
   * @param aDisplayLocale
   *        The display locale to be used. May be null.
   * @param eMode
   *        Print or parse? May not be null.
   * @return The created time formatter. Never null.
   * @since 10.0.0
   */
  @Nonnull
  public static DateTimeFormatter getFormatterOffsetTime (@Nonnull final FormatStyle eStyle,
                                                          @Nullable final Locale aDisplayLocale,
                                                          @Nonnull final EDTFormatterMode eMode)
  {
    return _getFormatter (new CacheKey (EDTType.OFFSET_TIME, aDisplayLocale, eStyle, eMode), aDisplayLocale);
  }

  /**
   * Get the date time formatter for the passed locale.
   *
   * @param eStyle
   *        The format style to be used. May not be null.
   * @param aDisplayLocale
   *        The display locale to be used. May be null.
   * @param eMode
   *        Print or parse? May not be null.
   * @return The created date time formatter. Never null.
   * @since 8.5.6
   */
  @Nonnull
  public static DateTimeFormatter getFormatterDateTime (@Nonnull final FormatStyle eStyle,
                                                        @Nullable final Locale aDisplayLocale,
                                                        @Nonnull final EDTFormatterMode eMode)
  {
    return _getFormatter (new CacheKey (EDTType.LOCAL_DATE_TIME, aDisplayLocale, eStyle, eMode), aDisplayLocale);
  }

  /**
   * Get the date time formatter for the passed locale.
   *
   * @param eStyle
   *        The format style to be used. May not be null.
   * @param aDisplayLocale
   *        The display locale to be used. May be null.
   * @param eMode
   *        Print or parse? May not be null.
   * @return The created date time formatter. Never null.
   * @since 10.1.2
   */
  @Nonnull
  public static DateTimeFormatter getFormatterOffsetDateTime (@Nonnull final FormatStyle eStyle,
                                                              @Nullable final Locale aDisplayLocale,
                                                              @Nonnull final EDTFormatterMode eMode)
  {
    return _getFormatter (new CacheKey (EDTType.OFFSET_DATE_TIME, aDisplayLocale, eStyle, eMode), aDisplayLocale);
  }

  /**
   * Get the date time formatter for the passed locale.
   *
   * @param eStyle
   *        The format style to be used. May not be null.
   * @param aDisplayLocale
   *        The display locale to be used. May be null.
   * @param eMode
   *        Print or parse? May not be null.
   * @return The created date time formatter. Never null.
   * @since 10.1.2
   */
  @Nonnull
  public static DateTimeFormatter getFormatterZonedDateTime (@Nonnull final FormatStyle eStyle,
                                                             @Nullable final Locale aDisplayLocale,
                                                             @Nonnull final EDTFormatterMode eMode)
  {
    return _getFormatter (new CacheKey (EDTType.ZONED_DATE_TIME, aDisplayLocale, eStyle, eMode), aDisplayLocale);
  }

  /**
   * Get the {@link DateTimeFormatter} for the given pattern, using our default
   * chronology.
   *
   * @param sPattern
   *        The pattern to be parsed
   * @return The formatter object.
   * @throws IllegalArgumentException
   *         If the pattern is illegal
   */
  @Nonnull
  public static DateTimeFormatter getForPattern (@Nonnull final String sPattern)
  {
    return getForPattern (sPattern, null);
  }

  /**
   * Get the STRICT {@link DateTimeFormatter} for the given pattern and locale,
   * using our default chronology.
   *
   * @param sPattern
   *        The pattern to be parsed
   * @param aDisplayLocale
   *        The locale to be used. May be null.
   * @return The formatter object.
   * @throws IllegalArgumentException
   *         If the pattern is illegal
   */
  @Nonnull
  public static DateTimeFormatter getForPattern (@Nonnull final String sPattern, @Nullable final Locale aDisplayLocale)
  {
    final DateTimeFormatter aDTF = DateTimeFormatterCache.getDateTimeFormatterStrict (sPattern);
    return getWithLocale (aDTF, aDisplayLocale);
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy