morris-rails 0.4.2

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.
Files changed (52) hide show
  1. data/Gemfile +3 -0
  2. data/Gemfile.lock +97 -0
  3. data/LICENSE +20 -0
  4. data/README.md +58 -0
  5. data/Rakefile +8 -0
  6. data/VERSION +1 -0
  7. data/app/assets/javascripts/morris.area.coffee +42 -0
  8. data/app/assets/javascripts/morris.bar.coffee +165 -0
  9. data/app/assets/javascripts/morris.coffee +42 -0
  10. data/app/assets/javascripts/morris.donut.coffee +182 -0
  11. data/app/assets/javascripts/morris.grid.coffee +392 -0
  12. data/app/assets/javascripts/morris.hover.coffee +41 -0
  13. data/app/assets/javascripts/morris.line.coffee +354 -0
  14. data/app/assets/stylesheets/morris.core.less +27 -0
  15. data/lib/morris-rails/rails.rb +1 -0
  16. data/morris-rails.gemspec +30 -0
  17. data/spec/dummy/README.rdoc +261 -0
  18. data/spec/dummy/Rakefile +7 -0
  19. data/spec/dummy/app/assets/javascripts/application.js +15 -0
  20. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  21. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  22. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  23. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  24. data/spec/dummy/config.ru +4 -0
  25. data/spec/dummy/config/application.rb +65 -0
  26. data/spec/dummy/config/boot.rb +10 -0
  27. data/spec/dummy/config/database.yml +25 -0
  28. data/spec/dummy/config/environment.rb +5 -0
  29. data/spec/dummy/config/environments/development.rb +37 -0
  30. data/spec/dummy/config/environments/production.rb +67 -0
  31. data/spec/dummy/config/environments/test.rb +37 -0
  32. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  33. data/spec/dummy/config/initializers/inflections.rb +15 -0
  34. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  35. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  36. data/spec/dummy/config/initializers/session_store.rb +8 -0
  37. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  38. data/spec/dummy/config/locales/en.yml +5 -0
  39. data/spec/dummy/config/routes.rb +58 -0
  40. data/spec/dummy/db/test.sqlite3 +0 -0
  41. data/spec/dummy/log/test.log +6 -0
  42. data/spec/dummy/public/404.html +26 -0
  43. data/spec/dummy/public/422.html +26 -0
  44. data/spec/dummy/public/500.html +25 -0
  45. data/spec/dummy/public/favicon.ico +0 -0
  46. data/spec/dummy/script/rails +6 -0
  47. data/spec/dummy/tmp/cache/assets/C87/360/sprockets%2F7f2a79129bb3445603eb704843de5397 +0 -0
  48. data/spec/dummy/tmp/cache/assets/D5B/040/sprockets%2F5d0b7809f616213c43a3ba43cbcda91d +0 -0
  49. data/spec/dummy/tmp/cache/assets/DAE/5D0/sprockets%2Fae427ff9e5b67de97e2ad285c5b623b6 +0 -0
  50. data/spec/morris-rails_spec.rb +12 -0
  51. data/spec/spec_helper.rb +16 -0
  52. metadata +271 -0
@@ -0,0 +1,182 @@
1
+ # Donut charts.
2
+ #
3
+ # @example
4
+ # Morris.Donut({
5
+ # el: $('#donut-container'),
6
+ # data: [
7
+ # { label: 'yin', value: 50 },
8
+ # { label: 'yang', value: 50 }
9
+ # ]
10
+ # });
11
+ class Morris.Donut
12
+ defaults:
13
+ colors: [
14
+ '#0B62A4'
15
+ '#3980B5'
16
+ '#679DC6'
17
+ '#95BBD7'
18
+ '#B0CCE1'
19
+ '#095791'
20
+ '#095085'
21
+ '#083E67'
22
+ '#052C48'
23
+ '#042135'
24
+ ],
25
+ backgroundColor: '#FFFFFF',
26
+ labelColor: '#000000',
27
+ formatter: Morris.commas
28
+
29
+ # Create and render a donut chart.
30
+ #
31
+ constructor: (options) ->
32
+ if not (this instanceof Morris.Donut)
33
+ return new Morris.Donut(options)
34
+
35
+ if typeof options.element is 'string'
36
+ @el = $ document.getElementById(options.element)
37
+ else
38
+ @el = $ options.element
39
+
40
+ @options = $.extend {}, @defaults, options
41
+
42
+ if @el == null || @el.length == 0
43
+ throw new Error("Graph placeholder not found.")
44
+
45
+ # bail if there's no data
46
+ if options.data is undefined or options.data.length is 0
47
+ return
48
+ @data = options.data
49
+
50
+ @redraw()
51
+
52
+ # Clear and redraw the chart.
53
+ #
54
+ # If you need to re-size your charts, call this method after changing the
55
+ # size of the container element.
56
+ redraw: ->
57
+ @el.empty()
58
+
59
+ @raphael = new Raphael(@el[0])
60
+
61
+ cx = @el.width() / 2
62
+ cy = @el.height() / 2
63
+ w = (Math.min(cx, cy) - 10) / 3
64
+
65
+ total = 0
66
+ total += x.value for x in @data
67
+
68
+ min = 5 / (2 * w)
69
+ C = 1.9999 * Math.PI - min * @data.length
70
+
71
+ last = 0
72
+ idx = 0
73
+ @segments = []
74
+ for d in @data
75
+ next = last + min + C * (d.value / total)
76
+ seg = new Morris.DonutSegment(cx, cy, w*2, w, last, next, @options.colors[idx % @options.colors.length], @options.backgroundColor, d, @raphael)
77
+ seg.render()
78
+ @segments.push seg
79
+ seg.on 'hover', @select
80
+ last = next
81
+ idx += 1
82
+ @text1 = @drawEmptyDonutLabel(cx, cy - 10, @options.labelColor, 15, 800)
83
+ @text2 = @drawEmptyDonutLabel(cx, cy + 10, @options.labelColor, 14)
84
+ max_value = Math.max.apply(null, d.value for d in @data)
85
+ idx = 0
86
+ for d in @data
87
+ if d.value == max_value
88
+ @select idx
89
+ break
90
+ idx += 1
91
+
92
+ # Select the segment at the given index.
93
+ select: (idx) =>
94
+ s.deselect() for s in @segments
95
+ if typeof idx is 'number' then segment = @segments[idx] else segment = idx
96
+ segment.select()
97
+ @setLabels segment.data.label, @options.formatter(segment.data.value, segment.data)
98
+
99
+ # @private
100
+ setLabels: (label1, label2) ->
101
+ inner = (Math.min(@el.width() / 2, @el.height() / 2) - 10) * 2 / 3
102
+ maxWidth = 1.8 * inner
103
+ maxHeightTop = inner / 2
104
+ maxHeightBottom = inner / 3
105
+ @text1.attr(text: label1, transform: '')
106
+ text1bbox = @text1.getBBox()
107
+ text1scale = Math.min(maxWidth / text1bbox.width, maxHeightTop / text1bbox.height)
108
+ @text1.attr(transform: "S#{text1scale},#{text1scale},#{text1bbox.x + text1bbox.width / 2},#{text1bbox.y + text1bbox.height}")
109
+ @text2.attr(text: label2, transform: '')
110
+ text2bbox = @text2.getBBox()
111
+ text2scale = Math.min(maxWidth / text2bbox.width, maxHeightBottom / text2bbox.height)
112
+ @text2.attr(transform: "S#{text2scale},#{text2scale},#{text2bbox.x + text2bbox.width / 2},#{text2bbox.y}")
113
+
114
+ drawEmptyDonutLabel: (xPos, yPos, color, fontSize, fontWeight) ->
115
+ text = @raphael.text(xPos, yPos, '')
116
+ .attr('font-size', fontSize)
117
+ .attr('fill', color)
118
+ text.attr('font-weight', fontWeight) if fontWeight?
119
+ return text
120
+
121
+
122
+ # A segment within a donut chart.
123
+ #
124
+ # @private
125
+ class Morris.DonutSegment extends Morris.EventEmitter
126
+ constructor: (@cx, @cy, @inner, @outer, p0, p1, @color, @backgroundColor, @data, @raphael) ->
127
+ @sin_p0 = Math.sin(p0)
128
+ @cos_p0 = Math.cos(p0)
129
+ @sin_p1 = Math.sin(p1)
130
+ @cos_p1 = Math.cos(p1)
131
+ @is_long = if (p1 - p0) > Math.PI then 1 else 0
132
+ @path = @calcSegment(@inner + 3, @inner + @outer - 5)
133
+ @selectedPath = @calcSegment(@inner + 3, @inner + @outer)
134
+ @hilight = @calcArc(@inner)
135
+
136
+ calcArcPoints: (r) ->
137
+ return [
138
+ @cx + r * @sin_p0,
139
+ @cy + r * @cos_p0,
140
+ @cx + r * @sin_p1,
141
+ @cy + r * @cos_p1]
142
+
143
+ calcSegment: (r1, r2) ->
144
+ [ix0, iy0, ix1, iy1] = @calcArcPoints(r1)
145
+ [ox0, oy0, ox1, oy1] = @calcArcPoints(r2)
146
+ return (
147
+ "M#{ix0},#{iy0}" +
148
+ "A#{r1},#{r1},0,#{@is_long},0,#{ix1},#{iy1}" +
149
+ "L#{ox1},#{oy1}" +
150
+ "A#{r2},#{r2},0,#{@is_long},1,#{ox0},#{oy0}" +
151
+ "Z")
152
+
153
+ calcArc: (r) ->
154
+ [ix0, iy0, ix1, iy1] = @calcArcPoints(r)
155
+ return (
156
+ "M#{ix0},#{iy0}" +
157
+ "A#{r},#{r},0,#{@is_long},0,#{ix1},#{iy1}")
158
+
159
+ render: ->
160
+ @arc = @drawDonutArc(@hilight, @color)
161
+ @seg = @drawDonutSegment(@path, @color, @backgroundColor, => @fire('hover', @))
162
+
163
+ drawDonutArc: (path, color) ->
164
+ @raphael.path(path)
165
+ .attr(stroke: color, 'stroke-width': 2, opacity: 0)
166
+
167
+ drawDonutSegment: (path, fillColor, strokeColor, hoverFunction) ->
168
+ @raphael.path(path)
169
+ .attr(fill: fillColor, stroke: strokeColor, 'stroke-width': 3)
170
+ .hover(hoverFunction)
171
+
172
+ select: =>
173
+ unless @selected
174
+ @seg.animate(path: @selectedPath, 150, '<>')
175
+ @arc.animate(opacity: 1, 150, '<>')
176
+ @selected = true
177
+
178
+ deselect: =>
179
+ if @selected
180
+ @seg.animate(path: @path, 150, '<>')
181
+ @arc.animate(opacity: 0, 150, '<>')
182
+ @selected = false
@@ -0,0 +1,392 @@
1
+ class Morris.Grid extends Morris.EventEmitter
2
+ # A generic pair of axes for line/area/bar charts.
3
+ #
4
+ # Draws grid lines and axis labels.
5
+ #
6
+ constructor: (options) ->
7
+ # find the container to draw the graph in
8
+ if typeof options.element is 'string'
9
+ @el = $ document.getElementById(options.element)
10
+ else
11
+ @el = $ options.element
12
+ if not @el? or @el.length == 0
13
+ throw new Error("Graph container element not found")
14
+
15
+ if @el.css('position') == 'static'
16
+ @el.css('position', 'relative')
17
+
18
+ @options = $.extend {}, @gridDefaults, (@defaults || {}), options
19
+
20
+ # backwards compatibility for units -> postUnits
21
+ if typeof @options.units is 'string'
22
+ @options.postUnits = options.units
23
+
24
+ # the raphael drawing instance
25
+ @raphael = new Raphael(@el[0])
26
+
27
+ # some redraw stuff
28
+ @elementWidth = null
29
+ @elementHeight = null
30
+ @dirty = false
31
+
32
+ # more stuff
33
+ @init() if @init
34
+
35
+ # load data
36
+ @setData @options.data
37
+
38
+ # hover
39
+ @el.bind 'mousemove', (evt) =>
40
+ offset = @el.offset()
41
+ @fire 'hovermove', evt.pageX - offset.left, evt.pageY - offset.top
42
+
43
+ @el.bind 'mouseout', (evt) =>
44
+ @fire 'hoverout'
45
+
46
+ @el.bind 'touchstart touchmove touchend', (evt) =>
47
+ touch = evt.originalEvent.touches[0] or evt.originalEvent.changedTouches[0]
48
+ offset = @el.offset()
49
+ @fire 'hover', touch.pageX - offset.left, touch.pageY - offset.top
50
+ touch
51
+
52
+ @postInit() if @postInit
53
+
54
+ # Default options
55
+ #
56
+ gridDefaults:
57
+ dateFormat: null
58
+ axes: true
59
+ grid: true
60
+ gridLineColor: '#aaa'
61
+ gridStrokeWidth: 0.5
62
+ gridTextColor: '#888'
63
+ gridTextSize: 12
64
+ hideHover: false
65
+ yLabelFormat: null
66
+ numLines: 5
67
+ padding: 25
68
+ parseTime: true
69
+ postUnits: ''
70
+ preUnits: ''
71
+ ymax: 'auto'
72
+ ymin: 'auto 0'
73
+ goals: []
74
+ goalStrokeWidth: 1.0
75
+ goalLineColors: [
76
+ '#666633'
77
+ '#999966'
78
+ '#cc6666'
79
+ '#663333'
80
+ ]
81
+ events: []
82
+ eventStrokeWidth: 1.0
83
+ eventLineColors: [
84
+ '#005a04'
85
+ '#ccffbb'
86
+ '#3a5f0b'
87
+ '#005502'
88
+ ]
89
+
90
+ # Update the data series and redraw the chart.
91
+ #
92
+ setData: (data, redraw = true) ->
93
+ if !data? or data.length == 0
94
+ @data = []
95
+ @raphael.clear()
96
+ @hover.hide() if @hover?
97
+ return
98
+
99
+ ymax = if @cumulative then 0 else null
100
+ ymin = if @cumulative then 0 else null
101
+
102
+ if @options.goals.length > 0
103
+ minGoal = Math.min.apply(null, @options.goals)
104
+ maxGoal = Math.max.apply(null, @options.goals)
105
+ ymin = if ymin? then Math.min(ymin, minGoal) else minGoal
106
+ ymax = if ymax? then Math.max(ymax, maxGoal) else maxGoal
107
+
108
+ @data = for row, index in data
109
+ ret = {}
110
+ ret.label = row[@options.xkey]
111
+ if @options.parseTime
112
+ ret.x = Morris.parseDate(ret.label)
113
+ if @options.dateFormat
114
+ ret.label = @options.dateFormat ret.x
115
+ else if typeof ret.label is 'number'
116
+ ret.label = new Date(ret.label).toString()
117
+ else
118
+ ret.x = index
119
+ total = 0
120
+ ret.y = for ykey, idx in @options.ykeys
121
+ yval = row[ykey]
122
+ yval = parseFloat(yval) if typeof yval is 'string'
123
+ yval = null if yval? and typeof yval isnt 'number'
124
+ if yval?
125
+ if @cumulative
126
+ total += yval
127
+ else
128
+ if ymax?
129
+ ymax = Math.max(yval, ymax)
130
+ ymin = Math.min(yval, ymin)
131
+ else
132
+ ymax = ymin = yval
133
+ if @cumulative and total?
134
+ ymax = Math.max(total, ymax)
135
+ ymin = Math.min(total, ymin)
136
+ yval
137
+ ret
138
+
139
+ if @options.parseTime
140
+ @data = @data.sort (a, b) -> (a.x > b.x) - (b.x > a.x)
141
+
142
+ # calculate horizontal range of the graph
143
+ @xmin = @data[0].x
144
+ @xmax = @data[@data.length - 1].x
145
+
146
+ @events = []
147
+ if @options.parseTime and @options.events.length > 0
148
+ @events = (Morris.parseDate(e) for e in @options.events)
149
+ @xmax = Math.max(@xmax, Math.max.apply(null, @events))
150
+ @xmin = Math.min(@xmin, Math.min.apply(null, @events))
151
+
152
+ if @xmin is @xmax
153
+ @xmin -= 1
154
+ @xmax += 1
155
+
156
+ @ymin = @yboundary('min', ymin)
157
+ @ymax = @yboundary('max', ymax)
158
+
159
+ if @ymin is @ymax
160
+ @ymin -= 1 if ymin
161
+ @ymax += 1
162
+
163
+ @yInterval = (@ymax - @ymin) / (@options.numLines - 1)
164
+ if @yInterval > 0 and @yInterval < 1
165
+ @precision = -Math.floor(Math.log(@yInterval) / Math.log(10))
166
+ else
167
+ @precision = 0
168
+
169
+ @dirty = true
170
+ @redraw() if redraw
171
+
172
+ yboundary: (boundaryType, currentValue) ->
173
+ boundaryOption = @options["y#{boundaryType}"]
174
+ if typeof boundaryOption is 'string'
175
+ if boundaryOption[0..3] is 'auto'
176
+ if boundaryOption.length > 5
177
+ suggestedValue = parseInt(boundaryOption[5..], 10)
178
+ return suggestedValue unless currentValue?
179
+ Math[boundaryType](currentValue, suggestedValue)
180
+ else
181
+ if currentValue? then currentValue else 0
182
+ else
183
+ parseInt(boundaryOption, 10)
184
+ else
185
+ boundaryOption
186
+
187
+ _calc: ->
188
+ w = @el.width()
189
+ h = @el.height()
190
+
191
+ if @elementWidth != w or @elementHeight != h or @dirty
192
+ @elementWidth = w
193
+ @elementHeight = h
194
+ @dirty = false
195
+ # recalculate grid dimensions
196
+ @left = @options.padding
197
+ @right = @elementWidth - @options.padding
198
+ @top = @options.padding
199
+ @bottom = @elementHeight - @options.padding
200
+ if @options.axes
201
+ maxYLabelWidth = Math.max(
202
+ @measureText(@yAxisFormat(@ymin), @options.gridTextSize).width,
203
+ @measureText(@yAxisFormat(@ymax), @options.gridTextSize).width)
204
+ @left += maxYLabelWidth
205
+ @bottom -= 1.5 * @options.gridTextSize
206
+ @width = @right - @left
207
+ @height = @bottom - @top
208
+ @dx = @width / (@xmax - @xmin)
209
+ @dy = @height / (@ymax - @ymin)
210
+ @calc() if @calc
211
+
212
+ # Quick translation helpers
213
+ #
214
+ transY: (y) -> @bottom - (y - @ymin) * @dy
215
+ transX: (x) ->
216
+ if @data.length == 1
217
+ (@left + @right) / 2
218
+ else
219
+ @left + (x - @xmin) * @dx
220
+
221
+ # Draw it!
222
+ #
223
+ # If you need to re-size your charts, call this method after changing the
224
+ # size of the container element.
225
+ redraw: ->
226
+ @raphael.clear()
227
+ @_calc()
228
+ @drawGrid()
229
+ @drawGoals()
230
+ @drawEvents()
231
+ @draw() if @draw
232
+
233
+ # @private
234
+ #
235
+ measureText: (text, fontSize = 12) ->
236
+ tt = @raphael.text(100, 100, text).attr('font-size', fontSize)
237
+ ret = tt.getBBox()
238
+ tt.remove()
239
+ ret
240
+
241
+ # @private
242
+ #
243
+ yAxisFormat: (label) -> @yLabelFormat(label)
244
+
245
+ # @private
246
+ #
247
+ yLabelFormat: (label) ->
248
+ if typeof @options.yLabelFormat is 'function'
249
+ @options.yLabelFormat(label)
250
+ else
251
+ "#{@options.preUnits}#{Morris.commas(label)}#{@options.postUnits}"
252
+
253
+ updateHover: (x, y) ->
254
+ hit = @hitTest(x, y)
255
+ if hit?
256
+ @hover.update(hit...)
257
+
258
+ # draw y axis labels, horizontal lines
259
+ #
260
+ drawGrid: ->
261
+ return if @options.grid is false and @options.axes is false
262
+ firstY = @ymin
263
+ lastY = @ymax
264
+ for lineY in [firstY..lastY] by @yInterval
265
+ v = parseFloat(lineY.toFixed(@precision))
266
+ y = @transY(v)
267
+ if @options.axes
268
+ @drawYAxisLabel(@left - @options.padding / 2, y, @yAxisFormat(v))
269
+ if @options.grid
270
+ @drawGridLine("M#{@left},#{y}H#{@left + @width}")
271
+
272
+ # draw goals horizontal lines
273
+ #
274
+ drawGoals: ->
275
+ for goal, i in @options.goals
276
+ color = @options.goalLineColors[i % @options.goalLineColors.length]
277
+ @drawGoal(goal, color)
278
+
279
+ # draw events vertical lines
280
+ drawEvents: ->
281
+ for event, i in @events
282
+ color = @options.eventLineColors[i % @options.eventLineColors.length]
283
+ @drawEvent(event, color)
284
+
285
+ drawGoal: (goal, color) ->
286
+ @raphael.path("M#{@left},#{@transY(goal)}H#{@right}")
287
+ .attr('stroke', color)
288
+ .attr('stroke-width', @options.goalStrokeWidth)
289
+
290
+ drawEvent: (event, color) ->
291
+ @raphael.path("M#{@transX(event)},#{@bottom}V#{@top}")
292
+ .attr('stroke', color)
293
+ .attr('stroke-width', @options.eventStrokeWidth)
294
+
295
+ drawYAxisLabel: (xPos, yPos, text) ->
296
+ @raphael.text(xPos, yPos, text)
297
+ .attr('font-size', @options.gridTextSize)
298
+ .attr('fill', @options.gridTextColor)
299
+ .attr('text-anchor', 'end')
300
+
301
+ drawGridLine: (path) ->
302
+ @raphael.path(path)
303
+ .attr('stroke', @options.gridLineColor)
304
+ .attr('stroke-width', @options.gridStrokeWidth)
305
+
306
+ # Parse a date into a javascript timestamp
307
+ #
308
+ #
309
+ Morris.parseDate = (date) ->
310
+ if typeof date is 'number'
311
+ return date
312
+ m = date.match /^(\d+) Q(\d)$/
313
+ n = date.match /^(\d+)-(\d+)$/
314
+ o = date.match /^(\d+)-(\d+)-(\d+)$/
315
+ p = date.match /^(\d+) W(\d+)$/
316
+ q = date.match /^(\d+)-(\d+)-(\d+)[ T](\d+):(\d+)(Z|([+-])(\d\d):?(\d\d))?$/
317
+ r = date.match /^(\d+)-(\d+)-(\d+)[ T](\d+):(\d+):(\d+(\.\d+)?)(Z|([+-])(\d\d):?(\d\d))?$/
318
+ if m
319
+ new Date(
320
+ parseInt(m[1], 10),
321
+ parseInt(m[2], 10) * 3 - 1,
322
+ 1).getTime()
323
+ else if n
324
+ new Date(
325
+ parseInt(n[1], 10),
326
+ parseInt(n[2], 10) - 1,
327
+ 1).getTime()
328
+ else if o
329
+ new Date(
330
+ parseInt(o[1], 10),
331
+ parseInt(o[2], 10) - 1,
332
+ parseInt(o[3], 10)).getTime()
333
+ else if p
334
+ # calculate number of weeks in year given
335
+ ret = new Date(parseInt(p[1], 10), 0, 1);
336
+ # first thursday in year (ISO 8601 standard)
337
+ if ret.getDay() isnt 4
338
+ ret.setMonth(0, 1 + ((4 - ret.getDay()) + 7) % 7);
339
+ # add weeks
340
+ ret.getTime() + parseInt(p[2], 10) * 604800000
341
+ else if q
342
+ if not q[6]
343
+ # no timezone info, use local
344
+ new Date(
345
+ parseInt(q[1], 10),
346
+ parseInt(q[2], 10) - 1,
347
+ parseInt(q[3], 10),
348
+ parseInt(q[4], 10),
349
+ parseInt(q[5], 10)).getTime()
350
+ else
351
+ # timezone info supplied, use UTC
352
+ offsetmins = 0
353
+ if q[6] != 'Z'
354
+ offsetmins = parseInt(q[8], 10) * 60 + parseInt(q[9], 10)
355
+ offsetmins = 0 - offsetmins if q[7] == '+'
356
+ Date.UTC(
357
+ parseInt(q[1], 10),
358
+ parseInt(q[2], 10) - 1,
359
+ parseInt(q[3], 10),
360
+ parseInt(q[4], 10),
361
+ parseInt(q[5], 10) + offsetmins)
362
+ else if r
363
+ secs = parseFloat(r[6])
364
+ isecs = Math.floor(secs)
365
+ msecs = Math.round((secs - isecs) * 1000)
366
+ if not r[8]
367
+ # no timezone info, use local
368
+ new Date(
369
+ parseInt(r[1], 10),
370
+ parseInt(r[2], 10) - 1,
371
+ parseInt(r[3], 10),
372
+ parseInt(r[4], 10),
373
+ parseInt(r[5], 10),
374
+ isecs,
375
+ msecs).getTime()
376
+ else
377
+ # timezone info supplied, use UTC
378
+ offsetmins = 0
379
+ if r[8] != 'Z'
380
+ offsetmins = parseInt(r[10], 10) * 60 + parseInt(r[11], 10)
381
+ offsetmins = 0 - offsetmins if r[9] == '+'
382
+ Date.UTC(
383
+ parseInt(r[1], 10),
384
+ parseInt(r[2], 10) - 1,
385
+ parseInt(r[3], 10),
386
+ parseInt(r[4], 10),
387
+ parseInt(r[5], 10) + offsetmins,
388
+ isecs,
389
+ msecs)
390
+ else
391
+ new Date(parseInt(date, 10), 0, 1).getTime()
392
+