feat(std/datetime): add is leap and difference functions (#4857)

This commit is contained in:
Ali Hasani 2020-06-27 15:40:45 +04:30 committed by GitHub
parent 5bc130be27
commit affba80454
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 259 additions and 77 deletions

View file

@ -3,6 +3,12 @@ import { assert } from "../_util/assert.ts";
export type DateFormat = "mm-dd-yyyy" | "dd-mm-yyyy" | "yyyy-mm-dd"; export type DateFormat = "mm-dd-yyyy" | "dd-mm-yyyy" | "yyyy-mm-dd";
export const SECOND = 1e3;
export const MINUTE = SECOND * 60;
export const HOUR = MINUTE * 60;
export const DAY = HOUR * 24;
export const WEEK = DAY * 7;
function execForce(reg: RegExp, pat: string): RegExpExecArray { function execForce(reg: RegExp, pat: string): RegExpExecArray {
const v = reg.exec(pat); const v = reg.exec(pat);
assert(v != null); assert(v != null);
@ -96,13 +102,12 @@ export function parseDateTime(
* @return Number of the day in year * @return Number of the day in year
*/ */
export function dayOfYear(date: Date): number { export function dayOfYear(date: Date): number {
const dayMs = 1000 * 60 * 60 * 24;
const yearStart = new Date(date.getFullYear(), 0, 0); const yearStart = new Date(date.getFullYear(), 0, 0);
const diff = const diff =
date.getTime() - date.getTime() -
yearStart.getTime() + yearStart.getTime() +
(yearStart.getTimezoneOffset() - date.getTimezoneOffset()) * 60 * 1000; (yearStart.getTimezoneOffset() - date.getTimezoneOffset()) * 60 * 1000;
return Math.floor(diff / dayMs); return Math.floor(diff / DAY);
} }
/** /**
@ -150,3 +155,128 @@ export function toIMF(date: Date): string {
months[date.getUTCMonth()] months[date.getUTCMonth()]
} ${y} ${h}:${min}:${s} GMT`; } ${y} ${h}:${min}:${s} GMT`;
} }
/**
* Check given year is a leap year or not.
* based on : https://docs.microsoft.com/en-us/office/troubleshoot/excel/determine-a-leap-year
* @param year year in number or Date format
*/
export function isLeap(year: Date | number): boolean {
const yearNumber = year instanceof Date ? year.getFullYear() : year;
return (
(yearNumber % 4 === 0 && yearNumber % 100 !== 0) || yearNumber % 400 === 0
);
}
export type Unit =
| "miliseconds"
| "seconds"
| "minutes"
| "hours"
| "days"
| "weeks"
| "months"
| "quarters"
| "years";
export type DifferenceFormat = Partial<Record<Unit, number>>;
export type DifferenceOptions = {
units?: Unit[];
};
/**
* Calculate difference between two dates.
* @param from Year to calculate difference
* @param to Year to calculate difference with
* @param options Options for determining how to respond
*
* example :
*
* ```typescript
* datetime.difference(new Date("2020/1/1"),new Date("2020/2/2"),{ units : ["days","months"] })
* ```
*/
export function difference(
from: Date,
to: Date,
options?: DifferenceOptions
): DifferenceFormat {
const uniqueUnits = options?.units
? [...new Set(options?.units)]
: [
"miliseconds",
"seconds",
"minutes",
"hours",
"days",
"weeks",
"months",
"quarters",
"years",
];
const bigger = Math.max(from.getTime(), to.getTime());
const smaller = Math.min(from.getTime(), to.getTime());
const differenceInMs = bigger - smaller;
const differences: DifferenceFormat = {};
for (const uniqueUnit of uniqueUnits) {
switch (uniqueUnit) {
case "miliseconds":
differences.miliseconds = differenceInMs;
break;
case "seconds":
differences.seconds = Math.floor(differenceInMs / SECOND);
break;
case "minutes":
differences.minutes = Math.floor(differenceInMs / MINUTE);
break;
case "hours":
differences.hours = Math.floor(differenceInMs / HOUR);
break;
case "days":
differences.days = Math.floor(differenceInMs / DAY);
break;
case "weeks":
differences.weeks = Math.floor(differenceInMs / WEEK);
break;
case "months":
differences.months = calculateMonthsDifference(bigger, smaller);
break;
case "quarters":
differences.quarters = Math.floor(
(typeof differences.months !== "undefined" &&
differences.months / 4) ||
calculateMonthsDifference(bigger, smaller) / 4
);
break;
case "years":
differences.years = Math.floor(
(typeof differences.months !== "undefined" &&
differences.months / 12) ||
calculateMonthsDifference(bigger, smaller) / 12
);
break;
}
}
return differences;
}
function calculateMonthsDifference(bigger: number, smaller: number): number {
const biggerDate = new Date(bigger);
const smallerDate = new Date(smaller);
const yearsDiff = biggerDate.getFullYear() - smallerDate.getFullYear();
const monthsDiff = biggerDate.getMonth() - smallerDate.getMonth();
const calendarDiffrences = Math.abs(yearsDiff * 12 + monthsDiff);
const compareResult = biggerDate > smallerDate ? 1 : -1;
biggerDate.setMonth(
biggerDate.getMonth() - compareResult * calendarDiffrences
);
const isLastMonthNotFull =
biggerDate > smallerDate ? 1 : -1 === -compareResult ? 1 : 0;
const months = compareResult * (calendarDiffrences - isLastMonthNotFull);
return months === 0 ? 0 : months;
}

View file

@ -1,8 +1,10 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
import { assertEquals, assertThrows } from "../testing/asserts.ts"; import { assert, assertEquals, assertThrows } from "../testing/asserts.ts";
import * as datetime from "./mod.ts"; import * as datetime from "./mod.ts";
Deno.test("parseDateTime", function (): void { Deno.test({
name: "[std/datetime] parseDate",
fn: () => {
assertEquals( assertEquals(
datetime.parseDateTime("01-03-2019 16:30", "mm-dd-yyyy hh:mm"), datetime.parseDateTime("01-03-2019 16:30", "mm-dd-yyyy hh:mm"),
new Date(2019, 0, 3, 16, 30) new Date(2019, 0, 3, 16, 30)
@ -27,9 +29,12 @@ Deno.test("parseDateTime", function (): void {
datetime.parseDateTime("16:35 2019-01-03", "hh:mm yyyy-mm-dd"), datetime.parseDateTime("16:35 2019-01-03", "hh:mm yyyy-mm-dd"),
new Date(2019, 0, 3, 16, 35) new Date(2019, 0, 3, 16, 35)
); );
},
}); });
Deno.test("invalidParseDateTimeFormatThrows", function (): void { Deno.test({
name: "[std/datetime] invalidParseDateTimeFormatThrows",
fn: () => {
assertThrows( assertThrows(
(): void => { (): void => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
@ -38,9 +43,12 @@ Deno.test("invalidParseDateTimeFormatThrows", function (): void {
Error, Error,
"Invalid datetime format!" "Invalid datetime format!"
); );
},
}); });
Deno.test("parseDate", function (): void { Deno.test({
name: "[std/datetime] parseDate",
fn: () => {
assertEquals( assertEquals(
datetime.parseDate("01-03-2019", "mm-dd-yyyy"), datetime.parseDate("01-03-2019", "mm-dd-yyyy"),
new Date(2019, 0, 3) new Date(2019, 0, 3)
@ -53,9 +61,12 @@ Deno.test("parseDate", function (): void {
datetime.parseDate("2019-01-03", "yyyy-mm-dd"), datetime.parseDate("2019-01-03", "yyyy-mm-dd"),
new Date(2019, 0, 3) new Date(2019, 0, 3)
); );
},
}); });
Deno.test("invalidParseDateFormatThrows", function (): void { Deno.test({
name: "[std/datetime] invalidParseDateFormatThrows",
fn: () => {
assertThrows( assertThrows(
(): void => { (): void => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
@ -64,20 +75,27 @@ Deno.test("invalidParseDateFormatThrows", function (): void {
Error, Error,
"Invalid date format!" "Invalid date format!"
); );
}); },
Deno.test("DayOfYear", function (): void {
assertEquals(1, datetime.dayOfYear(new Date("2019-01-01T03:24:00")));
assertEquals(70, datetime.dayOfYear(new Date("2019-03-11T03:24:00")));
assertEquals(365, datetime.dayOfYear(new Date("2019-12-31T03:24:00")));
});
Deno.test("currentDayOfYear", function (): void {
assertEquals(datetime.currentDayOfYear(), datetime.dayOfYear(new Date()));
}); });
Deno.test({ Deno.test({
name: "[DateTime] to IMF", name: "[std/datetime] DayOfYear",
fn: () => {
assertEquals(1, datetime.dayOfYear(new Date("2019-01-01T03:24:00")));
assertEquals(70, datetime.dayOfYear(new Date("2019-03-11T03:24:00")));
assertEquals(365, datetime.dayOfYear(new Date("2019-12-31T03:24:00")));
},
});
Deno.test({
name: "[std/datetime] currentDayOfYear",
fn: () => {
assertEquals(datetime.currentDayOfYear(), datetime.dayOfYear(new Date()));
},
});
Deno.test({
name: "[std/datetime] to IMF",
fn(): void { fn(): void {
const actual = datetime.toIMF(new Date(Date.UTC(1994, 3, 5, 15, 32))); const actual = datetime.toIMF(new Date(Date.UTC(1994, 3, 5, 15, 32)));
const expected = "Tue, 05 Apr 1994 15:32:00 GMT"; const expected = "Tue, 05 Apr 1994 15:32:00 GMT";
@ -86,10 +104,44 @@ Deno.test({
}); });
Deno.test({ Deno.test({
name: "[DateTime] to IMF 0", name: "[std/datetime] to IMF 0",
fn(): void { fn(): void {
const actual = datetime.toIMF(new Date(0)); const actual = datetime.toIMF(new Date(0));
const expected = "Thu, 01 Jan 1970 00:00:00 GMT"; const expected = "Thu, 01 Jan 1970 00:00:00 GMT";
assertEquals(actual, expected); assertEquals(actual, expected);
}, },
}); });
Deno.test({
name: "[std/datetime] isLeap",
fn(): void {
assert(datetime.isLeap(1992));
assert(datetime.isLeap(2000));
assert(!datetime.isLeap(2003));
assert(!datetime.isLeap(2007));
},
});
Deno.test({
name: "[std/datetime] Difference",
fn(): void {
const denoInit = new Date("2018/5/14");
const denoRelaseV1 = new Date("2020/5/13");
let difference = datetime.difference(denoRelaseV1, denoInit, {
units: ["days", "months", "years"],
});
assertEquals(difference.days, 730);
assertEquals(difference.months, 23);
assertEquals(difference.years, 1);
const birth = new Date("1998/2/23 10:10:10");
const old = new Date("1998/2/23 11:11:11");
difference = datetime.difference(birth, old, {
units: ["miliseconds", "minutes", "seconds", "hours"],
});
assertEquals(difference.miliseconds, 3661000);
assertEquals(difference.seconds, 3661);
assertEquals(difference.minutes, 61);
assertEquals(difference.hours, 1);
},
});