|
|
|
|
|
class SmartDateFormat
|
|
|
{
|
|
|
constructor(format)
|
|
|
{
|
|
|
this._day = null;
|
|
|
this._month = null;
|
|
|
this._year = null;
|
|
|
|
|
|
this.loadFormat(format);
|
|
|
|
|
|
this._parseState = new SmartDateParseState();
|
|
|
}
|
|
|
|
|
|
loadFormat(format)
|
|
|
{
|
|
|
let index = 0;
|
|
|
let lowFormat = format.toLowerCase();
|
|
|
|
|
|
this._format = format;
|
|
|
this._parts = [];
|
|
|
|
|
|
while (index < format.length) {
|
|
|
index = this._scanPart(index, format, lowFormat);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
_scanPart(index, format, lowFormat)
|
|
|
{
|
|
|
switch (lowFormat[index]) {
|
|
|
case SmartDateFormat.DAY_ANCHOR:
|
|
|
return this._scanDay(index, lowFormat);
|
|
|
|
|
|
case SmartDateFormat.MONTH_ANCHOR:
|
|
|
return this._scanMonth(index, lowFormat);
|
|
|
|
|
|
case SmartDateFormat.YEAR_ANCHOR:
|
|
|
return this._scanYear(index, lowFormat);
|
|
|
}
|
|
|
|
|
|
return this._scanStatic(index, format, lowFormat);
|
|
|
}
|
|
|
|
|
|
_scanDay(startIndex, lowFormat)
|
|
|
{
|
|
|
this._checkLastIsStatic();
|
|
|
|
|
|
this._day = new SmartDateDayPart();
|
|
|
|
|
|
this._parts.push(this._day);
|
|
|
|
|
|
return this._avoidAnchor(SmartDateFormat.DAY_ANCHOR, startIndex, lowFormat);
|
|
|
}
|
|
|
|
|
|
_scanMonth(startIndex, lowFormat)
|
|
|
{
|
|
|
this._checkLastIsStatic();
|
|
|
|
|
|
this._month = new SmartDateMonthPart();
|
|
|
|
|
|
this._parts.push(this._month);
|
|
|
|
|
|
return this._avoidAnchor(SmartDateFormat.MONTH_ANCHOR, startIndex, lowFormat);
|
|
|
}
|
|
|
|
|
|
_scanYear(startIndex, lowFormat)
|
|
|
{
|
|
|
this._checkLastIsStatic();
|
|
|
|
|
|
this._year = new SmartDateYearPart();
|
|
|
|
|
|
this._parts.push(this._year);
|
|
|
|
|
|
return this._avoidAnchor(SmartDateFormat.YEAR_ANCHOR, startIndex, lowFormat);
|
|
|
}
|
|
|
|
|
|
_avoidAnchor(anchor, startIndex, lowFormat)
|
|
|
{
|
|
|
let endingIndex = startIndex;
|
|
|
|
|
|
while (endingIndex < lowFormat.length && lowFormat[endingIndex] === anchor) {
|
|
|
endingIndex++;
|
|
|
}
|
|
|
|
|
|
return endingIndex;
|
|
|
}
|
|
|
|
|
|
_checkLastIsStatic()
|
|
|
{
|
|
|
if (this._parts.length === 0) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
if (!this._parts[this._parts.length - 1].isStatic()) {
|
|
|
throw new Error('Dynamic date parts must be separated by static substrings');
|
|
|
}
|
|
|
}
|
|
|
|
|
|
_scanStatic(startIndex, format, lowFormat)
|
|
|
{
|
|
|
let endingIndex = startIndex;
|
|
|
|
|
|
while (endingIndex < format.length && this._isStaticAnchorSymbol(lowFormat[endingIndex])) {
|
|
|
endingIndex++;
|
|
|
}
|
|
|
|
|
|
this._parts.push(new SmartDateStaticPart(format.substring(startIndex, endingIndex)));
|
|
|
|
|
|
return endingIndex;
|
|
|
}
|
|
|
|
|
|
_isStaticAnchorSymbol(symbol)
|
|
|
{
|
|
|
return symbol !== SmartDateFormat.DAY_ANCHOR
|
|
|
&& symbol !== SmartDateFormat.MONTH_ANCHOR
|
|
|
&& symbol !== SmartDateFormat.YEAR_ANCHOR;
|
|
|
}
|
|
|
|
|
|
getPlaceHolder()
|
|
|
{
|
|
|
let placeHolder = '';
|
|
|
|
|
|
for (let i = 0; i < this._parts.length; i++) {
|
|
|
placeHolder += this._parts[i].placeHolder();
|
|
|
}
|
|
|
|
|
|
return placeHolder;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* @param {Date} date
|
|
|
* @returns {String}
|
|
|
*/
|
|
|
formatDate(date)
|
|
|
{
|
|
|
var formattedDate = '';
|
|
|
|
|
|
for (let i = 0; i < this._parts.length; i++) {
|
|
|
if (this._parts[i].isStatic()) {
|
|
|
formattedDate += this._parts[i].placeHolder();
|
|
|
}
|
|
|
else {
|
|
|
formattedDate += this._parts[i].formatDate(date);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return formattedDate;
|
|
|
}
|
|
|
|
|
|
parse(value, autocomplete)
|
|
|
{
|
|
|
this._resetParseState();
|
|
|
|
|
|
this._parseState.start(value);
|
|
|
|
|
|
this._parseParts(autocomplete);
|
|
|
|
|
|
this._correctDate();
|
|
|
}
|
|
|
|
|
|
_resetParseState()
|
|
|
{
|
|
|
for (let i = 0; i < this._parts.length; i++) {
|
|
|
this._parts[i].resetState();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
_parseParts(autocomplete)
|
|
|
{
|
|
|
let index = 0;
|
|
|
|
|
|
while (index < this._parts.length && (autocomplete || !this._parseState.isOver())) {
|
|
|
this._parts[index].parse(this._parseState);
|
|
|
|
|
|
if (!this._parts[index].isComplete()) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
index++;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
_correctDate()
|
|
|
{
|
|
|
if (!this._day.isComplete() || !this._month.isComplete()) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
this._day.setMaximalValue(this._getMonthSize(this._month.numericValue() - 1, !this._year.isComplete() || this._isLeaPYear(this._year.numericValue())));
|
|
|
}
|
|
|
|
|
|
getParsedDate()
|
|
|
{
|
|
|
if (!this.isComplete()) {
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
return new Date(this._year.numericValue(), this._month.numericValue() - 1, this._day.numericValue());
|
|
|
}
|
|
|
|
|
|
_getMonthSize(monthIndex, isLeapYear)
|
|
|
{
|
|
|
if (monthIndex < 0 || monthIndex >= SmartDateMonthPart.MAXIMAL_VALUE) {
|
|
|
return SmartDateMonthPart.MAXIMAL_VALUE;
|
|
|
}
|
|
|
|
|
|
if (monthIndex === 1 && isLeapYear) { // February
|
|
|
return SmartDateFormat.BASIC_MONTH_SIZES[monthIndex] + 1;
|
|
|
}
|
|
|
|
|
|
return SmartDateFormat.BASIC_MONTH_SIZES[monthIndex];
|
|
|
}
|
|
|
|
|
|
_isLeaPYear(year)
|
|
|
{
|
|
|
return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
|
|
|
}
|
|
|
|
|
|
getCorrectedValue()
|
|
|
{
|
|
|
let index = 0;
|
|
|
let value = '';
|
|
|
|
|
|
while (index < this._parts.length && !this._parts[index].isNoState()) {
|
|
|
value += this._parts[index].cleanedValue();
|
|
|
index++;
|
|
|
}
|
|
|
|
|
|
return value;
|
|
|
}
|
|
|
|
|
|
isComplete()
|
|
|
{
|
|
|
return this._parts[this._parts.length - 1].isComplete();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
SmartDateFormat.DAY_ANCHOR = 'd';
|
|
|
SmartDateFormat.DAY_PLACEHOLDER = 'ДД';
|
|
|
|
|
|
SmartDateFormat.MONTH_ANCHOR = 'm';
|
|
|
SmartDateFormat.MONTH_PLACEHOLDER = 'ММ';
|
|
|
|
|
|
SmartDateFormat.YEAR_ANCHOR = 'y';
|
|
|
SmartDateFormat.YEAR_PLACEHOLDER = 'ГГГГ';
|
|
|
|
|
|
SmartDateFormat.BASIC_MONTH_SIZES = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|