Skip to content

Commit

Permalink
feat: existing "Z" token is now "ZZ" adds "Z" token style (#52)
Browse files Browse the repository at this point in the history
* Support for the timezone token "ZZ" and changing the timezone format style.

* Fix timezone offset parsing and applyOffset function
  • Loading branch information
Toru-Takagi authored Apr 17, 2024
1 parent 5874536 commit d42a114
Show file tree
Hide file tree
Showing 13 changed files with 191 additions and 41 deletions.
6 changes: 6 additions & 0 deletions src/__tests__/applyOffset.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,18 @@ process.env.TZ = "America/New_York"

describe("applyOffset", () => {
it("can apply a negative offset to a date", () => {
expect(applyOffset("2023-02-22T00:00:00Z", "-05:00").toISOString()).toBe(
"2023-02-21T19:00:00.000Z"
)
expect(applyOffset("2023-02-22T00:00:00Z", "-0500").toISOString()).toBe(
"2023-02-21T19:00:00.000Z"
)
})

it("can apply a positive offset to a date", () => {
expect(applyOffset("2023-04-13T10:15:00", "+02:00").toISOString()).toBe(
"2023-04-13T16:15:00.000Z"
)
expect(applyOffset("2023-04-13T10:15:00", "+0200").toISOString()).toBe(
"2023-04-13T16:15:00.000Z"
)
Expand Down
25 changes: 25 additions & 0 deletions src/__tests__/common.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { describe, expect, it } from "vitest"
import { getOffsetFormat } from "../common"

describe("getOffsetFormat", () => {
it("should return 'Z' for format 'YYYY-MM-DDTHH:mm:ssZ'", () => {
expect(getOffsetFormat("YYYY-MM-DDTHH:mm:ssZ")).toBe("Z")
})

it("should return 'ZZ' for format 'YYYY-MM-DDTHH:mm:ssZZ'", () => {
expect(getOffsetFormat("YYYY-MM-DDTHH:mm:ssZZ")).toBe("ZZ")
})

it("should return 'Z' for formats 'full', 'long', 'medium', and 'short'", () => {
expect(getOffsetFormat("full")).toBe("Z")
expect(getOffsetFormat("long")).toBe("Z")
expect(getOffsetFormat("medium")).toBe("Z")
expect(getOffsetFormat("short")).toBe("Z")
})

it("should return 'Z' for formats { date: 'full', time: 'full' }, { date: 'full' }, and { time: 'full' }", () => {
expect(getOffsetFormat({ date: "full", time: "full" })).toBe("Z")
expect(getOffsetFormat({ date: "full" })).toBe("Z")
expect(getOffsetFormat({ time: "full" })).toBe("Z")
})
})
37 changes: 34 additions & 3 deletions src/__tests__/format.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { describe, it, expect } from "vitest"
import { describe, expect, it } from "vitest"
import { format } from "../format"
import { tzDate } from "../tzDate"
process.env.TZ = "America/New_York"
Expand Down Expand Up @@ -138,7 +138,7 @@ describe("format", () => {
it("can render a long time in Japanese", () => {
expect(
format("2010-06-09T04:32:00Z", { time: "full" }, "ja")
).toBe("0時32分00秒 -0400")
).toBe("0時32分00秒 -04:00")
})
it("can format the russian month of february", () => {
expect(format("2023-03-14", { date: "medium" }, "ru")).toBe(
Expand All @@ -147,13 +147,16 @@ describe("format", () => {
})
it("can include the timezone of a date", () => {
expect(format("2023-05-05T05:30:10Z", "HH:mm:ss Z", "en")).toBe(
"01:30:10 -04:00"
)
expect(format("2023-05-05T05:30:10Z", "HH:mm:ss ZZ", "en")).toBe(
"01:30:10 -0400"
)
})
it("uses offsets in full date formatting", () => {
expect(
format("2023-05-05T05:30:10Z", { date: "full", time: "full" }, "en")
).toBe("Friday, May 5, 2023 at 1:30:10 AM -0400")
).toBe("Friday, May 5, 2023 at 1:30:10 AM -04:00")
})
it("can filter out the month part", () => {
expect(
Expand Down Expand Up @@ -201,20 +204,41 @@ describe("format with a timezone", () => {
format: "Z",
tz: "Asia/Kolkata",
})
).toBe("+05:30")
expect(
format({
date: "2022-10-29T11:30:50Z",
format: "ZZ",
tz: "Asia/Kolkata",
})
).toBe("+0530")
expect(
format({
date: "2023-02-20T10:15:00",
format: "D hh:mm a Z",
tz: "America/New_York",
})
).toBe("20 10:15 am -05:00")
expect(
format({
date: "2023-02-20T10:15:00",
format: "D hh:mm a ZZ",
tz: "America/New_York",
})
).toBe("20 10:15 am -0500")
expect(
format({
date: new Date("2024-02-16T11:00:00Z"),
format: "YYYY-MM-DDTHH:mm:ssZ",
tz: "Europe/Stockholm",
})
).toBe("2024-02-16T12:00:00+01:00")
expect(
format({
date: new Date("2024-02-16T11:00:00Z"),
format: "YYYY-MM-DDTHH:mm:ssZZ",
tz: "Europe/Stockholm",
})
).toBe("2024-02-16T12:00:00+0100")
})

Expand All @@ -225,6 +249,13 @@ describe("format with a timezone", () => {
format: "HH:mm:ssZ",
tz: "UTC",
})
).toBe("02:30:00+00:00")
expect(
format({
date: new Date("2024-03-10T02:30:00Z"),
format: "HH:mm:ssZZ",
tz: "UTC",
})
).toBe("02:30:00+0000")
})
it("can render a double character zero with leading zeros in zh (#41)", () => {
Expand Down
23 changes: 16 additions & 7 deletions src/__tests__/offset.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,38 @@ process.env.TZ = "America/New_York"

describe("offset", () => {
it("can determine the offset of a winter month to UTC", () => {
expect(offset("2023-02-22")).toBe("-0500")
expect(offset("2023-02-22")).toBe("-05:00")
})
it("changes the offset after daylight savings", () => {
expect(offset("2023-03-12T06:59:00Z")).toBe("-0500")
expect(offset("2023-03-12T07:00:00Z")).toBe("-0400")
expect(offset("2023-03-12T06:59:00Z")).toBe("-05:00")
expect(offset("2023-03-12T07:00:00Z")).toBe("-04:00")
})
it("can determine the offset to another base timezone", () => {
expect(offset("2023-02-22", "Europe/Amsterdam")).toBe("-0600")
expect(offset("2023-02-22", "Europe/Amsterdam")).toBe("-06:00")
})
it("can determine the offset to another base timezone with daylight savings", () => {
expect(offset("2023-03-26T00:59Z", "Europe/Amsterdam")).toBe("-0500")
expect(offset("2023-03-26T01:00Z", "Europe/Amsterdam")).toBe("-0600")
expect(offset("2023-03-26T00:59Z", "Europe/Amsterdam")).toBe("-05:00")
expect(offset("2023-03-26T01:00Z", "Europe/Amsterdam")).toBe("-06:00")
})
it("can determine the offset between two arbitrary timezones", () => {
expect(offset("2023-02-22", "Europe/Moscow", "America/Los_Angeles")).toBe(
"-1100"
"-11:00"
)
expect(offset("2023-02-22", "America/Los_Angeles", "Europe/Moscow")).toBe(
"+11:00"
)
expect(offset("2023-02-22", "Europe/Moscow", "America/Los_Angeles", "ZZ")).toBe(
"-1100"
)
expect(offset("2023-02-22", "America/Los_Angeles", "Europe/Moscow", "ZZ")).toBe(
"+1100"
)
})
it("can determine the offset to a non full-hour offset timezone", () => {
expect(offset("2023-02-22", "Europe/London", "Pacific/Chatham")).toBe(
"+13:45"
)
expect(offset("2023-02-22", "Europe/London", "Pacific/Chatham", "ZZ")).toBe(
"+1345"
)
})
Expand Down
29 changes: 24 additions & 5 deletions src/__tests__/parse.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,24 +184,30 @@ describe("parse", () => {
})
it("can parse a full date with a timezone offset", () => {
expect(
parse("Friday, May 5, 2023 at 1:30:10 AM -0600", {
parse("Friday, May 5, 2023 at 1:30:10 AM -06:00", {
date: "full",
time: "full",
}).toISOString()
).toBe("2023-05-05T07:30:10.000Z")
})
it("can parse a custom format with a timezone offset", () => {
expect(
parse("2023-02-24T13:44-0500", "YYYY-MM-DDTHH:mmZ", "en").toISOString()
parse("2023-02-24T13:44-05:00", "YYYY-MM-DDTHH:mmZ", "en").toISOString()
).toBe("2023-02-24T18:44:00.000Z")
expect(
parse("2023--0500-02-24T13:44", "YYYY-Z-MM-DDTHH:mm", "en").toISOString()
parse("2023--05:00-02-24T13:44", "YYYY-Z-MM-DDTHH:mm", "en").toISOString()
).toBe("2023-02-24T18:44:00.000Z")
expect(
parse("2023-02-24T13:44-0500", "YYYY-MM-DDTHH:mmZZ", "en").toISOString()
).toBe("2023-02-24T18:44:00.000Z")
expect(
parse("2023--0500-02-24T13:44", "YYYY-ZZ-MM-DDTHH:mm", "en").toISOString()
).toBe("2023-02-24T18:44:00.000Z")
})
it("can filter out the timezone offset", () => {
expect(
parse({
date: "Friday, May 7, 2023 at 1:30:10 AM -1000",
date: "Friday, May 7, 2023 at 1:30:10 AM -10:00",
format: {
date: "full",
time: "full",
Expand All @@ -214,7 +220,7 @@ describe("parse", () => {
it("can filter out the timezone offset", () => {
expect(
parse({
date: ", May 7, 2023 at 1:30:10 AM -1000",
date: ", May 7, 2023 at 1:30:10 AM -10:00",
format: {
date: "full",
time: "full",
Expand Down Expand Up @@ -250,4 +256,17 @@ describe("parse", () => {
}).toISOString()
).toThrow()
})
it("should throws an error if the Z token is specified and [+-]HHmm", () => {
expect(
() => parse("1994-06-22T04:22:32-0900", "YYYY-MM-DDTHH:mm:ssZ")
).toThrow("Invalid offset: -0900")
})
it("should throws an error when a FormatStyle is specified for [+-]HHmm", () => {
expect(
() => parse("Friday, May 5, 2023 at 1:30:10 AM -0600", {
date: "full",
time: "full",
})
).toThrow("Invalid offset: -0600")
})
})
6 changes: 6 additions & 0 deletions src/__tests__/removeOffset.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,18 @@ process.env.TZ = "America/New_York"

describe("removeOffset", () => {
it("can apply a negative offset to a date", () => {
expect(
removeOffset("2023-02-21T19:00:00.000Z", "-05:00").toISOString()
).toBe("2023-02-22T00:00:00.000Z")
expect(
removeOffset("2023-02-21T19:00:00.000Z", "-0500").toISOString()
).toBe("2023-02-22T00:00:00.000Z")
})

it("can apply a positive offset to a date", () => {
expect(
removeOffset("2023-04-13T16:15:00.000Z", "+02:00").toISOString()
).toBe("2023-04-13T14:15:00.000Z")
expect(
removeOffset("2023-04-13T16:15:00.000Z", "+0200").toISOString()
).toBe("2023-04-13T14:15:00.000Z")
Expand Down
10 changes: 8 additions & 2 deletions src/__tests__/validOffset.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,15 @@ process.env.TZ = "America/New_York"

describe("validOffset", () => {
it("returns its own value when valid", () => {
expect(validOffset("+0000")).toBe("+0000")
expect(validOffset("+0100")).toBe("+0100")
expect(validOffset("+0000", "ZZ")).toBe("+0000")
expect(validOffset("+0100", "ZZ")).toBe("+0100")
expect(validOffset("+00:00", "Z")).toBe("+00:00")
expect(validOffset("+01:00", "Z")).toBe("+01:00")
expect(validOffset("+00:00")).toBe("+00:00")
expect(validOffset("+01:00")).toBe("+01:00")
})
it("should throw an error when the timezone token does not match the format", () => {
expect(() => validOffset("+0000", "Z")).toThrow()
expect(() => validOffset("+00:00", "ZZ")).toThrow()
})
})
16 changes: 12 additions & 4 deletions src/applyOffset.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
import { date } from "./date"
import { offsetToMins } from "./common"
import { TimezoneToken, fixedLengthByOffset, offsetToMins } from "./common"
import type { DateInput } from "./types"

/**
* Apply a given offset to a date, returning a new date with the offset
* applied by adding or subtracting the given number of minutes.
* @param dateInput - The date to apply the offset to.
* @param offset - The offset to apply in the +-HHmm format.
* @param offset - The offset to apply in the +-HHmm or +-HH:mm format.
*/
export function applyOffset(dateInput: DateInput, offset = "+0000"): Date {
export function applyOffset(dateInput: DateInput, offset = "+00:00"): Date {
const d = date(dateInput)
const timeDiffInMins = offsetToMins(offset)
const token = ((): TimezoneToken => {
switch (fixedLengthByOffset(offset)) {
case 5:
return "ZZ"
case 6:
return "Z"
}
})()
const timeDiffInMins = offsetToMins(offset, token)
return new Date(d.getTime() + timeDiffInMins * 1000 * 60)
}
Loading

0 comments on commit d42a114

Please sign in to comment.