-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from foxbunny/feature/week
Add Week ctor implementation
- Loading branch information
Showing
4 changed files
with
545 additions
and
177 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,53 +1,239 @@ | ||
# DatumJS | ||
|
||
DatumJS is a library of abstractions for working with dates in JavaScript. | ||
Unlike most date libraries (e.g., moment) this library is not concerned with the | ||
human-facing aspects of dates (formatting, parsing, etc). It's main purpose is | ||
to complement the native `Date` objects to provide higher-level data | ||
abstractions for working with dates, and related concepts (e.g., weeks, months). | ||
|
||
This library can be used for many things but it was mainly developed as a | ||
building block for calendars and datepickers. | ||
|
||
## Utility functions | ||
|
||
The following are utility functions provided by the library. | ||
|
||
**NOTE:** Functions that take more than 2 arguments are auto-curried. This means | ||
that they will return a partially applied version of the function whenever less | ||
than the maximum number of arguments is passed. | ||
|
||
### `datum.resetTime(d)` | ||
|
||
Returns a copy of a `Date` object with time set to midnight local time. | ||
|
||
The input `Date` object is not modified in any way. | ||
|
||
```javascript | ||
datum.resetTime(new Date(2017, 1, 14, 11, 30, 2)); | ||
// => Date(2017, 1, 14, 0, 0, 0) | ||
``` | ||
|
||
### `datum.diff(d1, d2)` | ||
|
||
Returns the absolute difference between two `Date` objects in milliseconds. | ||
|
||
```javascript | ||
datum.diff(new Date(2017, 1, 14, 11, 30, 2), new Date(2017, 3, 12, 10, 0, 2)); | ||
// => 4924800000 | ||
datum.diff(new Date(2017, 3, 12, 10, 0, 2), new Date(2017, 1, 14, 11, 30, 2)); | ||
// => 4924800000 | ||
``` | ||
|
||
### `datum.shiftDate(n, d)` | ||
|
||
Returns a `Date` object representing the input `Date` object shifted by the | ||
specified number of days. Shifting is done towards future, where positive `n` | ||
shifts by adding, and negative by subtracting. | ||
|
||
The input `Date` object is not modified. | ||
|
||
```javascript | ||
datum.shiftDate(12, new Date(2017, 8, 2)); | ||
// => Date(2017, 8, 14, 0, 0, 0) | ||
# DatumJS | ||
|
||
DatumJS is a library of abstractions for working with dates in JavaScript. | ||
Unlike most date libraries (e.g., moment) this library is not concerned with the | ||
human-facing aspects of dates (formatting, parsing, etc). It's main purpose is | ||
to complement the native `Date` objects to provide higher-level data | ||
abstractions for working with dates, and related concepts (e.g., weeks, months). | ||
|
||
This library can be used for many things but it was mainly developed as a | ||
building block for calendars and datepickers. | ||
|
||
## Working with weeks | ||
|
||
The `datum.Week` constructor is used to create `Week` object which represent a | ||
single week. The constructor takes four arguments. The three required arguments | ||
are `year`, `month`, and `date`. They should be the same values used by the | ||
3-argument version of the `Date` constructor call. The fourth optional argument | ||
is an `options` object which is used to customize the `Week` objects. | ||
|
||
At its simplest, a `Week` object is creted like so: | ||
|
||
```javascript | ||
var w = new datum.Week(2017, 1, 15); | ||
``` | ||
|
||
The `Week` objects are constructed from the given date such that the result is a | ||
week that contains the specified date. The date does not have to be the start or | ||
end day of the week. | ||
|
||
Each week object has `start`, `end`, and `days` properties, which, respectively, | ||
represent the `Date` object of the first day of the week, `Date` object of the | ||
last day of the week, and an array of days belonging to that week. | ||
|
||
```javascript | ||
w.start | ||
// => Date(2017, 1, 12, 0, 0, 0) | ||
w.end | ||
// => Date(2017, 1, 18, 0, 0, 0) | ||
w.days | ||
// => [Date(2017, 1, 12, 0, 0, 0), Date(2017, 1, 13, 0, 0, 0), ..., Date(2017, 1, 18, 0, 0, 0)] | ||
``` | ||
|
||
By default, week objects start on Sunday (JavaScript default). This can be | ||
customized by passing in an `options` object that has a `weekDayOffset` | ||
function. This function takes a `Date` object and returns its index within the | ||
week, where 0 is the first day, and 6 the last. For convenience, | ||
`datum.weekDayOffset()` function can be used to create such functions. Here is | ||
an example for a week that starts on Monday: | ||
|
||
```javascript | ||
var wm = new datum.Week(2017, 1, 15, { weekDayOffset: datum.weekDayOffset(1) }); | ||
wm.start | ||
// => Date(2017, 1, 13, 0, 0, 0) | ||
wm.end | ||
// => Date(2017, 1, 19, 0, 0, 0) | ||
``` | ||
|
||
In some situations, it may be necessary to limit the week to a certain set of | ||
days. For instance, we may wish to exclude weekends. The desired number of days | ||
per week can be customized using the `daysPerWeek` option, which should be an | ||
integer. The following example represents a week only containing the work days. | ||
|
||
```javascript | ||
var wwo = new datum.Week(2017, 1, 15, { | ||
daysPerWeek: 5, | ||
weekDayOffset: datum.weekDayOffset(1) | ||
}); | ||
wwo.start | ||
// => Date(2017, 1, 13, 0, 0, 0) | ||
wwo.end | ||
// => Date(2017, 1, 17, 0, 0, 0) | ||
wwo.days.length | ||
// => 5 | ||
``` | ||
|
||
When using the `daysPerWeek` option, as long as the supplied is within the 7 day | ||
period between start and end days, the supplied date is not necessarily included | ||
in the range of dates represented by the `Week` object. For instance, given a | ||
week that starts on Monday, and `daysPerWeek` of 5, we can supply a Sunday as | ||
the input, and this date will not be found in the `days` array beause it does | ||
not fit the 5-day window. | ||
|
||
```javascript | ||
var wwo = new datum.Week(2017, 1, 18, { | ||
daysPerWeek: 5, | ||
weekDayOffset: datum.weekDayOffset(1) | ||
}); | ||
wwo.days.map(function (date) { return date.getDate(); }); | ||
// => [ 13, 14, 15, 16, 17 ] | ||
``` | ||
|
||
The `Week` object in its default form does not provide many pieces of | ||
information that may be critical for your particular use case. For example, it | ||
does not provide information about ISO week number. This is by design. Rather | ||
than writing a library that will satisfy a large number of use cases out fo the | ||
box, we opted for a lightweight library that can be easily extended to include | ||
such cases. The `decorate` option can be used for just that. | ||
|
||
In the following example, we will add an `isoWeek` property to a `Week` object | ||
by using a simple decorator. ISO weeks start on Monday, so we will also | ||
customize the `weekDayOffset` option as well. | ||
|
||
```javascript | ||
var monWeek = datum.weekDayOffset(1); | ||
var monWeekStart = datum.weekStart(monWeek); | ||
|
||
function firstIsoWeekStart(year) { | ||
// A week that contains January 4th of the given year | ||
return monWeekStart(new Date(year, 0, 4)); | ||
} | ||
|
||
function isoWeek(date) { | ||
// NOTE: Naive calculation, does not worry about edge cases | ||
var year = date.getFullYear(); | ||
return datum.diff(firstIsoWeekStart(year), date).days / 7 + 1; | ||
} | ||
|
||
var w = new datum.Week(2017, 1, 15, { | ||
weekDayOffset: monWeek, | ||
decorate: function (week) { | ||
week.isoWeek = isoWeek(week.start); | ||
} | ||
}); | ||
|
||
w.isoWeek | ||
// => 7 | ||
``` | ||
|
||
It may seem cumbersome that you need to supply the options every time you want | ||
to create a week object like the one in the example. To address this, the `Week` | ||
constructor has a `Week.factory()` function which takes an options object and | ||
returns a function that returns `Week` objects with just the `year`, `month`, | ||
and `date` arguments. For example: | ||
|
||
```javascript | ||
var isoWeeks = datum.Week.factory({ | ||
weekDayOffset: monWeek, | ||
decorate: function (week) { | ||
week.isoWeek = isoWeek(week.start); | ||
} | ||
}); | ||
|
||
w = isoWeeks(2017, 1, 15); | ||
w.isoWeek | ||
// => 7 | ||
``` | ||
|
||
`isoWeek` is not a constructor so `new` keyword is not required when invoking | ||
it. | ||
|
||
## Utility functions | ||
|
||
The following are utility functions provided by the library. | ||
|
||
**NOTE:** Functions that take more than 2 arguments are auto-curried. This means | ||
that they will return a partially applied version of the function whenever less | ||
than the maximum number of arguments is passed. | ||
|
||
### `datum.resetTime(date)` | ||
|
||
Returns a copy of a `Date` object with time set to midnight local time. | ||
|
||
The input `Date` object is not modified in any way. | ||
|
||
```javascript | ||
datum.resetTime(new Date(2017, 1, 14, 11, 30, 2)); | ||
// => Date(2017, 1, 14, 0, 0, 0) | ||
``` | ||
|
||
### `datum.diff(date1, date2)` | ||
|
||
Returns the absolute difference between two `Date` objects in milliseconds and | ||
days. The return value is an object with `days` and `milliseconds` properties. | ||
Since the return value is an absolute difference, the order of arguments does | ||
not matter. | ||
|
||
```javascript | ||
datum.diff(new Date(2017, 1, 14, 11, 30, 2), new Date(2017, 3, 12, 10, 0, 2)); | ||
// => { days: 57, milliseconds: 4924800000 } | ||
datum.diff(new Date(2017, 3, 12, 10, 0, 2), new Date(2017, 1, 14, 11, 30, 2)); | ||
// => { days: 57, milliseconds: 4924800000 } | ||
``` | ||
|
||
### `datum.shiftDate(n, date)` | ||
|
||
Returns a `Date` object representing the input `Date` object shifted by the | ||
specified number of days. Shifting is done towards future, where positive `n` | ||
shifts by adding, and negative by subtracting. | ||
|
||
The input `Date` object is not modified. | ||
|
||
```javascript | ||
datum.shiftDate(12, new Date(2017, 8, 2)); | ||
// => Date(2017, 8, 14, 0, 0, 0) | ||
``` | ||
|
||
### `datum.range(n, date)` | ||
|
||
Returns an array of `Date` object starting on the date of the input `Date` | ||
object `date`, and containig the specified number of days `n`. | ||
|
||
```javascript | ||
datum.range(4, new Date(2017, 3, 22, 12, 0, 3)); | ||
// => [ | ||
// Date(2017, 3, 22, 0, 0, 0), | ||
// Date(2017, 3, 23, 0, 0, 0), | ||
// Date(2017, 3, 24, 0, 0, 0), | ||
// Date(2017, 3, 25, 0, 0, 0) | ||
// ] | ||
``` | ||
|
||
### `datum.weekDayOffset(n, date)` | ||
|
||
Returns a function that calculates the index of a `Date` object's day of week | ||
(Monday, Tuesday, etc) when the first day is at index `n`, where `n === 0` is | ||
Sunday, and `n === 6` is Saturday. | ||
|
||
```javascript | ||
// What is the week day index of Wednesday, if first day of week is Monday? | ||
datum.weekDayOffset(1, new Date(2017, 1, 15)); | ||
// => 2 | ||
|
||
// What is the week day index of Thursday if first day of week is Saturday? | ||
datum.weekDayOffset(6, new Date(2017, 1, 16)); | ||
// => 5 | ||
``` | ||
|
||
## `datum.weekStart(offsetFn, date)` | ||
|
||
Returns a `Date` object representing the first day of the week in which `date` | ||
is at the index caluclated by the `offsetFn`. For convenience, | ||
`datum.weekDayOffset()` function can be partially applied to supply the | ||
appropriate `offsetFn` function. | ||
|
||
```javascript | ||
var offsetFromMonday = datum.weekDayOffset(1); | ||
datum.weekStart(offsetFromMonday, new Date(2017, 5, 1)); | ||
// => Date(2017, 4, 29, 0, 0, 0) | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
var datum = require('./datum'); | ||
|
||
test('Week creates a week object from a date', function () { | ||
var w = new datum.Week(2017, 1, 12); | ||
}); | ||
|
||
test('Week has an array of dates', function () { | ||
var w = new datum.Week(2017, 1, 12); | ||
var d0 = w.days[0]; | ||
var d6 = w.days[6]; | ||
expect(w.days.length).toBe(7); | ||
expect(d0.getDay()).toBe(0); | ||
expect(d6.getDay()).toBe(6); | ||
expect(d0.getDate()).toBe(12); | ||
expect(d6.getDate()).toBe(18); | ||
}); | ||
|
||
test('Week has start and end', function () { | ||
var w = new datum.Week(2016, 9, 20); | ||
expect(w.start.getDay()).toBe(0); | ||
expect(w.end.getDay()).toBe(6); | ||
expect(w.start.getDate()).toBe(16); | ||
expect(w.end.getDate()).toBe(22); | ||
}); | ||
|
||
test('Number of days in a week can be customized', function () { | ||
var w = new datum.Week(2016, 9, 20, { | ||
daysPerWeek: 5 | ||
}); | ||
expect(w.days.length).toBe(5); | ||
expect(w.start.getDate()).toBe(16); | ||
expect(w.end.getDate()).toBe(20); | ||
}); | ||
|
||
test('Week day offset formula can be changed', function () { | ||
var w = new datum.Week(2016, 9, 20, { | ||
weekDayOffset: datum.weekDayOffset(1) | ||
}); | ||
expect(w.start.getDate()).toBe(17); | ||
expect(w.end.getDate()).toBe(23); | ||
}); | ||
|
||
test('Week object can be decorated arbitrarily', function () { | ||
var w = new datum.Week(2017, 1, 15, { | ||
decorate: function (week) { week.foo = 'bar'; } | ||
}); | ||
expect(w.foo).toEqual('bar'); | ||
}); | ||
|
||
test('Week.factory can be used to create custom week factories', function () { | ||
var wf = datum.Week.factory({ | ||
decorate: function (week) { week.foo = 'bar'; } | ||
}); | ||
var w = wf(2017, 1, 15); | ||
expect(w.foo).toEqual('bar'); | ||
}); |
Oops, something went wrong.