morris-rails 0.4.2

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