Skip to content

Commit

Permalink
feat(formula): add hour/minute/second function
Browse files Browse the repository at this point in the history
  • Loading branch information
panxp authored and panxp committed Jul 12, 2024
1 parent 52dc8b2 commit 7e8efa8
Show file tree
Hide file tree
Showing 13 changed files with 487 additions and 54 deletions.
19 changes: 19 additions & 0 deletions packages/engine-formula/src/basics/date.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,25 @@ export function excelSerialToDate(serial: number): Date {
return resultDate;
}

export function excelSerialToDateTime(serial: number): Date {
const baseDate = new Date(Date.UTC(1900, 0, 1, 0, 0, 0)); // January 1, 1900, UTC
const leapDayDate = new Date(Date.UTC(1900, 1, 28, 0, 0, 0)); // February 28, 1900, UTC

let dayDifference = serial - 1; // Adjust for Excel serial number starting from 1

// If the serial number corresponds to a date later than February 28, 1900, adjust the day difference
if (dayDifference > (leapDayDate.getTime() - baseDate.getTime()) / (1000 * 3600 * 24)) {
dayDifference -= 1;
}

if (dayDifference < 0) {
dayDifference = serial;
}

const resultDate = new Date(baseDate.getTime() + dayDifference * (1000 * 3600 * 24));
return resultDate;
}

export function formatDateDefault(date: Date): string {
// Get the year from the date object
const year: number = date.getFullYear();
Expand Down
6 changes: 6 additions & 0 deletions packages/engine-formula/src/functions/date/function-map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,13 @@ import { Datevalue } from './datevalue';
import { Day } from './day';
import { Edate } from './edate';
import { FUNCTION_NAMES_DATE } from './function-names';
import { Hour } from './hour';
import { Minute } from './minute';
import { Month } from './month';
import { Networkdays } from './networkdays';
import { NetworkdaysIntl } from './networkdays-intl';
import { Now } from './now';
import { Second } from './second';
import { Time } from './time';
import { Timevalue } from './timevalue';
import { Today } from './today';
Expand All @@ -36,10 +39,13 @@ export const functionDate = [
[Datevalue, FUNCTION_NAMES_DATE.DATEVALUE],
[Day, FUNCTION_NAMES_DATE.DAY],
[Edate, FUNCTION_NAMES_DATE.EDATE],
[Hour, FUNCTION_NAMES_DATE.HOUR],
[Minute, FUNCTION_NAMES_DATE.MINUTE],
[Month, FUNCTION_NAMES_DATE.MONTH],
[Networkdays, FUNCTION_NAMES_DATE.NETWORKDAYS],
[NetworkdaysIntl, FUNCTION_NAMES_DATE.NETWORKDAYS_INTL],
[Now, FUNCTION_NAMES_DATE.NOW],
[Second, FUNCTION_NAMES_DATE.SECOND],
[Time, FUNCTION_NAMES_DATE.TIME],
[Timevalue, FUNCTION_NAMES_DATE.TIMEVALUE],
[Today, FUNCTION_NAMES_DATE.TODAY],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/**
* Copyright 2023-present DreamNum Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { describe, expect, it } from 'vitest';

import { FUNCTION_NAMES_DATE } from '../../function-names';
import { Hour } from '../index';
import { NumberValueObject, StringValueObject } from '../../../../engine/value-object/primitive-object';
import { ArrayValueObject, transformToValue, transformToValueObject } from '../../../../engine/value-object/array-value-object';
import { ErrorValueObject } from '../../../../engine/value-object/base-value-object';
import { ErrorType } from '../../../../basics/error-type';

describe('Test hour function', () => {
const testFunction = new Hour(FUNCTION_NAMES_DATE.HOUR);

describe('Hour', () => {
it('Serial number is normal', () => {
const serialNumber = NumberValueObject.create(43832.233);
const result = testFunction.calculate(serialNumber);
expect(result.getValue()).toStrictEqual(5);
});

it('Serial number is error', () => {
const serialNumber = ErrorValueObject.create(ErrorType.NAME);
const result = testFunction.calculate(serialNumber);
expect(result.getValue()).toStrictEqual(ErrorType.NAME);
});

it('Serial number is date string', () => {
const serialNumber = StringValueObject.create('2020-01-02 7:45');
const result = testFunction.calculate(serialNumber);
expect(result.getValue()).toStrictEqual(7);
});

it('Serial number is array', () => {
const serialNumber = ArrayValueObject.create({
calculateValueList: transformToValueObject([
[ErrorType.NAME, 0.75, '2011-7-18 7:45', '2012-4-21'],
[true, 'test', false, -1],
]),
rowCount: 2,
columnCount: 4,
unitId: '',
sheetId: '',
row: 0,
column: 0,
});
const result = testFunction.calculate(serialNumber);
expect(transformToValue(result.getArrayValue())).toStrictEqual([
[ErrorType.NAME, 18, 7, 0],
[0, ErrorType.VALUE, 0, ErrorType.NUM],
]);
});
});
});
79 changes: 79 additions & 0 deletions packages/engine-formula/src/functions/date/hour/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/**
* Copyright 2023-present DreamNum Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { excelSerialToDateTime, parseFormattedDate, parseFormattedTime } from '../../../basics/date';
import { ErrorType } from '../../../basics/error-type';
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';

export class Hour extends BaseFunction {
override minParams = 1;

override maxParams = 1;

override calculate(serialNumber: BaseValueObject) {
if (serialNumber.isError()) {
return serialNumber;
}

if (serialNumber.isArray()) {
return serialNumber.map((serialNumberObject) => {
if (serialNumberObject.isError()) {
return serialNumberObject;
}

return this._handleSingleObject(serialNumberObject);
});
}

return this._handleSingleObject(serialNumber);
}

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

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);
}

date = excelSerialToDateTime(dateSerial);
}

const hour = date.getUTCHours();

return NumberValueObject.create(hour);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/**
* Copyright 2023-present DreamNum Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { describe, expect, it } from 'vitest';

import { FUNCTION_NAMES_DATE } from '../../function-names';
import { Minute } from '../index';
import { NumberValueObject, StringValueObject } from '../../../../engine/value-object/primitive-object';
import { ArrayValueObject, transformToValue, transformToValueObject } from '../../../../engine/value-object/array-value-object';
import { ErrorValueObject } from '../../../../engine/value-object/base-value-object';
import { ErrorType } from '../../../../basics/error-type';

describe('Test minute function', () => {
const testFunction = new Minute(FUNCTION_NAMES_DATE.MINUTE);

describe('Minute', () => {
it('Serial number is normal', () => {
const serialNumber = NumberValueObject.create(43832.233);
const result = testFunction.calculate(serialNumber);
expect(result.getValue()).toStrictEqual(35);
});

it('Serial number is error', () => {
const serialNumber = ErrorValueObject.create(ErrorType.NAME);
const result = testFunction.calculate(serialNumber);
expect(result.getValue()).toStrictEqual(ErrorType.NAME);
});

it('Serial number is date string', () => {
const serialNumber = StringValueObject.create('2020-01-02 7:45');
const result = testFunction.calculate(serialNumber);
expect(result.getValue()).toStrictEqual(45);
});

it('Serial number is array', () => {
const serialNumber = ArrayValueObject.create({
calculateValueList: transformToValueObject([
[ErrorType.NAME, 0.75, '2011-7-18 7:45', '2012-4-21'],
[true, 'test', false, -1],
]),
rowCount: 2,
columnCount: 4,
unitId: '',
sheetId: '',
row: 0,
column: 0,
});
const result = testFunction.calculate(serialNumber);
expect(transformToValue(result.getArrayValue())).toStrictEqual([
[ErrorType.NAME, 0, 45, 0],
[0, ErrorType.VALUE, 0, ErrorType.NUM],
]);
});
});
});
79 changes: 79 additions & 0 deletions packages/engine-formula/src/functions/date/minute/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/**
* Copyright 2023-present DreamNum Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { excelSerialToDateTime, parseFormattedDate, parseFormattedTime } from '../../../basics/date';
import { ErrorType } from '../../../basics/error-type';
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';

export class Minute extends BaseFunction {
override minParams = 1;

override maxParams = 1;

override calculate(serialNumber: BaseValueObject) {
if (serialNumber.isError()) {
return serialNumber;
}

if (serialNumber.isArray()) {
return serialNumber.map((serialNumberObject) => {
if (serialNumberObject.isError()) {
return serialNumberObject;
}

return this._handleSingleObject(serialNumberObject);
});
}

return this._handleSingleObject(serialNumber);
}

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

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);
}

date = excelSerialToDateTime(dateSerial);
}

const hour = date.getUTCMinutes();

return NumberValueObject.create(hour);
}
}
Loading

0 comments on commit 7e8efa8

Please sign in to comment.