Skip to content

Dates as dates #1078

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 29 commits into from
Nov 10, 2016
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
d4bb89d
improve dateTime2ms edge cases and standardize BADNUM and FP_SAFE con…
alexcjohnson Oct 14, 2016
9e27955
switch date axes to use date strings for range and tick0
alexcjohnson Oct 14, 2016
00138aa
Merge branch 'master' into dates-as-dates
alexcjohnson Oct 14, 2016
9784b3b
axe parseDate - moved to streambed
alexcjohnson Oct 15, 2016
1db4448
refactor dates handling to use date strings as much as possible
alexcjohnson Oct 24, 2016
5ff9722
Merge branch 'master' into dates-as-dates
alexcjohnson Oct 24, 2016
c045bc5
wider acceptance range for mapbox plot size test so it works locally
alexcjohnson Oct 25, 2016
3dd294f
fix for range slider tests with new axis range machinery
alexcjohnson Oct 25, 2016
451ee24
fix date axis ranges and date interactions in gl2d
alexcjohnson Oct 25, 2016
9ccde7e
Merge branch 'master' into dates-as-dates
alexcjohnson Oct 26, 2016
51a0563
lib/constants -> constants/numerical
alexcjohnson Oct 26, 2016
5ccd083
td -> gd once and for all
alexcjohnson Oct 26, 2016
e63ea3b
test and fix shape default positioning
alexcjohnson Oct 26, 2016
2e9dbad
fix date and log axis manual tick0/dtick handling
alexcjohnson Oct 28, 2016
37d52fe
fix handling of default tick0 with dtick for date axes
alexcjohnson Oct 28, 2016
55b313c
get rid of '2012-01-22 12h' tick format and smarter date suffix handling
alexcjohnson Oct 29, 2016
b7121c3
fix #1058 - make sure no date ever returns sub-100microsec precision
alexcjohnson Oct 29, 2016
03b4a0c
Merge branch 'master' into dates-as-dates
alexcjohnson Oct 29, 2016
dd0940a
fix _forceTick0 error
alexcjohnson Oct 29, 2016
f629f71
update baseline with new date tick format
alexcjohnson Oct 29, 2016
a29ce33
fix images on date axes, and standardize axis object names
alexcjohnson Nov 9, 2016
065ac8a
fix annotation drag on date axes, and test date/log annotations
alexcjohnson Nov 9, 2016
4a0a866
Merge branch 'master' into dates-as-dates
alexcjohnson Nov 9, 2016
7679483
update jasmine test of annotation visibility & autorange
alexcjohnson Nov 9, 2016
0d61fcc
Merge branch 'master' into dates-as-dates
alexcjohnson Nov 9, 2016
5e11571
merge lint
alexcjohnson Nov 9, 2016
d807259
add ax.r2p and ax.p2r
alexcjohnson Nov 10, 2016
05c762d
date interval milliseconds constants
alexcjohnson Nov 10, 2016
1b0e133
documentation updates
alexcjohnson Nov 10, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
184 changes: 0 additions & 184 deletions src/lib/dates.js
Original file line number Diff line number Diff line change
Expand Up @@ -187,187 +187,3 @@ exports.ms2DateTime = function(ms, r) {
}
return s;
};

/**
* parseDate: forgiving attempt to turn any date string
* into a javascript date object
*
* first collate all the date formats we want to support, precompiled
* to d3 format objects see below for the string cleaning that happens
* before this separate out 2-digit (y) and 4-digit-year (Y) formats,
* formats with month names (b), and formats with am/pm (I) or no time (D)
* (also includes hour only, as the test is really for a colon) so we can
* cut down the number of tests we need to run for any given string
* (right now all are between 15 and 32 tests)
*/

// TODO: this is way out of date vs. the server-side version
var timeFormats = {
// 24 hour
H: ['%H:%M:%S~%L', '%H:%M:%S', '%H:%M'],
// with am/pm
I: ['%I:%M:%S~%L%p', '%I:%M:%S%p', '%I:%M%p'],
// no colon, ie only date or date with hour (could also support eg 12h34m?)
D: ['%H', '%I%p', '%Hh']
};

var dateFormats = {
Y: [
'%Y~%m~%d',
'%Y%m%d',
'%y%m%d', // YYMMDD, has 6 digits together so will match Y, not y
'%m~%d~%Y', // MM/DD/YYYY has first precedence
'%d~%m~%Y' // then DD/MM/YYYY
],
Yb: [
'%b~%d~%Y', // eg nov 21 2013
'%d~%b~%Y', // eg 21 nov 2013
'%Y~%d~%b', // eg 2013 21 nov (or 2013 q3, after replacement)
'%Y~%b~%d' // eg 2013 nov 21
],
/**
* the two-digit year cases have so many potential ambiguities
* it's not even funny, but we'll try them anyway.
*/
y: [
'%m~%d~%y',
'%d~%m~%y',
'%y~%m~%d'
],
yb: [
'%b~%d~%y',
'%d~%b~%y',
'%y~%d~%b',
'%y~%b~%d'
]
};

// use utc formatter since we're ignoring timezone info
var formatter = d3.time.format.utc;

/**
* ISO8601 and YYYYMMDDHHMMSS are the only ones where date and time
* are not separated by a space, so they get inserted specially here.
* Also a couple formats with no day (so time makes no sense)
*/
var dateTimeFormats = {
Y: {
H: ['%Y~%m~%dT%H:%M:%S', '%Y~%m~%dT%H:%M:%S~%L'].map(formatter),
I: [],
D: ['%Y%m%d%H%M%S', '%Y~%m', '%m~%Y'].map(formatter)
},
Yb: {H: [], I: [], D: ['%Y~%b', '%b~%Y'].map(formatter)},
y: {H: [], I: [], D: []},
yb: {H: [], I: [], D: []}
};
// all others get inserted in all possible combinations from dateFormats and timeFormats
['Y', 'Yb', 'y', 'yb'].forEach(function(dateType) {
dateFormats[dateType].forEach(function(dateFormat) {
// just a date (don't do just a time)
dateTimeFormats[dateType].D.push(formatter(dateFormat));
['H', 'I', 'D'].forEach(function(timeType) {
timeFormats[timeType].forEach(function(timeFormat) {
var a = dateTimeFormats[dateType][timeType];

// 'date time', then 'time date'
a.push(formatter(dateFormat + '~' + timeFormat));
a.push(formatter(timeFormat + '~' + dateFormat));
});
});
});
});

// precompiled regexps for performance
var matchword = /[a-z]*/g,
shortenword = function(m) { return m.substr(0, 3); },
weekdaymatch = /(mon|tue|wed|thu|fri|sat|sun|the|of|st|nd|rd|th)/g,
separatormatch = /[\s,\/\-\.\(\)]+/g,
ampmmatch = /~?([ap])~?m(~|$)/,
replaceampm = function(m, ap) { return ap + 'm '; },
match4Y = /\d\d\d\d/,
matchMonthName = /(^|~)[a-z]{3}/,
matchAMPM = /[ap]m/,
matchcolon = /:/,
matchquarter = /q([1-4])/,
quarters = ['31~mar', '30~jun', '30~sep', '31~dec'],
replacequarter = function(m, n) { return quarters[n - 1]; },
matchTZ = / ?([+\-]\d\d:?\d\d|Z)$/;

function getDateType(v) {
var dateType;
dateType = (match4Y.test(v) ? 'Y' : 'y');
dateType = dateType + (matchMonthName.test(v) ? 'b' : '');
return dateType;
}

function getTimeType(v) {
var timeType;
timeType = matchcolon.test(v) ? (matchAMPM.test(v) ? 'I' : 'H') : 'D';
return timeType;
}

exports.parseDate = function(v) {
// is it already a date? just return it
if(v.getTime) return v;
/**
* otherwise, if it's not a string, return nothing
* the case of numbers that just have years will get
* dealt with elsewhere.
*/
if(typeof v !== 'string') return false;

// first clean up the string a bit to reduce the number of formats we have to test
v = v.toLowerCase()
/**
* cut all words down to 3 characters - this will result in
* some spurious matches, ie whenever the first three characters
* of a word match a month or weekday but that seems more likely
* to fix typos than to make dates where they shouldn't be...
* and then we can omit the long form of months from our testing
*/
.replace(matchword, shortenword)
/**
* remove weekday names, as they get overridden anyway if they're
* inconsistent also removes a few more words
* (ie "tuesday the 26th of november")
* TODO: language support?
* for months too, but these seem to be built into d3
*/
.replace(weekdaymatch, '')
/**
* collapse all separators one ~ at a time, except : which seems
* pretty consistent for the time part use ~ instead of space or
* something since d3 can eat a space as padding on 1-digit numbers
*/
.replace(separatormatch, '~')
// in case of a.m. or p.m. (also take off any space before am/pm)
.replace(ampmmatch, replaceampm)
// turn quarters Q1-4 into dates (quarter ends)
.replace(matchquarter, replacequarter)
.trim()
// also try to ignore timezone info, at least for now
.replace(matchTZ, '');

// now test against the various formats that might match
var out = null,
dateType = getDateType(v),
timeType = getTimeType(v),
formatList,
len;

formatList = dateTimeFormats[dateType][timeType];
len = formatList.length;

for(var i = 0; i < len; i++) {
out = formatList[i].parse(v);
if(out) break;
}

// If not an instance of Date at this point, just return it.
if(!(out instanceof Date)) return false;
// parse() method interprets arguments with local time zone.
var tzoff = out.getTimezoneOffset();
// In general (default) this is not what we want, so force into UTC:
out.setTime(out.getTime() + tzoff * 60 * 1000);
return out;
};
1 change: 0 additions & 1 deletion src/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ var datesModule = require('./dates');
lib.dateTime2ms = datesModule.dateTime2ms;
lib.isDateTime = datesModule.isDateTime;
lib.ms2DateTime = datesModule.ms2DateTime;
lib.parseDate = datesModule.parseDate;
lib.MIN_MS = datesModule.MIN_MS;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉

lib.MAX_MS = datesModule.MAX_MS;

Expand Down
33 changes: 0 additions & 33 deletions test/jasmine/tests/lib_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,39 +12,6 @@ var Plots = PlotlyInternal.Plots;
describe('Test lib.js:', function() {
'use strict';

describe('parseDate() should', function() {
it('return false on bad (number) input:', function() {
expect(Lib.parseDate(0)).toBe(false);
});
it('return false on bad (string) input:', function() {
expect(Lib.parseDate('toto')).toBe(false);
});
it('work with yyyy-mm-dd string input:', function() {
var input = '2014-12-01',
res = Lib.parseDate(input),
res0 = new Date(2014, 11, 1);
expect(res.getTime()).toEqual(res0.getTime());
});
it('work with mm/dd/yyyy string input:', function() {
var input = '12/01/2014',
res = Lib.parseDate(input),
res0 = new Date(2014, 11, 1);
expect(res.getTime()).toEqual(res0.getTime());
});
it('work with yyyy-mm-dd HH:MM:SS.sss string input:', function() {
var input = '2014-12-01 09:50:05.124',
res = Lib.parseDate(input),
res0 = new Date(2014, 11, 1, 9, 50, 5, 124);
expect(res.getTime()).toEqual(res0.getTime());
});
it('work with mm/dd/yyyy HH:MM:SS string input:', function() {
var input = '2014-12-01 09:50:05',
res = Lib.parseDate(input),
res0 = new Date(2014, 11, 1, 9, 50, 5);
expect(res.getTime()).toEqual(res0.getTime());
});
});

describe('interp() should', function() {
it('return 1.75 as Q1 of [1, 2, 3, 4, 5]:', function() {
var input = [1, 2, 3, 4, 5],
Expand Down