diff options
Diffstat (limited to 'std/datetime')
-rw-r--r-- | std/datetime/README.md | 188 | ||||
-rw-r--r-- | std/datetime/formatter.ts | 594 | ||||
-rw-r--r-- | std/datetime/mod.ts | 249 | ||||
-rw-r--r-- | std/datetime/test.ts | 393 | ||||
-rw-r--r-- | std/datetime/tokenizer.ts | 76 |
5 files changed, 0 insertions, 1500 deletions
diff --git a/std/datetime/README.md b/std/datetime/README.md deleted file mode 100644 index b168cd08b..000000000 --- a/std/datetime/README.md +++ /dev/null @@ -1,188 +0,0 @@ -# datetime - -Simple helper to help parse date strings into `Date`, with additional functions. - -## Usage - -The following symbols from -[unicode LDML](http://www.unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table) -are supported: - -- `yyyy` - numeric year. -- `yy` - 2-digit year. -- `M` - numeric month. -- `MM` - 2-digit month. -- `d` - numeric day. -- `dd` - 2-digit day. - -- `H` - numeric hour (0-23 hours). -- `HH` - 2-digit hour (00-23 hours). -- `h` - numeric hour (1-12 hours). -- `hh` - 2-digit hour (01-12 hours). -- `m` - numeric minute. -- `mm` - 2-digit minute. -- `s` - numeric second. -- `ss` - 2-digit second. -- `S` - 1-digit fractionalSecond. -- `SS` - 2-digit fractionalSecond. -- `SSS` - 3-digit fractionalSecond. - -- `a` - dayPeriod, either `AM` or `PM`. - -- `'foo'` - quoted literal. -- `./-` - unquoted literal. - -## Methods - -### parse - -Takes an input `string` and a `formatString` to parse to a `date`. - -```ts -import { parse } from 'https://deno.land/std@$STD_VERSION/datetime/mod.ts' - -parse("20-01-2019", "dd-MM-yyyy") // output : new Date(2019, 0, 20) -parse("2019-01-20", "yyyy-MM-dd") // output : new Date(2019, 0, 20) -parse("20.01.2019", "dd.MM.yyyy") // output : new Date(2019, 0, 20) -parse("01-20-2019 16:34", "MM-dd-yyyy HH:mm") // output : new Date(2019, 0, 20, 16, 34) -parse("01-20-2019 04:34 PM", "MM-dd-yyyy hh:mm a") // output : new Date(2019, 0, 20, 16, 34) -parse("16:34 01-20-2019", "HH:mm MM-dd-yyyy") // output : new Date(2019, 0, 20, 16, 34) -parse("01-20-2019 16:34:23.123", "MM-dd-yyyy HH:mm:ss.SSS") // output : new Date(2019, 0, 20, 16, 34, 23, 123) -... -``` - -### format - -Takes an input `date` and a `formatString` to format to a `string`. - -```ts -import { format } from "https://deno.land/std@$STD_VERSION/datetime/mod.ts"; - -format(new Date(2019, 0, 20), "dd-MM-yyyy"); // output : "20-01-2019" -format(new Date(2019, 0, 20), "yyyy-MM-dd"); // output : "2019-01-20" -format(new Date(2019, 0, 20), "dd.MM.yyyy"); // output : "2019-01-20" -format(new Date(2019, 0, 20, 16, 34), "MM-dd-yyyy HH:mm"); // output : "01-20-2019 16:34" -format(new Date(2019, 0, 20, 16, 34), "MM-dd-yyyy hh:mm a"); // output : "01-20-2019 04:34 PM" -format(new Date(2019, 0, 20, 16, 34), "HH:mm MM-dd-yyyy"); // output : "16:34 01-20-2019" -format(new Date(2019, 0, 20, 16, 34, 23, 123), "MM-dd-yyyy HH:mm:ss.SSS"); // output : "01-20-2019 16:34:23.123" -format(new Date(2019, 0, 20), "'today:' yyyy-MM-dd"); // output : "today: 2019-01-20" -``` - -### dayOfYear - -Returns the number of the day in the year. - -```ts -import { dayOfYear } from "https://deno.land/std@$STD_VERSION/datetime/mod.ts"; - -dayOfYear(new Date("2019-03-11T03:24:00")); // output: 70 -``` - -### weekOfYear - -Returns the ISO week number of the provided date (1-53). - -```ts -import { weekOfYear } from "https://deno.land/std@$STD_VERSION/datetime/mod.ts"; - -weekOfYear(new Date("2020-12-28T03:24:00")); // Returns 53 -``` - -### toIMF - -Formats the given date to IMF date time format. (Reference: -https://tools.ietf.org/html/rfc7231#section-7.1.1.1 ) - -```js -import { toIMF } from "https://deno.land/std@$STD_VERSION/datetime/mod.ts"; - -toIMF(new Date(0)); // => returns "Thu, 01 Jan 1970 00:00:00 GMT" -``` - -### isLeap - -Returns true if the given date or year (in number) is a leap year. Returns false -otherwise. - -```js -import { isLeap } from "https://deno.land/std@$STD_VERSION/datetime/mod.ts"; - -isLeap(new Date("1970-01-01")); // => returns false -isLeap(new Date("1972-01-01")); // => returns true -isLeap(new Date("2000-01-01")); // => returns true -isLeap(new Date("2100-01-01")); // => returns false -isLeap(1972); // => returns true -``` - -### difference - -Returns the difference of the 2 given dates in the given units. If the units are -omitted, it returns the difference in the all available units. - -Available units: "milliseconds", "seconds", "minutes", "hours", "days", "weeks", -"months", "quarters", "years" - -```js -import { difference } from "https://deno.land/std@$STD_VERSION/datetime/mod.ts"; - -const date0 = new Date("2018-05-14"); -const date1 = new Date("2020-05-13"); - -difference(date0, date1, { units: ["days", "months", "years"] }); -// => returns { days: 730, months: 23, years: 1 } - -difference(date0, date1); -// => returns { -// milliseconds: 63072000000, -// seconds: 63072000, -// minutes: 1051200, -// hours: 17520, -// days: 730, -// weeks: 104, -// months: 23, -// quarters: 5, -// years: 1 -// } -``` - -## Constants - -### SECOND - -``` -import { SECOND } from "https://deno.land/std@$STD_VERSION/datetime/mod.ts"; - -console.log(SECOND); // => 1000 -``` - -### MINUTE - -``` -import { MINUTE } from "https://deno.land/std@$STD_VERSION/datetime/mod.ts"; - -console.log(MINUTE); // => 60000 (60 * 1000) -``` - -### HOUR - -``` -import { HOUR } from "https://deno.land/std@$STD_VERSION/datetime/mod.ts"; - -console.log(HOUR); // => 3600000 (60 * 60 * 1000) -``` - -### DAY - -``` -import { DAY } from "https://deno.land/std@$STD_VERSION/datetime/mod.ts"; - -console.log(DAY); // => 86400000 (24 * 60 * 60 * 1000) -``` - -### WEEK - -``` -import { WEEK } from "https://deno.land/std@$STD_VERSION/datetime/mod.ts"; - -console.log(WEEK); // => 604800000 (7 * 24 * 60 * 60 * 1000) -``` diff --git a/std/datetime/formatter.ts b/std/datetime/formatter.ts deleted file mode 100644 index 788de6d00..000000000 --- a/std/datetime/formatter.ts +++ /dev/null @@ -1,594 +0,0 @@ -// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. -import { - CallbackResult, - ReceiverResult, - Rule, - TestFunction, - TestResult, - Tokenizer, -} from "./tokenizer.ts"; - -function digits(value: string | number, count = 2): string { - return String(value).padStart(count, "0"); -} - -// as declared as in namespace Intl -type DateTimeFormatPartTypes = - | "day" - | "dayPeriod" - // | "era" - | "hour" - | "literal" - | "minute" - | "month" - | "second" - | "timeZoneName" - // | "weekday" - | "year" - | "fractionalSecond"; - -interface DateTimeFormatPart { - type: DateTimeFormatPartTypes; - value: string; -} - -type TimeZone = "UTC"; - -interface Options { - timeZone?: TimeZone; -} - -function createLiteralTestFunction(value: string): TestFunction { - return (string: string): TestResult => { - return string.startsWith(value) - ? { value, length: value.length } - : undefined; - }; -} - -function createMatchTestFunction(match: RegExp): TestFunction { - return (string: string): TestResult => { - const result = match.exec(string); - if (result) return { value: result, length: result[0].length }; - }; -} - -// according to unicode symbols (http://www.unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table) -const defaultRules = [ - { - test: createLiteralTestFunction("yyyy"), - fn: (): CallbackResult => ({ type: "year", value: "numeric" }), - }, - { - test: createLiteralTestFunction("yy"), - fn: (): CallbackResult => ({ type: "year", value: "2-digit" }), - }, - - { - test: createLiteralTestFunction("MM"), - fn: (): CallbackResult => ({ type: "month", value: "2-digit" }), - }, - { - test: createLiteralTestFunction("M"), - fn: (): CallbackResult => ({ type: "month", value: "numeric" }), - }, - { - test: createLiteralTestFunction("dd"), - fn: (): CallbackResult => ({ type: "day", value: "2-digit" }), - }, - { - test: createLiteralTestFunction("d"), - fn: (): CallbackResult => ({ type: "day", value: "numeric" }), - }, - - { - test: createLiteralTestFunction("HH"), - fn: (): CallbackResult => ({ type: "hour", value: "2-digit" }), - }, - { - test: createLiteralTestFunction("H"), - fn: (): CallbackResult => ({ type: "hour", value: "numeric" }), - }, - { - test: createLiteralTestFunction("hh"), - fn: (): CallbackResult => ({ - type: "hour", - value: "2-digit", - hour12: true, - }), - }, - { - test: createLiteralTestFunction("h"), - fn: (): CallbackResult => ({ - type: "hour", - value: "numeric", - hour12: true, - }), - }, - { - test: createLiteralTestFunction("mm"), - fn: (): CallbackResult => ({ type: "minute", value: "2-digit" }), - }, - { - test: createLiteralTestFunction("m"), - fn: (): CallbackResult => ({ type: "minute", value: "numeric" }), - }, - { - test: createLiteralTestFunction("ss"), - fn: (): CallbackResult => ({ type: "second", value: "2-digit" }), - }, - { - test: createLiteralTestFunction("s"), - fn: (): CallbackResult => ({ type: "second", value: "numeric" }), - }, - { - test: createLiteralTestFunction("SSS"), - fn: (): CallbackResult => ({ type: "fractionalSecond", value: 3 }), - }, - { - test: createLiteralTestFunction("SS"), - fn: (): CallbackResult => ({ type: "fractionalSecond", value: 2 }), - }, - { - test: createLiteralTestFunction("S"), - fn: (): CallbackResult => ({ type: "fractionalSecond", value: 1 }), - }, - - { - test: createLiteralTestFunction("a"), - fn: (value: unknown): CallbackResult => ({ - type: "dayPeriod", - value: value as string, - }), - }, - - // quoted literal - { - test: createMatchTestFunction(/^(')(?<value>\\.|[^\']*)\1/), - fn: (match: unknown): CallbackResult => ({ - type: "literal", - value: (match as RegExpExecArray).groups!.value as string, - }), - }, - // literal - { - test: createMatchTestFunction(/^.+?\s*/), - fn: (match: unknown): CallbackResult => ({ - type: "literal", - value: (match as RegExpExecArray)[0], - }), - }, -]; - -type FormatPart = { - type: DateTimeFormatPartTypes; - value: string | number; - hour12?: boolean; -}; -type Format = FormatPart[]; - -export class DateTimeFormatter { - #format: Format; - - constructor(formatString: string, rules: Rule[] = defaultRules) { - const tokenizer = new Tokenizer(rules); - this.#format = tokenizer.tokenize( - formatString, - ({ type, value, hour12 }) => { - const result = { - type, - value, - } as unknown as ReceiverResult; - if (hour12) result.hour12 = hour12 as boolean; - return result; - }, - ) as Format; - } - - format(date: Date, options: Options = {}): string { - let string = ""; - - const utc = options.timeZone === "UTC"; - - for (const token of this.#format) { - const type = token.type; - - switch (type) { - case "year": { - const value = utc ? date.getUTCFullYear() : date.getFullYear(); - switch (token.value) { - case "numeric": { - string += value; - break; - } - case "2-digit": { - string += digits(value, 2).slice(-2); - break; - } - default: - throw Error( - `FormatterError: value "${token.value}" is not supported`, - ); - } - break; - } - case "month": { - const value = (utc ? date.getUTCMonth() : date.getMonth()) + 1; - switch (token.value) { - case "numeric": { - string += value; - break; - } - case "2-digit": { - string += digits(value, 2); - break; - } - default: - throw Error( - `FormatterError: value "${token.value}" is not supported`, - ); - } - break; - } - case "day": { - const value = utc ? date.getUTCDate() : date.getDate(); - switch (token.value) { - case "numeric": { - string += value; - break; - } - case "2-digit": { - string += digits(value, 2); - break; - } - default: - throw Error( - `FormatterError: value "${token.value}" is not supported`, - ); - } - break; - } - case "hour": { - let value = utc ? date.getUTCHours() : date.getHours(); - value -= token.hour12 && date.getHours() > 12 ? 12 : 0; - switch (token.value) { - case "numeric": { - string += value; - break; - } - case "2-digit": { - string += digits(value, 2); - break; - } - default: - throw Error( - `FormatterError: value "${token.value}" is not supported`, - ); - } - break; - } - case "minute": { - const value = utc ? date.getUTCMinutes() : date.getMinutes(); - switch (token.value) { - case "numeric": { - string += value; - break; - } - case "2-digit": { - string += digits(value, 2); - break; - } - default: - throw Error( - `FormatterError: value "${token.value}" is not supported`, - ); - } - break; - } - case "second": { - const value = utc ? date.getUTCSeconds() : date.getSeconds(); - switch (token.value) { - case "numeric": { - string += value; - break; - } - case "2-digit": { - string += digits(value, 2); - break; - } - default: - throw Error( - `FormatterError: value "${token.value}" is not supported`, - ); - } - break; - } - case "fractionalSecond": { - const value = utc - ? date.getUTCMilliseconds() - : date.getMilliseconds(); - string += digits(value, Number(token.value)); - break; - } - // FIXME(bartlomieju) - case "timeZoneName": { - // string += utc ? "Z" : token.value - break; - } - case "dayPeriod": { - string += token.value ? (date.getHours() >= 12 ? "PM" : "AM") : ""; - break; - } - case "literal": { - string += token.value; - break; - } - - default: - throw Error(`FormatterError: { ${token.type} ${token.value} }`); - } - } - - return string; - } - - parseToParts(string: string): DateTimeFormatPart[] { - const parts: DateTimeFormatPart[] = []; - - for (const token of this.#format) { - const type = token.type; - - let value = ""; - switch (token.type) { - case "year": { - switch (token.value) { - case "numeric": { - value = /^\d{1,4}/.exec(string)?.[0] as string; - break; - } - case "2-digit": { - value = /^\d{1,2}/.exec(string)?.[0] as string; - break; - } - } - break; - } - case "month": { - switch (token.value) { - case "numeric": { - value = /^\d{1,2}/.exec(string)?.[0] as string; - break; - } - case "2-digit": { - value = /^\d{2}/.exec(string)?.[0] as string; - break; - } - case "narrow": { - value = /^[a-zA-Z]+/.exec(string)?.[0] as string; - break; - } - case "short": { - value = /^[a-zA-Z]+/.exec(string)?.[0] as string; - break; - } - case "long": { - value = /^[a-zA-Z]+/.exec(string)?.[0] as string; - break; - } - default: - throw Error( - `ParserError: value "${token.value}" is not supported`, - ); - } - break; - } - case "day": { - switch (token.value) { - case "numeric": { - value = /^\d{1,2}/.exec(string)?.[0] as string; - break; - } - case "2-digit": { - value = /^\d{2}/.exec(string)?.[0] as string; - break; - } - default: - throw Error( - `ParserError: value "${token.value}" is not supported`, - ); - } - break; - } - case "hour": { - switch (token.value) { - case "numeric": { - value = /^\d{1,2}/.exec(string)?.[0] as string; - if (token.hour12 && parseInt(value) > 12) { - console.error( - `Trying to parse hour greater than 12. Use 'H' instead of 'h'.`, - ); - } - break; - } - case "2-digit": { - value = /^\d{2}/.exec(string)?.[0] as string; - if (token.hour12 && parseInt(value) > 12) { - console.error( - `Trying to parse hour greater than 12. Use 'HH' instead of 'hh'.`, - ); - } - break; - } - default: - throw Error( - `ParserError: value "${token.value}" is not supported`, - ); - } - break; - } - case "minute": { - switch (token.value) { - case "numeric": { - value = /^\d{1,2}/.exec(string)?.[0] as string; - break; - } - case "2-digit": { - value = /^\d{2}/.exec(string)?.[0] as string; - break; - } - default: - throw Error( - `ParserError: value "${token.value}" is not supported`, - ); - } - break; - } - case "second": { - switch (token.value) { - case "numeric": { - value = /^\d{1,2}/.exec(string)?.[0] as string; - break; - } - case "2-digit": { - value = /^\d{2}/.exec(string)?.[0] as string; - break; - } - default: - throw Error( - `ParserError: value "${token.value}" is not supported`, - ); - } - break; - } - case "fractionalSecond": { - value = new RegExp(`^\\d{${token.value}}`).exec(string) - ?.[0] as string; - break; - } - case "timeZoneName": { - value = token.value as string; - break; - } - case "dayPeriod": { - value = /^(A|P)M/.exec(string)?.[0] as string; - break; - } - case "literal": { - if (!string.startsWith(token.value as string)) { - throw Error( - `Literal "${token.value}" not found "${string.slice(0, 25)}"`, - ); - } - value = token.value as string; - break; - } - - default: - throw Error(`${token.type} ${token.value}`); - } - - if (!value) { - throw Error( - `value not valid for token { ${type} ${value} } ${ - string.slice( - 0, - 25, - ) - }`, - ); - } - parts.push({ type, value }); - - string = string.slice(value.length); - } - - if (string.length) { - throw Error( - `datetime string was not fully parsed! ${string.slice(0, 25)}`, - ); - } - - return parts; - } - - /** sort & filter dateTimeFormatPart */ - sortDateTimeFormatPart(parts: DateTimeFormatPart[]): DateTimeFormatPart[] { - let result: DateTimeFormatPart[] = []; - const typeArray = [ - "year", - "month", - "day", - "hour", - "minute", - "second", - "fractionalSecond", - ]; - for (const type of typeArray) { - const current = parts.findIndex((el) => el.type === type); - if (current !== -1) { - result = result.concat(parts.splice(current, 1)); - } - } - result = result.concat(parts); - return result; - } - - partsToDate(parts: DateTimeFormatPart[]): Date { - const date = new Date(); - const utc = parts.find( - (part) => part.type === "timeZoneName" && part.value === "UTC", - ); - - utc ? date.setUTCHours(0, 0, 0, 0) : date.setHours(0, 0, 0, 0); - for (const part of parts) { - switch (part.type) { - case "year": { - const value = Number(part.value.padStart(4, "20")); - utc ? date.setUTCFullYear(value) : date.setFullYear(value); - break; - } - case "month": { - const value = Number(part.value) - 1; - utc ? date.setUTCMonth(value) : date.setMonth(value); - break; - } - case "day": { - const value = Number(part.value); - utc ? date.setUTCDate(value) : date.setDate(value); - break; - } - case "hour": { - let value = Number(part.value); - const dayPeriod = parts.find( - (part: DateTimeFormatPart) => part.type === "dayPeriod", - ); - if (dayPeriod?.value === "PM") value += 12; - utc ? date.setUTCHours(value) : date.setHours(value); - break; - } - case "minute": { - const value = Number(part.value); - utc ? date.setUTCMinutes(value) : date.setMinutes(value); - break; - } - case "second": { - const value = Number(part.value); - utc ? date.setUTCSeconds(value) : date.setSeconds(value); - break; - } - case "fractionalSecond": { - const value = Number(part.value); - utc ? date.setUTCMilliseconds(value) : date.setMilliseconds(value); - break; - } - } - } - return date; - } - - parse(string: string): Date { - const parts = this.parseToParts(string); - const sortParts = this.sortDateTimeFormatPart(parts); - return this.partsToDate(sortParts); - } -} diff --git a/std/datetime/mod.ts b/std/datetime/mod.ts deleted file mode 100644 index 8a3ec7c4f..000000000 --- a/std/datetime/mod.ts +++ /dev/null @@ -1,249 +0,0 @@ -// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. - -import { DateTimeFormatter } from "./formatter.ts"; - -export const SECOND = 1e3; -export const MINUTE = SECOND * 60; -export const HOUR = MINUTE * 60; -export const DAY = HOUR * 24; -export const WEEK = DAY * 7; -const DAYS_PER_WEEK = 7; - -enum Day { - Sun, - Mon, - Tue, - Wed, - Thu, - Fri, - Sat, -} - -/** - * Parse date from string using format string - * @param dateString Date string - * @param format Format string - * @return Parsed date - */ -export function parse(dateString: string, formatString: string): Date { - const formatter = new DateTimeFormatter(formatString); - const parts = formatter.parseToParts(dateString); - const sortParts = formatter.sortDateTimeFormatPart(parts); - return formatter.partsToDate(sortParts); -} - -/** - * Format date using format string - * @param date Date - * @param format Format string - * @return formatted date string - */ -export function format(date: Date, formatString: string): string { - const formatter = new DateTimeFormatter(formatString); - return formatter.format(date); -} - -/** - * Get number of the day in the year - * @return Number of the day in year - */ -export function dayOfYear(date: Date): number { - // Values from 0 to 99 map to the years 1900 to 1999. All other values are the actual year. (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/Date) - // Using setFullYear as a workaround - - const yearStart = new Date(date); - - yearStart.setUTCFullYear(date.getUTCFullYear(), 0, 0); - const diff = date.getTime() - - yearStart.getTime() + - (yearStart.getTimezoneOffset() - date.getTimezoneOffset()) * 60 * 1000; - - return Math.floor(diff / DAY); -} -/** - * 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( - Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()), - ); - - const day = workingDate.getUTCDay(); - - const nearestThursday = workingDate.getUTCDate() + - Day.Thu - - (day === Day.Sun ? DAYS_PER_WEEK : day); - - workingDate.setUTCDate(nearestThursday); - - // Get first day of year - const yearStart = new Date(Date.UTC(workingDate.getUTCFullYear(), 0, 1)); - - // return the calculated full weeks to nearest Thursday - return Math.ceil((workingDate.getTime() - yearStart.getTime() + DAY) / WEEK); -} - -/** - * Parse a date to return a IMF formatted 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 formatted string - */ -export function toIMF(date: Date): string { - function dtPad(v: string, lPad = 2): string { - return v.padStart(lPad, "0"); - } - 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(); - const days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; - const months = [ - "Jan", - "Feb", - "Mar", - "Apr", - "May", - "Jun", - "Jul", - "Aug", - "Sep", - "Oct", - "Nov", - "Dec", - ]; - return `${days[date.getUTCDay()]}, ${d} ${ - months[date.getUTCMonth()] - } ${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 = - | "milliseconds" - | "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)] : [ - "milliseconds", - "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 "milliseconds": - differences.milliseconds = 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 calendarDifferences = Math.abs(yearsDiff * 12 + monthsDiff); - const compareResult = biggerDate > smallerDate ? 1 : -1; - biggerDate.setMonth( - biggerDate.getMonth() - compareResult * calendarDifferences, - ); - const isLastMonthNotFull = biggerDate > smallerDate - ? 1 - : -1 === -compareResult - ? 1 - : 0; - const months = compareResult * (calendarDifferences - isLastMonthNotFull); - return months === 0 ? 0 : months; -} diff --git a/std/datetime/test.ts b/std/datetime/test.ts deleted file mode 100644 index b2614bc00..000000000 --- a/std/datetime/test.ts +++ /dev/null @@ -1,393 +0,0 @@ -// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. -import { assert, assertEquals, assertThrows } from "../testing/asserts.ts"; -import * as datetime from "./mod.ts"; - -Deno.test({ - name: "[std/datetime] parse", - fn: () => { - assertEquals( - datetime.parse("01-03-2019 16:30", "MM-dd-yyyy HH:mm"), - new Date(2019, 0, 3, 16, 30), - ); - assertEquals( - datetime.parse("01.03.2019 16:30", "MM.dd.yyyy HH:mm"), - new Date(2019, 0, 3, 16, 30), - ); - assertEquals( - datetime.parse("01.03.2019 16:30", "MM.dd.yyyy HH:mm"), - new Date(2019, 0, 3, 16, 30), - ); - assertEquals( - datetime.parse("03-01-2019 16:31", "dd-MM-yyyy HH:mm"), - new Date(2019, 0, 3, 16, 31), - ); - assertEquals( - datetime.parse("2019-01-03 16:32", "yyyy-MM-dd HH:mm"), - new Date(2019, 0, 3, 16, 32), - ); - assertEquals( - datetime.parse("16:33 01-03-2019", "HH:mm MM-dd-yyyy"), - new Date(2019, 0, 3, 16, 33), - ); - assertEquals( - datetime.parse("01-03-2019 16:33:23.123", "MM-dd-yyyy HH:mm:ss.SSS"), - new Date(2019, 0, 3, 16, 33, 23, 123), - ); - assertEquals( - datetime.parse("01-03-2019 09:33 PM", "MM-dd-yyyy HH:mm a"), - new Date(2019, 0, 3, 21, 33), - ); - assertEquals( - datetime.parse("16:34 03-01-2019", "HH:mm dd-MM-yyyy"), - new Date(2019, 0, 3, 16, 34), - ); - assertEquals( - datetime.parse("16:35 2019-01-03", "HH:mm yyyy-MM-dd"), - new Date(2019, 0, 3, 16, 35), - ); - assertEquals( - datetime.parse("01-03-2019", "MM-dd-yyyy"), - new Date(2019, 0, 3), - ); - assertEquals( - datetime.parse("03-01-2019", "dd-MM-yyyy"), - new Date(2019, 0, 3), - ); - assertEquals( - datetime.parse("31-10-2019", "dd-MM-yyyy"), - new Date(2019, 9, 31), - ); - assertEquals( - datetime.parse("2019-01-03", "yyyy-MM-dd"), - new Date(2019, 0, 3), - ); - }, -}); - -Deno.test({ - name: "[std/datetime] invalidParseDateTimeFormatThrows", - fn: () => { - assertThrows((): void => { - // deno-lint-ignore no-explicit-any - (datetime as any).parse("2019-01-01 00:00", "x-y-z"); - }, Error); - assertThrows((): void => { - // deno-lint-ignore no-explicit-any - (datetime as any).parse("2019-01-01", "x-y-z"); - }, Error); - }, -}); - -Deno.test({ - name: "[std/datetime] format", - fn: () => { - // 00 hours - assertEquals( - "01:00:00", - datetime.format(new Date("2019-01-01T01:00:00"), "HH:mm:ss"), - ); - assertEquals( - "13:00:00", - datetime.format(new Date("2019-01-01T13:00:00"), "HH:mm:ss"), - ); - - // 12 hours - assertEquals( - "01:00:00", - datetime.format(new Date("2019-01-01T01:00:00"), "hh:mm:ss"), - ); - assertEquals( - "01:00:00", - datetime.format(new Date("2019-01-01T13:00:00"), "hh:mm:ss"), - ); - - // milliseconds - assertEquals( - "13:00:00.000", - datetime.format(new Date("2019-01-01T13:00:00"), "HH:mm:ss.SSS"), - ); - assertEquals( - "13:00:00.000", - datetime.format(new Date("2019-01-01T13:00:00.000"), "HH:mm:ss.SSS"), - ); - assertEquals( - "13:00:00.123", - datetime.format(new Date("2019-01-01T13:00:00.123"), "HH:mm:ss.SSS"), - ); - - // day period - assertEquals( - "01:00:00 AM", - datetime.format(new Date("2019-01-01T01:00:00"), "HH:mm:ss a"), - ); - assertEquals( - "01:00:00 AM", - datetime.format(new Date("2019-01-01T01:00:00"), "hh:mm:ss a"), - ); - assertEquals( - "01:00:00 PM", - datetime.format(new Date("2019-01-01T13:00:00"), "hh:mm:ss a"), - ); - assertEquals( - "21:00:00 PM", - datetime.format(new Date("2019-01-01T21:00:00"), "HH:mm:ss a"), - ); - assertEquals( - "09:00:00 PM", - datetime.format(new Date("2019-01-01T21:00:00"), "hh:mm:ss a"), - ); - - // quoted literal - assertEquals( - datetime.format(new Date(2019, 0, 20), "'today:' yyyy-MM-dd"), - "today: 2019-01-20", - ); - }, -}); - -Deno.test({ - name: "[std/datetime] dayOfYear", - fn: () => { - // from https://golang.org/src/time/time_test.go - // Test YearDay in several different scenarios - // and corner cases - // Non-leap-year tests - assertEquals(datetime.dayOfYear(new Date("2007-01-01T00:00:00.000Z")), 1); - assertEquals(datetime.dayOfYear(new Date("2007-01-15T00:00:00.000Z")), 15); - assertEquals(datetime.dayOfYear(new Date("2007-02-01T00:00:00.000Z")), 32); - assertEquals(datetime.dayOfYear(new Date("2007-02-15T00:00:00.000Z")), 46); - assertEquals(datetime.dayOfYear(new Date("2007-03-01T00:00:00.000Z")), 60); - assertEquals(datetime.dayOfYear(new Date("2007-03-15T00:00:00.000Z")), 74); - assertEquals(datetime.dayOfYear(new Date("2007-04-01T00:00:00.000Z")), 91); - assertEquals(datetime.dayOfYear(new Date("2007-12-31T00:00:00.000Z")), 365); - - // Leap-year tests - assertEquals(datetime.dayOfYear(new Date("2008-01-01T00:00:00.000Z")), 1); - assertEquals(datetime.dayOfYear(new Date("2008-01-15T00:00:00.000Z")), 15); - assertEquals(datetime.dayOfYear(new Date("2008-02-01T00:00:00.000Z")), 32); - assertEquals(datetime.dayOfYear(new Date("2008-02-15T00:00:00.000Z")), 46); - assertEquals(datetime.dayOfYear(new Date("2008-03-01T00:00:00.000Z")), 61); - assertEquals(datetime.dayOfYear(new Date("2008-03-15T00:00:00.000Z")), 75); - assertEquals(datetime.dayOfYear(new Date("2008-04-01T00:00:00.000Z")), 92); - assertEquals(datetime.dayOfYear(new Date("2008-12-31T00:00:00.000Z")), 366); - - // Looks like leap-year (but isn't) tests - assertEquals(datetime.dayOfYear(new Date("1900-01-01T00:00:00.000Z")), 1); - assertEquals(datetime.dayOfYear(new Date("1900-01-15T00:00:00.000Z")), 15); - assertEquals(datetime.dayOfYear(new Date("1900-02-01T00:00:00.000Z")), 32); - assertEquals(datetime.dayOfYear(new Date("1900-02-15T00:00:00.000Z")), 46); - assertEquals(datetime.dayOfYear(new Date("1900-03-01T00:00:00.000Z")), 60); - assertEquals(datetime.dayOfYear(new Date("1900-03-15T00:00:00.000Z")), 74); - assertEquals(datetime.dayOfYear(new Date("1900-04-01T00:00:00.000Z")), 91); - assertEquals(datetime.dayOfYear(new Date("1900-12-31T00:00:00.000Z")), 365); - - // Year one tests (non-leap) - assertEquals(datetime.dayOfYear(new Date("0001-01-01T00:00:00.000Z")), 1); - assertEquals(datetime.dayOfYear(new Date("0001-01-15T00:00:00.000Z")), 15); - assertEquals(datetime.dayOfYear(new Date("0001-02-01T00:00:00.000Z")), 32); - assertEquals(datetime.dayOfYear(new Date("0001-02-15T00:00:00.000Z")), 46); - assertEquals(datetime.dayOfYear(new Date("0001-03-01T00:00:00.000Z")), 60); - assertEquals(datetime.dayOfYear(new Date("0001-03-15T00:00:00.000Z")), 74); - assertEquals(datetime.dayOfYear(new Date("0001-04-01T00:00:00.000Z")), 91); - assertEquals(datetime.dayOfYear(new Date("0001-12-31T00:00:00.000Z")), 365); - - // Year minus one tests (non-leap) - assertEquals( - datetime.dayOfYear(new Date("-000001-01-01T00:00:00.000Z")), - 1, - ); - assertEquals( - datetime.dayOfYear(new Date("-000001-01-15T00:00:00.000Z")), - 15, - ); - assertEquals( - datetime.dayOfYear(new Date("-000001-02-01T00:00:00.000Z")), - 32, - ); - assertEquals( - datetime.dayOfYear(new Date("-000001-02-15T00:00:00.000Z")), - 46, - ); - assertEquals( - datetime.dayOfYear(new Date("-000001-03-01T00:00:00.000Z")), - 60, - ); - assertEquals( - datetime.dayOfYear(new Date("-000001-03-15T00:00:00.000Z")), - 74, - ); - assertEquals( - datetime.dayOfYear(new Date("-000001-04-01T00:00:00.000Z")), - 91, - ); - assertEquals( - datetime.dayOfYear(new Date("-000001-12-31T00:00:00.000Z")), - 365, - ); - - // 400 BC tests (leap-year) - assertEquals( - datetime.dayOfYear(new Date("-000400-01-01T00:00:00.000Z")), - 1, - ); - assertEquals( - datetime.dayOfYear(new Date("-000400-01-15T00:00:00.000Z")), - 15, - ); - assertEquals( - datetime.dayOfYear(new Date("-000400-02-01T00:00:00.000Z")), - 32, - ); - assertEquals( - datetime.dayOfYear(new Date("-000400-02-15T00:00:00.000Z")), - 46, - ); - assertEquals( - datetime.dayOfYear(new Date("-000400-03-01T00:00:00.000Z")), - 61, - ); - assertEquals( - datetime.dayOfYear(new Date("-000400-03-15T00:00:00.000Z")), - 75, - ); - assertEquals( - datetime.dayOfYear(new Date("-000400-04-01T00:00:00.000Z")), - 92, - ); - assertEquals( - datetime.dayOfYear(new Date("-000400-12-31T00:00:00.000Z")), - 366, - ); - - // Special Cases - - // Gregorian calendar change (no effect) - assertEquals(datetime.dayOfYear(new Date("1582-10-04T03:24:00.000Z")), 277); - assertEquals(datetime.dayOfYear(new Date("1582-10-15T03:24:00.000Z")), 288); - }, -}); - -Deno.test({ - name: "[std/datetime] weekOfYear", - fn: () => { - assertEquals(datetime.weekOfYear(new Date("2020-01-05T03:00:00.000Z")), 1); - assertEquals(datetime.weekOfYear(new Date("2020-06-28T03:00:00.000Z")), 26); - - // iso weeks year starting sunday - assertEquals(datetime.weekOfYear(new Date(2012, 0, 1)), 52); - assertEquals(datetime.weekOfYear(new Date(2012, 0, 2)), 1); - assertEquals(datetime.weekOfYear(new Date(2012, 0, 8)), 1); - assertEquals(datetime.weekOfYear(new Date(2012, 0, 9)), 2); - assertEquals(datetime.weekOfYear(new Date(2012, 0, 15)), 2); - - // iso weeks year starting monday - assertEquals(datetime.weekOfYear(new Date(2007, 0, 1)), 1); - assertEquals(datetime.weekOfYear(new Date(2007, 0, 7)), 1); - assertEquals(datetime.weekOfYear(new Date(2007, 0, 8)), 2); - assertEquals(datetime.weekOfYear(new Date(2007, 0, 14)), 2); - assertEquals(datetime.weekOfYear(new Date(2007, 0, 15)), 3); - - // iso weeks year starting tuesday - assertEquals(datetime.weekOfYear(new Date(2007, 11, 31)), 1); - assertEquals(datetime.weekOfYear(new Date(2008, 0, 1)), 1); - assertEquals(datetime.weekOfYear(new Date(2008, 0, 6)), 1); - assertEquals(datetime.weekOfYear(new Date(2008, 0, 7)), 2); - assertEquals(datetime.weekOfYear(new Date(2008, 0, 13)), 2); - assertEquals(datetime.weekOfYear(new Date(2008, 0, 14)), 3); - - // iso weeks year starting wednesday - assertEquals(datetime.weekOfYear(new Date(2002, 11, 30)), 1); - assertEquals(datetime.weekOfYear(new Date(2003, 0, 1)), 1); - assertEquals(datetime.weekOfYear(new Date(2003, 0, 5)), 1); - assertEquals(datetime.weekOfYear(new Date(2003, 0, 6)), 2); - assertEquals(datetime.weekOfYear(new Date(2003, 0, 12)), 2); - assertEquals(datetime.weekOfYear(new Date(2003, 0, 13)), 3); - - // iso weeks year starting thursday - assertEquals(datetime.weekOfYear(new Date(2008, 11, 29)), 1); - assertEquals(datetime.weekOfYear(new Date(2009, 0, 1)), 1); - assertEquals(datetime.weekOfYear(new Date(2009, 0, 4)), 1); - assertEquals(datetime.weekOfYear(new Date(2009, 0, 5)), 2); - assertEquals(datetime.weekOfYear(new Date(2009, 0, 11)), 2); - assertEquals(datetime.weekOfYear(new Date(2009, 0, 13)), 3); - - // iso weeks year starting friday - assertEquals(datetime.weekOfYear(new Date(2009, 11, 28)), 53); - assertEquals(datetime.weekOfYear(new Date(2010, 0, 1)), 53); - assertEquals(datetime.weekOfYear(new Date(2010, 0, 3)), 53); - assertEquals(datetime.weekOfYear(new Date(2010, 0, 4)), 1); - assertEquals(datetime.weekOfYear(new Date(2010, 0, 10)), 1); - assertEquals(datetime.weekOfYear(new Date(2010, 0, 11)), 2); - - // iso weeks year starting saturday - assertEquals(datetime.weekOfYear(new Date(2010, 11, 27)), 52); - assertEquals(datetime.weekOfYear(new Date(2011, 0, 1)), 52); - assertEquals(datetime.weekOfYear(new Date(2011, 0, 2)), 52); - assertEquals(datetime.weekOfYear(new Date(2011, 0, 3)), 1); - assertEquals(datetime.weekOfYear(new Date(2011, 0, 9)), 1); - assertEquals(datetime.weekOfYear(new Date(2011, 0, 10)), 2); - }, -}); - -Deno.test({ - name: "[std/datetime] to IMF", - fn(): void { - const actual = datetime.toIMF(new Date(Date.UTC(1994, 3, 5, 15, 32))); - const expected = "Tue, 05 Apr 1994 15:32:00 GMT"; - assertEquals(actual, expected); - }, -}); - -Deno.test({ - name: "[std/datetime] to IMF 0", - fn(): void { - const actual = datetime.toIMF(new Date(0)); - const expected = "Thu, 01 Jan 1970 00:00:00 GMT"; - 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 denoReleaseV1 = new Date("2020/5/13"); - let difference = datetime.difference(denoReleaseV1, 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: ["milliseconds", "minutes", "seconds", "hours"], - }); - assertEquals(difference.milliseconds, 3661000); - assertEquals(difference.seconds, 3661); - assertEquals(difference.minutes, 61); - assertEquals(difference.hours, 1); - }, -}); - -Deno.test({ - name: "[std/datetime] constants", - fn(): void { - assertEquals(datetime.SECOND, 1e3); - assertEquals(datetime.MINUTE, datetime.SECOND * 60); - assertEquals(datetime.HOUR, datetime.MINUTE * 60); - assertEquals(datetime.DAY, datetime.HOUR * 24); - assertEquals(datetime.WEEK, datetime.DAY * 7); - }, -}); diff --git a/std/datetime/tokenizer.ts b/std/datetime/tokenizer.ts deleted file mode 100644 index 9a9d0daa8..000000000 --- a/std/datetime/tokenizer.ts +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. - -export type Token = { - type: string; - value: string | number; - index: number; - [key: string]: unknown; -}; - -export interface ReceiverResult { - [name: string]: string | number | unknown; -} -export type CallbackResult = { - type: string; - value: string | number; - [key: string]: unknown; -}; -type CallbackFunction = (value: unknown) => CallbackResult; - -export type TestResult = { value: unknown; length: number } | undefined; -export type TestFunction = ( - string: string, -) => TestResult | undefined; - -export interface Rule { - test: TestFunction; - fn: CallbackFunction; -} - -export class Tokenizer { - rules: Rule[]; - - constructor(rules: Rule[] = []) { - this.rules = rules; - } - - addRule(test: TestFunction, fn: CallbackFunction): Tokenizer { - this.rules.push({ test, fn }); - return this; - } - - tokenize( - string: string, - receiver = (token: Token): ReceiverResult => token, - ): ReceiverResult[] { - function* generator(rules: Rule[]): IterableIterator<ReceiverResult> { - let index = 0; - for (const rule of rules) { - const result = rule.test(string); - if (result) { - const { value, length } = result; - index += length; - string = string.slice(length); - const token = { ...rule.fn(value), index }; - yield receiver(token); - yield* generator(rules); - } - } - } - const tokenGenerator = generator(this.rules); - - const tokens: ReceiverResult[] = []; - - for (const token of tokenGenerator) { - tokens.push(token); - } - - if (string.length) { - throw new Error( - `parser error: string not fully parsed! ${string.slice(0, 25)}`, - ); - } - - return tokens; - } -} |