Skip to content

Commit dcddcee

Browse files
committed
support chinese calendar
1 parent 08f18ca commit dcddcee

File tree

7 files changed

+141
-24
lines changed

7 files changed

+141
-24
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@
9090
"tinycolor2": "^1.3.0",
9191
"topojson-client": "^2.1.0",
9292
"webgl-context": "^2.2.0",
93-
"world-calendars": "0.1.0"
93+
"world-calendars": "^1.0.0"
9494
},
9595
"devDependencies": {
9696
"brfs": "^1.4.3",

src/components/calendars/calendars.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ module.exports = require('world-calendars/dist/main');
1515

1616
require('world-calendars/dist/plus');
1717

18+
require('world-calendars/dist/calendars/chinese');
1819
require('world-calendars/dist/calendars/coptic');
1920
require('world-calendars/dist/calendars/discworld');
2021
require('world-calendars/dist/calendars/ethiopian');

src/components/calendars/index.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ var handleTraceDefaults = function(traceIn, traceOut, coords, layout) {
4141
// all support either of those dates. Instead I'll use the most significant
4242
// number they *do* support, biased toward the present day.
4343
var CANONICAL_TICK = {
44+
chinese: '2000-01-01',
4445
coptic: '2000-01-01',
4546
discworld: '2000-01-01',
4647
ethiopian: '2000-01-01',
@@ -58,11 +59,13 @@ var CANONICAL_TICK = {
5859
};
5960

6061
// Start on a Sunday - for week ticks
61-
// Discworld and Mayan calendars don't have 7-day weeks anyway so don't change them.
62+
// Discworld and Mayan calendars don't have 7-day weeks but we're going to give them
63+
// 7-day week ticks so start on our Sundays.
6264
// If anyone really cares we can customize the auto tick spacings for these calendars.
6365
var CANONICAL_SUNDAY = {
66+
chinese: '2000-01-02',
6467
coptic: '2000-01-03',
65-
discworld: '2000-01-01',
68+
discworld: '2000-01-03',
6669
ethiopian: '2000-01-05',
6770
hebrew: '5000-01-01',
6871
islamic: '1000-01-02',
@@ -78,6 +81,7 @@ var CANONICAL_SUNDAY = {
7881
};
7982

8083
var DFLTRANGE = {
84+
chinese: ['2000-01-01', '2001-01-01'],
8185
coptic: ['1700-01-01', '1701-01-01'],
8286
discworld: ['1800-01-01', '1801-01-01'],
8387
ethiopian: ['2000-01-01', '2001-01-01'],

src/lib/dates.js

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ var Registry = require('../registry');
2828
var utcFormat = d3.time.format.utc;
2929

3030
var DATETIME_REGEXP = /^\s*(-?\d\d\d\d|\d\d)(-(\d?\d)(-(\d?\d)([ Tt]([01]?\d|2[0-3])(:([0-5]\d)(:([0-5]\d(\.\d+)?))?(Z|z|[+\-]\d\d:?\d\d)?)?)?)?)?\s*$/m;
31+
// special regex for chinese calendars to support yyyy-mmi-dd etc for intercalary months
32+
var DATETIME_REGEXP_CN = /^\s*(-?\d\d\d\d|\d\d)(-(\d?\di?)(-(\d?\d)([ Tt]([01]?\d|2[0-3])(:([0-5]\d)(:([0-5]\d(\.\d+)?))?(Z|z|[+\-]\d\d:?\d\d)?)?)?)?)?\s*$/m;
3133

3234
// for 2-digit years, the first year we map them onto
3335
var YFIRST = new Date().getFullYear() - 70;
@@ -155,10 +157,12 @@ exports.dateTime2ms = function(s, calendar) {
155157
calendar = '';
156158
}
157159

158-
var match = s.match(DATETIME_REGEXP);
160+
var isChinese = calendar && calendar.substr(0, 7) === 'chinese';
161+
162+
var match = s.match(isChinese ? DATETIME_REGEXP_CN : DATETIME_REGEXP);
159163
if(!match) return BADNUM;
160164
var y = match[1],
161-
m = Number(match[3] || 1),
165+
m = match[3] || '1',
162166
d = Number(match[5] || 1),
163167
H = Number(match[7] || 0),
164168
M = Number(match[9] || 0),
@@ -167,11 +171,19 @@ exports.dateTime2ms = function(s, calendar) {
167171
if(isWorld) {
168172
// disallow 2-digit years for world calendars
169173
if(y.length === 2) return BADNUM;
174+
y = Number(y);
170175

171176
var cDate;
172177
try {
173-
cDate = Registry.getComponentMethod('calendars', 'getCal')(calendar)
174-
.newDate(Number(y), m, d);
178+
var calInstance = Registry.getComponentMethod('calendars', 'getCal')(calendar);
179+
if(isChinese) {
180+
var isIntercalary = m.charAt(m.length - 1) === 'i';
181+
m = Number(isIntercalary ? m.substr(0, m.length - 1) : m);
182+
cDate = calInstance.newDate(y, calInstance.toMonthIndex(y, m, isIntercalary), d);
183+
}
184+
else {
185+
cDate = calInstance.newDate(y, Number(m), d);
186+
}
175187
}
176188
catch(e) { return BADNUM; } // Invalid ... date
177189

src/plots/cartesian/axes.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -924,9 +924,9 @@ function autoTickRound(ax) {
924924
// If tick0 is unusual, give tickround a bit more information
925925
// not necessarily *all* the information in tick0 though, if it's really odd
926926
// minimal string length for tick0: 'd' is 10, 'M' is 16, 'S' is 19
927-
// take off a leading minus (year < 0 so length is consistent)
927+
// take off a leading minus (year < 0) and i (intercalary month) so length is consistent
928928
var tick0ms = ax.r2l(ax.tick0),
929-
tick0str = ax.l2r(tick0ms).replace(/^-/, ''),
929+
tick0str = ax.l2r(tick0ms).replace(/(^-|i)/g, ''),
930930
tick0len = tick0str.length;
931931

932932
if(String(dtick).charAt(0) === 'M') {

test/image/mocks/gl3d_world-cals.json

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,12 @@
2727
],
2828
"type": "mesh3d",
2929
"x": [
30-
"2000-01-01",
31-
"2000-02-01",
32-
"2000-03-01",
33-
"2000-01-01"
30+
"2001-04-01",
31+
"2001-04i-01",
32+
"2001-05-01",
33+
"2001-04-01"
3434
],
35-
"xcalendar": "ethiopian",
35+
"xcalendar": "chinese",
3636
"y": [
3737
"0100-01-01",
3838
"0100-01-01",
@@ -50,12 +50,12 @@
5050
},
5151
{
5252
"x": [
53-
"2000-01-01",
54-
"2000-01-01",
55-
"2000-03-01",
56-
"2000-03-01"
53+
"2001-04-01",
54+
"2001-04-01",
55+
"2001-05-01",
56+
"2001-05-01"
5757
],
58-
"xcalendar": "ethiopian",
58+
"xcalendar": "chinese",
5959
"y": [
6060
"0100-01-01",
6161
"0100-03-01",
@@ -74,10 +74,10 @@
7474
},
7575
{
7676
"x": [
77-
"2000-01-01",
78-
"2000-03-01"
77+
"2001-04-01",
78+
"2001-05-01"
7979
],
80-
"xcalendar": "ethiopian",
80+
"xcalendar": "chinese",
8181
"y": [
8282
"0100-01-01",
8383
"0100-03-01"
@@ -111,8 +111,8 @@
111111
"type": "date"
112112
},
113113
"zaxis": {
114-
"title": "ethiopian",
115-
"calendar": "ethiopian",
114+
"title": "chinese",
115+
"calendar": "chinese",
116116
"type": "date"
117117
},
118118
"camera": {

test/jasmine/tests/lib_date_test.js

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
var isNumeric = require('fast-isnumeric');
2+
23
var Lib = require('@src/lib');
4+
var calComponent = require('@src/components/calendars');
5+
6+
// use only the parts of world-calendars that we've imported for our tests
7+
var calendars = require('@src/components/calendars/calendars');
38

49
describe('dates', function() {
510
'use strict';
@@ -240,6 +245,7 @@ describe('dates', function() {
240245
[
241246
[undefined, '1970-01-01'],
242247
['gregorian', '1970-01-01'],
248+
['chinese', '1969-11-24'],
243249
['coptic', '1686-04-23'],
244250
['discworld', '1798-12-27'],
245251
['ethiopian', '1962-04-23'],
@@ -284,6 +290,55 @@ describe('dates', function() {
284290
expect(Lib.dateTime2ms(expected_lastinstant, calendar)).toBe(lastInstant, calendar);
285291
});
286292
});
293+
294+
it('should contain canonical ticks sundays, ranges for all calendars', function() {
295+
var calList = Object.keys(calendars.calendars).filter(function(v) {
296+
return v !== 'gregorian';
297+
});
298+
299+
var canonicalTick = calComponent.CANONICAL_TICK,
300+
canonicalSunday = calComponent.CANONICAL_SUNDAY,
301+
dfltRange = calComponent.DFLTRANGE;
302+
expect(Object.keys(canonicalTick).length).toBe(calList.length);
303+
expect(Object.keys(canonicalSunday).length).toBe(calList.length);
304+
expect(Object.keys(dfltRange).length).toBe(calList.length);
305+
306+
calList.forEach(function(calendar) {
307+
expect(Lib.dateTime2ms(canonicalTick[calendar], calendar)).toBeDefined(calendar);
308+
var sunday = Lib.dateTime2ms(canonicalSunday[calendar], calendar);
309+
// convert back implicitly with gregorian calendar
310+
expect(Lib.formatDate(sunday, '%A')).toBe('Sunday', calendar);
311+
312+
expect(Lib.dateTime2ms(dfltRange[calendar][0], calendar)).toBeDefined(calendar);
313+
expect(Lib.dateTime2ms(dfltRange[calendar][1], calendar)).toBeDefined(calendar);
314+
});
315+
});
316+
317+
it('should handle Chinese intercalary months correctly', function() {
318+
var intercalaryDates = [
319+
'1995-08i-01',
320+
'1995-08i-29',
321+
'1984-10i-15',
322+
'2023-02i-29'
323+
];
324+
intercalaryDates.forEach(function(v) {
325+
var ms = Lib.dateTime2ms(v, 'chinese');
326+
expect(Lib.ms2DateTime(ms, 0, 'chinese')).toBe(v);
327+
328+
// should also work without leading zeros
329+
var vShort = v.replace(/-0/g, '-');
330+
expect(Lib.dateTime2ms(vShort, 'chinese')).toBe(ms, vShort);
331+
});
332+
333+
var badIntercalaryDates = [
334+
'1995-07i-01',
335+
'1995-08i-30',
336+
'1995-09i-01'
337+
];
338+
badIntercalaryDates.forEach(function(v) {
339+
expect(Lib.dateTime2ms(v, 'chinese')).toBeUndefined(v);
340+
});
341+
});
287342
});
288343

289344
describe('cleanDate', function() {
@@ -341,6 +396,51 @@ describe('dates', function() {
341396
});
342397
});
343398

399+
describe('incrementMonth', function() {
400+
it('should include Chinese intercalary months', function() {
401+
var start = '1995-06-01';
402+
var expected = [
403+
'1995-07-01',
404+
'1995-08-01',
405+
'1995-08i-01',
406+
'1995-09-01',
407+
'1995-10-01',
408+
'1995-11-01',
409+
'1995-12-01',
410+
'1996-01-01'
411+
];
412+
var tick = Lib.dateTime2ms(start, 'chinese');
413+
expected.forEach(function(v) {
414+
tick = Lib.incrementMonth(tick, 1, 'chinese');
415+
expect(tick).toBe(Lib.dateTime2ms(v, 'chinese'), v);
416+
});
417+
});
418+
419+
it('should increment years even over leap years', function() {
420+
var start = '1995-06-01';
421+
var expected = [
422+
'1996-06-01',
423+
'1997-06-01',
424+
'1998-06-01',
425+
'1999-06-01',
426+
'2000-06-01',
427+
'2001-06-01',
428+
'2002-06-01',
429+
'2003-06-01',
430+
'2004-06-01',
431+
'2005-06-01',
432+
'2006-06-01',
433+
'2007-06-01',
434+
'2008-06-01'
435+
];
436+
var tick = Lib.dateTime2ms(start, 'chinese');
437+
expected.forEach(function(v) {
438+
tick = Lib.incrementMonth(tick, 12, 'chinese');
439+
expect(tick).toBe(Lib.dateTime2ms(v, 'chinese'), v);
440+
});
441+
});
442+
});
443+
344444
describe('isJSDate', function() {
345445
it('should return true for any Date object but not the equivalent numbers', function() {
346446
[

0 commit comments

Comments
 (0)