timespan 0.5.2 → 0.5.3

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -324,6 +324,38 @@ dr.between?(4.days) # => true
324
324
 
325
325
  You can also use Range#intersect from *sugar-high* gem to test intersection of time ranges ;)
326
326
 
327
+ ## Client side helpers
328
+
329
+ This gem now includes som javascript assets to assist in performing date and timespan (duration) calculations on the client side also:
330
+
331
+ * `moment.js` (1.7.2)
332
+ * `date_ext.js` DP_DateExtensions
333
+ * `timespan.js`
334
+
335
+ ### Example usage
336
+
337
+ ```javascript
338
+ Date.timeleft("12/10/2012", "07/05/2013");
339
+ ```
340
+
341
+ ```javascript
342
+ Date.timeleft(Date.create("12/10/2012"), "07/05/2013");
343
+ ```
344
+
345
+ Aliases for timeleft are: `duration` and `timespan`.
346
+
347
+ See the javascript source for the full API or check out http://momentjs.com/docs/ and http://depressedpress.com/javascript-extensions/dp_dateextensions/.
348
+
349
+ Note: timeleft was extracted from (http://www.proglogic.com/code/javascript/time/timeleft.php)
350
+
351
+ To use these assets with the Asset pipeline, simply add this to your `application.js` or similar manifest file :) (after jquery which is required!)
352
+
353
+ ```javascript
354
+ //= require moment.js
355
+ //= require date_ext.js
356
+ //= require timespan.js
357
+ ```
358
+
327
359
  ### Timespan
328
360
 
329
361
  ## Contributing to Timespan
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.5.2
1
+ 0.5.3
@@ -1,6 +1,7 @@
1
1
  module Timespan
2
2
  module Rails
3
3
  class Engine < ::Rails::Engine
4
+ # Also automatically adds the assets in vendor/assets :)
4
5
  initializer 'Timespan setup' do
5
6
  I18n.load_path << Dir[Rails.root.join('config', 'locales', 'timespan', '*.yml')]
6
7
  end
data/timespan.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "timespan"
8
- s.version = "0.5.2"
8
+ s.version = "0.5.3"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Kristian Mandrup"]
12
- s.date = "2012-11-19"
12
+ s.date = "2012-12-12"
13
13
  s.description = "Makes it easy to calculate time distance in different units"
14
14
  s.email = "kmandrup@gmail.com"
15
15
  s.extra_rdoc_files = [
@@ -63,7 +63,10 @@ Gem::Specification.new do |s|
63
63
  "spec/timespan/units_spec.rb",
64
64
  "spec/timespan_init_spec.rb",
65
65
  "spec/timespan_spec.rb",
66
- "timespan.gemspec"
66
+ "timespan.gemspec",
67
+ "vendor/assets/javascripts/date_ext.js",
68
+ "vendor/assets/javascripts/moment.js",
69
+ "vendor/assets/javascripts/timespan.js"
67
70
  ]
68
71
  s.homepage = "http://github.com/kristianmandrup/timespan"
69
72
  s.licenses = ["MIT"]
@@ -0,0 +1,1300 @@
1
+ /* DepressedPress.com DP_DateExtensions
2
+
3
+ Author: Jim Davis, the Depressed Press of Boston
4
+ Date: June 20, 2006
5
+ Contact: webmaster@depressedpress.com
6
+ Website: www.depressedpress.com
7
+
8
+ Full documentation can be found at:
9
+ http://www.depressedpress.com/Content/Development/JavaScript/Extensions/
10
+
11
+ DP_DateExtensions adds features to the JavaScript "Date" datatype.
12
+
13
+ Copyright (c) 1996-2013, The Depressed Press of Boston (depressedpress.com)
14
+
15
+ All rights reserved.
16
+
17
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
18
+
19
+ +) Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
20
+
21
+ +) Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
22
+
23
+ +) Neither the name of the DEPRESSED PRESS OF BOSTON (DEPRESSEDPRESS.COM) nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
24
+
25
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
+
27
+ */
28
+
29
+ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
30
+ /* "Date" Object Extensions */
31
+ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
32
+
33
+ // Date.DP_DateExtensions
34
+ // Simple static variable allowing code to determine if DP_DateExtentions have been loaded.
35
+ Date.DP_DateExtensions = true;
36
+
37
+ // Set the First Day of Week (Sunday=0, Default)
38
+ Date.FirstDayOfWeek = 0;
39
+
40
+ // Set the Multiplication factors for unambigiuos times (MS times, used by various internal functions)
41
+ Date.Factors = new Object();
42
+ Date.Factors.milliseconds = 1; // 1 ms to the ms
43
+ Date.Factors.seconds = 1000; // 1000 ms to the second (1 * 1000)
44
+ Date.Factors.minutes = 60000; // 60 seconds to the minute (1 * 1000 * 60)
45
+ Date.Factors.quarterhours = 900000; // 15 minutes to the quarter hour (1 * 1000 * 60 * 15)
46
+ Date.Factors.warhols = 900000; // 15 minutes of fame (1 * 1000 * 60 * 15)
47
+ Date.Factors.halfhours = 1800000; // 30 minutes to the half hour (1 * 1000 * 60 * 15)
48
+ Date.Factors.hours = 3600000; // 60 minutes to the hour (1 * 1000 * 60 * 60)
49
+ Date.Factors.days = 86400000; // 24 hours to the day (1 * 1000 * 60 * 60 * 24)
50
+ Date.Factors.weeks = 604800000; // 7 days per week (1 * 1000 * 60 * 60 * 24 * 7)
51
+
52
+ // is
53
+ // Checks if an object is a Date object
54
+ Date.is = function(Ob) {
55
+ try {
56
+ if ( typeof Ob == "object" ) {
57
+ if ( Ob.constructor == Date ) {
58
+ return true;
59
+ };
60
+ };
61
+ } catch (CurError) { };
62
+ return false;
63
+ };
64
+
65
+
66
+ // parseFormat
67
+ // Accepts a date/time format and a string and returns either a date (if the format matches) or null
68
+ Date.parseFormat = function(CurDate, Mask, DefaultTo, CenturyMark) {
69
+
70
+ // Check the input parameters
71
+ if ( typeof CurDate != "string" || CurDate == "" ) {
72
+ return null;
73
+ };
74
+ if ( typeof Mask != "string" || Mask == "" ) {
75
+ return null;
76
+ };
77
+ if ( typeof DefaultTo != "number" && DefaultTo != 0 && DefaultTo != 1 && DefaultTo != 2 ) {
78
+ DefaultTo = 0;
79
+ };
80
+ if ( typeof CenturyMark != "number" || CenturyMark < 0 || CenturyMark > 99 ) {
81
+ CenturyMark = 50;
82
+ };
83
+
84
+ // Set Mask Characters
85
+ var MaskChars = "DMYhHmsltTz";
86
+ // SetRegEx chars (these need to be escaped in the Mask)
87
+ var RegExChars = "^$.*+?=!:|\\/()[]{}-";
88
+ // Set a reference object for month names
89
+ var MonthRef = {jan:0,feb:1,mar:2,apr:3,may:4,jun:5,jul:6,aug:7,sep:8,oct:9,nov:10,dec:11}
90
+ // Begin the RegEx
91
+ var RegEx = "";
92
+ // Tack a temporary space at the end of the mask to ensure that the last character isn't a mask character
93
+ Mask += " ";
94
+
95
+ // Default the positions of the date fragments
96
+ // 0 = year, 1 = month, 2 = day, 3 = hour, 4 = minute, 5 = second, 6 = millisecond, 7 = ampmind, 8 = TimeZone
97
+ var DF = [null,null,null,null,null,null,null, null];
98
+
99
+ // Parse the Mask
100
+ var CurChar;
101
+ var MaskPart = "";
102
+ var MaskPartCnt = 1;
103
+ // Loop over the mask, character by character
104
+ for ( var Cnt = 0; Cnt < Mask.length; Cnt++ ) {
105
+ // Get the character
106
+ CurChar = Mask.charAt(Cnt);
107
+ // Determine if the character is a mask element
108
+ if ( ( MaskChars.indexOf(CurChar) == -1 ) || ( MaskPart != "" && CurChar != MaskPart.charAt(MaskPart.length - 1) ) ) {
109
+ // Determine if we need to parse a MaskPart or not
110
+ if ( MaskPart != "" ) {
111
+ // Set the position of the mask part
112
+ switch (MaskPart) {
113
+ case "YY":
114
+ case "YYYY":
115
+ DF[0] = MaskPartCnt;
116
+ break;
117
+ case "M":
118
+ case "MM":
119
+ case "MMM":
120
+ case "MMMM":
121
+ DF[1] = MaskPartCnt;
122
+ break;
123
+ case "D":
124
+ case "DD":
125
+ case "DDD":
126
+ case "DDDD":
127
+ DF[2] = MaskPartCnt;
128
+ break;
129
+ case "h":
130
+ case "hh":
131
+ case "H":
132
+ case "HH":
133
+ DF[3] = MaskPartCnt;
134
+ break;
135
+ case "m":
136
+ case "mm":
137
+ DF[4] = MaskPartCnt;
138
+ break;
139
+ case "s":
140
+ case "ss":
141
+ DF[5] = MaskPartCnt;
142
+ break;
143
+ case "l":
144
+ DF[6] = MaskPartCnt;
145
+ break;
146
+ case "t":
147
+ case "T":
148
+ case "tt":
149
+ case "TT":
150
+ DF[7] = MaskPartCnt;
151
+ break;
152
+ case "z":
153
+ DF[8] = MaskPartCnt;
154
+ break;
155
+ };
156
+ // Convert the mask part to a regex fragment
157
+ switch (MaskPart) {
158
+ case "h":
159
+ RegEx += "(1[0-2]|[1-9])";
160
+ break;
161
+ case "hh":
162
+ RegEx += "(1[0-2]|0[1-9])";
163
+ break;
164
+ case "H":
165
+ RegEx += "(2[0-4]|1[0-9]|[0-9])";
166
+ break;
167
+ case "HH":
168
+ RegEx += "(2[0-4]|1[0-9]|0[0-9])";
169
+ break;
170
+ case "s":
171
+ case "m":
172
+ RegEx += "([0-5]?[0-9])";
173
+ break;
174
+ case "ss":
175
+ case "mm":
176
+ RegEx += "([0-5]?[0-9])";
177
+ break;
178
+ case "l":
179
+ RegEx += "([0-9]+)";
180
+ break;
181
+ case "t":
182
+ case "T":
183
+ RegEx += "(a|p)";
184
+ break;
185
+ case "tt":
186
+ case "TT":
187
+ RegEx += "(am|pm)";
188
+ break;
189
+ case "D":
190
+ RegEx += "((?:3[01])|(?:[12][0-9])|(?:0[1-9])|[1-9])";
191
+ break;
192
+ case "DD":
193
+ RegEx += "((?:3[01])|(?:[12][0-9])|(?:0[1-9]))";
194
+ break;
195
+ case "DDD":
196
+ RegEx += "(sun|mon|tue|wed|thu|fri|sat)";
197
+ break;
198
+ case "DDDD":
199
+ RegEx += "(sunday|monday|tuesday|wednesday|thursday|friday|saturday)";
200
+ break;
201
+ case "M":
202
+ RegEx += "((?:1[012])|(?:0[1-9])|[1-9])";
203
+ break;
204
+ case "MM":
205
+ RegEx += "((?:1[012])|(?:0[1-9]))";
206
+ break;
207
+ case "MMM":
208
+ RegEx += "(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)";
209
+ break;
210
+ case "MMMM":
211
+ RegEx += "(january|february|march|april|may|june|july|august|september|october|november|december)";
212
+ break;
213
+ case "YY":
214
+ RegEx += "([0-9]{2})";
215
+ break;
216
+ case "YYYY":
217
+ RegEx += "((?:1[6-9]|[2-9][0-9])[0-9]{2})";
218
+ break;
219
+ case "z":
220
+ RegEx += "(Z|UT|[\+\-](?:1[012]|[0]?[0-9])(?::?[0-5]?[0-9]))";
221
+ break;
222
+ };
223
+ // Reset the MaskPart to nothing
224
+ MaskPart = "";
225
+ // Increment the count of discovered mask parts
226
+ MaskPartCnt++;
227
+ };
228
+ // If the char is a mask char, start a new mask part, otherwise, dump it to the output
229
+ if ( MaskChars.indexOf(CurChar) > -1 ) {
230
+ MaskPart = CurChar;
231
+ } else {
232
+ // The character isn't a mask character
233
+ if ( RegExChars.indexOf(CurChar) >= 0 ) {
234
+ RegEx += "\\";
235
+ };
236
+ RegEx += CurChar;
237
+ };
238
+ } else {
239
+ // Add the current mask character to the MaskPart
240
+ MaskPart += CurChar;
241
+ };
242
+ };
243
+ // Remove the temporary space from the end of the formatted date
244
+ RegEx = RegEx.substring(0,RegEx.length - 1);
245
+
246
+ // Try to parse the input
247
+ var ParsedDF;
248
+ if (ParsedDF = new RegExp("^" + RegEx + "$", "i").exec(CurDate)) {};
249
+ // If the date couldn't be parsed, return null
250
+ if ( !ParsedDF ) { return null };
251
+ // Replace the date fragment positions with the actual dates
252
+
253
+ for (var Cnt = 0; Cnt < DF.length; Cnt++) {
254
+ if ( DF[Cnt] != null ) {
255
+ DF[Cnt] = ParsedDF[DF[Cnt]];
256
+ };
257
+ };
258
+
259
+ // Set Month, if present
260
+ if ( DF[1] != null ) {
261
+ var CurDF = MonthRef[DF[1].substring(0,3).toLowerCase()];
262
+ if ( CurDF != null ) {
263
+ DF[1] = CurDF;
264
+ } else {
265
+ DF[1] = DF[1]-1;
266
+ };
267
+ };
268
+
269
+
270
+ // Handle two digit years, if present
271
+ if ( DF[0] != null && DF[0] < 100 ) {
272
+ var CurDF = DF[0] * 1;
273
+ if ( CurDF < CenturyMark ) {
274
+ DF[0] = parseInt(CurDF, 10) + 2000;
275
+ } else {
276
+ DF[0] = parseInt(CurDF, 10) + 1900;
277
+ };
278
+ };
279
+
280
+
281
+ // Set AM/PM, if present (Hours must be present and less than 12 for it to matter)
282
+ if ( DF[7] != null && DF[3] != null ) {
283
+ var CurAP = DF[7].substring(0,1).toLowerCase();
284
+ if ( CurAP == "p" ) {
285
+ // Add 12 if PM and hours is less than 12 (use parseInt to ensure that values with leading zeroes are not mistaken for strings)
286
+ if ( DF[3] < 12 ) {
287
+ DF[3] = parseInt(DF[3], 10) + 12;
288
+ };
289
+ } else {
290
+ // 12 AM is zero, not 12
291
+ if ( DF[3] == 12 ) {
292
+ DF[3] = 0;
293
+ };
294
+ };
295
+ };
296
+
297
+ // Set defaults for null date parts
298
+ if ( DefaultTo == 0 ) {
299
+ // No changes needed for this default
300
+ } else if ( DefaultTo == 1 ) {
301
+ // Set the Current Date Parts
302
+ var NowDate = new Date();
303
+ if ( DF[0] == null ) { DF[0] = NowDate.getFullYear() };
304
+ if ( DF[1] == null ) { DF[1] = NowDate.getMonth() };
305
+ if ( DF[2] == null ) { DF[2] = NowDate.getDate() };
306
+ if ( DF[3] == null ) { DF[3] = NowDate.getHours() };
307
+ if ( DF[4] == null ) { DF[4] = NowDate.getMinutes() };
308
+ if ( DF[5] == null ) { DF[5] = NowDate.getSeconds() };
309
+ if ( DF[6] == null ) { DF[6] = NowDate.getMilliseconds() };
310
+ } else if ( DefaultTo == 2 ) {
311
+ var NowDate = new Date();
312
+ if ( DF[0] == null ) { DF[0] = NowDate.getUTCFullYear() };
313
+ if ( DF[1] == null ) { DF[1] = NowDate.getUTCMonth() };
314
+ if ( DF[2] == null ) { DF[2] = NowDate.getUTCDate() };
315
+ if ( DF[3] == null ) { DF[3] = NowDate.getUTCHours() };
316
+ if ( DF[4] == null ) { DF[4] = NowDate.getUTCMinutes() };
317
+ if ( DF[5] == null ) { DF[5] = NowDate.getUTCSeconds() };
318
+ if ( DF[6] == null ) { DF[6] = NowDate.getUTCMilliseconds() };
319
+ };
320
+
321
+ // If there's no timezone info the data is local time
322
+ if (DF[8] == null) {
323
+ return new Date(DF[0], DF[1], DF[2], DF[3], DF[4], DF[5], DF[6]);
324
+ } else {
325
+ // If Timezone indicator is "Z" or "UT", it's UTC, otherwise it's an offset and needs to be figured out
326
+ if (DF[8] == "Z" || DF[8] == "UT") {
327
+ return new Date(Date.UTC(DF[0], DF[1], DF[2], DF[3], DF[4], DF[5], DF[6]));
328
+ } else {
329
+ // Regex value to split the parts
330
+ var ParsedTZ = new RegExp("^([\+\-])(1[012]|[0]?[0-9])(?::?)([0-5]?[0-9])$").exec(DF[8])
331
+ // Get current Timezone information
332
+ var CurTZ = new Date().getTimezoneOffset();
333
+ var CurTZh = ParsedTZ[1] + ParsedTZ[2] - ((CurTZ >= 0 ? "-" : "+") + Math.floor(Math.abs(CurTZ) / 60))
334
+ var CurTZm = ParsedTZ[1] + ParsedTZ[3] - ((CurTZ >= 0 ? "-" : "+") + (Math.abs(CurTZ) % 60))
335
+ // Return the date
336
+ return new Date(DF[0], DF[1], DF[2], DF[3] - CurTZh, DF[4] - CurTZm, DF[5], DF[6]);
337
+ };
338
+ };
339
+ // If we've reached here we couldn't deal with the input, return null
340
+ return null;
341
+
342
+ };
343
+
344
+ // parseHttpTimeFormat
345
+ // Converts a string formated as a date using RFC 822 specification
346
+ Date.parseHttpTimeFormat = function(CurDate) {
347
+
348
+ // Check the input parameters
349
+ if ( typeof CurDate != "string" || CurDate == "" ) {
350
+ return null;
351
+ };
352
+
353
+ return Date.parseFormat(CurDate, "DDD, D MMM YYYY HH:mm:ss z");
354
+
355
+ };
356
+
357
+ // parseIso8601
358
+ // Attempts to convert ISO8601 input to a date
359
+ Date.parseIso8601 = function(CurDate) {
360
+
361
+ // Check the input parameters
362
+ if ( typeof CurDate != "string" || CurDate == "" ) {
363
+ return null;
364
+ };
365
+ // Set the fragment expressions
366
+ var S = "[\\-/:.]";
367
+ var Yr = "((?:1[6-9]|[2-9][0-9])[0-9]{2})";
368
+ var Mo = S + "((?:1[012])|(?:0[1-9])|[1-9])";
369
+ var Dy = S + "((?:3[01])|(?:[12][0-9])|(?:0[1-9])|[1-9])";
370
+ var Hr = "(2[0-4]|[01]?[0-9])";
371
+ var Mn = S + "([0-5]?[0-9])";
372
+ var Sd = "(?:" + S + "([0-5]?[0-9])(?:[.,]([0-9]+))?)?";
373
+ var TZ = "(?:(Z)|(?:([\+\-])(1[012]|[0]?[0-9])(?::?([0-5]?[0-9]))?))?";
374
+ // RegEx the input
375
+ // First check: Just date parts (month and day are optional)
376
+ // Second check: Full date plus time (seconds, milliseconds and TimeZone info are optional)
377
+ var TF;
378
+ if ( TF = new RegExp("^" + Yr + "(?:" + Mo + "(?:" + Dy + ")?)?" + "$").exec(CurDate) ) {} else if ( TF = new RegExp("^" + Yr + Mo + Dy + "[Tt ]" + Hr + Mn + Sd + TZ + "$").exec(CurDate) ) {};
379
+ // If the date couldn't be parsed, return null
380
+ if ( !TF ) { return null };
381
+ // Default the Time Fragments if they're not present
382
+ if ( !TF[2] ) { TF[2] = 1 } else { TF[2] = TF[2] - 1 };
383
+ if ( !TF[3] ) { TF[3] = 1 };
384
+ if ( !TF[4] ) { TF[4] = 0 };
385
+ if ( !TF[5] ) { TF[5] = 0 };
386
+ if ( !TF[6] ) { TF[6] = 0 };
387
+ if ( !TF[7] ) { TF[7] = 0 };
388
+ if ( !TF[8] ) { TF[8] = null };
389
+ if ( TF[9] != "-" && TF[9] != "+" ) { TF[9] = null };
390
+ if ( !TF[10] ) { TF[10] = 0 } else { TF[10] = TF[9] + TF[10] };
391
+ if ( !TF[11] ) { TF[11] = 0 } else { TF[11] = TF[9] + TF[11] };
392
+ // If there's no timezone info the data is local time
393
+ if ( !TF[8] && !TF[9] ) {
394
+ return new Date(TF[1], TF[2], TF[3], TF[4], TF[5], TF[6], TF[7]);
395
+ };
396
+ // If the UTC indicator is set the date is UTC
397
+ if ( TF[8] == "Z" ) {
398
+ return new Date(Date.UTC(TF[1], TF[2], TF[3], TF[4], TF[5], TF[6], TF[7]));
399
+ };
400
+ // If the date has a timezone offset
401
+ if ( TF[9] == "-" || TF[9] == "+" ) {
402
+ // Get current Timezone information
403
+ var CurTZ = new Date().getTimezoneOffset();
404
+ var CurTZh = TF[10] - ((CurTZ >= 0 ? "-" : "+") + Math.floor(Math.abs(CurTZ) / 60))
405
+ var CurTZm = TF[11] - ((CurTZ >= 0 ? "-" : "+") + (Math.abs(CurTZ) % 60))
406
+ // Return the date
407
+ return new Date(TF[1], TF[2], TF[3], TF[4] - CurTZh, TF[5] - CurTZm, TF[6], TF[7]);
408
+ };
409
+ // If we've reached here we couldn't deal with the input, return null
410
+ return null;
411
+
412
+ };
413
+
414
+ // getUSDST
415
+ // Accepts a full (four digit) year and returns an array with two date objects containing the beginning and end of DST in the US.
416
+ // Only functions for years post 1987 and assumes 2007 rules for later years. Returns "null" for unknown or out of range values.
417
+ Date.getUSDST = function(CurYear) {
418
+
419
+ // Check input parameters
420
+ if ( typeof CurYear != "number" || CurYear == "" ) {
421
+ return null;
422
+ };
423
+
424
+ // Return an empty array if out-of-range
425
+ if ( CurYear < 1987 ) { return null };
426
+
427
+ // Determine the last possible (extreme range) dates.
428
+ if ( CurYear < 2007 ) {
429
+ var exOn = 38;
430
+ var exOff = 31;
431
+ } else {
432
+ var exOn = 14;
433
+ var exOff = 38;
434
+ };
435
+ // Create the dates (first pass)
436
+ var DSTOn = new Date(CurYear, 2, exOn, 2);
437
+ var DSTOff = new Date(CurYear, 9, exOff, 2);
438
+ // Set date to previous Sunday
439
+ DSTOn.setDate(DSTOn.getDate() - DSTOn.getDay());
440
+ DSTOff.setDate(DSTOff.getDate() - DSTOff.getDay());
441
+
442
+ // Return the Array
443
+ return [DSTOn, DSTOff]
444
+
445
+ };
446
+
447
+
448
+
449
+ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
450
+ /* "Date" Object Prototype Extensions - Decision functions */
451
+ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
452
+
453
+ // isWeekday
454
+ // Returns "true" if the date is Monday-Friday, inclusive
455
+ Date.prototype.isWeekday = function() {
456
+
457
+ if ( this.getDay() != 0 && this.getDay() != 6 ) {
458
+ return true;
459
+ } else {
460
+ return false;
461
+ };
462
+
463
+ };
464
+
465
+ // isLeapYear
466
+ // Returns "true" if the date is contained within a leap year
467
+ Date.prototype.isLeapYear = function() {
468
+
469
+ var CurYear = this.getFullYear();
470
+ if ( CurYear % 400 == 0 ) {
471
+ return true;
472
+ } else if ( CurYear % 100 == 0 ) {
473
+ return false;
474
+ } else if ( CurYear % 4 == 0 ) {
475
+ return true;
476
+ } else {
477
+ return false;
478
+ };
479
+
480
+ };
481
+
482
+ // isDST
483
+ // Returns "true" if the date appears to fall within the local area's Daylight Saving Time (or similar scheme), returns false if the date does not (or it appears that the region doesn't observe DST).
484
+ Date.prototype.isDST = function() {
485
+
486
+ // Generate test dates
487
+ var Jan1 = new Date(this.getFullYear(), 0);
488
+ var Jul1 = new Date(this.getFullYear(), 6);
489
+
490
+ // DST in the Northern hemisphere is "fall back"
491
+ if ( Jan1.getTimezoneOffset() > Jul1.getTimezoneOffset() && this.getTimezoneOffset() != Jan1.getTimezoneOffset() ){
492
+ return true;
493
+ };
494
+ // DST in Southern hemisphere is "leap ahead"
495
+ if ( Jan1.getTimezoneOffset() < Jul1.getTimezoneOffset() && this.getTimezoneOffset() != Jul1.getTimezoneOffset()){
496
+ return true;
497
+ };
498
+ // We're not in DST
499
+ return false;
500
+ };
501
+
502
+
503
+
504
+ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
505
+ /* "Date" Object Prototype Extensions - Formatting functions */
506
+ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
507
+
508
+ // dateFormat
509
+ // Formats the date portion of a date object for display
510
+ Date.prototype.dateFormat = function(Mask) {
511
+
512
+ var FormattedDate = "";
513
+ var MaskChars = "DMY";
514
+ var Ref_MonthFullName = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
515
+ var Ref_MonthAbbreviation = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
516
+ var Ref_DayFullName = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
517
+ var Ref_DayAbbreviation = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
518
+
519
+ // Convert any supported simple masks into "real" masks
520
+ switch (Mask) {
521
+ case "short":
522
+ Mask = "M/D/YY";
523
+ break;
524
+ case "medium":
525
+ Mask = "MMM D, YYYY";
526
+ break;
527
+ case "long":
528
+ Mask = "MMMM D, YYYY";
529
+ break;
530
+ case "full":
531
+ Mask = "DDDD, MMMM D, YYYY";
532
+ break;
533
+ };
534
+
535
+ // Tack a temporary space at the end of the mask to ensure that the last character isn't a mask character
536
+ Mask += " ";
537
+
538
+ // Parse the Mask
539
+ var CurChar;
540
+ var MaskPart = "";
541
+ for ( var Cnt = 0; Cnt < Mask.length; Cnt++ ) {
542
+ // Get the character
543
+ CurChar = Mask.charAt(Cnt);
544
+ // Determine if the character is a mask element
545
+ if ( ( MaskChars.indexOf(CurChar) == -1 ) || ( MaskPart != "" && CurChar != MaskPart.charAt(MaskPart.length - 1) ) ) {
546
+ // Determine if we need to parse a MaskPart or not
547
+ if ( MaskPart != "" ) {
548
+ // Convert the mask part to the date value
549
+ switch (MaskPart) {
550
+ case "D":
551
+ FormattedDate += this.getDate();
552
+ break;
553
+ case "DD":
554
+ FormattedDate += ("0" + this.getDate()).slice(-2);
555
+ break;
556
+ case "DDD":
557
+ FormattedDate += Ref_DayAbbreviation[this.getDay()];
558
+ break;
559
+ case "DDDD":
560
+ FormattedDate += Ref_DayFullName[this.getDay()];
561
+ break;
562
+ case "M":
563
+ FormattedDate += this.getMonth() + 1;
564
+ break;
565
+ case "MM":
566
+ FormattedDate += ("0" + (this.getMonth() + 1)).slice(-2);
567
+ break;
568
+ case "MMM":
569
+ FormattedDate += Ref_MonthAbbreviation[this.getMonth()];
570
+ break;
571
+ case "MMMM":
572
+ FormattedDate += Ref_MonthFullName[this.getMonth()];
573
+ break;
574
+ case "YY":
575
+ FormattedDate += ("0" + this.getFullYear()).slice(-2);
576
+ break;
577
+ case "YYYY":
578
+ FormattedDate += ("000" + this.getFullYear()).slice(-4);
579
+ break;
580
+ };
581
+ // Reset the MaskPart to nothing
582
+ MaskPart = "";
583
+ };
584
+ // If the char is a mask char, start a new mask part, otherwise, dump it to the output
585
+ if ( MaskChars.indexOf(CurChar) > -1 ) {
586
+ MaskPart = CurChar;
587
+ } else {
588
+ FormattedDate += CurChar;
589
+ };
590
+ } else {
591
+ // Add the current mask character to the MaskPart
592
+ MaskPart += CurChar;
593
+ };
594
+ };
595
+
596
+ // Remove the temporary space from the end of the formatted date
597
+ FormattedDate = FormattedDate.substring(0,FormattedDate.length - 1);
598
+
599
+ // Return the formatted date
600
+ return FormattedDate;
601
+
602
+ };
603
+
604
+
605
+ // timeFormat
606
+ // Formats the time portion of a Date object for display
607
+ Date.prototype.timeFormat = function(Mask) {
608
+
609
+ var FormattedTime = "";
610
+ var MaskChars = "hHmsltT";
611
+
612
+ // Convert any supported simple masks into "real" masks
613
+ switch (Mask) {
614
+ case "short":
615
+ Mask = "h:mm tt";
616
+ break;
617
+ case "medium":
618
+ Mask = "h:mm:ss tt";
619
+ break;
620
+ case "long":
621
+ Mask = "h:mm:ss.l tt";
622
+ break;
623
+ case "full":
624
+ Mask = "h:mm:ss.l tt";
625
+ break;
626
+ };
627
+
628
+ // Tack a temporary space at the end of the mask to ensure that the last character isn't a mask character
629
+ Mask += " ";
630
+
631
+ // Parse the Mask
632
+ var CurChar;
633
+ var MaskPart = "";
634
+ for ( var Cnt = 0; Cnt < Mask.length; Cnt++ ) {
635
+ // Get the character
636
+ CurChar = Mask.charAt(Cnt);
637
+ // Determine if the character is a mask element
638
+ if ( ( MaskChars.indexOf(CurChar) == -1 ) || ( MaskPart != "" && CurChar != MaskPart.charAt(MaskPart.length - 1) ) ) {
639
+ // Determine if we need to parse a MaskPart or not
640
+ if ( MaskPart != "" ) {
641
+ // Convert the mask part to the date value
642
+ switch (MaskPart) {
643
+ case "h":
644
+ var CurValue = this.getHours();
645
+ if ( CurValue > 12 ) {
646
+ CurValue = CurValue - 12;
647
+ };
648
+ FormattedTime += CurValue;
649
+ break;
650
+ case "hh":
651
+ var CurValue = this.getHours();
652
+ if ( CurValue > 12 ) {
653
+ CurValue = CurValue - 12;
654
+ };
655
+ FormattedTime += ("0" + CurValue).slice(-2);
656
+ break;
657
+ case "H":
658
+ FormattedTime += ("0" + this.getHours()).slice(-2);
659
+ break;
660
+ case "HH":
661
+ FormattedTime += ("0" + this.getHours()).slice(-2);
662
+ break;
663
+ case "m":
664
+ FormattedTime += this.getMinutes();
665
+ break;
666
+ case "mm":
667
+ FormattedTime += ("0" + this.getMinutes()).slice(-2);
668
+ break;
669
+ case "s":
670
+ FormattedTime += this.getSeconds();
671
+ break;
672
+ case "ss":
673
+ FormattedTime += ("0" + this.getSeconds()).slice(-2);
674
+ break;
675
+ case "l":
676
+ FormattedTime += ("00" + this.getMilliseconds()).slice(-3);
677
+ break;
678
+ case "t":
679
+ if ( this.getHours() > 11 ) {
680
+ FormattedTime += "p";
681
+ } else {
682
+ FormattedTime += "a";
683
+ };
684
+ break;
685
+ case "tt":
686
+ if ( this.getHours() > 11 ) {
687
+ FormattedTime += "pm";
688
+ } else {
689
+ FormattedTime += "am";
690
+ };
691
+ break;
692
+ case "T":
693
+ if ( this.getHours() > 11 ) {
694
+ FormattedTime += "P";
695
+ } else {
696
+ FormattedTime += "A";
697
+ };
698
+ break;
699
+ case "TT":
700
+ if ( this.getHours() > 11 ) {
701
+ FormattedTime += "PM";
702
+ } else {
703
+ FormattedTime += "AM";
704
+ };
705
+ break;
706
+ };
707
+ // Reset the MaskPart to nothing
708
+ MaskPart = "";
709
+ };
710
+ // If the char is a mask char, start a new mask part, otherwise, dump it to the output
711
+ if ( MaskChars.indexOf(CurChar) > -1 ) {
712
+ MaskPart = CurChar;
713
+ } else {
714
+ FormattedTime += CurChar;
715
+ };
716
+ } else {
717
+ // Add the current mask character to the MaskPart
718
+ MaskPart += CurChar;
719
+ };
720
+ };
721
+
722
+ // Remove the temporary space from the end of the formatted date
723
+ FormattedTime = FormattedTime.substring(0,FormattedTime.length - 1);
724
+
725
+ // Return the formatted date
726
+ return FormattedTime;
727
+
728
+ };
729
+
730
+
731
+ // iso8601Format
732
+ // Formats a date using an ISO8601-compliant format
733
+ Date.prototype.iso8601Format = function(Style, isUTC) {
734
+
735
+ // Set the default
736
+ if ( typeof Style != "string" && typeof Style != "number" ) {
737
+ var Style = "YMDHMSM";
738
+ };
739
+
740
+ var FormattedDate = "";
741
+ var AddTZ = false;
742
+
743
+ switch (Style) {
744
+ case "Y":
745
+ case 1:
746
+ FormattedDate += this.dateFormat("YYYY");
747
+ break;
748
+ case "YM":
749
+ case 2:
750
+ FormattedDate += this.dateFormat("YYYY-MM");
751
+ break;
752
+ case "YMD":
753
+ case 3:
754
+ FormattedDate += this.dateFormat("YYYY-MM-DD");
755
+ break;
756
+ case "YMDHM":
757
+ case 4:
758
+ FormattedDate += this.dateFormat("YYYY-MM-DD") + "T" + this.timeFormat("HH:mm");
759
+ AddTZ = true;
760
+ break;
761
+ case "YMDHMS":
762
+ case 5:
763
+ FormattedDate += this.dateFormat("YYYY-MM-DD") + "T" + this.timeFormat("HH:mm:ss");
764
+ AddTZ = true;
765
+ break;
766
+ case "YMDHMSM":
767
+ case 6:
768
+ FormattedDate += this.dateFormat("YYYY-MM-DD") + "T" + this.timeFormat("HH:mm:ss.l");
769
+ AddTZ = true;
770
+ break;
771
+ };
772
+
773
+ if ( AddTZ ) {
774
+ if ( isUTC ) {
775
+ FormattedDate += "Z";
776
+ } else {
777
+ // Get TimeZone Information
778
+ var TimeZoneOffset = this.getTimezoneOffset();
779
+ var TimeZoneInfo = (TimeZoneOffset >= 0 ? "-" : "+") + ("0" + (Math.floor(Math.abs(TimeZoneOffset) / 60))).slice(-2) + ":" + ("00" + (Math.abs(TimeZoneOffset) % 60)).slice(-2);
780
+ FormattedDate += TimeZoneInfo;
781
+ };
782
+ };
783
+
784
+ // Return the date
785
+ return FormattedDate;
786
+
787
+ };
788
+
789
+ // httpTimeFormat
790
+ // Formats a date using the specification in RFC 822-defined format
791
+ Date.prototype.httpTimeFormat = function(isUTC) {
792
+
793
+ var FormattedDate = "";
794
+ FormattedDate += this.dateFormat("DDD, D MMM YYYY ");
795
+ FormattedDate += this.timeFormat("HH:mm:ss ");
796
+
797
+ if ( isUTC ) {
798
+ FormattedDate += "UT";
799
+ } else {
800
+ // Get TimeZone Information
801
+ var TimeZoneOffset = this.getTimezoneOffset();
802
+ var TimeZoneInfo = (TimeZoneOffset >= 0 ? "-" : "+") + ("0" + (Math.floor(Math.abs(TimeZoneOffset) / 60))).slice(-2) + ("00" + (Math.abs(TimeZoneOffset) % 60)).slice(-2);
803
+ FormattedDate += TimeZoneInfo;
804
+ };
805
+
806
+ // Return the date
807
+ return FormattedDate;
808
+
809
+ };
810
+
811
+
812
+
813
+ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
814
+ /* "Date" Object Prototype Extensions - Convenience functions */
815
+ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
816
+
817
+ // dayOfYear
818
+ // Returns the day of the year
819
+ Date.prototype.dayOfYear = function() {
820
+
821
+ var FirstOfYear = new Date(this.getFullYear(), 0, 1);
822
+ return this.diff(FirstOfYear, "days") + 1;
823
+
824
+ };
825
+
826
+ // weekOfYear
827
+ // Returns the week of the year
828
+ Date.prototype.weekOfYear = function() {
829
+
830
+ var FirstOfYear = new Date(this.getFullYear(), 0, 1);
831
+ return this.diff(FirstOfYear, "weeks") + 1;
832
+
833
+ };
834
+
835
+
836
+
837
+ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
838
+ /* "Date" Object Prototype Extensions - Math functions */
839
+ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
840
+
841
+ // round
842
+ // Rounds a date to the closest specified date part.
843
+ Date.prototype.round = function(DatePart, Destructive) {
844
+
845
+ // Manage Input Params
846
+ if ( typeof DatePart == "string" ) { DatePart = DatePart.toLowerCase() } else { DatePart = "seconds" };
847
+ if ( typeof Destructive != "boolean" ) { Destructive = false };
848
+
849
+ // Set a working/temp date
850
+ var ReturnDate = new Date(this);
851
+ var Ef;
852
+
853
+ // Set non-significant values to baseline
854
+ switch (DatePart) {
855
+ case "years":
856
+ // Mid-year is July Second
857
+ var MidYear = new Date(this.getFullYear(), 6, 2, 12);
858
+ // Do the rounding
859
+ if ( this.round("days").getTime() < MidYear.getTime() ) { Ef = "F" } else { Ef = "C" };
860
+ break;
861
+ case "months":
862
+ // Get the Mid-point of the month
863
+ var TempDate = new Date(this);
864
+ TempDate.setDate(32);
865
+ TempDate.setDate(0);
866
+ var MidPoint = Math.round(TempDate.getDate() / 2);
867
+ // Do the rounding
868
+ if ( this.round("days").getDate() < MidPoint ) { Ef = "F" } else { Ef = "C" };
869
+ break;
870
+ case "weeks":
871
+ // Deal with first day of week
872
+ if ( this.floor("weeks").diff(this.round("Days"), "Days") < 4 ) {
873
+ Ef = "F";
874
+ } else {
875
+ Ef = "C";
876
+ };
877
+ break;
878
+ case "days":
879
+ case "businessdays":
880
+ if ( this.getDay() == 6 ) { Ef = "F" } else if ( this.getDay() == 0 ) { Ef = "C" } else if ( this.getHours() < 12 ) { Ef = "F" } else { Ef = "C" };
881
+ break;
882
+ case "hours":
883
+ if ( this.getMinutes() < 30 ) { Ef = "F" } else { Ef = "C" };
884
+ break;
885
+ case "halfhours":
886
+ var M = this.round("Minutes").getMinutes();
887
+ if ( M<15 || (M>29 && M<45) ) { Ef = "F" } else { Ef = "C" };
888
+ break;
889
+ case "quarterhours":
890
+ case "warhols":
891
+ var M = this.round("Minutes").getMinutes();
892
+ if ( M<8 || (M>14 && M<23) || (M>29 && M<38) || (M>44 && M<53) ) { Ef = "F" } else { Ef = "C" };
893
+ break;
894
+ case "minutes":
895
+ if ( this.getSeconds() < 30 ) { Ef = "F" } else { Ef = "C" };
896
+ break;
897
+ case "seconds":
898
+ if ( this.getMilliseconds() < 500 ) { Ef = "F" } else { Ef = "C" };
899
+ break;
900
+ case "milliseconds":
901
+ break;
902
+ };
903
+
904
+ // Call floor/ceil to finish the job
905
+ if ( Ef == "F" ) {
906
+ ReturnDate = this.floor(DatePart);
907
+ } else if ( Ef == "C") {
908
+ ReturnDate = this.ceil(DatePart);
909
+ };
910
+
911
+ // Return
912
+ if ( !Destructive ) {
913
+ return ReturnDate;
914
+ } else {
915
+ this.setTime(ReturnDate.getTime());
916
+ return this;
917
+ };
918
+
919
+ };
920
+
921
+ // ceil
922
+ // Rounds a date upwards to the closest specified date part.
923
+ Date.prototype.ceil = function(DatePart, Destructive) {
924
+
925
+ // Manage Input Params
926
+ if ( typeof DatePart == "string" ) { DatePart = DatePart.toLowerCase() } else { DatePart = "seconds" };
927
+ if ( typeof Destructive != "boolean" ) { Destructive = false };
928
+
929
+ // Set a working/temp date
930
+ var ReturnDate;
931
+
932
+ // Set non-significant values to baseline
933
+ switch (DatePart) {
934
+ case "years":
935
+ ReturnDate = new Date( this.getFullYear() + 1, 0, 1);
936
+ break;
937
+ case "months":
938
+ ReturnDate = new Date( this.getFullYear(), this.getMonth() + 1, 1 );
939
+ break;
940
+ case "weeks":
941
+ ReturnDate = new Date( this.getFullYear(), this.getMonth(), this.getDate() );
942
+ // Deal with first day of week
943
+ if ( Date.FirstDayOfWeek <= this.getDay() ) {
944
+ var FirstDayOffset = 7 + (Date.FirstDayOfWeek - this.getDay());
945
+ } else {
946
+ var FirstDayOffset = -this.getDay() + Date.FirstDayOfWeek;
947
+ };
948
+ ReturnDate = ReturnDate.add(FirstDayOffset, "days");
949
+ break;
950
+ case "days":
951
+ ReturnDate = new Date( this.getFullYear(), this.getMonth(), this.getDate() + 1 );
952
+ break;
953
+ case "businessdays":
954
+ ReturnDate = new Date( this.getFullYear(), this.getMonth(), this.getDate() + 1 );
955
+ // If the result is a business day, keep it, otherwise shift it.
956
+ if ( ReturnDate.getDay() == 0 ) {
957
+ ReturnDate = ReturnDate.add(1, "days");
958
+ } else if ( ReturnDate.getDay() == 6 ) {
959
+ ReturnDate = ReturnDate.add(2, "days");
960
+ };
961
+ break;
962
+ case "hours":
963
+ ReturnDate = new Date( this.getFullYear(), this.getMonth(), this.getDate(), this.getHours() + 1 );
964
+ break;
965
+ case "halfhours":
966
+ ReturnDate = new Date( this.getFullYear(), this.getMonth(), this.getDate(), this.getHours() );
967
+ if ( this.getMinutes() < 30 ) {
968
+ ReturnDate.setMinutes(30);
969
+ } else {
970
+ ReturnDate.setMinutes(60);
971
+ };
972
+ break;
973
+ case "quarterhours":
974
+ case "warhols":
975
+ ReturnDate = new Date( this.getFullYear(), this.getMonth(), this.getDate(), this.getHours() );
976
+ if ( this.getMinutes() < 15 ) {
977
+ ReturnDate.setMinutes(15);
978
+ } else if ( this.getMinutes() < 30 ) {
979
+ ReturnDate.setMinutes(30);
980
+ } else if ( this.getMinutes() < 45 ) {
981
+ ReturnDate.setMinutes(45);
982
+ } else {
983
+ ReturnDate.setMinutes(60);
984
+ };
985
+ break;
986
+ case "minutes":
987
+ ReturnDate = new Date( this.getFullYear(), this.getMonth(), this.getDate(), this.getHours(), this.getMinutes() + 1 );
988
+ break;
989
+ case "seconds":
990
+ ReturnDate = new Date( this.getFullYear(), this.getMonth(), this.getDate(), this.getHours(), this.getMinutes(), this.getSeconds() + 1 );
991
+ break;
992
+ case "milliseconds":
993
+ ReturnDate = new Date( this );
994
+ break;
995
+ };
996
+
997
+ // Return
998
+ if ( !Destructive ) {
999
+ return ReturnDate;
1000
+ } else {
1001
+ this.setTime(ReturnDate.getTime());
1002
+ return this;
1003
+ };
1004
+
1005
+ };
1006
+
1007
+ // floor
1008
+ // Rounds a date downward to the closest specified date part.
1009
+ Date.prototype.floor = function(DatePart, Destructive) {
1010
+
1011
+ // Manage Input Params
1012
+ if ( typeof DatePart == "string" ) { DatePart = DatePart.toLowerCase() } else { DatePart = "seconds" };
1013
+ if ( typeof Destructive != "boolean" ) { Destructive = false };
1014
+
1015
+ // Set a working/temp date
1016
+ var ReturnDate;
1017
+
1018
+ // Set non-significant values to baseline
1019
+ switch (DatePart) {
1020
+ case "years":
1021
+ ReturnDate = new Date( this.getFullYear(), 0, 1 );
1022
+ break;
1023
+ case "months":
1024
+ ReturnDate = new Date( this.getFullYear(), this.getMonth(), 1 );
1025
+ break;
1026
+ case "weeks":
1027
+ ReturnDate = new Date( this.getFullYear(), this.getMonth(), this.getDate() );
1028
+ ReturnDate = ReturnDate.ceil("weeks").add(-7, "days");
1029
+ break;
1030
+ case "days":
1031
+ ReturnDate = new Date( this.getFullYear(), this.getMonth(), this.getDate() );
1032
+ break;
1033
+ case "businessdays":
1034
+ ReturnDate = new Date( this.getFullYear(), this.getMonth(), this.getDate() );
1035
+ // If the result is a business day, keep it, otherwise shift it.
1036
+ if ( ReturnDate.getDay() == 0 ) {
1037
+ ReturnDate = ReturnDate.add(-2, "days");
1038
+ } else if ( ReturnDate.getDay() == 6 ) {
1039
+ ReturnDate = ReturnDate.add(-1, "days");
1040
+ };
1041
+ break;
1042
+ case "hours":
1043
+ ReturnDate = new Date( this.getFullYear(), this.getMonth(), this.getDate(), this.getHours() );
1044
+ break;
1045
+ case "halfhours":
1046
+ ReturnDate = new Date( this.getFullYear(), this.getMonth(), this.getDate(), this.getHours() );
1047
+ if ( this.getMinutes() < 30 ) {
1048
+ ReturnDate.setMinutes(0);
1049
+ } else {
1050
+ ReturnDate.setMinutes(30);
1051
+ };
1052
+ break;
1053
+ case "quarterhours":
1054
+ case "warhols":
1055
+ ReturnDate = new Date( this.getFullYear(), this.getMonth(), this.getDate(), this.getHours() );
1056
+ if ( this.getMinutes() < 15 ) {
1057
+ ReturnDate.setMinutes(0);
1058
+ } else if ( this.getMinutes() < 30 ) {
1059
+ ReturnDate.setMinutes(15);
1060
+ } else if ( this.getMinutes() < 45 ) {
1061
+ ReturnDate.setMinutes(30);
1062
+ } else {
1063
+ ReturnDate.setMinutes(45);
1064
+ };
1065
+ break;
1066
+ case "minutes":
1067
+ ReturnDate = new Date( this.getFullYear(), this.getMonth(), this.getDate(), this.getHours(), this.getMinutes() );
1068
+ break;
1069
+ case "seconds":
1070
+ ReturnDate = new Date( this.getFullYear(), this.getMonth(), this.getDate(), this.getHours(), this.getMinutes(), this.getSeconds() );
1071
+ break;
1072
+ case "milliseconds":
1073
+ ReturnDate = new Date( this );
1074
+ break;
1075
+ };
1076
+
1077
+ // Return
1078
+ if ( !Destructive ) {
1079
+ return ReturnDate;
1080
+ } else {
1081
+ this.setTime(ReturnDate.getTime());
1082
+ return this;
1083
+ };
1084
+
1085
+ };
1086
+
1087
+ // compare
1088
+ // Compares two dates (optionally using a specific datepart precision)
1089
+ Date.prototype.compare = function(CompareDate, DatePart) {
1090
+
1091
+ // Manage Input Params
1092
+ if ( !Date.is(CompareDate) ) { CompareDate = new Date() };
1093
+ if ( typeof DatePart == "string" ) { DatePart = DatePart.toLowerCase() } else { DatePart = "milliseconds" };
1094
+
1095
+ // Set working/temp vars
1096
+ var Date1 = new Date(this);
1097
+ var Date2 = new Date(CompareDate);
1098
+ var Result;
1099
+
1100
+ // Set the precision by equalizing higher precision elements
1101
+ Date1.floor(DatePart);
1102
+ Date2.floor(DatePart);
1103
+
1104
+ // Do the comparison
1105
+ if ( Date1.getTime() == Date2.getTime() ) {
1106
+ Result = 0;
1107
+ } else if ( Date1.getTime() < Date2.getTime() ) {
1108
+ Result = -1;
1109
+ } else {
1110
+ Result = 1;
1111
+ };
1112
+
1113
+ // Return the result
1114
+ return Result;
1115
+
1116
+ };
1117
+
1118
+ // add
1119
+ // Adds a specified number of a specified date unit to a date
1120
+ Date.prototype.add = function(Amount, DatePart, Destructive) {
1121
+
1122
+ // Manage Input Params
1123
+ if ( typeof Amount == "number" ) { Math.abs(Amount) } else { Amount = 0 };
1124
+ if ( typeof DatePart == "string" ) { DatePart = DatePart.toLowerCase() } else { DatePart = "milliseconds" };
1125
+
1126
+ var ReturnDate = new Date(this);
1127
+
1128
+ // Can't add zero date parts
1129
+ if ( Amount != 0 ) {
1130
+
1131
+ // Do the math
1132
+ switch (DatePart) {
1133
+ // The following are all unambigously convertable to ms equivalents
1134
+ case "milliseconds":
1135
+ ReturnDate.setMilliseconds(ReturnDate.getMilliseconds() + Amount);
1136
+ break;
1137
+ case "seconds":
1138
+ ReturnDate.setSeconds(ReturnDate.getSeconds() + Amount);
1139
+ break;
1140
+ case "minutes":
1141
+ ReturnDate.setMinutes(ReturnDate.getMinutes() + Amount);
1142
+ break;
1143
+ case "quarterhours":
1144
+ case "warhols":
1145
+ ReturnDate.setMinutes(ReturnDate.getSeconds() + (Amount*15));
1146
+ break;
1147
+ case "halfhours":
1148
+ ReturnDate.setMinutes(ReturnDate.getSeconds() + (Amount*30));
1149
+ break;
1150
+ case "hours":
1151
+ ReturnDate.setHours(ReturnDate.getHours() + Amount);
1152
+ break;
1153
+ case "days":
1154
+ ReturnDate.setDate(ReturnDate.getDate() + Amount);
1155
+ break;
1156
+ case "weeks":
1157
+ ReturnDate.setDate(ReturnDate.getDate() + (Amount*7));
1158
+ break;
1159
+ case "businessdays":
1160
+ ReturnDate.setDate(ReturnDate.getDate() + Amount);
1161
+ break;
1162
+ case "months":
1163
+ ReturnDate.setMonth(ReturnDate.getMonth() + Amount);
1164
+ break;
1165
+ case "years":
1166
+ ReturnDate.setFullYear(ReturnDate.getFullYear() + Amount);
1167
+ break;
1168
+ };
1169
+ };
1170
+
1171
+ // Return the time
1172
+ if ( !Destructive ) {
1173
+ return ReturnDate;
1174
+ } else {
1175
+ this.setTime(ReturnDate.getTime());
1176
+ return this;
1177
+ };
1178
+
1179
+ };
1180
+
1181
+
1182
+ // diff
1183
+ // Returns the difference between two dates.
1184
+ Date.prototype.diff = function(CompareDate, DatePart, CompareMethod, NormalizeDST) {
1185
+
1186
+ // Manage input params and defaults
1187
+ if ( !Date.is(CompareDate) ) { CompareDate = new Date() };
1188
+ if ( typeof DatePart == "string" ) { DatePart = DatePart.toLowerCase() } else { DatePart = "milliseconds" };
1189
+ if ( typeof CompareMethod == "string" ) { CompareMethod = CompareMethod.toLowerCase() } else { CompareMethod = "actual" };
1190
+ if ( typeof NormalizeDST != "boolean" ) { NormalizeDST = true };
1191
+
1192
+ // Declare variables
1193
+ var Diff, BaseDiff, Date1, Date2, Date1TZO, Date2TZO;
1194
+
1195
+ // Set the dates in order, Date1 previous
1196
+ if ( this.getTime() <= CompareDate.getTime() ) {
1197
+ Date1 = new Date(this);
1198
+ Date2 = new Date(CompareDate);
1199
+ } else {
1200
+ Date1 = new Date(CompareDate);
1201
+ Date2 = new Date(this);
1202
+ };
1203
+
1204
+ // Attempt to normalize DST
1205
+ if ( NormalizeDST ) {
1206
+ Date1TZO = Date1.getTimezoneOffset();
1207
+ Date2TZO = Date2.getTimezoneOffset();
1208
+
1209
+ if ( Date1TZO > Date2TZO ) {
1210
+ Date2 = Date2.add(Date1TZO - Date2TZO, "Minutes");
1211
+ } else
1212
+ if ( Date1.getTimezoneOffset() < Date2.getTimezoneOffset() ) {
1213
+ Date1 = Date1.add(Date2TZO - Date1TZO, "Minutes");
1214
+ };
1215
+ };
1216
+
1217
+ // Set the Base Different (in ms)
1218
+ BaseDiff = (Date1.getTime() - Date2.getTime());
1219
+
1220
+ // Prepare the two dates
1221
+ if ( CompareMethod == "logical" ) {
1222
+ Date1.floor(DatePart, true);
1223
+ Date2.floor(DatePart, true);
1224
+ // Add to address dates on the instant of change
1225
+ Date2.add(1, "Milliseconds", true);
1226
+ };
1227
+ if ( CompareMethod == "complete" ) {
1228
+ // Add to address dates on the instant of change
1229
+ Date1.add(-1, "Milliseconds", true);
1230
+ // Move to the next full date part
1231
+ Date1.ceil(DatePart, true);
1232
+ };
1233
+
1234
+ // Do the math
1235
+ //
1236
+ // For simple date parts:
1237
+ // "actual" is the difference between ms divided by the appropriate factors.
1238
+ // "logical" zeros out the non-significant date parts and does a Diff on the result.
1239
+ // "complete" moves Date1 to the ms before the beginning of the next period, zeros out non-significant date parts, moves Date1 to the beginning of the next period and does a Diff on the result
1240
+ switch (DatePart) {
1241
+
1242
+ case "milliseconds":
1243
+ Diff = BaseDiff;
1244
+ break;
1245
+ case "seconds":
1246
+ case "minutes":
1247
+ case "quarterhours":
1248
+ case "warhols":
1249
+ case "halfhours":
1250
+ case "hours":
1251
+ case "days":
1252
+ case "weeks":
1253
+ if ( CompareMethod == "actual" ) {
1254
+ Diff = parseInt( BaseDiff / Date.Factors[DatePart] );
1255
+ } else {
1256
+ Diff = Date1.diff(Date2, DatePart, "Actual", NormalizeDST);
1257
+ };
1258
+ break;
1259
+ case "businessdays":
1260
+ // Set Date2 to the end of a non-business day
1261
+ if ( Date2.getDay() == 0 || Date2.getDay() == 6 ) {
1262
+ Date2.ceil(DatePart, true);
1263
+ };
1264
+ // First get the number of days between the dates
1265
+ var IntDiff = Date1.diff(Date2, "Days", CompareMethod, NormalizeDST);
1266
+ // Count through the days and remove the Saturdays and Sundays
1267
+ while ( Date1.getTime() < Date2.getTime() ) {
1268
+ if ( Date1.getDay() == 0 || Date1.getDay() == 6 ) {
1269
+ --IntDiff;
1270
+ };
1271
+ Date1 = Date1.add(1, "days");
1272
+ };
1273
+ Diff = IntDiff;
1274
+ break;
1275
+ case "months":
1276
+ var MonthsCnt = 0;
1277
+ // Count up the months
1278
+ while ( Date1.getTime() < Date2.getTime() ) {
1279
+ Date1.setMonth(Date1.getMonth() + 1);
1280
+ MonthsCnt++;
1281
+ // Determine if a "full" month has passed, date to date
1282
+ if ( Date1.getFullYear() == Date2.getFullYear() && Date1.getMonth() == Date2.getMonth() ) {
1283
+ if ( Date1.getDate() > Date2.getDate() ) {
1284
+ --MonthsCnt;
1285
+ } else {
1286
+ break;
1287
+ };
1288
+ };
1289
+ };
1290
+ Diff = MonthsCnt;
1291
+ break;
1292
+ case "years":
1293
+ Diff = parseInt(Date1.diff(Date2, "Months", "Actual", NormalizeDST) / 12) ;
1294
+ break;
1295
+ };
1296
+
1297
+ // Return the time (abs to eliminate negative returns)
1298
+ return Math.abs(Diff);
1299
+
1300
+ };