date-format 2.1.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
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
  });