date-format 2.1.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.js CHANGED
@@ -30,10 +30,6 @@ function offset(timezoneOffset) {
30
30
  return timezoneOffset < 0 ? "+" + h + m : "-" + h + m;
31
31
  }
32
32
 
33
- function datePart(date, displayUTC, part) {
34
- return displayUTC ? date["getUTC" + part]() : date["get" + part]();
35
- }
36
-
37
33
  function asString(format, date) {
38
34
  if (typeof format !== "string") {
39
35
  date = format;
@@ -43,20 +39,19 @@ function asString(format, date) {
43
39
  date = module.exports.now();
44
40
  }
45
41
 
46
- var displayUTC = format.indexOf("O") > -1;
42
+ // Issue # 14 - Per ISO8601 standard, the time string should be local time
43
+ // with timezone info.
44
+ // See https://en.wikipedia.org/wiki/ISO_8601 section "Time offsets from UTC"
47
45
 
48
- var vDay = addZero(datePart(date, displayUTC, "Date"));
49
- var vMonth = addZero(datePart(date, displayUTC, "Month") + 1);
50
- var vYearLong = addZero(datePart(date, displayUTC, "FullYear"));
46
+ var vDay = addZero(date.getDate());
47
+ var vMonth = addZero(date.getMonth() + 1);
48
+ var vYearLong = addZero(date.getFullYear());
51
49
  var vYearShort = addZero(vYearLong.substring(2, 4));
52
50
  var vYear = format.indexOf("yyyy") > -1 ? vYearLong : vYearShort;
53
- var vHour = addZero(datePart(date, displayUTC, "Hours"));
54
- var vMinute = addZero(datePart(date, displayUTC, "Minutes"));
55
- var vSecond = addZero(datePart(date, displayUTC, "Seconds"));
56
- var vMillisecond = padWithZeros(
57
- datePart(date, displayUTC, "Milliseconds"),
58
- 3
59
- );
51
+ var vHour = addZero(date.getHours());
52
+ var vMinute = addZero(date.getMinutes());
53
+ var vSecond = addZero(date.getSeconds());
54
+ var vMillisecond = padWithZeros(date.getMilliseconds(), 3);
60
55
  var vTimeZone = offset(date.getTimezoneOffset());
61
56
  var formatted = format
62
57
  .replace(/dd/g, vDay)
@@ -70,55 +65,63 @@ function asString(format, date) {
70
65
  return formatted;
71
66
  }
72
67
 
68
+ function setDatePart(date, part, value, local) {
69
+ date['set' + (local ? '' : 'UTC') + part](value);
70
+ }
71
+
73
72
  function extractDateParts(pattern, str, missingValuesDate) {
73
+ // Javascript Date object doesn't support custom timezone. Sets all felds as
74
+ // GMT based to begin with. If the timezone offset is provided, then adjust
75
+ // it using provided timezone, otherwise, adjust it with the system timezone.
76
+ var local = pattern.indexOf('O') < 0;
74
77
  var matchers = [
75
78
  {
76
79
  pattern: /y{1,4}/,
77
80
  regexp: "\\d{1,4}",
78
81
  fn: function(date, value) {
79
- date.setFullYear(value);
82
+ setDatePart(date, 'FullYear', value, local);
80
83
  }
81
84
  },
82
85
  {
83
86
  pattern: /MM/,
84
87
  regexp: "\\d{1,2}",
85
88
  fn: function(date, value) {
86
- date.setMonth(value - 1);
89
+ setDatePart(date, 'Month', (value - 1), local);
87
90
  }
88
91
  },
89
92
  {
90
93
  pattern: /dd/,
91
94
  regexp: "\\d{1,2}",
92
95
  fn: function(date, value) {
93
- date.setDate(value);
96
+ setDatePart(date, 'Date', value, local);
94
97
  }
95
98
  },
96
99
  {
97
100
  pattern: /hh/,
98
101
  regexp: "\\d{1,2}",
99
102
  fn: function(date, value) {
100
- date.setHours(value);
103
+ setDatePart(date, 'Hours', value, local);
101
104
  }
102
105
  },
103
106
  {
104
107
  pattern: /mm/,
105
108
  regexp: "\\d\\d",
106
109
  fn: function(date, value) {
107
- date.setMinutes(value);
110
+ setDatePart(date, 'Minutes', value, local);
108
111
  }
109
112
  },
110
113
  {
111
114
  pattern: /ss/,
112
115
  regexp: "\\d\\d",
113
116
  fn: function(date, value) {
114
- date.setSeconds(value);
117
+ setDatePart(date, 'Seconds', value, local);
115
118
  }
116
119
  },
117
120
  {
118
121
  pattern: /SSS/,
119
122
  regexp: "\\d\\d\\d",
120
123
  fn: function(date, value) {
121
- date.setMilliseconds(value);
124
+ setDatePart(date, 'Milliseconds', value, local);
122
125
  }
123
126
  },
124
127
  {
@@ -129,8 +132,28 @@ function extractDateParts(pattern, str, missingValuesDate) {
129
132
  value = 0;
130
133
  }
131
134
  var offset = Math.abs(value);
132
- var minutes = (offset % 100) + Math.floor(offset / 100) * 60;
133
- date.setMinutes(date.getMinutes() + (value > 0 ? minutes : -minutes));
135
+ var timezoneOffset = (value > 0 ? -1 : 1 ) * ((offset % 100) + Math.floor(offset / 100) * 60);
136
+ // Per ISO8601 standard: UTC = local time - offset
137
+ //
138
+ // For example, 2000-01-01T01:00:00-0700
139
+ // local time: 2000-01-01T01:00:00
140
+ // ==> UTC : 2000-01-01T08:00:00 ( 01 - (-7) = 8 )
141
+ //
142
+ // To make it even more confusing, the date.getTimezoneOffset() is
143
+ // opposite sign of offset string in the ISO8601 standard. So if offset
144
+ // is '-0700' the getTimezoneOffset() would be (+)420. The line above
145
+ // calculates timezoneOffset to matche Javascript's behavior.
146
+ //
147
+ // The date/time of the input is actually the local time, so the date
148
+ // object that was constructed is actually local time even thought the
149
+ // UTC setters are used. This means the date object's internal UTC
150
+ // representation was wrong. It needs to be fixed by substracting the
151
+ // offset (or adding the offset minutes as they are opposite sign).
152
+ //
153
+ // Note: the time zone has to be processed after all other fileds are
154
+ // set. The result would be incorrect if the offset was calculated
155
+ // first then overriden by the other filed setters.
156
+ date.setUTCMinutes(date.getUTCMinutes() + timezoneOffset);
134
157
  }
135
158
  }
136
159
  ];
@@ -162,6 +185,7 @@ function extractDateParts(pattern, str, missingValuesDate) {
162
185
  dateFns.forEach(function(f, i) {
163
186
  f.fn(date, matches[i + 1]);
164
187
  });
188
+
165
189
  return date;
166
190
  }
167
191
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "date-format",
3
- "version": "2.1.0",
3
+ "version": "3.0.0",
4
4
  "description": "Formatting Date objects as strings since 2013",
5
5
  "main": "lib/index.js",
6
6
  "repository": {
@@ -29,23 +29,21 @@ describe('date_format', function() {
29
29
 
30
30
  it('should provide a ISO8601 with timezone offset format', function() {
31
31
  var tzDate = createFixedDate();
32
- tzDate.setMinutes(tzDate.getMinutes() - tzDate.getTimezoneOffset() - 660);
33
32
  tzDate.getTimezoneOffset = function () {
34
33
  return -660;
35
34
  };
36
35
 
37
- // when tz offset is in the pattern, the date should be in UTC
36
+ // when tz offset is in the pattern, the date should be in local time
38
37
  dateFormat.asString(dateFormat.ISO8601_WITH_TZ_OFFSET_FORMAT, tzDate)
39
- .should.eql('2010-01-11T03:31:30.005+1100');
38
+ .should.eql('2010-01-11T14:31:30.005+1100');
40
39
 
41
40
  tzDate = createFixedDate();
42
- tzDate.setMinutes((tzDate.getMinutes() - tzDate.getTimezoneOffset()) + 120);
43
41
  tzDate.getTimezoneOffset = function () {
44
42
  return 120;
45
43
  };
46
44
 
47
45
  dateFormat.asString(dateFormat.ISO8601_WITH_TZ_OFFSET_FORMAT, tzDate)
48
- .should.eql('2010-01-11T16:31:30.005-0200');
46
+ .should.eql('2010-01-11T14:31:30.005-0200');
49
47
  });
50
48
 
51
49
  it('should provide a just-the-time format', function() {
@@ -54,11 +52,10 @@ describe('date_format', function() {
54
52
 
55
53
  it('should provide a custom format', function() {
56
54
  var customDate = createFixedDate();
57
- customDate.setMinutes((customDate.getMinutes() - customDate.getTimezoneOffset()) + 120);
58
55
  customDate.getTimezoneOffset = function () {
59
56
  return 120;
60
57
  };
61
58
 
62
- dateFormat.asString('O.SSS.ss.mm.hh.dd.MM.yy', customDate).should.eql('-0200.005.30.31.16.11.01.10');
59
+ dateFormat.asString('O.SSS.ss.mm.hh.dd.MM.yy', customDate).should.eql('-0200.005.30.31.14.11.01.10');
63
60
  });
64
61
  });
@@ -33,21 +33,19 @@ describe("dateFormat.parse", function() {
33
33
 
34
34
  it("should return the correct date if the string matches", function() {
35
35
  var testDate = new Date();
36
- testDate.setFullYear(2018);
37
- testDate.setMonth(8);
38
- testDate.setDate(13);
39
- testDate.setHours(18);
40
- testDate.setMinutes(10);
41
- testDate.setSeconds(12);
42
- testDate.setMilliseconds(392);
43
- testDate.getTimezoneOffset = function() {
44
- return 600;
45
- };
36
+ testDate.setUTCFullYear(2018);
37
+ testDate.setUTCMonth(8);
38
+ testDate.setUTCDate(13);
39
+ testDate.setUTCHours(18);
40
+ testDate.setUTCMinutes(10);
41
+ testDate.setUTCSeconds(12);
42
+ testDate.setUTCMilliseconds(392);
46
43
 
47
44
  dateFormat
48
- .parse(pattern, "2018-09-13 08:10:12.392+1000")
45
+ .parse(pattern, "2018-09-14 04:10:12.392+1000")
49
46
  .getTime()
50
- .should.eql(testDate.getTime());
47
+ .should.eql(testDate.getTime())
48
+ ;
51
49
  });
52
50
 
53
51
  it("should throw if the string does not match", function() {
@@ -65,7 +63,10 @@ describe("dateFormat.parse", function() {
65
63
  return testDate;
66
64
  };
67
65
 
68
- function verifyDate(actual, expected) {
66
+ /**
67
+ * If there's no timezone in the format, then we verify against the local date
68
+ */
69
+ function verifyLocalDate(actual, expected) {
69
70
  actual.getFullYear().should.eql(expected.year || testDate.getFullYear());
70
71
  actual.getMonth().should.eql(expected.month || testDate.getMonth());
71
72
  actual.getDate().should.eql(expected.day || testDate.getDate());
@@ -77,19 +78,34 @@ describe("dateFormat.parse", function() {
77
78
  .should.eql(expected.milliseconds || testDate.getMilliseconds());
78
79
  }
79
80
 
81
+ /**
82
+ * If a timezone is specified, let's verify against the UTC time it is supposed to be
83
+ */
84
+ function verifyDate(actual, expected) {
85
+ actual.getUTCFullYear().should.eql(expected.year || testDate.getUTCFullYear());
86
+ actual.getUTCMonth().should.eql(expected.month || testDate.getUTCMonth());
87
+ actual.getUTCDate().should.eql(expected.day || testDate.getUTCDate());
88
+ actual.getUTCHours().should.eql(expected.hours || testDate.getUTCHours());
89
+ actual.getUTCMinutes().should.eql(expected.minutes || testDate.getUTCMinutes());
90
+ actual.getUTCSeconds().should.eql(expected.seconds || testDate.getUTCSeconds());
91
+ actual
92
+ .getMilliseconds()
93
+ .should.eql(expected.milliseconds || testDate.getMilliseconds());
94
+ }
95
+
80
96
  it("should return a date with missing values defaulting to current time", function() {
81
97
  var date = dateFormat.parse("yyyy-MM", "2015-09");
82
- verifyDate(date, { year: 2015, month: 8 });
98
+ verifyLocalDate(date, { year: 2015, month: 8 });
83
99
  });
84
100
 
85
101
  it("should use a passed in date for missing values", function() {
86
- var missingValueDate = new Date(2010, 1, 11, 10, 30, 12, 100);
102
+ var missingValueDate = new Date(2010, 1, 8, 22, 30, 12, 100);
87
103
  var date = dateFormat.parse("yyyy-MM", "2015-09", missingValueDate);
88
- verifyDate(date, {
104
+ verifyLocalDate(date, {
89
105
  year: 2015,
90
106
  month: 8,
91
- day: 11,
92
- hours: 10,
107
+ day: 8,
108
+ hours: 22,
93
109
  minutes: 30,
94
110
  seconds: 12,
95
111
  milliseconds: 100
@@ -98,80 +114,107 @@ describe("dateFormat.parse", function() {
98
114
 
99
115
  it("should handle variations on the same pattern", function() {
100
116
  var date = dateFormat.parse("MM-yyyy", "09-2015");
101
- verifyDate(date, { year: 2015, month: 8 });
117
+ verifyLocalDate(date, { year: 2015, month: 8 });
102
118
 
103
119
  date = dateFormat.parse("yyyy MM", "2015 09");
104
- verifyDate(date, { year: 2015, month: 8 });
120
+ verifyLocalDate(date, { year: 2015, month: 8 });
105
121
 
106
122
  date = dateFormat.parse("MM, yyyy.", "09, 2015.");
107
- verifyDate(date, { year: 2015, month: 8 });
123
+ verifyLocalDate(date, { year: 2015, month: 8 });
108
124
  });
109
125
 
110
- it("should match all the date parts", function() {
111
- var date = dateFormat.parse("dd", "21");
112
- verifyDate(date, { day: 21 });
126
+ describe("should match all the date parts", function() {
127
+ it("works with dd", function() {
128
+ var date = dateFormat.parse("dd", "21");
129
+ verifyLocalDate(date, { day: 21 });
130
+ });
113
131
 
114
- date = dateFormat.parse("hh", "12");
115
- verifyDate(date, { hours: 12 });
132
+ it("works with hh", function() {
133
+ var date = dateFormat.parse("hh", "12");
134
+ verifyLocalDate(date, { hours: 12 });
135
+ });
116
136
 
117
- date = dateFormat.parse("mm", "34");
118
- verifyDate(date, { minutes: 34 });
137
+ it("works with mm", function() {
138
+ var date = dateFormat.parse("mm", "34");
139
+ verifyLocalDate(date, { minutes: 34 });
140
+ });
119
141
 
120
- date = dateFormat.parse("ss", "59");
121
- verifyDate(date, { seconds: 59 });
142
+ it("works with ss", function() {
143
+ var date = dateFormat.parse("ss", "59");
144
+ verifyLocalDate(date, { seconds: 59 });
145
+ });
122
146
 
123
- date = dateFormat.parse("ss.SSS", "23.452");
124
- verifyDate(date, { seconds: 23, milliseconds: 452 });
147
+ it("works with ss.SSS", function() {
148
+ var date = dateFormat.parse("ss.SSS", "23.452");
149
+ verifyLocalDate(date, { seconds: 23, milliseconds: 452 });
150
+ });
125
151
 
126
- date = dateFormat.parse("hh:mm O", "05:23 +1000");
127
- verifyDate(date, { hours: 15, minutes: 23 });
152
+ it("works with hh:mm O (+1000)", function() {
153
+ var date = dateFormat.parse("hh:mm O", "05:23 +1000");
154
+ verifyDate(date, { hours: 19, minutes: 23 });
155
+ });
128
156
 
129
- date = dateFormat.parse("hh:mm O", "05:23 -200");
130
- verifyDate(date, { hours: 3, minutes: 23 });
157
+ it("works with hh:mm O (-200)", function() {
158
+ var date = dateFormat.parse("hh:mm O", "05:23 -200");
159
+ verifyDate(date, { hours: 7, minutes: 23 });
160
+ });
131
161
 
132
- date = dateFormat.parse("hh:mm O", "05:23 +0930");
133
- verifyDate(date, { hours: 14, minutes: 53 });
162
+ it("works with hh:mm O (+0930)", function() {
163
+ var date = dateFormat.parse("hh:mm O", "05:23 +0930");
164
+ verifyDate(date, { hours: 19, minutes: 53 });
165
+ });
134
166
  });
135
167
  });
136
168
 
137
169
  describe("with a date formatted by this library", function() {
138
- var testDate = new Date();
139
- testDate.setUTCFullYear(2018);
140
- testDate.setUTCMonth(8);
141
- testDate.setUTCDate(13);
142
- testDate.setUTCHours(18);
143
- testDate.setUTCMinutes(10);
144
- testDate.setUTCSeconds(12);
145
- testDate.setUTCMilliseconds(392);
146
-
147
- it("should format and then parse back to the same date", function() {
148
- dateFormat
149
- .parse(
150
- dateFormat.ISO8601_WITH_TZ_OFFSET_FORMAT,
151
- dateFormat(dateFormat.ISO8601_WITH_TZ_OFFSET_FORMAT, testDate)
152
- )
153
- .should.eql(testDate);
170
+ describe("should format and then parse back to the same date", function() {
171
+ function testDateInitWithUTC() {
172
+ var td = new Date();
173
+ td.setUTCFullYear(2018);
174
+ td.setUTCMonth(8);
175
+ td.setUTCDate(13);
176
+ td.setUTCHours(18);
177
+ td.setUTCMinutes(10);
178
+ td.setUTCSeconds(12);
179
+ td.setUTCMilliseconds(392);
180
+ return td;
181
+ }
182
+
183
+ it("works with ISO8601_WITH_TZ_OFFSET_FORMAT", function() {
184
+ // For this test case to work, the date object must be initialized with
185
+ // UTC timezone
186
+ var td = testDateInitWithUTC();
187
+ var d = dateFormat(dateFormat.ISO8601_WITH_TZ_OFFSET_FORMAT, td);
188
+ dateFormat.parse(dateFormat.ISO8601_WITH_TZ_OFFSET_FORMAT, d)
189
+ .should.eql(td);
190
+ });
154
191
 
155
- dateFormat
156
- .parse(
157
- dateFormat.ISO8601_FORMAT,
158
- dateFormat(dateFormat.ISO8601_FORMAT, testDate)
159
- )
160
- .should.eql(testDate);
192
+ it("works with ISO8601_FORMAT", function() {
193
+ var td = new Date();
194
+ var d = dateFormat(dateFormat.ISO8601_FORMAT, td);
195
+ var actual = dateFormat.parse(dateFormat.ISO8601_FORMAT, d);
196
+ actual.should.eql(td);
197
+ });
161
198
 
162
- dateFormat
199
+ it("works with DATETIME_FORMAT", function() {
200
+ var testDate = new Date();
201
+ dateFormat
163
202
  .parse(
164
203
  dateFormat.DATETIME_FORMAT,
165
204
  dateFormat(dateFormat.DATETIME_FORMAT, testDate)
166
205
  )
167
206
  .should.eql(testDate);
207
+ });
168
208
 
169
- dateFormat
209
+ it("works with ABSOLUTETIME_FORMAT", function() {
210
+ var testDate = new Date();
211
+ dateFormat
170
212
  .parse(
171
213
  dateFormat.ABSOLUTETIME_FORMAT,
172
214
  dateFormat(dateFormat.ABSOLUTETIME_FORMAT, testDate)
173
215
  )
174
216
  .should.eql(testDate);
217
+ });
175
218
  });
176
219
  });
177
220
  });