ParsedDateFormat.java
/*
** Module : ParsedDateFormat.java
** Abstract : parsed form of the date format string
**
** Copyright (c) 2015-2024, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------------Description---------------------------------------
** 001 GES 20150115 Shared support for date format parsing and parse results access.
** 002 OM 20200616 Extracted static factory to allow better validation control.
** 003 VVT 20210317 Updated for #4880: Format parsing is re-written, no DisplayFormatParsingException
** exist in the system anymore, proper numbered errors are used instead.
** VVT 20210322 Fixed after the review. See #4880-107.
** 004 VVT 20230918 The result of compilation for the 99999999 format
** must depend on current date component order settings. See #7515-17.
** 005 OM 20240508 Runtime parsing of date literal takes into account the current date-format.
** Avoid wrapping String literals, when possible.
*/
/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero 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 Affero General Public License for more details.
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
**
** Additional terms under GNU Affero GPL version 3 section 7:
**
** Under Section 7 of the GNU Affero GPL version 3, the following additional
** terms apply to the works covered under the License. These additional terms
** are non-permissive additional terms allowed under Section 7 of the GNU
** Affero GPL version 3 and may not be removed by you.
**
** 0. Attribution Requirement.
**
** You must preserve all legal notices or author attributions in the covered
** work or Appropriate Legal Notices displayed by works containing the covered
** work. You may not remove from the covered work any author or developer
** credit already included within the covered work.
**
** 1. No License To Use Trademarks.
**
** This license does not grant any license or rights to use the trademarks
** Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
** of Golden Code Development Corporation. You are not authorized to use the
** name Golden Code, FWD, or the names of any author or contributor, for
** publicity purposes without written authorization.
**
** 2. No Misrepresentation of Affiliation.
**
** You may not represent yourself as Golden Code Development Corporation or FWD.
**
** You may not represent yourself for publicity purposes as associated with
** Golden Code Development Corporation, FWD, or any author or contributor to
** the covered work, without written authorization.
**
** 3. No Misrepresentation of Source or Origin.
**
** You may not represent the covered work as solely your work. All modified
** versions of the covered work must be marked in a reasonable way to make it
** clear that the modified work is not originating from Golden Code Development
** Corporation or FWD. All modified versions must contain the notices of
** attribution required in this license.
*/
package com.goldencode.p2j.util;
/**
* This centralizes 4GL date format parsing and provides public access to the parse results as
* simple member fields.
*/
public class ParsedDateFormat
{
/** Separator character between the first two date components. */
public final char sep1;
/** Separator character between the last two date components. */
public final char sep2;
/** Size of each date component. */
public final int[] size;
/**
* Construct an instance from the components.
*
* @param size
* the array of 3 elements with sizes of date format components
* @param sep1
* the value of the separator between the first and second format components or '\0' if the
* format string has no separators.
* @param sep2
* the value of the separator between the second and third format components or '\0' if the
* format string has no second separator.
*/
private ParsedDateFormat(final int[] size, final char sep1, final char sep2)
{
this.size = size;
this.sep1 = sep1;
this.sep2 = sep2;
}
/**
* Test if the format has separators.
*
* @return see above
*/
public final boolean hasSep()
{
return sep1 != '\0' && sep2 != '\0';
}
/**
* Parse the given format and use the parse results to create a new instance. If the format requested is
* invalid (not parsable), depending on the {@code throwExceptionOnError} an appropriate exception is
* thrown or {@code null} is returned to allow the caller to take whatever action it considers necessary.
* This method will never return an invalid object.
*
* @param fmt
* The 4GL date format string to parse.
* @return A {@code ParsedDateFormat} for the {@code fmt} requested, or {@code null} if that is invalid.
*
* @throws ErrorConditionException
* if the format is invalid
*/
public final static ParsedDateFormat getInstance(final String fmt)
throws ErrorConditionException
{
// special cases
switch (fmt)
{
case "999999":
return new ParsedDateFormat(new int[] { 2, 2, 2 }, '\0', '\0');
case "99999999":
final String dateOrder = date.getDateOrderNative();
final int[] sizes = new int[3];
for(int i = 0; i < 3; i++)
{
final int size = dateOrder.charAt(i) == 'y' ? 4 : 2;
sizes[i] = size;
}
return new ParsedDateFormat(sizes, '\0', '\0');
default:
}
final int len = fmt.length();
int idx = 0;
// The first format part may be empty
for (; idx < len && fmt.charAt(idx) == '9'; idx++)
;
if (idx == len)
{
genDateFormatIncomleteError(fmt);
return null;
}
final char sep1;
final int size1;
char c = fmt.charAt(idx);
switch (c)
{
case '.':
case '-':
case '/':
sep1 = c;
size1 = idx++;
break;
default:
ErrorManager.genInvalidCharError(idx, fmt);
return null;
}
int sectionStart = idx;
for (; idx < len && fmt.charAt(idx) == '9'; idx++)
;
if (idx == len)
{
genDateFormatIncomleteError(fmt);
return null;
}
if (idx == sectionStart)
{
ErrorManager.genInvalidCharError(idx, fmt);
return null;
}
final int size2 = idx - sectionStart;
final char sep2;
c = fmt.charAt(idx);
switch (c)
{
case '.':
case '-':
case '/':
sep2 = c;
idx++;
break;
default:
ErrorManager.genInvalidCharError(idx, fmt);
return null;
}
sectionStart = idx;
for (; idx < len && fmt.charAt(idx) == '9'; idx++)
;
// We must check these two error conditions in this very order to match the OE error handling.
if (idx < len)
{
ErrorManager.genInvalidCharError(idx, fmt);
return null;
}
final int size3 = idx - sectionStart;
if (size3 == 0)
{
genDateFormatIncomleteError(fmt);
return null;
}
return new ParsedDateFormat(new int[] { size1, size2, size3 }, sep1, sep2);
}
/**
* Record or throw the error number 154.
*
* @param fmt
* the problematic format string
*/
private static final void genDateFormatIncomleteError(final String fmt)
{
ErrorManager.recordOrThrowError(154, "Date format " + fmt + " is incomplete");
}
}