Skip to content

Commit

Permalink
Merge pull request #52 from davinov/year-utc
Browse files Browse the repository at this point in the history
ISO 8601 year (%G and %g)
  • Loading branch information
Fil authored Aug 23, 2020
2 parents 09d57d9 + 0e7a6b7 commit 21fe0a1
Show file tree
Hide file tree
Showing 6 changed files with 147 additions and 8 deletions.
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

0 comments on commit 21fe0a1

Please sign in to comment.