Skip to content

Commit

Permalink
feat(formula): fix date bug
Browse files Browse the repository at this point in the history
  • Loading branch information
panxp authored and panxp committed Jul 12, 2024
1 parent 83d313f commit 946a6ad
Show file tree
Hide file tree
Showing 9 changed files with 184 additions and 403 deletions.
105 changes: 68 additions & 37 deletions packages/engine-formula/src/basics/date.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@

// @ts-ignore
import numfmt from 'numfmt';
import type { BaseValueObject } from '../engine/value-object/base-value-object';
import { ErrorValueObject } from '../engine/value-object/base-value-object';
import { ErrorType } from './error-type';

export const DEFAULT_DATE_FORMAT = 'yyyy/mm/dd;@';
export const DEFAULT_NOW_FORMAT = 'yyyy/mm/dd hh:mm';
Expand Down Expand Up @@ -228,34 +231,27 @@ export function getWeekendArray(weekend: number | string): number[] {
return weekendNumberMap[Number(weekend)] || [];
}

export function countWorkingDays(startDate: Date, endDate: Date, weekend: number | string = 1, holidays?: (Date | string)[]): number {
const start = new Date(Date.UTC(startDate.getFullYear(), startDate.getMonth(), startDate.getDate())).getTime();
const end = new Date(Date.UTC(endDate.getFullYear(), endDate.getMonth(), endDate.getDate())).getTime();
const diffTime = end > start ? end - start : start - end;
const startTime = end > start ? start : end;

const millisecondsPerDay = 24 * 60 * 60 * 1000;
const daysDiff = Math.floor(diffTime / millisecondsPerDay) + 1;
export function countWorkingDays(startDateSerialNumber: number, endDateSerialNumber: number, weekend: number | string = 1, holidays?: number[]): number {
const weekendArray = getWeekendArray(weekend);

const start = Math.floor(startDateSerialNumber);
const end = Math.floor(endDateSerialNumber);
const startSerialNumber = end > start ? start : end;

let workingDays = 0;

for (let i = 0; i < daysDiff; i++) {
const currentDate = new Date(startTime + i * millisecondsPerDay);
const daysDiff = Math.abs(Math.floor(endDateSerialNumber) - Math.floor(startDateSerialNumber)) + 1;

if (holidays && holidays.length > 0 && holidays.some((item) => {
if (typeof item === 'string') {
item = new Date(item);
}
for (let i = 0; i < daysDiff; i++) {
const currentDateSerialNumber = startSerialNumber + i;

return item.getFullYear() === currentDate.getFullYear() &&
item.getMonth() === currentDate.getMonth() &&
item.getDate() === currentDate.getDate();
})) {
if (holidays && holidays.length > 0 && holidays.some((item) => Math.floor(item) === currentDateSerialNumber)) {
continue;
}

if (weekendArray.includes(currentDate.getDay())) {
const weekDay = getWeekDayByDateSerialNumber(currentDateSerialNumber);

if (weekendArray.includes(weekDay)) {
continue;
}

Expand All @@ -265,38 +261,73 @@ export function countWorkingDays(startDate: Date, endDate: Date, weekend: number
return end > start ? workingDays : -workingDays;
}

export function getDateByWorkingDays(startDate: Date, workingDays: number, weekend: number | string = 1, holidays?: (Date | string)[]): Date {
const start = new Date(Date.UTC(startDate.getFullYear(), startDate.getMonth(), startDate.getDate())).getTime();
const millisecondsPerDay = 24 * 60 * 60 * 1000;
export function getDateSerialNumberByWorkingDays(startDateSerialNumber: number, workingDays: number, weekend: number | string = 1, holidays?: number[]): (number | ErrorValueObject) {
const weekendArray = getWeekendArray(weekend);

startDateSerialNumber = Math.floor(startDateSerialNumber);
let targetDateSerialNumber = startDateSerialNumber;

let days = Math.abs(workingDays);
let targetDate: Date = startDate;

for (let i = 0; i < days; i++) {
const dayTimes = (i + 1) * millisecondsPerDay;
const currentDate = new Date(workingDays < 0 ? start - dayTimes : start + dayTimes);
for (let i = 1; i <= days; i++) {
const currentDateSerialNumber = workingDays < 0 ? startDateSerialNumber - i : startDateSerialNumber + i;

if (holidays && holidays.length > 0 && holidays.some((item) => {
if (typeof item === 'string') {
item = new Date(item);
}
if (currentDateSerialNumber < 0) {
return ErrorValueObject.create(ErrorType.NUM);
}

return item.getFullYear() === currentDate.getFullYear() &&
item.getMonth() === currentDate.getMonth() &&
item.getDate() === currentDate.getDate();
})) {
if (holidays && holidays.length > 0 && holidays.some((item) => Math.floor(item) === currentDateSerialNumber)) {
days++;
continue;
}

if (weekendArray.includes(currentDate.getDay())) {
const weekDay = getWeekDayByDateSerialNumber(currentDateSerialNumber);

if (weekendArray.includes(weekDay)) {
days++;
continue;
}

targetDate = currentDate;
targetDateSerialNumber = currentDateSerialNumber;
}

return targetDateSerialNumber;
}

export function getDateSerialNumberByObject(serialNumberObject: BaseValueObject): (ErrorValueObject | number) {
const dateValue = serialNumberObject.getValue();

if (serialNumberObject.isString()) {
if (parseFormattedDate(`${dateValue}`)) {
return parseFormattedDate(`${dateValue}`).v;
} else if (parseFormattedTime(`${dateValue}`)) {
return parseFormattedTime(`${dateValue}`).v;
} else {
return ErrorValueObject.create(ErrorType.VALUE);
}
} else {
const dateSerial = +serialNumberObject.getValue();

if (dateSerial < 0) {
return ErrorValueObject.create(ErrorType.NUM);
}

return dateSerial;
}
}

export function getWeekDayByDateSerialNumber(dateSerialNumber: number): number {
// special date 1990-02-29(serialNumber = 60)
const isDate19000229 = Math.floor(dateSerialNumber) === 60;

let date = excelSerialToDate(dateSerialNumber);

const dateTime = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate())).getTime();
const leapDayDateTime = new Date(Date.UTC(1900, 1, 28)).getTime(); // February 28, 1900, UTC

if (!isDate19000229 && dateTime <= leapDayDateTime) {
date = new Date(dateTime - 24 * 3600 * 1000);
}

return targetDate;
return new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate())).getUTCDay();
}
38 changes: 10 additions & 28 deletions packages/engine-formula/src/functions/date/hour/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,8 @@
* limitations under the License.
*/

import { excelSerialToDateTime, parseFormattedDate, parseFormattedTime } from '../../../basics/date';
import { ErrorType } from '../../../basics/error-type';
import { excelSerialToDateTime, getDateSerialNumberByObject } from '../../../basics/date';
import type { BaseValueObject } from '../../../engine/value-object/base-value-object';
import { ErrorValueObject } from '../../../engine/value-object/base-value-object';
import { NumberValueObject } from '../../../engine/value-object/primitive-object';
import { BaseFunction } from '../../base-function';

Expand Down Expand Up @@ -45,35 +43,19 @@ export class Hour extends BaseFunction {
}

private _handleSingleObject(serialNumberObject: BaseValueObject) {
let date: Date;
const dateValue = serialNumberObject.getValue();
const dateSerialNumber = getDateSerialNumberByObject(serialNumberObject);

if (serialNumberObject.isString()) {
if (parseFormattedDate(`${dateValue}`)) {
date = excelSerialToDateTime(parseFormattedDate(`${dateValue}`).v);
} else if (parseFormattedTime(`${dateValue}`)) {
date = excelSerialToDateTime(parseFormattedTime(`${dateValue}`).v);
} else {
return ErrorValueObject.create(ErrorType.VALUE);
}
} else {
const dateSerial = +serialNumberObject.getValue();

if (dateSerial < 0) {
return ErrorValueObject.create(ErrorType.NUM);
}

// Excel serial 0 is 1900-01-00
// Google Sheets serial 0 is 1899-12-30
if (dateSerial === 0) {
return NumberValueObject.create(0);
}
if (typeof dateSerialNumber !== 'number') {
return dateSerialNumber;
}

date = excelSerialToDateTime(dateSerial);
if (dateSerialNumber === 0) {
return NumberValueObject.create(0);
}

const hour = date.getUTCHours();
const date = excelSerialToDateTime(dateSerialNumber);
const hours = date.getUTCHours();

return NumberValueObject.create(hour);
return NumberValueObject.create(hours);
}
}
38 changes: 10 additions & 28 deletions packages/engine-formula/src/functions/date/minute/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,8 @@
* limitations under the License.
*/

import { excelSerialToDateTime, parseFormattedDate, parseFormattedTime } from '../../../basics/date';
import { ErrorType } from '../../../basics/error-type';
import { excelSerialToDateTime, getDateSerialNumberByObject } from '../../../basics/date';
import type { BaseValueObject } from '../../../engine/value-object/base-value-object';
import { ErrorValueObject } from '../../../engine/value-object/base-value-object';
import { NumberValueObject } from '../../../engine/value-object/primitive-object';
import { BaseFunction } from '../../base-function';

Expand Down Expand Up @@ -45,35 +43,19 @@ export class Minute extends BaseFunction {
}

private _handleSingleObject(serialNumberObject: BaseValueObject) {
let date: Date;
const dateValue = serialNumberObject.getValue();
const dateSerialNumber = getDateSerialNumberByObject(serialNumberObject);

if (serialNumberObject.isString()) {
if (parseFormattedDate(`${dateValue}`)) {
date = excelSerialToDateTime(parseFormattedDate(`${dateValue}`).v);
} else if (parseFormattedTime(`${dateValue}`)) {
date = excelSerialToDateTime(parseFormattedTime(`${dateValue}`).v);
} else {
return ErrorValueObject.create(ErrorType.VALUE);
}
} else {
const dateSerial = +serialNumberObject.getValue();

if (dateSerial < 0) {
return ErrorValueObject.create(ErrorType.NUM);
}

// Excel serial 0 is 1900-01-00
// Google Sheets serial 0 is 1899-12-30
if (dateSerial === 0) {
return NumberValueObject.create(0);
}
if (typeof dateSerialNumber !== 'number') {
return dateSerialNumber;
}

date = excelSerialToDateTime(dateSerial);
if (dateSerialNumber === 0) {
return NumberValueObject.create(0);
}

const hour = date.getUTCMinutes();
const date = excelSerialToDateTime(dateSerialNumber);
const minutes = date.getUTCMinutes();

return NumberValueObject.create(hour);
return NumberValueObject.create(minutes);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

import { countWorkingDays, excelSerialToDate, isValidDateStr, isValidWeekend } from '../../../basics/date';
import { countWorkingDays, getDateSerialNumberByObject, isValidWeekend } from '../../../basics/date';
import { ErrorType } from '../../../basics/error-type';
import type { ArrayValueObject } from '../../../engine/value-object/array-value-object';
import type { BaseValueObject } from '../../../engine/value-object/base-value-object';
Expand Down Expand Up @@ -98,42 +98,16 @@ export class NetworkdaysIntl extends BaseFunction {
return ErrorValueObject.create(ErrorType.VALUE);
}

let startDateObject: Date;
const startDateValue = startDate.getValue();
const startDateSerialNumber = getDateSerialNumberByObject(startDate);

if (startDate.isString()) {
if (!isValidDateStr(`${startDateValue}`)) {
return ErrorValueObject.create(ErrorType.VALUE);
}

startDateObject = new Date(`${startDateValue}`);
} else {
const dateSerial = +startDateValue;

if (dateSerial < 0) {
return ErrorValueObject.create(ErrorType.NUM);
}

startDateObject = excelSerialToDate(dateSerial);
if (typeof startDateSerialNumber !== 'number') {
return startDateSerialNumber;
}

let endDateObject: Date;
const endDateValue = endDate.getValue();

if (endDate.isString()) {
if (!isValidDateStr(`${endDateValue}`)) {
return ErrorValueObject.create(ErrorType.VALUE);
}

endDateObject = new Date(`${endDateValue}`);
} else {
const dateSerial = +endDateValue;
const endDateSerialNumber = getDateSerialNumberByObject(endDate);

if (dateSerial < 0) {
return ErrorValueObject.create(ErrorType.NUM);
}

endDateObject = excelSerialToDate(dateSerial);
if (typeof endDateSerialNumber !== 'number') {
return endDateSerialNumber;
}

let result: number;
Expand All @@ -148,62 +122,37 @@ export class NetworkdaysIntl extends BaseFunction {
for (let r = 0; r < rowCount; r++) {
for (let c = 0; c < columnCount; c++) {
const cell = (holidays as ArrayValueObject).get(r, c) as BaseValueObject;

if (cell.isBoolean()) {
return ErrorValueObject.create(ErrorType.VALUE);
}

let holidaysObject: Date;
const holidaysValue = cell.getValue();

if (cell.isString()) {
if (!isValidDateStr(`${holidaysValue}`)) {
return ErrorValueObject.create(ErrorType.VALUE);
}

holidaysObject = new Date(`${holidaysValue}`);
} else {
const dateSerial = +holidaysValue;
const holidaySerialNumber = getDateSerialNumberByObject(cell);

if (dateSerial < 0) {
return ErrorValueObject.create(ErrorType.NUM);
}

holidaysObject = excelSerialToDate(dateSerial);
if (typeof holidaySerialNumber !== 'number') {
return holidaySerialNumber;
}

holidaysValueArray.push(holidaysObject);
holidaysValueArray.push(holidaySerialNumber);
}
}
} else {
let holidaysObject: Date;
const holidaysValue = holidays.getValue();

if (holidays.isBoolean()) {
return ErrorValueObject.create(ErrorType.VALUE);
}

if (holidays.isString()) {
if (!isValidDateStr(`${holidaysValue}`)) {
return ErrorValueObject.create(ErrorType.VALUE);
}

holidaysObject = new Date(`${holidaysValue}`);
} else {
const dateSerial = +holidaysValue;

if (dateSerial < 0) {
return ErrorValueObject.create(ErrorType.NUM);
}
const holidaySerialNumber = getDateSerialNumberByObject(holidays);

holidaysObject = excelSerialToDate(dateSerial);
if (typeof holidaySerialNumber !== 'number') {
return holidaySerialNumber;
}

holidaysValueArray.push(holidaysObject);
holidaysValueArray.push(holidaySerialNumber);
}

result = countWorkingDays(startDateObject, endDateObject, weekendValue, holidaysValueArray);
result = countWorkingDays(startDateSerialNumber, endDateSerialNumber, weekendValue, holidaysValueArray);
} else {
result = countWorkingDays(startDateObject, endDateObject, weekendValue);
result = countWorkingDays(startDateSerialNumber, endDateSerialNumber, weekendValue);
}

return NumberValueObject.create(result);
Expand Down
Loading

0 comments on commit 946a6ad

Please sign in to comment.