I will absolutely convert the time string!

This is ubansi in charge of the second day of the Java Advent calendar! Around last year Fighted devilish Java code.

Alright, I'll analyze big data!

Dump files of various DBs are put into Bashibashi BigQuery!

Eh, the date isn't parsed by BigQuery ...? Can I change the output because other systems are running? Is there a time difference? Eh, add DB?

・ ・ ・

For those who encounter such things (Rather, I met such an eye) Let's create a process to convert all date strings to LocalDateTime in Java8.

Class used for analysis

Introducing the squid time class! ZonedDateTime A class that handles ** dates with time ** for each time zone. It also considers daylight saving time.

OffsetDateTime A class that handles ** dates with time ** for each time difference. If there is a time difference specification instead of a time zone specification, this is the turn.

LocalDateTime Needless to say, this class deals with ** time without time difference **. This time we will convert to this class.

LocalDate A class that handles ** dates ** with no time difference. If no time is specified, this class will be used for analysis. You can convert it to LocalDateTime as midnight with LocalDate # atStartOfDay ().

Timestamp Used for conversion from Unixtime.

flow

Yes, let's start the explanation. As a procedure, conversion is attempted in descending order of the amount of information as the analysis result. Then, when the conversion is completed, the result is returned. ** I will only throw all errors ** if all parsing fails.

Constitution

Create an abstract class to parse and inherit it.

クラス図

code

Abstract class of parsing class

TimeParser.java



public abstract class TimeParser {

	/**
	 * fails info
	 */
	protected List<String> failsMessages = new ArrayList<>();

	/**
	 * This function forcibly converts a string to a date.
	 *<p>
	 * If the return value is null, please throw an {@code DateTimeParseException}.
	 * </p>
	 * @param input
	 * @return LocalDateTime instance or {@code null}
	 */
	public abstract LocalDateTime parse(String input);

	protected void addExceptionMessage(Exception e) {
		failsMessages.add(e.getMessage() + " ("+this.getClass().getSimpleName()+")");
	}

	public List<String> getExceptionInfo(){
		return failsMessages;
	};
}

It has an analysis part and a log acquisition part. For the time being, I keep a log so that I can see what happened if everything failed.

Parsing class implementation

LocalDateParser.java




public class LocalDateParser extends TimeParser {

	//Define the format from the beginning
	private final static List<DateTimeFormatter> FORMATS = new ArrayList<DateTimeFormatter>() {
		{
			add(DateTimeFormatter.ISO_LOCAL_DATE);
			add(DateTimeFormatter.BASIC_ISO_DATE);
			add(DateTimeFormatter.ISO_DATE);
			add(DateTimeFormatter.ofPattern("yy-MM-dd"));
			add(DateTimeFormatter.ofPattern("yy-M-d"));
			add(DateTimeFormatter.ofPattern("yyyy/MM/dd"));
			add(DateTimeFormatter.ofPattern("yyyy/M/d"));
			add(DateTimeFormatter.ofPattern("yyyy year M month d day"));

		}
	};

	@Override
	public LocalDateTime parse(String input) {
		failsMessages.clear();

		//Try default analysis
		try {
			return LocalDate.parse(input).atStartOfDay();
		} catch (DateTimeParseException e) {
			addExceptionMessage(e);
		}

		//Analyze in specified format
		for (DateTimeFormatter formatter : FORMATS) {
			try {
				return LocalDate.parse(input, formatter).atStartOfDay();
			} catch (DateTimeParseException e) {
				//Save the failed log to the list
				addExceptionMessage(e);
			}
		}
		return null;
	}
}

If a DateTimeParseException occurs, just stock the log and I will squeeze everything.

The reason why the logger does not output when an exception occurs is that it is output even if there is no problem in processing.

Create a class that inherits TimeParser like this in other date classes.

The class that will be the facade of the analysis

DateTimeParser.java



public class DateTimeParser {

	//Error retention list
	private List<String> errors = new ArrayList<>();
	//Create analysis class
	private final static List<TimeParser> PARSERS = new ArrayList<TimeParser>() {
		{
			//Analyze in descending order of amount of information
			add(new ZonedDateTimeParser());
			add(new OffsetDateTimeParser());
			add(new LocalDateTimeParser());
			add(new LocalDateParser());
			add(new TimestampParser());
		}
	};

	public LocalDateTime parse(String input) {
		errors.clear();

		LocalDateTime result = null;

		for (TimeParser parser : PARSERS) {
			result = parser.parse(input);

			//Returns the result when it succeeds
			if (result != null) {
				return result;
			}
			errors.addAll(parser.getExceptionInfo());
		}

		throw new DateTimeParseException("Analysis failed.(\"" + input + "\")", input, 0);

		//There is no return!
	}

	public List<String> getErrors(){
		return errors;
	}

}

It has a list of parsing classes and loops. If the analysis is successful, an instance of LocalDateTime will be returned, so Returns the result if it is not null.

In addition, parsing exceptions in the middle can be retrieved after parsing with getErrors (). (It's not thread-safe, but ...)

And only if it fails, it throws an exception at the end. It's a weird method without a return.

test

DateTimeParserTest.java


	@Test
	public void testDateFormat() {
		//Test if this date can be converted
		List<String> dates = new ArrayList<String>() {
			{
				add("2017-12-02");
				add("17-12-02");
				add("17-12-2");
				add("20171202");
				add("2017/12/02");
				add("2017/12/2");
				add("December 2, 2017");
			}
		};

		for (String date : dates) {
			LocalDateTime time = dtp.parse(date);
			assertEquals("2017-12-02T00:00:00", time.format(formater));
		}

	}

It's a rough test, but if you analyze all the dates like this, All could be analyzed as "2017-12-02T 00:00:00". You should be able to convert any date by adding formats to the ArrayList of the various parser classes.

You did it!

By the way, the sample code used this time is published on GitHub. https://github.com/ubansi/DateTimeParser

Recommended Posts

I will absolutely convert the time string!
I want to convert InputStream to String
I read the source of String
I tried using Docker for the first time
I tried touching Docker for the first time
[CircleCI] I will explain the stupid configuration file (config.yml) that I wrote for the first time.
[Rails] I tried using the button_to method for the first time
Set the date and time from the character string with POI
I tried to convert a string to a LocalDate type in Java