Skip to content

Commit

Permalink
Feature/simplify dateparts (#15)
Browse files Browse the repository at this point in the history
* Removed request object

* Added some docs

* Added some docs

* Added some docs

* Fixed typo

---------

Co-authored-by: Rob Blackbourn <rob.blackbourn@gmail.com>
  • Loading branch information
rob-blackbourn and Rob Blackbourn committed Jun 25, 2023
1 parent 28f5af0 commit adaac33
Show file tree
Hide file tree
Showing 20 changed files with 62 additions and 136 deletions.
14 changes: 14 additions & 0 deletions manual/guide/calculations.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,20 @@ London the clocks went back on the last Sunday in March at 2am. This means to
calculate the time in UTC from the local time between 1am and 2am we would need
to know if the adjustment had already been made.

## Using IANA Timezones With Date

Given that we know the `Date` object is a wrapper around the number of
milliseconds since 1970, and the IANA timezone is an offset in minutes from UTC,
we can see that adding the offset given from the UTC date and time will provide
the date we want.

The expensive part of this operation is finding the offset, as this is specific
to a given date and time. As this library uses the native `Date` this must be
done for each operation. To improve efficiency the `Timezone` class has the
functions `makeDate` and `dateParts`. These functions do the offset lookup
once. Contrast this with getting the year (`tz.year(date)`) and then the month
(`tz.monthIndex(date)`) which would need to find the offset twice.

## What next ?

{@page ./date-arithmetic.md}
7 changes: 7 additions & 0 deletions manual/guide/design-choices.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ be achieved by making the `days` argument negative.
While classes might provide a more elegant interface, few tree shaking libraries
and able to prune unused class methods, so functions are preferred to classes.

### Provide Unminified Bundle

This may change, but the current view is that the majority of developers
use bundlers, rather than include manual `<script>` tags. With this in
mind the code is provided as a single javascript file, as the downstream
bundler will do the tree shaking.

## What next ?

{@page ./calculations.md}
2 changes: 1 addition & 1 deletion src/Calendar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { Timezone } from './Timezone'
*
* There is a tutorial [here](../../pages/guide/calendars.html).
*
* Two calendars are classes are defined: {@link WeekendCalendar} and
* Two calendar classes are defined: {@link WeekendCalendar} and
* {@link HolidayCalendar}. The object {@link calWeekends} is the
* default calendar. It simply defines Saturday and Sunday as holiday
* dates.
Expand Down
6 changes: 3 additions & 3 deletions src/IANATimezone.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { MILLISECONDS_IN_MINUTE } from './constants'
import { Timezone } from './Timezone'
import { DatePartRequest, DatePartResponse } from './types'
import { DatePartResponse } from './types'
import { tzUtc } from './UTCTimezone'
import { getClosestValues } from './utils'

Expand Down Expand Up @@ -105,9 +105,9 @@ export class IANATimezone extends Timezone {
return this.#fromLocal(date)
}

dateParts(date: Date, request: DatePartRequest): DatePartResponse {
dateParts(date: Date): DatePartResponse {
const local = this.#toLocal(date)
return tzUtc.dateParts(local, request)
return tzUtc.dateParts(local)
}

offset(date: Date): number {
Expand Down
20 changes: 10 additions & 10 deletions src/LocalTimezone.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Timezone } from './Timezone'
import { DatePartRequest, DatePartResponse } from './types'
import { DatePartResponse } from './types'

class LocalTimezone extends Timezone {
constructor() {
Expand All @@ -26,16 +26,16 @@ class LocalTimezone extends Timezone {
)
}

dateParts(date: Date, request: DatePartRequest): DatePartResponse {
dateParts(date: Date): DatePartResponse {
return {
year: request.year ? date.getFullYear() : 0,
monthIndex: request.monthIndex ? date.getMonth() : 0,
weekday: request.weekday ? date.getDay() : 0,
day: request.day ? date.getDate() : 0,
hours: request.hours ? date.getHours() : 0,
minutes: request.minutes ? date.getMinutes() : 0,
seconds: request.seconds ? date.getSeconds() : 0,
milliseconds: request.milliseconds ? date.getMilliseconds() : 0
year: date.getFullYear(),
monthIndex: date.getMonth(),
weekday: date.getDay(),
day: date.getDate(),
hours: date.getHours(),
minutes: date.getMinutes(),
seconds: date.getSeconds(),
milliseconds: date.getMilliseconds()
}
}

Expand Down
24 changes: 4 additions & 20 deletions src/Timezone.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { DatePartRequest, DatePartResponse } from './types'
import { DatePartResponse } from './types'
import { padNumber } from './utils'

/**
Expand Down Expand Up @@ -103,7 +103,7 @@ export abstract class Timezone {
* @param request The request.
* @returns The date parts.
*/
abstract dateParts(date: Date, request: DatePartRequest): DatePartResponse
abstract dateParts(date: Date): DatePartResponse

/**
* The signed offset in minutes from UTC for the given date.
Expand Down Expand Up @@ -194,15 +194,7 @@ export abstract class Timezone {
*/
as(date: Date, tz: Timezone): Date {
const { year, monthIndex, day, hours, minutes, seconds, milliseconds } =
this.dateParts(date, {
year: true,
monthIndex: true,
day: true,
hours: true,
minutes: true,
seconds: true,
milliseconds: true
})
this.dateParts(date)
return tz.makeDate(
year,
monthIndex,
Expand All @@ -221,15 +213,7 @@ export abstract class Timezone {
*/
toISOString(date: Date) {
const { year, monthIndex, day, hours, minutes, seconds, milliseconds } =
this.dateParts(date, {
year: true,
monthIndex: true,
day: true,
hours: true,
minutes: true,
seconds: true,
milliseconds: true
})
this.dateParts(date)
const offset = this.offset(date)
const offsetSign = Math.sign(offset)
const offsetHours = offsetSign * Math.trunc(offset / 60)
Expand Down
20 changes: 10 additions & 10 deletions src/UTCTimezone.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Timezone } from './Timezone'
import { DatePartRequest, DatePartResponse } from './types'
import { DatePartResponse } from './types'

class UtcTimezone extends Timezone {
constructor() {
Expand All @@ -20,16 +20,16 @@ class UtcTimezone extends Timezone {
)
}

dateParts(date: Date, request: DatePartRequest): DatePartResponse {
dateParts(date: Date): DatePartResponse {
return {
year: request.year ? date.getUTCFullYear() : 0,
monthIndex: request.monthIndex ? date.getUTCMonth() : 0,
weekday: request.weekday ? date.getUTCDay() : 0,
day: request.day ? date.getUTCDate() : 0,
hours: request.hours ? date.getUTCHours() : 0,
minutes: request.minutes ? date.getUTCMinutes() : 0,
seconds: request.seconds ? date.getUTCSeconds() : 0,
milliseconds: request.milliseconds ? date.getUTCMilliseconds() : 0
year: date.getUTCFullYear(),
monthIndex: date.getUTCMonth(),
weekday: date.getUTCDay(),
day: date.getUTCDate(),
hours: date.getUTCHours(),
minutes: date.getUTCMinutes(),
seconds: date.getUTCSeconds(),
milliseconds: date.getUTCMilliseconds()
}
}

Expand Down
10 changes: 1 addition & 9 deletions src/addDays.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,7 @@ export function addDays(
tz: Timezone = tzLocal
): Date {
const { year, monthIndex, day, hours, minutes, seconds, milliseconds } =
tz.dateParts(date, {
year: true,
monthIndex: true,
day: true,
hours: true,
minutes: true,
seconds: true,
milliseconds: true
})
tz.dateParts(date)

return tz.makeDate(
year,
Expand Down
10 changes: 1 addition & 9 deletions src/addDuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,7 @@ export function addDuration(
tz: Timezone = tzLocal
): Date {
const { year, monthIndex, day, hours, minutes, seconds, milliseconds } =
tz.dateParts(date, {
year: true,
monthIndex: true,
day: true,
hours: true,
minutes: true,
seconds: true,
milliseconds: true
})
tz.dateParts(date)

return tz.makeDate(
year + duration.years,
Expand Down
10 changes: 1 addition & 9 deletions src/addMonths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,7 @@ export function addMonths(
tz: Timezone = tzLocal
): Date {
const { year, monthIndex, day, hours, minutes, seconds, milliseconds } =
tz.dateParts(date, {
year: true,
monthIndex: true,
day: true,
hours: true,
minutes: true,
seconds: true,
milliseconds: true
})
tz.dateParts(date)

return tz.makeDate(
year,
Expand Down
10 changes: 1 addition & 9 deletions src/addYears.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,7 @@ export function addYears(
tz: Timezone = tzLocal
): Date {
const { year, monthIndex, day, hours, minutes, seconds, milliseconds } =
tz.dateParts(date, {
year: true,
monthIndex: true,
day: true,
hours: true,
minutes: true,
seconds: true,
milliseconds: true
})
tz.dateParts(date)

return tz.makeDate(
year + numberOfYears,
Expand Down
6 changes: 1 addition & 5 deletions src/endOfDay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,6 @@ import { Timezone } from './Timezone'
* @returns A new date which is the end of the day.
*/
export function endOfDay(date: Date, tz: Timezone = tzLocal): Date {
const { year, monthIndex, day } = tz.dateParts(date, {
year: true,
monthIndex: true,
day: true
})
const { year, monthIndex, day } = tz.dateParts(date)
return tz.makeDate(year, monthIndex, day, 23, 59, 59, 999)
}
5 changes: 1 addition & 4 deletions src/endOfMonth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,7 @@ import { Timezone } from './Timezone'
* @returns A date which is the last day of the month for the given year and month.
*/
export function endOfMonth(date: Date, tz: Timezone = tzLocal): Date {
const { year, monthIndex } = tz.dateParts(date, {
year: true,
monthIndex: true
})
const { year, monthIndex } = tz.dateParts(date)

return tz.makeDate(
year,
Expand Down
4 changes: 2 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,8 @@ export { startOfDay } from './startOfDay'
export { startOfToday } from './startOfToday'
export { startOfWeekYear } from './startOfWeekYear'
export { Timezone } from './Timezone'
import { DatePartRequest, DatePartResponse } from './types'
export type { DatePartRequest, DatePartResponse }
import { DatePartResponse } from './types'
export type { DatePartResponse }
export {
tzDataReviver,
dataToTimezoneOffset,
Expand Down
5 changes: 1 addition & 4 deletions src/lastDayOfMonth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,7 @@ import { Timezone } from './Timezone'
* @returns A date which is the last day of the month.
*/
export function lastDayOfMonth(date: Date, tz: Timezone = tzLocal): Date {
const { year, monthIndex } = tz.dateParts(date, {
year: true,
monthIndex: true
})
const { year, monthIndex } = tz.dateParts(date)

return tz.makeDate(year, monthIndex, daysInMonth(year, monthIndex))
}
6 changes: 1 addition & 5 deletions src/startOfDay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,7 @@ import { Timezone } from './Timezone'
* @returns The start of the day.
*/
export function startOfDay(date: Date, tz: Timezone = tzLocal): Date {
const { year, monthIndex, day } = tz.dateParts(date, {
year: true,
monthIndex: true,
day: true
})
const { year, monthIndex, day } = tz.dateParts(date)

return tz.makeDate(year, monthIndex, day)
}
5 changes: 1 addition & 4 deletions src/startOfMonth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,6 @@ import { Timezone } from './Timezone'
* @returns The start of the month.
*/
export function startOfMonth(date: Date, tz: Timezone = tzLocal): Date {
const { year, monthIndex } = tz.dateParts(date, {
year: true,
monthIndex: true
})
const { year, monthIndex } = tz.dateParts(date)
return tz.makeDate(year, monthIndex, 1)
}
10 changes: 1 addition & 9 deletions src/subDuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,7 @@ export function subDuration(
tz: Timezone = tzLocal
): Date {
const { year, monthIndex, day, hours, minutes, seconds, milliseconds } =
tz.dateParts(date, {
year: true,
monthIndex: true,
day: true,
hours: true,
minutes: true,
seconds: true,
milliseconds: true
})
tz.dateParts(date)

return tz.makeDate(
year - duration.years,
Expand Down
14 changes: 0 additions & 14 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,3 @@
/**
* A request for date parts.
*/
export interface DatePartRequest {
year?: boolean
monthIndex?: boolean
weekday?: boolean
day?: boolean
hours?: boolean
minutes?: boolean
seconds?: boolean
milliseconds?: boolean
}

/**
* A response for date parts.
*/
Expand Down
10 changes: 1 addition & 9 deletions test/timezone.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,7 @@ describe('timezone', () => {

it('should destruct a date', () => {
const { year, monthIndex, day, hours, minutes, seconds, milliseconds } =
tzBrussels.dateParts(new Date('1999-12-31T23:00:00Z'), {
year: true,
monthIndex: true,
day: true,
hours: true,
minutes: true,
seconds: true,
milliseconds: true
})
tzBrussels.dateParts(new Date('1999-12-31T23:00:00Z'))
expect(year).toBe(2000)
expect(monthIndex).toBe(0)
expect(day).toBe(1)
Expand Down

0 comments on commit adaac33

Please sign in to comment.