diff --git a/.github/workflows/bump-alpha.yml b/.github/workflows/bump-alpha.yml new file mode 100644 index 0000000..260cea7 --- /dev/null +++ b/.github/workflows/bump-alpha.yml @@ -0,0 +1,36 @@ +name: "Bump Alpha" + +on: + push: + branches: + - "master-4" +jobs: + bump-version: + name: "Bump alpha version" + timeout-minutes: 60 + runs-on: ubuntu-latest + if: "!contains(github.event.head_commit.message, 'ci: version bump to ')" + + steps: + - name: "Checkout source code" + uses: "actions/checkout@v3" + with: + token: ${{ secrets.ADMIN_TOKEN }} + - name: "Setup Node" + uses: "actions/setup-node@v3" + with: + node-version: current + cache: "npm" + - name: Update NPM + run: npm install -g npm + - name: "Version Bump" + id: version-bump + uses: "phips28/gh-action-bump-version@master" + with: + default: prerelease + version-type: "prerelease" + preid: alpha + rc-wording: "" + tag-prefix: "release-" + env: + GITHUB_TOKEN: ${{ secrets.ADMIN_TOKEN }} diff --git a/.vscode/settings.json b/.vscode/settings.json index 0fa0262..468fd82 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -50,6 +50,7 @@ "isly", "isoly", "langly", + "måndag", "paramly", "persistly", "selectivly", @@ -58,10 +59,10 @@ "smoothly", "tidily", "transactly", + "transcoders", "typedly", "uply", - "måndag", - "transcoders" + "Utily" ], "typescript.tsdk": "node_modules/typescript/lib" } diff --git a/Address/GB.ts b/Address/GB.ts index 0cf7a7f..bad4091 100644 --- a/Address/GB.ts +++ b/Address/GB.ts @@ -9,16 +9,16 @@ export type GB = { } export namespace GB { - export const type = isly.object( - { - countryCode: isly.string<"GB">("GB"), - street: isly.string(), - building: isly.string(), - zipCode: isly.string(), - city: isly.string(), - }, - "isoly.Address.GB" - ) - export const is = type.is - export const flaw = type.flaw + export const { type, is, flawed } = isly + .object( + { + countryCode: isly.string("value", "GB"), + street: isly.string(), + building: isly.string(), + zipCode: isly.string(), + city: isly.string(), + }, + "isoly.Address.GB" + ) + .bind() } diff --git a/Address/Generic.ts b/Address/Generic.ts index 4575a2c..c002598 100644 --- a/Address/Generic.ts +++ b/Address/Generic.ts @@ -11,17 +11,17 @@ export type Generic = { } export namespace Generic { - export const type = isly.object( - { - countryCode: CountryCode.Alpha2.type, - street: isly.string(), - zipCode: isly.string(), - city: isly.string(), - county: isly.string().optional(), - state: isly.string().optional(), - }, - "isoly.Address.Generic" - ) - export const is = type.is - export const flaw = type.flaw + export const { type, is, flawed } = isly + .object( + { + countryCode: CountryCode.Alpha2.type, + street: isly.string(), + zipCode: isly.string(), + city: isly.string(), + county: isly.string().optional(), + state: isly.string().optional(), + }, + "isoly.Address.Generic" + ) + .bind() } diff --git a/Address/SE.ts b/Address/SE.ts index 5ae61d8..7393112 100644 --- a/Address/SE.ts +++ b/Address/SE.ts @@ -8,15 +8,15 @@ export type SE = { } export namespace SE { - export const type = isly.object( - { - countryCode: isly.string<"SE">("SE"), - street: isly.string(), - zipCode: isly.string(), - city: isly.string(), - }, - "isoly.Address.SE" - ) - export const is = type.is - export const flaw = type.flaw + export const { type, is, flawed } = isly + .object( + { + countryCode: isly.string<"SE">("value", "SE"), + street: isly.string(), + zipCode: isly.string(), + city: isly.string(), + }, + "isoly.Address.SE" + ) + .bind() } diff --git a/Address/index.ts b/Address/index.ts index 37d9996..7c334b7 100644 --- a/Address/index.ts +++ b/Address/index.ts @@ -9,5 +9,5 @@ export namespace Address { export import Generic = AddressGeneric export import GB = AddressGB export import SE = AddressSE - export const type = isly.named("isoly.Address", isly.union
(Generic.type, GB.type, SE.type)) + export const { type, is, flawed } = isly.union
(Generic.type, GB.type, SE.type).rename("isoly.Address").bind() } diff --git a/CallingCode/index.ts b/CallingCode/index.ts index b74d513..af01571 100644 --- a/CallingCode/index.ts +++ b/CallingCode/index.ts @@ -248,9 +248,10 @@ export namespace CallingCode { "+260", "+263", ] as const - export const type = isly.named("isoly.CallingCode", isly.string(values)) - export const is = type.is - export const flaw = type.flaw + export const { type, is, flawed } = isly + .string("value", ...values) + .rename("isoly.CallingCode") + .bind() export function from(country: CountryCode.Alpha2): CallingCode | undefined { return alpha2toCallingCode[country] } diff --git a/CountryCode/Alpha2.ts b/CountryCode/Alpha2.ts index 3d8a14a..de4555b 100644 --- a/CountryCode/Alpha2.ts +++ b/CountryCode/Alpha2.ts @@ -257,9 +257,9 @@ export namespace Alpha2 { "ZM", "ZW", ] as const - export const type = isly.named("isoly.CountryCode.Alpha2", isly.string(values)) - export const is = type.is - export const flaw = type.flaw + export const { type, is, flawed } = isly.string("value", ...values) + .rename("isoly.CountryCode.Alpha2") + .bind() export function from(country: Numeric | Alpha3): Alpha2 export function from(country: Numeric | number | Alpha3 | string): Alpha2 | undefined export function from(country: Numeric | number | Alpha3 | string): Alpha2 | undefined { diff --git a/CountryCode/Alpha3.ts b/CountryCode/Alpha3.ts index c6e3b7e..a30986f 100644 --- a/CountryCode/Alpha3.ts +++ b/CountryCode/Alpha3.ts @@ -257,9 +257,10 @@ export namespace Alpha3 { "ZMB", "ZWE", ] as const - export const type = isly.named("isoly.CountryCode.Alpha3", isly.string(values)) - export const is = type.is - export const flaw = type.flaw + export const { type, is, flawed } = isly + .string("value", ...values) + .rename("isoly.CountryCode.Alpha3") + .bind() export function from(country: Alpha2 | Numeric): Alpha3 { return typeof country == "number" ? from(Alpha2.from(country)) : alpha2ToAlpha3[country] } diff --git a/CountryCode/Numeric.ts b/CountryCode/Numeric.ts index 4de29a5..6601c10 100644 --- a/CountryCode/Numeric.ts +++ b/CountryCode/Numeric.ts @@ -18,9 +18,10 @@ export namespace Numeric { 740, 744, 748, 752, 756, 760, 762, 764, 768, 772, 776, 780, 784, 788, 792, 795, 796, 798, 800, 804, 807, 818, 826, 831, 832, 833, 834, 840, 850, 854, 858, 860, 862, 876, 882, 887, 894, 926, ] as const - export const type = isly.named("isoly.CountryCode.Numeric", isly.number(values)) - export const is = type.is - export const flaw = type.flaw + export const { type, is, flawed } = isly + .number("value", ...values) + .rename("isoly.CountryCode.Numeric") + .bind() export function from(country: Alpha2 | Alpha3): Numeric { return Alpha2.is(country) ? alpha2ToNumeric[country] : from(Alpha2.from(country)) } diff --git a/CountryCode/index.ts b/CountryCode/index.ts index 4fa50bb..f41f495 100644 --- a/CountryCode/index.ts +++ b/CountryCode/index.ts @@ -11,7 +11,8 @@ export namespace CountryCode { export import Alpha3 = CountryCodeAlpha3 export import Name = CountryCodeName export import Numeric = CountryCodeNumeric - export const type = isly.union(Alpha2.type, Alpha3.type, Numeric.type) - export const is = type.is - export const flaw = type.flaw + export const { type, is, flawed } = isly + .union(Alpha2.type, Alpha3.type, Numeric.type) + .rename("isoly.CountryCode") + .bind() } diff --git a/Currency/Code.ts b/Currency/Code.ts index c7d0070..6caac48 100644 --- a/Currency/Code.ts +++ b/Currency/Code.ts @@ -185,9 +185,10 @@ export namespace Code { "997", "999", ] as const - export const type = isly.named("isoly.CurrencyCode", isly.string(values)) - export const is = type.is - export const flaw = type.flaw + export const { type, is, flawed } = isly + .string("value", ...values) + .rename("isoly.CurrencyCode") + .bind() export function from(currency: Currency): Code { return { ALL: "008", diff --git a/Currency/index.ts b/Currency/index.ts index 329b848..3dd9c94 100644 --- a/Currency/index.ts +++ b/Currency/index.ts @@ -185,9 +185,10 @@ export namespace Currency { "ZMW", "ZWL", ] as const - export const type = isly.named("isoly.Currency", isly.string(values)) - export const is = type.is - export const flaw = type.flaw + export const { type, is, flawed } = isly + .string("value", ...values) + .rename("isoly.Currency") + .bind() export type Code = CurrencyCode export const Code = CurrencyCode export function round(value: number, currency: Currency): number { diff --git a/Date/Day/Numeric.ts b/Date/Day/Numeric.ts index 1ab6b8d..cac594c 100644 --- a/Date/Day/Numeric.ts +++ b/Date/Day/Numeric.ts @@ -7,9 +7,10 @@ export namespace Numeric { export const values = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, ] as const - export const type = isly.named("isoly.Date.Day.Numeric", isly.number(values)) - export const is = type.is - export const flaw = type.flaw + export const { type, is, flawed } = isly + .number("value", ...values) + .rename("isoly.Date.Day.Numeric") + .bind() export function parse(value: Day): Numeric export function parse(value: string): Numeric | undefined diff --git a/Date/Day/index.ts b/Date/Day/index.ts index 8a8135c..6ab62a2 100644 --- a/Date/Day/index.ts +++ b/Date/Day/index.ts @@ -38,9 +38,10 @@ export namespace Day { "30", "31", ] as const - export const type = isly.named("isoly.Date.Day", isly.string(values)) - export const is = type.is - export const flaw = type.flaw + export const { type, is, flawed } = isly + .string("value", ...values) + .rename("isoly.Date.Day") + .bind() export function create(value: Day.Numeric): Day export function create(value: number): Day | undefined export function create(value: number): string | undefined { diff --git a/Date/Month/Numeric.ts b/Date/Month/Numeric.ts index f5b826f..a09ea41 100644 --- a/Date/Month/Numeric.ts +++ b/Date/Month/Numeric.ts @@ -5,9 +5,10 @@ export type Numeric = typeof Numeric.values[number] export namespace Numeric { export const values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] as const - export const type = isly.named("isoly.Date.Month.Numeric", isly.number(values)) - export const is = type.is - export const flaw = type.flaw + export const { type, is, flawed } = isly + .number("value", ...values) + .rename("isoly.Date.Month.Numeric") + .bind() export function parse(value: Month): Numeric export function parse(value: string): Numeric | undefined diff --git a/Date/Month/index.ts b/Date/Month/index.ts index bdeb31a..80b9620 100644 --- a/Date/Month/index.ts +++ b/Date/Month/index.ts @@ -7,9 +7,10 @@ export type Month = typeof Month.values[number] export namespace Month { export import Numeric = MonthNumeric export const values = ["01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"] as const - export const type = isly.named("isoly.Date.Month", isly.string(values)) - export const is = type.is - export const flaw = type.flaw + export const { type, is, flawed } = isly + .string("value", ...values) + .rename("isoly.Date.Month") + .bind() export function create(value: Numeric): Month export function create(value: number): Month | undefined export function create(value: number): string | undefined { diff --git a/Date/Numeric.spec.ts b/Date/Numeric.spec.ts index fa80cc3..7f29975 100644 --- a/Date/Numeric.spec.ts +++ b/Date/Numeric.spec.ts @@ -9,10 +9,10 @@ describe("isoly.Date.Numeric", () => { ["--", {}], ["-25-50", { days: 49, months: 24 }], [undefined, {}], - ] as const)("parse(%s) == %s", (value, expected) => expect(isoly.Date.Numeric.parse(value)).toEqual(expected)) + ] as const)("parse(%s) == %s", (value, expected) => expect(isoly.Date.Numeric.parse(value).values).toEqual(expected)) it.each([ [{ years: 10, months: 1, days: 0 }, "1910-02-01"], [{}, "1900-01-01"], [{ days: 36 }, "1900-02-06"], - ] as const)("format(%s) == %s", (value, expected) => expect(isoly.Date.Numeric.format(value)).toBe(expected)) + ] as const)("format(%s) == %s", (value, expected) => expect(isoly.Date.Numeric.create(value)).toBe(expected)) }) diff --git a/Date/Numeric.ts b/Date/Numeric.ts index 5a42f82..7b3db30 100644 --- a/Date/Numeric.ts +++ b/Date/Numeric.ts @@ -1,34 +1,205 @@ import { isly } from "isly" +import { DateSpan } from "../DateSpan" +import type { DayOfWeek } from "../DayOfWeek" +import { Locale } from "../Locale" import type { Date } from "./index" -export interface Numeric { - years?: number - months?: number - days?: number -} - -export namespace Numeric { - export const type = isly.object( - { years: isly.number().optional(), months: isly.number().optional(), days: isly.number().optional() }, - "isoly.Date.Numeric" - ) - export const is = type.is - export const flaw = type.flaw - export function parse(value: Date | string | undefined): Numeric { +export class Numeric { + get values(): Numeric.Values { + return { + ...(this.years ? { years: this.years } : {}), + ...(this.months ? { months: this.months - 1 } : {}), + ...(this.days ? { days: this.days - 1 } : {}), + } + } + constructor( + readonly years: number | undefined, + readonly months: number | undefined, + readonly days: number | undefined + ) {} + add(change: Partial>): Numeric { + return new Numeric( + ...((["years", "months", "days"] as const).map(p => + this[p] == undefined && change[p] == undefined ? undefined : (this[p] ?? 0) + (change[p] ?? 0) + ) as [number | undefined, number | undefined, number | undefined]) + ) + } + toJSON(): Partial> { + return this.values + } + format(): Date { + return this.to("system").toISOString().substring(0, 10) + } + normalize(): Numeric { + return Numeric.create(this.to("system")) + } + to(type: "number"): number + to(type: "system"): globalThis.Date + to(type: "number" | "system"): number | globalThis.Date + to(type: "number" | "system"): number | globalThis.Date { + return type == "number" + ? globalThis.Date.UTC(this.years ?? 0, this.months ?? 0, (this.days ?? 0) + 1, 12) + : new globalThis.Date(this.to("number")) + } + localize(locale?: Locale, timeZone?: string): Date { + return ( + this.to("system") + .toLocaleString(locale ? locale : Intl.DateTimeFormat().resolvedOptions().locale, { + year: "numeric", + month: "2-digit", + day: "2-digit", + timeZone: timeZone ?? Intl.DateTimeFormat().resolvedOptions().timeZone, + }) + .substring(0, 10) + // See DateTime:localize for note. + .replaceAll(" ", " ") + ) + } + invert(): Numeric { + return new Numeric(9999 - (this.years ?? 0), 12 - (this.months ?? 0), 31 - (this.days ?? 0)) + } + next(days: number | DateSpan = 1): Numeric { + let result: Numeric + if (typeof days == "number") { + const r = this.to("system") + r.setDate(r.getDate() + days) + result = Numeric.create(r) + } else { + // eslint-disable-next-line @typescript-eslint/no-this-alias + result = this + if (days.years) + result = result.nextYear(days.years) + if (days.months) + result = result.nextMonth(days.months) + if (days.days) + result = result.next(days.days) + } + return result + } + previous(days: number | DateSpan = 1): Numeric { + let result: Numeric + if (typeof days == "number") { + const r = this.to("system") + r.setDate(r.getDate() - days) + result = Numeric.create(r) + } else { + // eslint-disable-next-line @typescript-eslint/no-this-alias + result = this + if (days.years) + result = result.previousYear(days.years) + if (days.months) + result = result.previousMonth(days.months) + if (days.days) + result = result.previous(days.days) + } + return result + } + nextMonth(months = 1): Numeric { + const result = this.to("system") + result.setMonth(result.getMonth() + months) + return Numeric.create(result) + } + previousMonth(months = 1): Numeric { + return this.nextMonth(-months) + } + nextYear(years = 1): Numeric { + const result = this.to("system") + result.setFullYear(result.getFullYear() + years) + return Numeric.create(result) + } + previousYear(years = 1): Numeric { + return this.nextYear(-years) + } + firstOfYear(): Numeric { + const result = this.to("system") + result.setMonth(0) + result.setDate(1) + return Numeric.create(result) + } + lastOfYear(): Numeric { + const result = this.to("system") + result.setFullYear(result.getFullYear() + 1) + result.setMonth(0) + result.setDate(0) + return Numeric.create(result) + } + firstOfMonth(): Numeric { + const result = this.to("system") + result.setDate(1) + return Numeric.create(result) + } + lastOfMonth(): Numeric { + const result = this.to("system") + result.setMonth(result.getMonth() + 1) + result.setDate(0) + return Numeric.create(result) + } + firstOfWeek(): Numeric { + const result = this.to("system") + const relativeDay = result.getDate() - (result.getDay() || 7) + 1 + result.setDate(relativeDay) + return Numeric.create(result) + } + lastOfWeek(): Numeric { + const result = this.to("system") + const relativeDay = result.getDate() - result.getDay() + 7 + result.setDate(relativeDay) + return Numeric.create(result) + } + getWeek(): number { + const parsed = this.to("system") + parsed.setHours(0, 0, 0, 0) + parsed.setDate(parsed.getDate() + 3 - ((parsed.getDay() + 6) % 7)) + const week1 = new globalThis.Date(parsed.getFullYear(), 0, 4) + return 1 + Math.round(((parsed.getTime() - week1.getTime()) / 86_400_000 - 3 + ((week1.getDay() + 6) % 7)) / 7) + } + getDayOfWeek(): DayOfWeek { + return (["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"] as const)[ + this.to("system").getUTCDay() + ] + } + nextBusinessDay( + businessDays = 1, + holidays: Set = new Set(), + weekend: DayOfWeek[] = ["saturday", "sunday"] + ): Numeric { + const isBusinessDay = this.isBusinessDay(holidays, weekend) + return businessDays <= 0 && isBusinessDay + ? this + : this.next().nextBusinessDay(businessDays + (isBusinessDay ? -1 : 0), holidays, weekend) + } + isBusinessDay(holidays: Set = new Set(), weekend: DayOfWeek[] = ["saturday", "sunday"]): boolean { + return !(weekend.includes(this.getDayOfWeek()) || holidays.has(this.format())) + } + static create(value: globalThis.Date | Numeric.Values): Numeric { + return value instanceof globalThis.Date + ? Numeric.create({ years: value.getFullYear(), months: value.getUTCMonth() - 1, days: value.getUTCDate() - 1 }) + : new Numeric(value.years, value.months, value.days) + } + static now(): Numeric { + return Numeric.create(new globalThis.Date()) + } + static parse(value: Date | string | undefined): Numeric { const [year, month, day] = value ?.split("-", 3) .map(v => Number.parseInt(v)) .map(v => (Number.isSafeInteger(v) ? v : undefined)) ?? [] - return { - ...(year ? { years: year } : {}), - ...(month ? { months: month - 1 } : {}), - ...(day ? { days: day - 1 } : {}), - } + return new Numeric(year, month != undefined ? month - 1 : undefined, day ? day - 1 : undefined) + } + static get epoch(): [Numeric, Numeric] { + return [new Numeric(0, 0, 0), new Numeric(9999, 12, 31)] } - export function format(value: Numeric): Date { - return new globalThis.Date(globalThis.Date.UTC(value.years ?? 0, value.months ?? 0, (value.days ?? 0) + 1, 12)) - .toISOString() - .substring(0, 10) +} +export namespace Numeric { + export const { type, is, flawed } = isly.instance(Numeric, "isoly.Date.Numeric").bind() + export type Values = Partial> + export namespace Values { + export const { type, is, flawed } = isly + .object>( + { years: isly.number().optional(), months: isly.number().optional(), days: isly.number().optional() }, + "isoly.Date.Numeric" + ) + .bind() } } diff --git a/Date/Year/Numeric.ts b/Date/Year/Numeric.ts index a4ddaa0..23338aa 100644 --- a/Date/Year/Numeric.ts +++ b/Date/Year/Numeric.ts @@ -3,12 +3,12 @@ import type { Year } from "./index" export type Numeric = number export namespace Numeric { - export const type = isly.named( - "isoly.Date.Year.Numeric", - isly.number(value => value >= 0 && value <= 9999 && Number.isInteger(value)) - ) - export const is = type.is - export const flaw = type.flaw + export const { type, is, flawed } = isly + .number("range", 0, 9999) + .restrict("integer") + .rename("isoly.Date.Year.Numeric") + .describe("Numeric year between 0 and 9999.") + .bind() export function parse(value: Year): Numeric export function parse(value: string): Numeric | undefined export function parse(value: string | Year): Numeric | undefined { diff --git a/Date/Year/index.ts b/Date/Year/index.ts index 78367de..1d43252 100644 --- a/Date/Year/index.ts +++ b/Date/Year/index.ts @@ -6,12 +6,11 @@ export type Year = `${Digit}${Digit}${Digit}${Digit}` export namespace Year { export import Numeric = YearNumeric - export const type = isly.named( - "isoly.Date.Year", - isly.string((value: string) => /^[0-9]{4}$/.test(value), "YYYY") - ) - export const is = type.is - export const flaw = type.flaw + export const { type, is, flawed } = isly + .string("value", /^[0-9]{4}$/) + .rename("isoly.Date.Year") + .describe("Year in a 4-digit form (YYYY).") + .bind() export function create(value: Numeric): Year export function create(value: number): Year | undefined export function create(value: number | Numeric): Year | undefined { diff --git a/Date/index.spec.ts b/Date/index.spec.ts index 80702df..0943ec7 100644 --- a/Date/index.spec.ts +++ b/Date/index.spec.ts @@ -174,7 +174,7 @@ describe("Date", () => { }) if (new Date(Date.UTC(2020, 11, 31, 23, 59, 59)).getTimezoneOffset() == -60) { it("zero-pads localized", () => { - expect(isoly.Date.localize(new Date("4 Jul 2020 10:20:30 GMT"), "sv-SE")).toEqual("2020-07-04") + expect(isoly.Date.localize(isoly.Date.create(new Date("4 Jul 2020 10:20:30 GMT")), "sv-SE")).toEqual("2020-07-04") }) const data = [ ["20 Jul 2019 10:30:40 GMT+2", "2019-07-20"], @@ -182,7 +182,7 @@ describe("Date", () => { ] for (const date of data) it("localize with locale " + date[0], () => - expect(isoly.Date.localize(new Date(date[0]), "sv-SE")).toEqual(date[1]) + expect(isoly.Date.localize(isoly.Date.create(new Date(date[0])), "sv-SE")).toEqual(date[1]) ) it("localize Date with locale", () => { @@ -208,48 +208,48 @@ describe("Date", () => { expect(isoly.Date.getWeek("2023-01-01")).toEqual(52) }) it("getDay", () => expect(isoly.DateTime.getDay("2020-12-31")).toEqual(31)) - it("getWeekDay", () => { - expect(isoly.Date.getWeekDay("2022-05-02")).toEqual(1) // Monday - expect(isoly.Date.getWeekDay("2022-05-03")).toEqual(2) // Tuesday - expect(isoly.Date.getWeekDay("2022-05-04")).toEqual(3) // Wednesday - expect(isoly.Date.getWeekDay("2022-05-05")).toEqual(4) // Thursday - expect(isoly.Date.getWeekDay("2022-05-06")).toEqual(5) // Friday - expect(isoly.Date.getWeekDay("2022-05-07")).toEqual(6) // Saturday - expect(isoly.Date.getWeekDay("2022-05-08")).toEqual(0) // Sunday - expect(isoly.Date.getWeekDay("2022-05-02", "en-US", { format: "long" })).toEqual("Monday") - expect(isoly.Date.getWeekDay("2022-05-02", "en-US")).toEqual("Monday") - expect(isoly.Date.getWeekDay("2022-05-02", "en-US", {})).toEqual("Monday") - expect(isoly.Date.getWeekDay("2022-05-02", "en-US", { format: "short" })).toEqual("Mon") - expect(isoly.Date.getWeekDay("2022-05-02", "en-US", { format: "narrow" })).toEqual("M") - expect(isoly.Date.getWeekDay("2022-05-02", "sv-SE", { format: "long" })).toEqual("måndag") - expect(isoly.Date.getWeekDay("2022-05-02", "sv-SE", { format: "short" })).toEqual("mån") - expect(isoly.Date.getWeekDay("2022-05-02", "sv-SE", { format: "narrow" })).toEqual("M") - expect(isoly.Date.getWeekDay("2022-05-03", "en-US", { format: "short" })).toEqual("Tue") - expect(isoly.Date.getWeekDay("2022-05-04", "en-US", { format: "short" })).toEqual("Wed") - expect(isoly.Date.getWeekDay("2022-05-05", "en-US", { format: "short" })).toEqual("Thu") - expect(isoly.Date.getWeekDay("2022-05-06", "en-US", { format: "short" })).toEqual("Fri") - expect(isoly.Date.getWeekDay("2022-05-07", "en-US", { format: "short" })).toEqual("Sat") - expect(isoly.Date.getWeekDay("2022-05-08", "en-US", { format: "short" })).toEqual("Sun") - }) - it("nextWeekday", () => { - expect(isoly.Date.nextWeekday("2022-05-04")).toEqual("2022-05-05") // Wednesday -> Thursday - expect(isoly.Date.nextWeekday("2022-05-04", 2)).toEqual("2022-05-06") // Wednesday -> Friday - expect(isoly.Date.nextWeekday("2022-05-04", 3)).toEqual("2022-05-09") // Wednesday -> Monday - expect(isoly.Date.nextWeekday("2022-05-04", 4)).toEqual("2022-05-09") // Wednesday -> Monday - expect(isoly.Date.nextWeekday("2022-05-04", 5)).toEqual("2022-05-09") // Wednesday -> Monday - expect(isoly.Date.nextWeekday("2022-05-04", 6)).toEqual("2022-05-10") // Wednesday -> Tuesday - expect(isoly.Date.nextWeekday("2023-11-30", 1, ["2023-12-01"])).toEqual("2023-12-04") // Thursday -> Monday - expect(isoly.Date.nextWeekday("2023-11-30", 1, ["2023-12-01", "2023-12-04"])).toEqual("2023-12-05") // Thursday -> Tuesday - }) + // it("getWeekDay", () => { + // expect(isoly.Date.getWeekDay("2022-05-02")).toEqual(1) // Monday + // expect(isoly.Date.getWeekDay("2022-05-03")).toEqual(2) // Tuesday + // expect(isoly.Date.getWeekDay("2022-05-04")).toEqual(3) // Wednesday + // expect(isoly.Date.getWeekDay("2022-05-05")).toEqual(4) // Thursday + // expect(isoly.Date.getWeekDay("2022-05-06")).toEqual(5) // Friday + // expect(isoly.Date.getWeekDay("2022-05-07")).toEqual(6) // Saturday + // expect(isoly.Date.getWeekDay("2022-05-08")).toEqual(0) // Sunday + // expect(isoly.Date.getWeekDay("2022-05-02", "en-US", { format: "long" })).toEqual("Monday") + // expect(isoly.Date.getWeekDay("2022-05-02", "en-US")).toEqual("Monday") + // expect(isoly.Date.getWeekDay("2022-05-02", "en-US", {})).toEqual("Monday") + // expect(isoly.Date.getWeekDay("2022-05-02", "en-US", { format: "short" })).toEqual("Mon") + // expect(isoly.Date.getWeekDay("2022-05-02", "en-US", { format: "narrow" })).toEqual("M") + // expect(isoly.Date.getWeekDay("2022-05-02", "sv-SE", { format: "long" })).toEqual("måndag") + // expect(isoly.Date.getWeekDay("2022-05-02", "sv-SE", { format: "short" })).toEqual("mån") + // expect(isoly.Date.getWeekDay("2022-05-02", "sv-SE", { format: "narrow" })).toEqual("M") + // expect(isoly.Date.getWeekDay("2022-05-03", "en-US", { format: "short" })).toEqual("Tue") + // expect(isoly.Date.getWeekDay("2022-05-04", "en-US", { format: "short" })).toEqual("Wed") + // expect(isoly.Date.getWeekDay("2022-05-05", "en-US", { format: "short" })).toEqual("Thu") + // expect(isoly.Date.getWeekDay("2022-05-06", "en-US", { format: "short" })).toEqual("Fri") + // expect(isoly.Date.getWeekDay("2022-05-07", "en-US", { format: "short" })).toEqual("Sat") + // expect(isoly.Date.getWeekDay("2022-05-08", "en-US", { format: "short" })).toEqual("Sun") + // }) + // it("nextWeekday", () => { + // expect(isoly.Date.nextWeekday("2022-05-04")).toEqual("2022-05-05") // Wednesday -> Thursday + // expect(isoly.Date.nextWeekday("2022-05-04", 2)).toEqual("2022-05-06") // Wednesday -> Friday + // expect(isoly.Date.nextWeekday("2022-05-04", 3)).toEqual("2022-05-09") // Wednesday -> Monday + // expect(isoly.Date.nextWeekday("2022-05-04", 4)).toEqual("2022-05-09") // Wednesday -> Monday + // expect(isoly.Date.nextWeekday("2022-05-04", 5)).toEqual("2022-05-09") // Wednesday -> Monday + // expect(isoly.Date.nextWeekday("2022-05-04", 6)).toEqual("2022-05-10") // Wednesday -> Tuesday + // expect(isoly.Date.nextWeekday("2023-11-30", 1, ["2023-12-01"])).toEqual("2023-12-04") // Thursday -> Monday + // expect(isoly.Date.nextWeekday("2023-11-30", 1, ["2023-12-01", "2023-12-04"])).toEqual("2023-12-05") // Thursday -> Tuesday + // }) it("invalid date", () => expect(isoly.Date.is("2020-13-31")).toEqual(false)) it("valid date", () => expect(isoly.Date.is("2020-02-29")).toEqual(true)) it("invalid date", () => expect(isoly.Date.is("2022-02-29")).toEqual(false)) - it("span", () => - expect(isoly.Date.span("2022-02-28", "2022-03-10")).toEqual({ - days: 18, - months: -1, - years: 0, - })) + // it("span", () => + // expect(isoly.Date.span("2022-02-28", "2022-03-10")).toEqual({ + // days: 18, + // months: -1, + // years: 0, + // })) it("nextBusinessDay", () => { expect(isoly.Date.nextBusinessDay("2022-05-04", 0)).toEqual("2022-05-04") // Wednesday -> Wednesday expect(isoly.Date.nextBusinessDay("2022-05-04", 1)).toEqual("2022-05-05") // Wednesday -> Thursday @@ -262,11 +262,11 @@ describe("Date", () => { expect(isoly.Date.nextBusinessDay("2022-05-07", 1)).toEqual("2022-05-09") // Saturday -> Monday expect(isoly.Date.nextBusinessDay("2022-05-07", 2)).toEqual("2022-05-10") // Saturday -> Tuesday expect(isoly.Date.nextBusinessDay("2022-05-07", 3)).toEqual("2022-05-11") // Saturday -> Wednesday - expect(isoly.Date.nextBusinessDay("2022-05-04", 1, ["2022-05-05", "2022-05-06"])).toEqual("2022-05-09") // Saturday -> Monday - expect(isoly.Date.nextBusinessDay("2022-05-04", 2, ["2022-05-05", "2022-05-06"])).toEqual("2022-05-10") // Saturday -> Tuesday - expect(isoly.Date.nextBusinessDay("2022-05-04", 3, ["2022-05-05", "2022-05-06"])).toEqual("2022-05-11") // Saturday -> Wednesday + expect(isoly.Date.nextBusinessDay("2022-05-04", 1, new Set(["2022-05-05", "2022-05-06"]))).toEqual("2022-05-09") // Saturday -> Monday + expect(isoly.Date.nextBusinessDay("2022-05-04", 2, new Set(["2022-05-05", "2022-05-06"]))).toEqual("2022-05-10") // Saturday -> Tuesday + expect(isoly.Date.nextBusinessDay("2022-05-04", 3, new Set(["2022-05-05", "2022-05-06"]))).toEqual("2022-05-11") // Saturday -> Wednesday expect( - isoly.Date.nextBusinessDay("2022-05-04", 3, ["2022-05-05", "2022-05-06", "2022-05-10", "2022-05-11"]) + isoly.Date.nextBusinessDay("2022-05-04", 3, new Set(["2022-05-05", "2022-05-06", "2022-05-10", "2022-05-11"])) ).toEqual("2022-05-13") // Saturday -> Friday }) }) diff --git a/Date/index.ts b/Date/index.ts index f145be5..218ad2c 100644 --- a/Date/index.ts +++ b/Date/index.ts @@ -1,5 +1,6 @@ import { isly } from "isly" import { DateSpan } from "../DateSpan" +import { DayOfWeek } from "../DayOfWeek" import { Locale } from "../Locale" import { Day as DateDay } from "./Day" import { Month as DateMonth } from "./Month" @@ -14,10 +15,9 @@ export namespace Date { export import Numeric = DateNumeric export import Year = DateYear - export const type = isly.named( - "isoly.Date", - isly.string((value: string) => { - const splitted = /^\d{4}-\d{2}-\d{2}$/.test(value) && Date.split(value) + export const { type, is, flawed } = isly + .string((value: string) => { + const splitted = /^\d{4}-\d{2}-\d{2}$/.test(value) && (value.split("-", 3) as [Year, Month, Day]) return ( splitted && Date.Year.type.is(splitted[0]) && @@ -26,187 +26,85 @@ export namespace Date { Date.Month.length(splitted[1], splitted[0]) >= Date.Day.Numeric.parse(splitted[2]) ) }, "YYYY-MM-DD") - ) - export const is = type.is - export const flaw = type.flaw - - export function split(value: Date): [Year, Month, Day] { - return value.split("-", 3) as [Year, Month, Day] + .rename("isoly.Date") + .describe("Date string in format YYYY-MM-DD.") + .bind() + export function normalize(date: Date): Date { + return Numeric.parse(date).normalize().format() } - export function parse(value: Date, time?: string): globalThis.Date { - return new globalThis.Date(value + (time ?? "T12:00:00.000Z")) + export function to(date: Date, type: "number"): number + export function to(date: Date, type: "system"): globalThis.Date + export function to(date: Date, type: "number" | "system"): number | globalThis.Date + export function to(date: Date, type: "number" | "system"): number | globalThis.Date { + return Numeric.parse(date).to(type) } - export function create(value: globalThis.Date): Date { - return value.toISOString().substring(0, 10) + export function localize(date: Date, locale?: Locale, timeZone?: string): Date { + return Numeric.parse(date).localize(locale, timeZone) } - export function now(): Date { - return create(new globalThis.Date()) - } - export function normalize(value: Date): Date { - return Numeric.format(Numeric.parse(value)) - } - export function localize(value: Date | globalThis.Date, locale?: Locale, timezone?: string): Date { - return ( - (is(value) ? parse(value) : value) - .toLocaleString(locale ? locale : Intl.DateTimeFormat().resolvedOptions().locale, { - year: "numeric", - month: "2-digit", - day: "2-digit", - timeZone: timezone ?? Intl.DateTimeFormat().resolvedOptions().timeZone, - }) - .substring(0, 10) - // See DateTime:localize for note. - .replaceAll(" ", " ") - ) + export function invert(date: Date): Date { + return Numeric.parse(date).invert().format() } export function next(date: Date, days: number | DateSpan = 1): Date { - let result: Date - if (typeof days == "number") { - const r = parse(date) - r.setDate(r.getDate() + days) - result = Date.create(r) - } else { - result = date - if (days.years) - result = nextYear(result, days.years) - if (days.months) - result = nextMonth(result, days.months) - if (days.days) - result = next(result, days.days) - } - return result + return Numeric.parse(date).next(days).format() } export function previous(date: Date, days: number | DateSpan = 1): Date { - let result: Date - if (typeof days == "number") { - const r = parse(date) - r.setDate(r.getDate() - days) - result = Date.create(r) - } else { - result = date - if (days.years) - result = previousYear(result, days.years) - if (days.months) - result = previousMonth(result, days.months) - if (days.days) - result = previous(result, days.days) - } - return result + return Numeric.parse(date).previous(days).format() } export function nextMonth(date: Date, months = 1): Date { - const result = parse(date) - result.setMonth(result.getMonth() + months) - return Date.create(result) + return Numeric.parse(date).nextMonth(months).format() } export function previousMonth(date: Date, months = 1): Date { - return nextMonth(date, -months) + return Numeric.parse(date).previousMonth(months).format() } export function nextYear(date: Date, years = 1): Date { - const result = parse(date) - result.setFullYear(result.getFullYear() + years) - return Date.create(result) + return Numeric.parse(date).nextYear(years).format() } export function previousYear(date: Date, years = 1): Date { - return nextYear(date, -years) + return Numeric.parse(date).previousYear(years).format() } export function firstOfYear(date: Date): Date { - const result = parse(date) - result.setMonth(0) - result.setDate(1) - return Date.create(result) + return Numeric.parse(date).firstOfYear().format() } export function lastOfYear(date: Date): Date { - const result = parse(date) - result.setFullYear(result.getFullYear() + 1) - result.setMonth(0) - result.setDate(0) - return Date.create(result) + return Numeric.parse(date).lastOfYear().format() } export function firstOfMonth(date: Date): Date { - const result = parse(date) - result.setDate(1) - return Date.create(result) + return Numeric.parse(date).firstOfMonth().format() } export function lastOfMonth(date: Date): Date { - const result = parse(date) - result.setMonth(result.getMonth() + 1) - result.setDate(0) - return Date.create(result) + return Numeric.parse(date).lastOfMonth().format() } export function firstOfWeek(date: Date): Date { - const result = parse(date) - const relativeDay = result.getDate() - (result.getDay() || 7) + 1 - result.setDate(relativeDay) - return Date.create(result) + return Numeric.parse(date).firstOfWeek().format() } export function lastOfWeek(date: Date): Date { - const result = parse(date) - const relativeDay = result.getDate() - result.getDay() + 7 - result.setDate(relativeDay) - return Date.create(result) + return Numeric.parse(date).lastOfWeek().format() } - export function getYear(date: Date, options?: { digits?: 2 | 4 }): number { - return options?.digits != 2 ? Number.parseInt(date.substring(0, 4)) : +getYear(date).toString().slice(-2) + export function getWeek(date: Date): number { + return Numeric.parse(date).getWeek() } - export function getMonth(date: Date): number { - return Number.parseInt(date.substring(5, 7)) + export function getDayOfWeek(date: Date): DayOfWeek { + return Numeric.parse(date).getDayOfWeek() } - export function getWeek(date: Date): number { - const parsed = new globalThis.Date(date) - parsed.setHours(0, 0, 0, 0) - parsed.setDate(parsed.getDate() + 3 - ((parsed.getDay() + 6) % 7)) - const week1 = new globalThis.Date(parsed.getFullYear(), 0, 4) - return 1 + Math.round(((parsed.getTime() - week1.getTime()) / 86_400_000 - 3 + ((week1.getDay() + 6) % 7)) / 7) - } - export function getDay(date: Date): number { - return Number.parseInt(date.substring(8, 10)) - } - export function getWeekDay(date: Date): number - export function getWeekDay(date: Date, locale: Locale, options?: { format?: "long" | "short" | "narrow" }): string - export function getWeekDay( + export function nextBusinessDay( date: Date, - locale?: Locale, - options?: { format?: "long" | "short" | "narrow" } - ): number | string { - const format = options?.format ?? "long" - return locale - ? new globalThis.Date(date).toLocaleDateString(locale, { weekday: format }) - : new globalThis.Date(date).getDay() - } - export function nextWeekday(date: Date, days: number | DateSpan = 1, holidays: Date[] = []): Date { - const holidaySet = new Set(holidays) - let result = next(date, days) - let weekday = getWeekDay(result) - while (weekday == 6 || weekday == 0 || holidaySet.has(result)) { - result = next(result, weekday == 6 ? 2 : 1) - weekday = getWeekDay(result) - } - return result - } - export function nextBusinessDay(date: Date, bankingDays = 1, bankingHolidays: Date[] | Set = []): Date { - const holidaySet = new Set(bankingHolidays) - if (bankingDays <= 0 && isBusinessDay(date, holidaySet)) - return date - const tomorrow = next(date) - const tomorrowIsBusinessDay = isBusinessDay(tomorrow, holidaySet) - return nextBusinessDay(tomorrow, tomorrowIsBusinessDay ? bankingDays - 1 : bankingDays, holidaySet) - } - function isBusinessDay(date: Date, holidaySet: Set = new Set()) { - const weekday = getWeekDay(date) - return !(weekday == 6 || weekday == 0 || holidaySet.has(date)) - } - export function span(date: Date, relative: Date): DateSpan { - return { - years: getYear(date) - getYear(relative), - months: getMonth(date) - getMonth(relative), - days: getDay(date) - getDay(relative), - } - } - export const epochStart = "0000-01-01" as const - export const epochEnd = "9999-12-31" as const - export function invert(date: Date): Date { - return `${(9999 - getYear(date)).toFixed(0).padStart(4, "0")}-${(13 - getMonth(date)) - .toFixed(0) - .padStart(2, "0")}-${(32 - getDay(date)).toFixed(0).padStart(2, "0")}` + businessDays = 1, + holidays: Set = new Set(), + weekend: DayOfWeek[] = ["saturday", "sunday"] + ): Date { + return Numeric.parse(date).nextBusinessDay(businessDays, holidays, weekend).format() + } + export function isBusinessDay(date: Date): boolean { + return Numeric.parse(date).isBusinessDay() + } + export function create(value: globalThis.Date): Date { + return Numeric.create(value).format() + } + export function now(): Date { + return Numeric.now().format() + } + export function parse(value: Date | string | undefined): Date { + return Numeric.parse(value).format() } + export const epoch = ["0000-01-01", "9999-12-31"] as const } diff --git a/DateRange/index.ts b/DateRange/index.ts index 194a20d..18c5693 100644 --- a/DateRange/index.ts +++ b/DateRange/index.ts @@ -7,9 +7,9 @@ export interface DateRange { } export namespace DateRange { - export const type = isly.object({ start: Date.type, end: Date.type }, "isoly.DateRange") - export const is = type.is - export const flaw = type.flaw + export const { type, is, flawed } = isly + .object({ start: Date.type, end: Date.type }, "isoly.DateRange") + .bind() export function create(start: Date, end: Date): DateRange export function create(date: Date, length: DateSpan): DateRange export function create(start: Date, end: Date | DateSpan): DateRange { diff --git a/DateSpan/index.ts b/DateSpan/index.ts index 5aaed0e..ac99595 100644 --- a/DateSpan/index.ts +++ b/DateSpan/index.ts @@ -7,11 +7,11 @@ export interface DateSpan { } export namespace DateSpan { - export const type = isly.object({ - years: isly.number().optional(), - months: isly.number().optional(), - days: isly.number().optional(), - }) - export const is = type.is - export const flaw = type.flaw + export const { type, is, flawed } = isly + .object({ + years: isly.number().optional(), + months: isly.number().optional(), + days: isly.number().optional(), + }) + .bind() } diff --git a/DateTime/Numeric.ts b/DateTime/Numeric.ts index f6ab10a..e9558e5 100644 --- a/DateTime/Numeric.ts +++ b/DateTime/Numeric.ts @@ -8,13 +8,14 @@ import { DateTime } from "./index" export type Numeric = Date.Numeric & Time.Numeric & { zone?: TimeZone.Offset } export namespace Numeric { - export const type = isly.union( - Date.Numeric.type, - Time.Numeric.type, - isly.object<{ zone?: TimeZone.Offset }>({ zone: TimeZone.Offset.type.optional() }) - ) - export const is = type.is - export const flaw = type.flaw + export const { type, is, flawed } = isly + .union( + Date.Numeric.type, + Time.Numeric.type, + isly.object<{ zone?: TimeZone.Offset }>({ zone: TimeZone.Offset.type.optional() }) + ) + .rename("isoly.DateTime.Numeric") + .bind() export function parse(value: DateTime | string): Numeric { const [date, splitted]: (string | undefined)[] = value.split("T", 2) const [time, zone]: (string | undefined)[] = splitted?.split(/(Z|[+-].{5})?$/, 2) ?? [] diff --git a/DateTime/index.ts b/DateTime/index.ts index dedb124..6d66f68 100644 --- a/DateTime/index.ts +++ b/DateTime/index.ts @@ -10,15 +10,14 @@ export type DateTime = string export namespace DateTime { export import Numeric = DateTimeNumeric - export const type = isly.named( - "isoly.DateTime", - isly.string((value: string) => { + export const { type, is, flawed } = isly + .string((value: string) => { const { date, time, timeZoneOffset } = DateTime.split(value) return Date.is(date) && Time.type.optional().is(time) && TimeZone.Offset.type.optional().is(timeZoneOffset) - }) - ) - export const is = type.is - export const flaw = type.flaw + }, "YYYY-MM-DDTHH[:mm[:ss[.fff]]][Z|(+|-)HH:MM]") + .rename("isoly.DateTime") + .describe("Valid ISO 8601 date-time formats include YYYY-MM-DDTHH[:mm[:ss[.fff]]][Z|(+|-)HH:MM].") + .bind() export function split(value: DateTime): { date: Date time: Time | undefined diff --git a/DayOfWeek/Numeric.ts b/DayOfWeek/Numeric.ts index 87ee9fc..96ebfd0 100644 --- a/DayOfWeek/Numeric.ts +++ b/DayOfWeek/Numeric.ts @@ -5,10 +5,11 @@ export type Numeric = typeof Numeric.values[number] export namespace Numeric { export const values = [1, 2, 3, 4, 5, 6, 7] as const - export const type = isly.named("isoly.DayOfWeek.Numeric", isly.number(values)) - export const is = type.is - export const flaw = type.flaw + export const { type, is, flawed } = isly + .number("value", ...values) + .rename("isoly.DayOfWeek.Numeric") + .bind() export function from(date: Date): Numeric { - return (((Date.getWeekDay(date) + 7 - 1) % 7) + 1) as Numeric + return (((Date.getDayOfWeek(date) + 7 - 1) % 7) + 1) as Numeric } } diff --git a/DayOfWeek/index.ts b/DayOfWeek/index.ts index e757c08..fb6abed 100644 --- a/DayOfWeek/index.ts +++ b/DayOfWeek/index.ts @@ -7,12 +7,14 @@ export type DayOfWeek = typeof DayOfWeek.values[number] export namespace DayOfWeek { export import Numeric = DayOfWeekNumeric export const values = ["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"] as const - export const type = isly.named("isoly.DayOfWeek", isly.string(values)) - export const is = type.is - export const flaw = type.flaw + export const { type, is, flawed } = isly + .string("value", ...values) + .rename("isoly.DayOfWeek") + .describe("Lower case weekday name in English.") + .bind() export function from(value: Numeric | Date): DayOfWeek { - return Numeric.is(value) ? values[value - 1] : from(Numeric.from(value)) + return Numeric.is(value) ? values[value - 1] : from(Date.getDayOfWeek(value)) } export function toNumeric(value: DayOfWeek): Numeric { return (values.indexOf(value) + 1) as Numeric diff --git a/Encoding/index.ts b/Encoding/index.ts index 89f8160..6896902 100644 --- a/Encoding/index.ts +++ b/Encoding/index.ts @@ -77,9 +77,10 @@ export namespace Encoding { "ISO-2022-KR", "T.51", ] as const - export const type = isly.named("isoly.Encoding", isly.string(values)) - export const is = type.is - export const flaw = type.flaw + export const { type, is, flawed } = isly + .string("value", ...values) + .rename("isoly.Encoding") + .bind() export function parse(value: string): Encoding | undefined { let result: Encoding | undefined switch (value.toUpperCase()) { diff --git a/Language/index.ts b/Language/index.ts index 6e2f58e..fef4baf 100644 --- a/Language/index.ts +++ b/Language/index.ts @@ -189,9 +189,10 @@ export namespace Language { "za", "zu", ] as const - export const type = isly.named("isoly.Language", isly.string(values)) - export const is = type.is - export const flaw = type.flaw + export const { type, is, flawed } = isly + .string("value", ...values) + .rename("isoly.Language") + .bind() export function toName(language: Language): string { const names: Record = { ab: "Abkhazian", diff --git a/Locale/index.ts b/Locale/index.ts index 9f6f587..d0d4bfc 100644 --- a/Locale/index.ts +++ b/Locale/index.ts @@ -216,9 +216,10 @@ export namespace Locale { "zh-TW", "zu-ZA", ] as const - export const type = isly.named("isoly.Locale", isly.string(values)) - export const is = type.is - export const flaw = type.flaw + export const { type, is, flawed } = isly + .string("value", ...values) + .rename("isoly.Locale") + .bind() export function toLanguage(locale: Locale): Language | undefined { const result = locale.split("-").shift() diff --git a/Month/index.ts b/Month/index.ts index 7b923f2..9f9bf7d 100644 --- a/Month/index.ts +++ b/Month/index.ts @@ -4,15 +4,14 @@ import { Date } from "../Date" export type Month = `${number}-${Date.Month}` export namespace Month { - export const type = isly.named( - "isoly.Month", - isly.string(value => { + export const { type, is, flawed } = isly + .string(value => { const match = /^(\d{4})-(\d{2})$/.exec(value) return !!match && Date.Year.is(match[1]) && Date.Month.is(match[2]) }, "YYYY-MM") - ) - export const is = type.is - export const flaw = type.flaw + .rename("isoly.Month") + .describe("ISO 8601 month in the format YYYY-MM.") + .bind() export function now(): Month { return from(Date.now()) diff --git a/Time/Hour.ts b/Time/Hour.ts index d860d35..4bb77d3 100644 --- a/Time/Hour.ts +++ b/Time/Hour.ts @@ -29,17 +29,19 @@ export namespace Hour { "22", "23", ] as const - export const type = isly.named("isoly.Time.Hour", isly.string(values)) - export const is = type.is - export const flaw = type.flaw + export const { type, is, flawed } = isly + .string("value", ...values) + .rename("isoly.Time.Hour") + .bind() export type Numeric = typeof Numeric.values[number] export namespace Numeric { export const values = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, ] as const - export const type = isly.named("isoly.Time.Hour.Numeric", isly.number(values)) - export const is = type.is - export const flaw = type.flaw + export const { type, is, flawed } = isly + .number("value", ...values) + .rename("isoly.Time.Hour.Numeric") + .bind() } export function parse(value: Hour): Hour.Numeric export function parse(value: string): Hour.Numeric | undefined diff --git a/Time/Millisecond.ts b/Time/Millisecond.ts index 13e60e2..7038334 100644 --- a/Time/Millisecond.ts +++ b/Time/Millisecond.ts @@ -4,17 +4,17 @@ type Digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" export type Millisecond = `${Digit}${Digit}${Digit}` export namespace Millisecond { - export const type = isly.named("isoly.Time.Millisecond", isly.string(/^[0-9]{3}$/)) - export const is = type.is - export const flaw = type.flaw + export const { type, is, flawed } = isly + .string("value", /^[0-9]{3}$/) + .rename("isoly.Time.Millisecond") + .describe("Milliseconds expressed as 3 digits left padded with zeros when required.") + .bind() export type Numeric = number export namespace Numeric { - export const type = isly.named( - "isoly.Time.Millisecond.Numeric", - isly.number(value => value >= 0 && value <= 999) - ) - export const is = type.is - export const flaw = type.flaw + export const { type, is, flawed } = isly + .number("range", 0, 999) + .rename("isoly.Time.Millisecond.Numeric") + .bind() } export function parse(value: Millisecond): Millisecond.Numeric export function parse(value: string): Millisecond.Numeric | undefined diff --git a/Time/Minute.ts b/Time/Minute.ts index 40b42c2..d50ee75 100644 --- a/Time/Minute.ts +++ b/Time/Minute.ts @@ -65,9 +65,10 @@ export namespace Minute { "58", "59", ] as const - export const type = isly.named("isoly.Time.Minute", isly.string(values)) - export const is = type.is - export const flaw = type.flaw + export const { type, is, flawed } = isly + .string("value", ...values) + .rename("isoly.Time.Minute") + .bind() export type Numeric = typeof Numeric.values[number] export namespace Numeric { export const values = [ @@ -75,9 +76,10 @@ export namespace Minute { 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, ] as const - export const type = isly.named("isoly.Time.Minute.Numeric", isly.number(values)) - export const is = type.is - export const flaw = type.flaw + export const { type, is, flawed } = isly + .number("value", ...values) + .rename("isoly.Time.Minute.Numeric") + .bind() } export function parse(value: Minute): Minute.Numeric export function parse(value: string): Minute.Numeric | undefined diff --git a/Time/Numeric.ts b/Time/Numeric.ts index 7a33667..73f5635 100644 --- a/Time/Numeric.ts +++ b/Time/Numeric.ts @@ -5,14 +5,17 @@ import { Precision } from "./Precision" export type Numeric = Partial> export namespace Numeric { - export const type = isly.object({ - hours: isly.number().optional(), - minutes: isly.number().optional(), - seconds: isly.number().optional(), - milliseconds: isly.number().optional(), - }) - export const is = type.is - export const flaw = type.flaw + export const { type, is, flawed } = isly + .object( + { + hours: isly.number().optional(), + minutes: isly.number().optional(), + seconds: isly.number().optional(), + milliseconds: isly.number().optional(), + }, + "isoly.Time.Numeric" + ) + .bind() export function create(epoch: number, precision: Precision = "seconds"): Required { const integerDivision = (dividend: number, divisor: number) => [Math.trunc(dividend / divisor), dividend % divisor] const [s, milliseconds] = integerDivision(epoch * Precision.factor[precision], 1000) diff --git a/Time/Precision.ts b/Time/Precision.ts index 96be0a4..4478021 100644 --- a/Time/Precision.ts +++ b/Time/Precision.ts @@ -3,9 +3,10 @@ import { isly } from "isly" export type Precision = typeof Precision.values[number] export namespace Precision { export const values = ["hours", "minutes", "seconds", "milliseconds"] as const - export const type = isly.string(Precision.values) - export const is = type.is - export const flaw = type.flaw + export const { type, is, flawed } = isly + .string("value", ...values) + .rename("isoly.Time.Minute.Numeric") + .bind() export const factor = { hours: 3600000, minutes: 60000, diff --git a/Time/Second.ts b/Time/Second.ts index 67ebb04..aa30555 100644 --- a/Time/Second.ts +++ b/Time/Second.ts @@ -66,9 +66,10 @@ export namespace Second { "59", "60", // Added leap second ] as const - export const type = isly.named("isoly.Time.Second", isly.string(values)) - export const is = type.is - export const flaw = type.flaw + export const { type, is, flawed } = isly + .string("value", ...values) + .rename("isoly.Time.Second") + .bind() export type Numeric = typeof Numeric.values[number] export namespace Numeric { export const values = [ @@ -134,9 +135,10 @@ export namespace Second { 59, 60, // Added leap second ] as const - export const type = isly.named("isoly.Time.Second.Numeric", isly.number(values)) - export const is = type.is - export const flaw = type.flaw + export const { type, is, flawed } = isly + .number("value", ...values) + .rename("isoly.Time.Second.Numeric") + .bind() } export function parse(value: Second): Second.Numeric export function parse(value: string): Second.Numeric | undefined diff --git a/Time/index.ts b/Time/index.ts index 7c7a9ce..32f5319 100644 --- a/Time/index.ts +++ b/Time/index.ts @@ -16,9 +16,8 @@ export namespace Time { export import Precision = TimePrecision export import Second = TimeSecond - export const type = isly.named( - "isoly.Time", - isly.string