248 lines
6.2 KiB
JavaScript
248 lines
6.2 KiB
JavaScript
|
||
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];
|