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

de.huxhorn.sulky.stax.DateTimeFormatter Maven / Gradle / Ivy

Go to download

This file is part of the sulky modules. It contains helper methods to simplify usage of StAX.

The newest version!
/*
 * sulky-modules - several general-purpose modules.
 * Copyright (C) 2007-2021 Joern Huxhorn
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see .
 */

/*
 * Copyright 2007-2021 Joern Huxhorn
 *
 * 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 de.huxhorn.sulky.stax;

import java.text.ParseException;
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatterBuilder;
import java.time.temporal.ChronoField;
import java.time.temporal.TemporalAccessor;
import java.util.Date;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class DateTimeFormatter
{
	private static final String TIMEZONE_DATE_FORMAT_PATTERN = ".*([+-]\\d{2})(\\d{2})$";
	private static final int TIMEZONE_DATE_FORMAT_LENGTH = 5;

	private static final java.time.format.DateTimeFormatter ISO_DATE_TIME_PARSER =
			new DateTimeFormatterBuilder()
					.parseCaseInsensitive()
					.append(java.time.format.DateTimeFormatter.ISO_LOCAL_DATE)
					.appendLiteral('T')
					.appendValue(ChronoField.HOUR_OF_DAY, 2)
					.appendLiteral(':')
					.appendValue(ChronoField.MINUTE_OF_HOUR, 2)
					.appendLiteral(':')
					.appendValue(ChronoField.SECOND_OF_MINUTE, 2)
					.optionalStart()
					.appendFraction(ChronoField.MILLI_OF_SECOND, 3, 3, true)
					.optionalEnd()
					.appendOffset("+HH:MM", "Z")
					.toFormatter(Locale.US)
					.withZone(ZoneOffset.UTC);

	private static final java.time.format.DateTimeFormatter ISO_DATE_TIME_FORMATTER_WITH_MILLIS =
			new DateTimeFormatterBuilder()
					.parseCaseInsensitive()
					.append(java.time.format.DateTimeFormatter.ISO_LOCAL_DATE)
					.appendLiteral('T')
					.appendValue(ChronoField.HOUR_OF_DAY, 2)
					.appendLiteral(':')
					.appendValue(ChronoField.MINUTE_OF_HOUR, 2)
					.appendLiteral(':')
					.appendValue(ChronoField.SECOND_OF_MINUTE, 2)
					.appendFraction(ChronoField.MILLI_OF_SECOND, 3, 3, true)
					.appendOffset("+HH:MM", "+00:00")
					.toFormatter(Locale.US)
					.withZone(ZoneOffset.UTC);

	private static final java.time.format.DateTimeFormatter ISO_DATE_TIME_FORMATTER_WITHOUT_MILLIS =
			new DateTimeFormatterBuilder()
					.parseCaseInsensitive()
					.append(java.time.format.DateTimeFormatter.ISO_LOCAL_DATE)
					.appendLiteral('T')
					.appendValue(ChronoField.HOUR_OF_DAY, 2)
					.appendLiteral(':')
					.appendValue(ChronoField.MINUTE_OF_HOUR, 2)
					.appendLiteral(':')
					.appendValue(ChronoField.SECOND_OF_MINUTE, 2)
					.appendOffset("+HH:MM", "+00:00")
					.toFormatter(Locale.US)
					.withZone(ZoneOffset.UTC);

	private final Pattern javaTimezonePattern = Pattern.compile(TIMEZONE_DATE_FORMAT_PATTERN);

	private static final boolean IS_JAVA_8;

	static {
		boolean java8 = true;
		String javaVersion = System.getProperty("java.version");
		if(javaVersion != null)
		{
			java8 = javaVersion.startsWith("1.");
		}
		IS_JAVA_8 = java8;
	}

	/**
	 * This method parses a given string containing a dateTime in ISO8601 notation into a date.
	 *
	 * It can handle an optional millisecond fraction as well as timezone with either explicit '+/-HH:MM' or 'Z' UTC designator.
	 *
	 * @param dateTime a string containing a dateTime in ISO8601 notation.
	 * @return the parsed date
	 * @throws ParseException If the dateTime string is invalid.
	 */
	public Date parse(String dateTime)
		throws ParseException
	{
		Matcher matcher = javaTimezonePattern.matcher(dateTime);
		if(matcher.matches())
		{
			// correct +/-hhmm to +/-hh:mm
			String hh = matcher.group(1);
			String mm = matcher.group(2);
			dateTime = dateTime.substring(0, dateTime.length() - TIMEZONE_DATE_FORMAT_LENGTH) + hh + ":" + mm;
		}
		TemporalAccessor temporal = ISO_DATE_TIME_PARSER.parse(dateTime);
		long seconds = temporal.getLong(ChronoField.INSTANT_SECONDS);
		if(IS_JAVA_8)
		{
			// http://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8183913
			seconds = seconds - temporal.getLong(ChronoField.OFFSET_SECONDS);
		}

		long millis = seconds * 1000 + temporal.getLong(ChronoField.MILLI_OF_SECOND);

		return new Date(millis);
	}

	/**
	 * Returns a simplified ISO8601 datetime string in UTC.
	 *
	 * It will always contain a three-number millisecond field regardless if it is "needed"
	 * (i.e. MILLI_OF_SECOND != 0) or not. The timezone of the date is always UTC but isn't using
	 * the UTC designator 'Z'. Instead, it's using an explicit '+00:00'.
	 * That way a date formatted by this method will always have the same number of characters while creating output
	 * that less intelligent date-parsing frameworks (incapable of the 'Z' notation) are still able to process.
	 *
	 * @param date the date to be formatted.
	 * @return a simplified ISO8601 datetime string in UTC.
	 */
	public String format(Date date)
	{
		return this.format(date, true);
	}

	/**
	 * Returns a simplified ISO8601 datetime string in UTC.
	 *
	 * It will always contain a three-number millisecond field regardless if it is "needed"
	 * (i.e. MILLI_OF_SECOND != 0) or not if withMillis is true. The timezone of the date is always UTC but isn't using
	 * the UTC designator 'Z'. Instead, it's using an explicit '+00:00'.
	 * That way a date formatted by this method will always have the same number of characters while creating output
	 * that less intelligent date-parsing frameworks (incapable of the 'Z' notation) are still able to process.
	 *
	 * @param date the date to be formatted.
	 * @param withMillis whether or not milliseconds should be printed.
	 * @return a simplified ISO8601 datetime string in UTC.
	 */
	public String format(Date date, boolean withMillis)
	{
		Instant instant = Instant.ofEpochMilli(date.getTime());
		ZonedDateTime zoned = ZonedDateTime.ofInstant(instant, ZoneOffset.UTC);
		if(withMillis)
		{
			return ISO_DATE_TIME_FORMATTER_WITH_MILLIS.format(zoned);
		}
		return ISO_DATE_TIME_FORMATTER_WITHOUT_MILLIS.format(zoned);
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy