2020-01-02 15:13:47 -05:00
|
|
|
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
2020-06-07 09:20:33 -04:00
|
|
|
import { assert } from "../_util/assert.ts";
|
2019-04-27 19:07:11 -04:00
|
|
|
|
2019-01-11 00:19:42 -05:00
|
|
|
export type DateFormat = "mm-dd-yyyy" | "dd-mm-yyyy" | "yyyy-mm-dd";
|
|
|
|
|
2020-06-27 07:10:45 -04:00
|
|
|
export const SECOND = 1e3;
|
|
|
|
export const MINUTE = SECOND * 60;
|
|
|
|
export const HOUR = MINUTE * 60;
|
|
|
|
export const DAY = HOUR * 24;
|
|
|
|
export const WEEK = DAY * 7;
|
2020-07-14 14:23:54 -04:00
|
|
|
const DAYS_PER_WEEK = 7;
|
|
|
|
|
|
|
|
enum Day {
|
|
|
|
Sun,
|
|
|
|
Mon,
|
|
|
|
Tue,
|
|
|
|
Wed,
|
|
|
|
Thu,
|
|
|
|
Fri,
|
|
|
|
Sat,
|
|
|
|
}
|
2020-06-27 07:10:45 -04:00
|
|
|
|
2020-02-07 02:23:38 -05:00
|
|
|
function execForce(reg: RegExp, pat: string): RegExpExecArray {
|
|
|
|
const v = reg.exec(pat);
|
|
|
|
assert(v != null);
|
|
|
|
return v;
|
|
|
|
}
|
2019-01-03 10:19:20 -05:00
|
|
|
/**
|
|
|
|
* Parse date from string using format string
|
2019-03-14 10:24:54 -04:00
|
|
|
* @param dateStr Date string
|
|
|
|
* @param format Format string
|
|
|
|
* @return Parsed date
|
2019-01-03 10:19:20 -05:00
|
|
|
*/
|
2019-01-11 00:19:42 -05:00
|
|
|
export function parseDate(dateStr: string, format: DateFormat): Date {
|
2019-01-03 10:19:20 -05:00
|
|
|
let m, d, y: string;
|
2019-03-11 11:00:30 -04:00
|
|
|
let datePattern: RegExp;
|
2019-01-03 10:19:20 -05:00
|
|
|
|
2019-03-11 11:00:30 -04:00
|
|
|
switch (format) {
|
|
|
|
case "mm-dd-yyyy":
|
|
|
|
datePattern = /^(\d{2})-(\d{2})-(\d{4})$/;
|
2020-02-07 02:23:38 -05:00
|
|
|
[, m, d, y] = execForce(datePattern, dateStr);
|
2019-03-11 11:00:30 -04:00
|
|
|
break;
|
|
|
|
case "dd-mm-yyyy":
|
|
|
|
datePattern = /^(\d{2})-(\d{2})-(\d{4})$/;
|
2020-02-07 02:23:38 -05:00
|
|
|
[, d, m, y] = execForce(datePattern, dateStr);
|
2019-03-11 11:00:30 -04:00
|
|
|
break;
|
|
|
|
case "yyyy-mm-dd":
|
|
|
|
datePattern = /^(\d{4})-(\d{2})-(\d{2})$/;
|
2020-02-07 02:23:38 -05:00
|
|
|
[, y, m, d] = execForce(datePattern, dateStr);
|
2019-03-11 11:00:30 -04:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
throw new Error("Invalid date format!");
|
2019-01-03 10:19:20 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
return new Date(Number(y), Number(m) - 1, Number(d));
|
|
|
|
}
|
|
|
|
|
2019-01-11 00:19:42 -05:00
|
|
|
export type DateTimeFormat =
|
|
|
|
| "mm-dd-yyyy hh:mm"
|
|
|
|
| "dd-mm-yyyy hh:mm"
|
|
|
|
| "yyyy-mm-dd hh:mm"
|
|
|
|
| "hh:mm mm-dd-yyyy"
|
|
|
|
| "hh:mm dd-mm-yyyy"
|
|
|
|
| "hh:mm yyyy-mm-dd";
|
|
|
|
|
2019-01-03 10:19:20 -05:00
|
|
|
/**
|
|
|
|
* Parse date & time from string using format string
|
2019-03-14 10:24:54 -04:00
|
|
|
* @param dateStr Date & time string
|
|
|
|
* @param format Format string
|
|
|
|
* @return Parsed date
|
2019-01-03 10:19:20 -05:00
|
|
|
*/
|
2019-01-11 00:19:42 -05:00
|
|
|
export function parseDateTime(
|
|
|
|
datetimeStr: string,
|
2020-07-14 15:24:17 -04:00
|
|
|
format: DateTimeFormat,
|
2019-01-11 00:19:42 -05:00
|
|
|
): Date {
|
2019-01-03 10:19:20 -05:00
|
|
|
let m, d, y, ho, mi: string;
|
2019-03-11 11:00:30 -04:00
|
|
|
let datePattern: RegExp;
|
2019-01-03 10:19:20 -05:00
|
|
|
|
2019-03-11 11:00:30 -04:00
|
|
|
switch (format) {
|
|
|
|
case "mm-dd-yyyy hh:mm":
|
|
|
|
datePattern = /^(\d{2})-(\d{2})-(\d{4}) (\d{2}):(\d{2})$/;
|
2020-02-07 02:23:38 -05:00
|
|
|
[, m, d, y, ho, mi] = execForce(datePattern, datetimeStr);
|
2019-03-11 11:00:30 -04:00
|
|
|
break;
|
|
|
|
case "dd-mm-yyyy hh:mm":
|
|
|
|
datePattern = /^(\d{2})-(\d{2})-(\d{4}) (\d{2}):(\d{2})$/;
|
2020-02-07 02:23:38 -05:00
|
|
|
[, d, m, y, ho, mi] = execForce(datePattern, datetimeStr);
|
2019-03-11 11:00:30 -04:00
|
|
|
break;
|
|
|
|
case "yyyy-mm-dd hh:mm":
|
|
|
|
datePattern = /^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2})$/;
|
2020-02-07 02:23:38 -05:00
|
|
|
[, y, m, d, ho, mi] = execForce(datePattern, datetimeStr);
|
2019-03-11 11:00:30 -04:00
|
|
|
break;
|
|
|
|
case "hh:mm mm-dd-yyyy":
|
|
|
|
datePattern = /^(\d{2}):(\d{2}) (\d{2})-(\d{2})-(\d{4})$/;
|
2020-02-07 02:23:38 -05:00
|
|
|
[, ho, mi, m, d, y] = execForce(datePattern, datetimeStr);
|
2019-03-11 11:00:30 -04:00
|
|
|
break;
|
|
|
|
case "hh:mm dd-mm-yyyy":
|
|
|
|
datePattern = /^(\d{2}):(\d{2}) (\d{2})-(\d{2})-(\d{4})$/;
|
2020-02-07 02:23:38 -05:00
|
|
|
[, ho, mi, d, m, y] = execForce(datePattern, datetimeStr);
|
2019-03-11 11:00:30 -04:00
|
|
|
break;
|
|
|
|
case "hh:mm yyyy-mm-dd":
|
|
|
|
datePattern = /^(\d{2}):(\d{2}) (\d{4})-(\d{2})-(\d{2})$/;
|
2020-02-07 02:23:38 -05:00
|
|
|
[, ho, mi, y, m, d] = execForce(datePattern, datetimeStr);
|
2019-03-11 11:00:30 -04:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
throw new Error("Invalid datetime format!");
|
2019-01-03 10:19:20 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
return new Date(Number(y), Number(m) - 1, Number(d), Number(ho), Number(mi));
|
|
|
|
}
|
|
|
|
|
2019-03-11 11:00:30 -04:00
|
|
|
/**
|
|
|
|
* Get number of the day in the year
|
2019-03-14 10:24:54 -04:00
|
|
|
* @return Number of the day in year
|
2019-03-11 11:00:30 -04:00
|
|
|
*/
|
2019-03-14 10:24:54 -04:00
|
|
|
export function dayOfYear(date: Date): number {
|
2019-03-11 11:00:30 -04:00
|
|
|
const yearStart = new Date(date.getFullYear(), 0, 0);
|
2020-07-14 15:24:17 -04:00
|
|
|
const diff = date.getTime() -
|
2019-03-11 11:00:30 -04:00
|
|
|
yearStart.getTime() +
|
|
|
|
(yearStart.getTimezoneOffset() - date.getTimezoneOffset()) * 60 * 1000;
|
2020-06-27 07:10:45 -04:00
|
|
|
return Math.floor(diff / DAY);
|
2019-03-11 11:00:30 -04:00
|
|
|
}
|
|
|
|
|
2020-07-14 14:23:54 -04:00
|
|
|
/**
|
|
|
|
* Get number of current day in year
|
|
|
|
* @return Number of current day in year
|
|
|
|
*/
|
|
|
|
export function currentDayOfYear(): number {
|
|
|
|
return dayOfYear(new Date());
|
|
|
|
}
|
|
|
|
|
2020-07-09 15:50:42 -04:00
|
|
|
/**
|
|
|
|
* Get number of the week in the year (ISO-8601)
|
|
|
|
* @return Number of the week in year
|
|
|
|
*/
|
|
|
|
export function weekOfYear(date: Date): number {
|
|
|
|
const workingDate = new Date(
|
2020-07-14 15:24:17 -04:00
|
|
|
Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()),
|
2020-07-09 15:50:42 -04:00
|
|
|
);
|
|
|
|
|
2020-07-14 14:23:54 -04:00
|
|
|
const day = workingDate.getUTCDay();
|
|
|
|
|
2020-07-14 15:24:17 -04:00
|
|
|
const nearestThursday = workingDate.getUTCDate() +
|
2020-07-14 14:23:54 -04:00
|
|
|
Day.Thu -
|
|
|
|
(day === Day.Sun ? DAYS_PER_WEEK : day);
|
|
|
|
|
|
|
|
workingDate.setUTCDate(nearestThursday);
|
2020-07-09 15:50:42 -04:00
|
|
|
|
|
|
|
// Get first day of year
|
|
|
|
const yearStart = new Date(Date.UTC(workingDate.getUTCFullYear(), 0, 1));
|
|
|
|
|
|
|
|
// return the calculated full weeks to nearest Thursday
|
2020-07-14 14:23:54 -04:00
|
|
|
return Math.ceil((workingDate.getTime() - yearStart.getTime() + DAY) / WEEK);
|
2019-01-03 10:19:20 -05:00
|
|
|
}
|
2019-04-27 19:07:11 -04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Parse a date to return a IMF formated string date
|
|
|
|
* RFC: https://tools.ietf.org/html/rfc7231#section-7.1.1.1
|
|
|
|
* IMF is the time format to use when generating times in HTTP
|
|
|
|
* headers. The time being formatted must be in UTC for Format to
|
|
|
|
* generate the correct format.
|
|
|
|
* @param date Date to parse
|
|
|
|
* @return IMF date formated string
|
|
|
|
*/
|
|
|
|
export function toIMF(date: Date): string {
|
2019-10-05 12:02:34 -04:00
|
|
|
function dtPad(v: string, lPad = 2): string {
|
2020-03-31 06:34:13 -04:00
|
|
|
return v.padStart(lPad, "0");
|
2019-04-27 19:07:11 -04:00
|
|
|
}
|
|
|
|
const d = dtPad(date.getUTCDate().toString());
|
|
|
|
const h = dtPad(date.getUTCHours().toString());
|
|
|
|
const min = dtPad(date.getUTCMinutes().toString());
|
|
|
|
const s = dtPad(date.getUTCSeconds().toString());
|
|
|
|
const y = date.getUTCFullYear();
|
2019-11-16 08:24:07 -05:00
|
|
|
const days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
2019-04-27 19:07:11 -04:00
|
|
|
const months = [
|
|
|
|
"Jan",
|
|
|
|
"Feb",
|
|
|
|
"Mar",
|
2019-11-16 08:24:07 -05:00
|
|
|
"Apr",
|
2019-04-27 19:07:11 -04:00
|
|
|
"May",
|
|
|
|
"Jun",
|
|
|
|
"Jul",
|
|
|
|
"Aug",
|
|
|
|
"Sep",
|
|
|
|
"Oct",
|
|
|
|
"Nov",
|
2020-03-28 13:03:49 -04:00
|
|
|
"Dec",
|
2019-04-27 19:07:11 -04:00
|
|
|
];
|
2019-05-13 14:03:24 -04:00
|
|
|
return `${days[date.getUTCDay()]}, ${d} ${
|
2019-04-27 19:07:11 -04:00
|
|
|
months[date.getUTCMonth()]
|
|
|
|
} ${y} ${h}:${min}:${s} GMT`;
|
|
|
|
}
|
2020-06-27 07:10:45 -04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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,
|
2020-07-14 15:24:17 -04:00
|
|
|
options?: DifferenceOptions,
|
2020-06-27 07:10:45 -04:00
|
|
|
): DifferenceFormat {
|
2020-07-14 15:24:17 -04:00
|
|
|
const uniqueUnits = options?.units ? [...new Set(options?.units)] : [
|
|
|
|
"miliseconds",
|
|
|
|
"seconds",
|
|
|
|
"minutes",
|
|
|
|
"hours",
|
|
|
|
"days",
|
|
|
|
"weeks",
|
|
|
|
"months",
|
|
|
|
"quarters",
|
|
|
|
"years",
|
|
|
|
];
|
2020-06-27 07:10:45 -04:00
|
|
|
|
|
|
|
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) ||
|
2020-07-14 15:24:17 -04:00
|
|
|
calculateMonthsDifference(bigger, smaller) / 4,
|
2020-06-27 07:10:45 -04:00
|
|
|
);
|
|
|
|
break;
|
|
|
|
case "years":
|
|
|
|
differences.years = Math.floor(
|
|
|
|
(typeof differences.months !== "undefined" &&
|
|
|
|
differences.months / 12) ||
|
2020-07-14 15:24:17 -04:00
|
|
|
calculateMonthsDifference(bigger, smaller) / 12,
|
2020-06-27 07:10:45 -04:00
|
|
|
);
|
|
|
|
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(
|
2020-07-14 15:24:17 -04:00
|
|
|
biggerDate.getMonth() - compareResult * calendarDiffrences,
|
2020-06-27 07:10:45 -04:00
|
|
|
);
|
2020-07-14 15:24:17 -04:00
|
|
|
const isLastMonthNotFull = biggerDate > smallerDate
|
|
|
|
? 1
|
|
|
|
: -1 === -compareResult
|
|
|
|
? 1
|
|
|
|
: 0;
|
2020-06-27 07:10:45 -04:00
|
|
|
const months = compareResult * (calendarDiffrences - isLastMonthNotFull);
|
|
|
|
return months === 0 ? 0 : months;
|
|
|
|
}
|