Skip to content

Commit

Permalink
feat: add week and dayofyear time units (#6526)
Browse files Browse the repository at this point in the history
  • Loading branch information
domoritz committed May 25, 2020
1 parent 698bbb2 commit 72a6838
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 44 deletions.
24 changes: 24 additions & 0 deletions build/vega-lite-schema.json
Expand Up @@ -12180,11 +12180,21 @@
"yearmonthdatehours",
"yearmonthdatehoursminutes",
"yearmonthdatehoursminutesseconds",
"yearweek",
"yearweekday",
"yearweekdayhours",
"yearweekdayhoursminutes",
"yearweekdayhoursminutesseconds",
"yeardayofyear",
"quartermonth",
"monthdate",
"monthdatehours",
"monthdatehoursminutes",
"monthdatehoursminutesseconds",
"weekday",
"weeksdayhours",
"weekdayhoursminutes",
"weekdayhoursminutesseconds",
"dayhours",
"dayhoursminutes",
"dayhoursminutesseconds",
Expand All @@ -12200,7 +12210,9 @@
"year",
"quarter",
"month",
"week",
"day",
"dayofyear",
"date",
"hours",
"minutes",
Expand Down Expand Up @@ -19864,11 +19876,21 @@
"utcyearmonthdatehours",
"utcyearmonthdatehoursminutes",
"utcyearmonthdatehoursminutesseconds",
"utcyearweek",
"utcyearweekday",
"utcyearweekdayhours",
"utcyearweekdayhoursminutes",
"utcyearweekdayhoursminutesseconds",
"utcyeardayofyear",
"utcquartermonth",
"utcmonthdate",
"utcmonthdatehours",
"utcmonthdatehoursminutes",
"utcmonthdatehoursminutesseconds",
"utcweekday",
"utcweeksdayhours",
"utcweekdayhoursminutes",
"utcweekdayhoursminutesseconds",
"utcdayhours",
"utcdayhoursminutes",
"utcdayhoursminutesseconds",
Expand All @@ -19884,7 +19906,9 @@
"utcyear",
"utcquarter",
"utcmonth",
"utcweek",
"utcday",
"utcdayofyear",
"utcdate",
"utchours",
"utcminutes",
Expand Down
3 changes: 2 additions & 1 deletion site/docs/transform/timeunit.md
Expand Up @@ -12,8 +12,9 @@ Vega-Lite supports the following time units:
- `"quarter"` - Three-month intervals, starting in one of January, April, July, and October.
- `"month"` - Calendar months (January, February, _etc._).
- `"date"` - Calendar day of the month (January 1, January 2, _etc._).
<!-- - `"week"` - Sunday-based weeks. Days before the first Sunday of the year are considered to be in week 0, the first Sunday of the year is the start of week 1, the second Sunday week 2, _etc._. -->
- `"week"` - Sunday-based weeks. Days before the first Sunday of the year are considered to be in week 0, the first Sunday of the year is the start of week 1, the second Sunday week 2, _etc._.
- `"day"` - Day of the week (Sunday, Monday, _etc._).
- `"dayofyear"` - Day of the year (1, 2, ..., 365, _etc._).
- `"hours"` - Hours of the day (12:00am, 1:00am, _etc._).
- `"minutes"` - Minutes in an hour (12:00, 12:01, _etc._).
- `"seconds"` - Seconds in a minute (12:00:00, 12:00:01, _etc._).
Expand Down
70 changes: 46 additions & 24 deletions src/timeunit.ts
Expand Up @@ -8,8 +8,9 @@ export const LOCAL_SINGLE_TIMEUNIT_INDEX = {
year: 1,
quarter: 1,
month: 1,
// week: 1,
week: 1,
day: 1,
dayofyear: 1,
date: 1,
hours: 1,
minutes: 1,
Expand All @@ -29,8 +30,9 @@ export const UTC_SINGLE_TIMEUNIT_INDEX = {
utcyear: 1,
utcquarter: 1,
utcmonth: 1,
// utcweek: 1,
utcweek: 1,
utcday: 1,
utcdayofyear: 1,
utcdate: 1,
utchours: 1,
utcminutes: 1,
Expand All @@ -52,11 +54,13 @@ export const LOCAL_MULTI_TIMEUNIT_INDEX = {
yearmonthdatehoursminutes: 1,
yearmonthdatehoursminutesseconds: 1,

// yearweek: 1,
// yearweekday: 1,
// yearweekdayhours: 1,
// yearweekdayhoursminutes: 1,
// yearweekdayhoursminutesseconds: 1,
yearweek: 1,
yearweekday: 1,
yearweekdayhours: 1,
yearweekdayhoursminutes: 1,
yearweekdayhoursminutesseconds: 1,

yeardayofyear: 1,

quartermonth: 1,

Expand All @@ -65,10 +69,10 @@ export const LOCAL_MULTI_TIMEUNIT_INDEX = {
monthdatehoursminutes: 1,
monthdatehoursminutesseconds: 1,

// weekday: 1,
// weeksdayhours: 1,
// weekdayhoursminutes: 1,
// weekdayhoursminutesseconds: 1,
weekday: 1,
weeksdayhours: 1,
weekdayhoursminutes: 1,
weekdayhoursminutesseconds: 1,

dayhours: 1,
dayhoursminutes: 1,
Expand All @@ -94,11 +98,13 @@ export const UTC_MULTI_TIMEUNIT_INDEX = {
utcyearmonthdatehoursminutes: 1,
utcyearmonthdatehoursminutesseconds: 1,

// utcyearweek: 1,
// utcyearweekday: 1,
// utcyearweekdayhours: 1,
// utcyearweekdayhoursminutes: 1,
// utcyearweekdayhoursminutesseconds: 1,
utcyearweek: 1,
utcyearweekday: 1,
utcyearweekdayhours: 1,
utcyearweekdayhoursminutes: 1,
utcyearweekdayhoursminutesseconds: 1,

utcyeardayofyear: 1,

utcquartermonth: 1,

Expand All @@ -107,10 +113,10 @@ export const UTC_MULTI_TIMEUNIT_INDEX = {
utcmonthdatehoursminutes: 1,
utcmonthdatehoursminutesseconds: 1,

// utcweekday: 1,
// utcweeksdayhours: 1,
// utcweekdayhoursminutes: 1,
// utcweekdayhoursminutesseconds: 1,
utcweekday: 1,
utcweeksdayhours: 1,
utcweekdayhoursminutes: 1,
utcweekdayhoursminutesseconds: 1,

utcdayhours: 1,
utcdayhoursminutes: 1,
Expand Down Expand Up @@ -148,7 +154,7 @@ export type TimeUnitFormat =
| 'quarter'
| 'month'
| 'date'
// | 'week'
| 'week'
| 'day'
| 'hours'
| 'hours-minutes'
Expand Down Expand Up @@ -203,9 +209,25 @@ export function getTimeUnitParts(timeUnit: TimeUnit) {
/** Returns true if fullTimeUnit contains the timeUnit, false otherwise. */
export function containsTimeUnit(fullTimeUnit: TimeUnit, timeUnit: TimeUnit) {
const index = fullTimeUnit.indexOf(timeUnit);
return (
index > -1 && (timeUnit !== 'seconds' || index === 0 || fullTimeUnit.charAt(index - 1) !== 'i') // exclude milliseconds
);

if (index < 0) {
return false;
}

// exclude milliseconds
if (index > 0 && timeUnit === 'seconds' && fullTimeUnit.charAt(index - 1) === 'i') {
return false;
}

// exclude dayofyear
if (fullTimeUnit.length > index + 3 && timeUnit === 'day' && fullTimeUnit.charAt(index + 3) === 'o') {
return false;
}
if (index > 0 && timeUnit === 'year' && fullTimeUnit.charAt(index - 1) === 'f') {
return false;
}

return true;
}

/**
Expand Down
48 changes: 29 additions & 19 deletions test/timeunit.test.ts
@@ -1,82 +1,92 @@
import {containsTimeUnit, fieldExpr, formatExpression} from '../src/timeunit';
import {containsTimeUnit, fieldExpr, formatExpression, TIMEUNIT_PARTS} from '../src/timeunit';

describe('timeUnit', () => {
describe('containsTimeUnit', () => {
it('should return true for quarter given quarter', () => {
expect(containsTimeUnit('quarter', 'quarter')).toBe(true);
it('should return true for time unit parts', () => {
for (const part of TIMEUNIT_PARTS) {
expect(containsTimeUnit(part, part)).toBe(true);
}
});

it('should return true for yearquarter given quarter', () => {
expect(containsTimeUnit('yearquarter', 'quarter')).toBe(true);
});

it('should return true for SECONDS and MILLISECONDS given SECONDSMILLISECONDS', () => {
it('should return true for seconds and milliseconds given secondsmilliseconds', () => {
expect(containsTimeUnit('secondsmilliseconds', 'seconds')).toBe(true);
});

it('should return true for MILLISECONDS given SECONDSMILLISECONDS', () => {
it('should return true for milliseconds given secondsmilliseconds', () => {
expect(containsTimeUnit('secondsmilliseconds', 'milliseconds')).toBe(true);
});

it('should return false for quarter given year', () => {
expect(containsTimeUnit('year', 'quarter')).toBeFalsy();
});

it('should return false for SECONDS given MILLISECONDS', () => {
it('should return false for seconds given milliseconds', () => {
expect(containsTimeUnit('milliseconds', 'seconds')).toBeFalsy();
});

it('should return false for day given dayofyear', () => {
expect(containsTimeUnit('dayofyear', 'day')).toBeFalsy();
});

it('should return false for year given dayofyear', () => {
expect(containsTimeUnit('dayofyear', 'year')).toBeFalsy();
});
});

describe('fieldExpr', () => {
it('should return correct field expression for YEARMONTHDATEHOURSMINUTESSECONDS', () => {
it('should return correct field expression for yearmonthdatehoursminutesseconds', () => {
expect(fieldExpr('yearmonthdatehoursminutesseconds', 'x')).toBe(
'datetime(year(datum["x"]), month(datum["x"]), date(datum["x"]), hours(datum["x"]), minutes(datum["x"]), seconds(datum["x"]), 0)'
);
});

it('should return correct field expression for QUARTER', () => {
it('should return correct field expression for quarter', () => {
expect(fieldExpr('quarter', 'x')).toBe('datetime(2012, (quarter(datum["x"])-1)*3, 1, 0, 0, 0, 0)');
});

it('should return correct field expression for DAY', () => {
it('should return correct field expression for day', () => {
expect(fieldExpr('day', 'x')).toBe('datetime(2012, 0, day(datum["x"])+1, 0, 0, 0, 0)');
});

it('should return correct field expression for MILLISECONDS', () => {
it('should return correct field expression for milliseconds', () => {
expect(fieldExpr('milliseconds', 'x')).toBe('datetime(2012, 0, 1, 0, 0, 0, milliseconds(datum["x"]))');
});

it('should return correct field expression for MONTHDATE', () => {
it('should return correct field expression for monthdate', () => {
expect(fieldExpr('monthdate', 'x')).toBe('datetime(2012, month(datum["x"]), date(datum["x"]), 0, 0, 0, 0)');
});

it('should return correct field expression with utc for MILLISECONDS', () => {
it('should return correct field expression with utc for milliseconds', () => {
expect(fieldExpr('utcquarter', 'x')).toBe('datetime(2012, (utcquarter(datum["x"])-1)*3, 1, 0, 0, 0, 0)');

expect(fieldExpr('utcmilliseconds', 'x')).toBe('datetime(2012, 0, 1, 0, 0, 0, utcmilliseconds(datum["x"]))');
});
});

describe('template', () => {
it('should return correct template for YEARMONTHDATEHOURSMINUTESSECONDS', () => {
it('should return correct template for yearmonthdatehoursminutesseconds', () => {
expect(formatExpression('yearmonthdatehoursminutesseconds', 'datum.x', false)).toBe(
'timeFormat(datum.x, timeUnitSpecifier(["year","month","date","hours","minutes","seconds"], {"year-month":"%b %Y ","year-month-date":"%b %d, %Y "}))'
);
});

it('should return correct template for YEARMONTH (No comma)', () => {
it('should return correct template for yearmonth (No comma)', () => {
expect(formatExpression('yearmonth', 'datum.x', false)).toBe(
'timeFormat(datum.x, timeUnitSpecifier(["year","month"], {"year-month":"%b %Y ","year-month-date":"%b %d, %Y "}))'
);
});

it('should return correct template for DAY', () => {
it('should return correct template for day', () => {
expect(formatExpression('day', 'datum.x', false)).toBe(
'timeFormat(datum.x, timeUnitSpecifier(["day"], {"year-month":"%b %Y ","year-month-date":"%b %d, %Y "}))'
);
});

it('should return correct template for DAY (shortened)', () => {
it('should return correct template for day (shortened)', () => {
expect(formatExpression('day', 'datum.x', false)).toBe(
'timeFormat(datum.x, timeUnitSpecifier(["day"], {"year-month":"%b %Y ","year-month-date":"%b %d, %Y "}))'
);
Expand All @@ -88,7 +98,7 @@ describe('timeUnit', () => {
);
});

it('should return correct template for YEARQUARTER', () => {
it('should return correct template for yearquarter', () => {
expect(formatExpression('yearquarter', 'datum.x', false)).toBe(
'timeFormat(datum.x, timeUnitSpecifier(["year","quarter"], {"year-month":"%b %Y ","year-month-date":"%b %d, %Y "}))'
);
Expand All @@ -104,13 +114,13 @@ describe('timeUnit', () => {
expect(formatExpression(undefined, 'datum.x', false)).toBeUndefined();
});

it('should return correct template for YEARMONTH (No comma) with utc scale', () => {
it('should return correct template for yearmonth (No comma) with utc scale', () => {
expect(formatExpression('yearmonth', 'datum.x', true)).toBe(
'utcFormat(datum.x, timeUnitSpecifier(["year","month"], {"year-month":"%b %Y ","year-month-date":"%b %d, %Y "}))'
);
});

it('should return correct template for UTCYEARMONTH (No comma)', () => {
it('should return correct template for utcyearmonth (No comma)', () => {
expect(formatExpression('utcyearmonth', 'datum.x', true)).toBe(
'utcFormat(datum.x, timeUnitSpecifier(["year","month"], {"year-month":"%b %Y ","year-month-date":"%b %d, %Y "}))'
);
Expand Down

0 comments on commit 72a6838

Please sign in to comment.