Skip to content
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

ISO 8601 year (%G and %g) #52

Merged
merged 4 commits into from
Aug 23, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ Returns a new formatter for the given string *specifier*. The specifier string m
* `%d` - zero-padded day of the month as a decimal number [01,31].
* `%e` - space-padded day of the month as a decimal number [ 1,31]; equivalent to `%_d`.
* `%f` - microseconds as a decimal number [000000, 999999].
* `%g` - ISO 8601 week-based year without century as a decimal number [00,99].
* `%G` - ISO 8601 week-based year with century as a decimal number.
* `%H` - hour (24-hour clock) as a decimal number [00,23].
* `%I` - hour (12-hour clock) as a decimal number [01,12].
* `%j` - day of the year as a decimal number [001,366].
Expand Down Expand Up @@ -136,9 +138,9 @@ Directives marked with an asterisk (\*) may be affected by the [locale definitio

For `%U`, all days in a new year preceding the first Sunday are considered to be in week 0. For `%W`, all days in a new year preceding the first Monday are considered to be in week 0. Week numbers are computed using [*interval*.count](https://github.com/d3/d3-time/blob/master/README.md#interval_count). For example, 2015-52 and 2016-00 represent Monday, December 28, 2015, while 2015-53 and 2016-01 represent Monday, January 4, 2016. This differs from the [ISO week date](https://en.wikipedia.org/wiki/ISO_week_date) specification (`%V`), which uses a more complicated definition!

For `%V`, per the [strftime man page](http://man7.org/linux/man-pages/man3/strftime.3.html):
For `%V`,`%g` and `%G`, per the [strftime man page](http://man7.org/linux/man-pages/man3/strftime.3.html):

> In this system, weeks start on a Monday, and are numbered from 01, for the first week, up to 52 or 53, for the last week. Week 1 is the first week where four or more days fall within the new year (or, synonymously, week 01 is: the first week of the year that contains a Thursday; or, the week that has 4 January in it).
> In this system, weeks start on a Monday, and are numbered from 01, for the first week, up to 52 or 53, for the last week. Week 1 is the first week where four or more days fall within the new year (or, synonymously, week 01 is: the first week of the year that contains a Thursday; or, the week that has 4 January in it). If the ISO week number belongs to the previous or next year, that year is used instead.

The `%` sign indicating a directive may be immediately followed by a padding modifier:

Expand Down
44 changes: 40 additions & 4 deletions src/locale.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ export default function formatLocale(locale) {
"d": formatDayOfMonth,
"e": formatDayOfMonth,
"f": formatMicroseconds,
"g": formatYearISO,
"G": formatFullYearISO,
"H": formatHour24,
"I": formatHour12,
"j": formatDayOfYear,
Expand Down Expand Up @@ -96,6 +98,8 @@ export default function formatLocale(locale) {
"d": formatUTCDayOfMonth,
"e": formatUTCDayOfMonth,
"f": formatUTCMicroseconds,
"g": formatUTCYearISO,
"G": formatUTCFullYearISO,
"H": formatUTCHour24,
"I": formatUTCHour12,
"j": formatUTCDayOfYear,
Expand Down Expand Up @@ -129,6 +133,8 @@ export default function formatLocale(locale) {
"d": parseDayOfMonth,
"e": parseDayOfMonth,
"f": parseMicroseconds,
"g": parseYear,
"G": parseFullYear,
"H": parseHour24,
"I": parseHour24,
"j": parseDayOfYear,
Expand Down Expand Up @@ -550,9 +556,13 @@ function formatWeekNumberSunday(d, p) {
return pad(timeSunday.count(timeYear(d) - 1, d), p, 2);
}

function formatWeekNumberISO(d, p) {
function dISO(d) {
var day = d.getDay();
d = (day >= 4 || day === 0) ? timeThursday(d) : timeThursday.ceil(d);
return (day >= 4 || day === 0) ? timeThursday(d) : timeThursday.ceil(d);
}

function formatWeekNumberISO(d, p) {
d = dISO(d);
return pad(timeThursday.count(timeYear(d), d) + (timeYear(d).getDay() === 4), p, 2);
}

Expand All @@ -568,10 +578,21 @@ function formatYear(d, p) {
return pad(d.getFullYear() % 100, p, 2);
}

function formatYearISO(d, p) {
d = dISO(d);
return pad(d.getFullYear() % 100, p, 2);
}

function formatFullYear(d, p) {
return pad(d.getFullYear() % 10000, p, 4);
}

function formatFullYearISO(d, p) {
var day = d.getDay();
d = (day >= 4 || day === 0) ? timeThursday(d) : timeThursday.ceil(d);
return pad(d.getFullYear() % 10000, p, 4);
}

function formatZone(d) {
var z = d.getTimezoneOffset();
return (z > 0 ? "-" : (z *= -1, "+"))
Expand Down Expand Up @@ -624,9 +645,13 @@ function formatUTCWeekNumberSunday(d, p) {
return pad(utcSunday.count(utcYear(d) - 1, d), p, 2);
}

function formatUTCWeekNumberISO(d, p) {
function UTCdISO(d) {
var day = d.getUTCDay();
d = (day >= 4 || day === 0) ? utcThursday(d) : utcThursday.ceil(d);
return (day >= 4 || day === 0) ? utcThursday(d) : utcThursday.ceil(d);
}

function formatUTCWeekNumberISO(d, p) {
d = UTCdISO(d);
return pad(utcThursday.count(utcYear(d), d) + (utcYear(d).getUTCDay() === 4), p, 2);
}

Expand All @@ -642,10 +667,21 @@ function formatUTCYear(d, p) {
return pad(d.getUTCFullYear() % 100, p, 2);
}

function formatUTCYearISO(d, p) {
d = UTCdISO(d);
return pad(d.getUTCFullYear() % 100, p, 2);
}

function formatUTCFullYear(d, p) {
return pad(d.getUTCFullYear() % 10000, p, 4);
}

function formatUTCFullYearISO(d, p) {
var day = d.getUTCDay();
d = (day >= 4 || day === 0) ? utcThursday(d) : utcThursday.ceil(d);
return pad(d.getUTCFullYear() % 10000, p, 4);
}

function formatUTCZone() {
return "+0000";
}
Expand Down
16 changes: 16 additions & 0 deletions test/format-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,22 @@ tape("timeFormat(\"%e\")(date) formats space-padded dates", function(test) {
test.end();
});

tape("timeFormat(\"%g\")(date) formats zero-padded two-digit ISO 8601 years", function (test) {
var f = timeFormat.timeFormat("%g");
test.equal(f(date.local(2018, 11, 30, 0)), "18"); // Sunday
test.equal(f(date.local(2018, 11, 31, 0)), "19"); // Monday
test.equal(f(date.local(2019, 0, 1, 0)), "19");
test.end();
});

tape("timeFormat(\"%G\")(date) formats zero-padded four-digit ISO 8601 years", function (test) {
var f = timeFormat.timeFormat("%G");
test.equal(f(date.local(2018, 11, 30, 0)), "2018"); // Sunday
test.equal(f(date.local(2018, 11, 31, 0)), "2019"); // Monday
test.equal(f(date.local(2019, 0, 1, 0)), "2019");
test.end();
});

tape("timeFormat(\"%H\")(date) formats zero-padded hours (24)", function(test) {
var f = timeFormat.timeFormat("%H");
test.equal(f(date.local(1990, 0, 1, 0)), "00");
Expand Down
30 changes: 28 additions & 2 deletions test/parse-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,39 @@ tape("timeParse(\"%w %U %Y\")(date) parses numeric weekday (Sunday), week number
test.end();
});

tape("timeParse(\"%w %V %Y\")(date) parses numeric weekday, week number (ISO) and year", function(test) {
var p = timeFormat.timeParse("%w %V %Y");
tape("timeParse(\"%w %V %G\")(date) parses numeric weekday, week number (ISO) and corresponding year", function(test) {
var p = timeFormat.timeParse("%w %V %G");
test.deepEqual(p("1 01 1990"), date.local(1990, 0, 1));
test.deepEqual(p("0 05 1991"), date.local(1991, 1, 3));
test.deepEqual(p("4 53 1992"), date.local(1992, 11, 31));
test.deepEqual(p("0 52 1994"), date.local(1995, 0, 1));
test.deepEqual(p("0 01 1995"), date.local(1995, 0, 8));
test.deepEqual(p("1 01 2018"), date.local(2018, 0, 1));
test.deepEqual(p("1 01 2019"), date.local(2018, 11, 31));
test.end();
});

tape("timeParse(\"%w %V %g\")(date) parses numeric weekday, week number (ISO) and corresponding two-digits year", function(test) {
var p = timeFormat.timeParse("%w %V %g");
test.deepEqual(p("1 01 90"), date.local(1990, 0, 1));
test.deepEqual(p("0 05 91"), date.local(1991, 1, 3));
test.deepEqual(p("4 53 92"), date.local(1992, 11, 31));
test.deepEqual(p("0 52 94"), date.local(1995, 0, 1));
test.deepEqual(p("0 01 95"), date.local(1995, 0, 8));
test.deepEqual(p("1 01 18"), date.local(2018, 0, 1));
test.deepEqual(p("1 01 19"), date.local(2018, 11, 31));
test.end();
});

tape("timeParse(\"%V %g\")(date) parses week number (ISO) and corresponding two-digits year", function(test) {
var p = timeFormat.timeParse("%V %g");
test.deepEqual(p("01 90"), date.local(1990, 0, 1));
test.deepEqual(p("05 91"), date.local(1991, 0, 28));
test.deepEqual(p("53 92"), date.local(1992, 11, 28));
test.deepEqual(p("52 94"), date.local(1994, 11, 26));
test.deepEqual(p("01 95"), date.local(1995, 0, 2));
test.deepEqual(p("01 18"), date.local(2018, 0, 1));
test.deepEqual(p("01 19"), date.local(2018, 11, 31));
test.end();
});

Expand Down
16 changes: 16 additions & 0 deletions test/utcFormat-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,22 @@ tape("utcFormat(\"%e\")(date) formats space-padded dates", function(test) {
test.end();
});

tape("timeFormat(\"%g\")(date) formats zero-padded two-digit ISO 8601 years", function (test) {
var f = timeFormat.utcFormat("%g");
test.equal(f(date.utc(2018, 11, 30, 0)), "18"); // Sunday
test.equal(f(date.utc(2018, 11, 31, 0)), "19"); // Monday
test.equal(f(date.utc(2019, 0, 1, 0)), "19");
test.end();
});

tape("utcFormat(\"%G\")(date) formats zero-padded four-digit ISO 8601 years", function (test) {
var f = timeFormat.utcFormat("%G");
test.equal(f(date.utc(2018, 11, 30, 0)), "2018"); // Sunday
test.equal(f(date.utc(2018, 11, 31, 0)), "2019"); // Monday
test.equal(f(date.utc(2019, 0, 1, 0)), "2019");
test.end();
});

tape("utcFormat(\"%H\")(date) formats zero-padded hours (24)", function(test) {
var f = timeFormat.utcFormat("%H");
test.equal(f(date.utc(1990, 0, 1, 0)), "00");
Expand Down
43 changes: 43 additions & 0 deletions test/utcParse-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,19 @@ tape("utcParse(\"%w %V %Y\")(date) parses numeric weekday, week number (ISO) and
test.end();
});

tape("utcParse(\"%w %V %G\")(date) parses numeric weekday, week number (ISO) and corresponding year", function(test) {
var p = timeFormat.utcParse("%w %V %G");
test.deepEqual(p("1 01 1990"), date.utc(1990, 0, 1));
test.deepEqual(p("0 05 1991"), date.utc(1991, 1, 3));
test.deepEqual(p("4 53 1992"), date.utc(1992, 11, 31));
test.deepEqual(p("0 52 1994"), date.utc(1995, 0, 1));
test.deepEqual(p("0 01 1995"), date.utc(1995, 0, 8));
test.deepEqual(p("1 01 2018"), date.utc(2018, 0, 1));
test.deepEqual(p("1 01 2019"), date.utc(2018, 11, 31));
test.equal(p("X 03 2010"), null);
test.end();
});

tape("utcParse(\"%V %Y\")(date) week number (ISO) and year", function(test) {
var p = timeFormat.utcParse("%V %Y");
test.deepEqual(p("01 1990"), date.utc(1990, 0, 1));
Expand All @@ -163,6 +176,36 @@ tape("utcParse(\"%V %Y\")(date) week number (ISO) and year", function(test) {
test.end();
});

tape("utcParse(\"%V %g\")(date) week number (ISO) and corresponding two-digits year", function(test) {
var p = timeFormat.utcParse("%V %g");
test.deepEqual(p("01 90"), date.utc(1990, 0, 1));
test.deepEqual(p("05 91"), date.utc(1991, 0, 28));
test.deepEqual(p("53 92"), date.utc(1992, 11, 28));
test.deepEqual(p("01 93"), date.utc(1993, 0, 4));
test.deepEqual(p("01 95"), date.utc(1995, 0, 2));
test.deepEqual(p("01 18"), date.utc(2018, 0, 1));
test.deepEqual(p("01 19"), date.utc(2018, 11, 31));
test.deepEqual(p("00 95"), null);
test.deepEqual(p("54 95"), null);
test.deepEqual(p("X 95"), null);
test.end();
});

tape("utcParse(\"%V %G\")(date) week number (ISO) and corresponding year", function(test) {
var p = timeFormat.utcParse("%V %G");
test.deepEqual(p("01 1990"), date.utc(1990, 0, 1));
test.deepEqual(p("05 1991"), date.utc(1991, 0, 28));
test.deepEqual(p("53 1992"), date.utc(1992, 11, 28));
test.deepEqual(p("01 1993"), date.utc(1993, 0, 4));
test.deepEqual(p("01 1995"), date.utc(1995, 0, 2));
test.deepEqual(p("01 2018"), date.utc(2018, 0, 1));
test.deepEqual(p("01 2019"), date.utc(2018, 11, 31));
test.deepEqual(p("00 1995"), null);
test.deepEqual(p("54 1995"), null);
test.deepEqual(p("X 1995"), null);
test.end();
});

tape("utcParse(\"%Q\")(date) parses UNIX timestamps", function(test) {
var p = timeFormat.utcParse("%Q");
test.deepEqual(p("0"), date.utc(1970, 0, 1));
Expand Down