local_time 1.0.3 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/MIT-LICENSE +1 -1
- data/README.md +48 -25
- data/app/assets/javascripts/local-time.js +1 -0
- data/app/helpers/local_time_helper.rb +6 -8
- data/lib/local_time.rb +3 -0
- data/test/helpers/local_time_helper_test.rb +4 -2
- data/test/javascripts/src/i18n_test.coffee +47 -0
- data/test/javascripts/src/local_time_test.coffee +36 -0
- data/test/javascripts/src/relative_date_test.coffee +89 -0
- data/test/javascripts/src/strftime_test.coffee +48 -0
- data/test/javascripts/src/test.coffee +43 -0
- data/test/javascripts/src/time_ago_test.coffee +56 -0
- metadata +17 -89
- data/app/assets/javascripts/local_time.js.coffee +0 -251
- data/test/javascripts/src/local_time_test.js.coffee +0 -36
- data/test/javascripts/src/page_events_test.js.coffee +0 -25
- data/test/javascripts/src/public_api_test.js.coffee +0 -11
- data/test/javascripts/src/relative_date_test.js.coffee +0 -96
- data/test/javascripts/src/strftime_test.js.coffee +0 -47
- data/test/javascripts/src/test.js.coffee +0 -34
- data/test/javascripts/src/time_ago_test.js.coffee +0 -51
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9d4048768cb9f06471188cbbfab6127a1eaf7035
|
4
|
+
data.tar.gz: 2249470471b3efb5b61006372a6cea2ec31bd8ea
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b3399affc900a62906e2d4087ab90a7f988af993a1ae174a133c816f5cef5d91bfb50c69938398a46aae0f393206c09325ff8f366b259f84109fd876bab88457
|
7
|
+
data.tar.gz: a6d267fb48c9be393af4ba60b30ec6090d6d3a4838b5a362fea672034182103697f83badf5c91d274ca98ac79fc66935e71d67a82512e233f20c7283b9d393f0
|
data/MIT-LICENSE
CHANGED
data/README.md
CHANGED
@@ -1,8 +1,23 @@
|
|
1
|
-
Local Time
|
1
|
+
# Local Time
|
2
2
|
|
3
|
-
|
3
|
+
Local Time makes it easy to display times and dates to users in their local time. Its Rails helpers render `<time>` elements in UTC (making them cache friendly), and its JavaScript component immediately converts those elements from UTC to the browser's local time.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
1. Add `gem 'local_time'` to your Gemfile.
|
8
|
+
2. Include `local-time.js` in your application's JavaScript bundle.
|
9
|
+
|
10
|
+
Using the asset pipeline:
|
11
|
+
```js
|
12
|
+
//= require local-time
|
13
|
+
```
|
14
|
+
Using the npm package:
|
15
|
+
```js
|
16
|
+
import LocalTime from "local-time"
|
17
|
+
LocalTime.start()
|
18
|
+
```
|
4
19
|
|
5
|
-
|
20
|
+
## Example
|
6
21
|
|
7
22
|
```ruby
|
8
23
|
> comment.created_at
|
@@ -21,7 +36,7 @@ Renders:
|
|
21
36
|
datetime="2013-11-27T23:43:22Z">November 27, 2013 11:43pm</time>
|
22
37
|
```
|
23
38
|
|
24
|
-
|
39
|
+
And is converted client-side to:
|
25
40
|
|
26
41
|
```html
|
27
42
|
<time data-format="%B %e, %Y %l:%M%P"
|
@@ -33,7 +48,7 @@ When the DOM loads, the content is immediately replaced with a local, formatted
|
|
33
48
|
|
34
49
|
*(Line breaks added for readability)*
|
35
50
|
|
36
|
-
|
51
|
+
## Time and date helpers
|
37
52
|
|
38
53
|
```erb
|
39
54
|
<%= local_time(time) %>
|
@@ -67,7 +82,7 @@ To use a strftime format already defined in your app, pass a symbol as the forma
|
|
67
82
|
|
68
83
|
Note: The included strftime JavaScript implementation is not 100% complete. It supports the following directives: `%a %A %b %B %c %d %e %H %I %l %m %M %p %P %S %w %y %Y %Z`
|
69
84
|
|
70
|
-
|
85
|
+
## Time ago helpers
|
71
86
|
|
72
87
|
```erb
|
73
88
|
<%= local_time_ago(time) %>
|
@@ -83,7 +98,7 @@ Examples (in quotes):
|
|
83
98
|
* This year: "on Nov 17"
|
84
99
|
* Last year: "on Jan 31, 2012"
|
85
100
|
|
86
|
-
|
101
|
+
## Relative time helpers
|
87
102
|
|
88
103
|
Preset time and date formats that vary with age. The available types are `date`, `time-ago`, `time-or-date`, and `weekday`. Like the `local_time` helper, `:type` can be passed a string or in an options hash.
|
89
104
|
|
@@ -100,32 +115,40 @@ Preset time and date formats that vary with age. The available types are `date`,
|
|
100
115
|
* `weekday` Displays "Today", "Yesterday", or the weekday (e.g. Wednesday) if the time is within a week of today.
|
101
116
|
* `weekday-or-date` Displays the weekday if it occurs within a week or the date if not. "Yesterday" or "Apr 11"
|
102
117
|
|
103
|
-
#### Installation
|
104
118
|
|
105
|
-
|
106
|
-
2. Run `bundle install`.
|
107
|
-
3. Add `//= require local_time` to your JavaScript manifest file (usually found at app/assets/javascripts/application.js).
|
108
|
-
|
109
|
-
#### JavaScript events and library compatibility
|
110
|
-
|
111
|
-
The included JavaScript does not depend on any frameworks or libraries, and listens for a `DOMContentLoaded` event to run initially. It also listens on `document` for `page:update` if you're using Turbolinks and `ajaxSuccess` if you're using jQuery. This should catch most cases where new `<time>` elements have been added to the DOM and process them automatically. If you're adding new elements in another context, trigger `time:elapse` to process them.
|
119
|
+
## Configuration
|
112
120
|
|
113
|
-
|
121
|
+
**Internationalization (I18n)**
|
114
122
|
|
115
|
-
|
123
|
+
Local Time includes a [set of default `en` translations](lib/assets/javascripts/src/local-time/config/i18n.coffee) which can be updated directly. Or, you can provide an entirely new set in a different locale:
|
116
124
|
|
117
125
|
```js
|
118
|
-
|
119
|
-
|
126
|
+
LocalTime.config.i18n["es"] = {
|
127
|
+
date: {
|
128
|
+
dayNames: [ … ],
|
129
|
+
monthNames: [ … ],
|
130
|
+
…
|
131
|
+
},
|
132
|
+
time: {
|
133
|
+
…
|
134
|
+
},
|
135
|
+
datetime: {
|
136
|
+
…
|
137
|
+
}
|
138
|
+
}
|
139
|
+
|
140
|
+
LocalTime.config.locale = "es"
|
141
|
+
```
|
120
142
|
|
121
|
-
|
122
|
-
> LocalTime.run()
|
143
|
+
## Version History
|
123
144
|
|
124
|
-
|
125
|
-
"February 9, 2014 12:55pm"
|
126
|
-
```
|
145
|
+
**2.0.0** (August 7, 2017)
|
127
146
|
|
128
|
-
|
147
|
+
* Add internationalization (I18n) API
|
148
|
+
* Switch to `MutationObserver` instead of listening for various DOM, Turbolinks, and jQuery events
|
149
|
+
* Publish JavaScript module on npm
|
150
|
+
* Drop coffee-rails gem dependency
|
151
|
+
* Renamed `local_time.js` to `local-time.js`
|
129
152
|
|
130
153
|
**1.0.3**
|
131
154
|
|
@@ -0,0 +1 @@
|
|
1
|
+
(function(){var t=this;(function(){(function(){var t=[].slice;this.LocalTime={config:{},run:function(){return this.getController().processElements()},process:function(){var e,n,r,a;for(n=1<=arguments.length?t.call(arguments,0):[],r=0,a=n.length;r<a;r++)e=n[r],this.getController().processElement(e);return n.length},getController:function(){return null!=this.controller?this.controller:this.controller=new e.Controller}}}).call(this)}).call(t);var e=t.LocalTime;(function(){(function(){e.config.i18n={en:{date:{dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],abbrDayNames:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],abbrMonthNames:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],yesterday:"yesterday",today:"today",tomorrow:"tomorrow",on:"on {date}",formats:{"default":"%b %e, %Y",thisYear:"%b %e"}},time:{am:"am",pm:"pm",singular:"a {time}",singularAn:"an {time}",elapsed:"{time} ago",second:"second",seconds:"seconds",minute:"minute",minutes:"minutes",hour:"hour",hours:"hours",formats:{"default":"%l:%M%P"}},datetime:{at:"{date} at {time}",formats:{"default":"%B %e, %Y at %l:%M%P %Z"}}}}}).call(this),function(){e.config.locale="en",e.config.defaultLocale="en"}.call(this),function(){e.config.timerInterval=6e4}.call(this),function(){var t,n,r;r=!isNaN(Date.parse("2011-01-01T12:00:00-05:00")),e.parseDate=function(t){return t=t.toString(),r||(t=n(t)),new Date(Date.parse(t))},t=/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(Z|[-+]?[\d:]+)$/,n=function(e){var n,r,a,i,o,s,u,c,l;if(a=e.match(t))return a[0],c=a[1],o=a[2],n=a[3],r=a[4],i=a[5],u=a[6],l=a[7],"Z"!==l&&(s=l.replace(":","")),c+"/"+o+"/"+n+" "+r+":"+i+":"+u+" GMT"+[s]}}.call(this),function(){e.elementMatchesSelector=function(){var t,e,n,r,a,i;return t=document.documentElement,e=null!=(n=null!=(r=null!=(a=null!=(i=t.matches)?i:t.matchesSelector)?a:t.webkitMatchesSelector)?r:t.mozMatchesSelector)?n:t.msMatchesSelector,function(t,n){if((null!=t?t.nodeType:void 0)===Node.ELEMENT_NODE)return e.call(t,n)}}()}.call(this),function(){var t,n,r;t=e.config,r=t.i18n,e.getI18nValue=function(a,i){var o,s;return null==a&&(a=""),o=(null!=i?i:{locale:t.locale}).locale,s=n(r[o],a),null!=s?s:o!==t.defaultLocale?e.getI18nValue(a,{locale:t.defaultLocale}):void 0},e.translate=function(t,n,r){var a,i,o;null==n&&(n={}),o=e.getI18nValue(t,r);for(a in n)i=n[a],o=o.replace("{"+a+"}",i);return o},n=function(t,e){var n,r,a,i,o;for(o=t,i=e.split("."),n=0,a=i.length;n<a;n++){if(r=i[n],null==o[r])return null;o=o[r]}return o}}.call(this),function(){var t,n,r,a,i;t=e.getI18nValue,i=e.translate,e.strftime=a=function(e,o){var s,u,c,l,d,h,f;return u=e.getDay(),s=e.getDate(),d=e.getMonth(),f=e.getFullYear(),c=e.getHours(),l=e.getMinutes(),h=e.getSeconds(),o.replace(/%([%aAbBcdeHIlmMpPSwyYZ])/g,function(o){switch(o[0],o[1]){case"%":return"%";case"a":return t("date.abbrDayNames")[u];case"A":return t("date.dayNames")[u];case"b":return t("date.abbrMonthNames")[d];case"B":return t("date.monthNames")[d];case"c":return e.toString();case"d":return n(s);case"e":return s;case"H":return n(c);case"I":return n(a(e,"%l"));case"l":return 0===c||12===c?12:(c+12)%12;case"m":return n(d+1);case"M":return n(l);case"p":return i("time."+(c>11?"pm":"am")).toUpperCase();case"P":return i("time."+(c>11?"pm":"am"));case"S":return n(h);case"w":return u;case"y":return n(f%100);case"Y":return f;case"Z":return r(e)}})},n=function(t){return("0"+t).slice(-2)},r=function(t){var e,n,r,a,i;return i=t.toString(),(e=null!=(n=i.match(/\(([\w\s]+)\)$/))?n[1]:void 0)?/\s/.test(e)?e.match(/\b(\w)/g).join(""):e:(e=null!=(r=i.match(/(\w{3,4})\s\d{4}$/))?r[1]:void 0)?e:(e=null!=(a=i.match(/(UTC[\+\-]\d+)/))?a[1]:void 0)?e:""}}.call(this),function(){e.CalendarDate=function(){function t(t,e,n){this.date=new Date(Date.UTC(t,e-1)),this.date.setUTCDate(n),this.year=this.date.getUTCFullYear(),this.month=this.date.getUTCMonth()+1,this.day=this.date.getUTCDate(),this.value=this.date.getTime()}return t.fromDate=function(t){return new this(t.getFullYear(),t.getMonth()+1,t.getDate())},t.today=function(){return this.fromDate(new Date)},t.prototype.equals=function(t){return(null!=t?t.value:void 0)===this.value},t.prototype.is=function(t){return this.equals(t)},t.prototype.isToday=function(){return this.is(this.constructor.today())},t.prototype.occursOnSameYearAs=function(t){return this.year===(null!=t?t.year:void 0)},t.prototype.occursThisYear=function(){return this.occursOnSameYearAs(this.constructor.today())},t.prototype.daysSince=function(t){if(t)return(this.date-t.date)/864e5},t.prototype.daysPassed=function(){return this.constructor.today().daysSince(this)},t}()}.call(this),function(){var t,n,r;n=e.strftime,r=e.translate,t=e.getI18nValue,e.RelativeTime=function(){function a(t){this.date=t,this.calendarDate=e.CalendarDate.fromDate(this.date)}return a.prototype.toString=function(){var t,e;return(e=this.toTimeElapsedString())?r("time.elapsed",{time:e}):(t=this.toWeekdayString())?(e=this.toTimeString(),r("datetime.at",{date:t,time:e})):r("date.on",{date:this.toDateString()})},a.prototype.toTimeOrDateString=function(){return this.calendarDate.isToday()?this.toTimeString():this.toDateString()},a.prototype.toTimeElapsedString=function(){var t,e,n,a,i;return n=(new Date).getTime()-this.date.getTime(),a=Math.round(n/1e3),e=Math.round(a/60),t=Math.round(e/60),n<0?null:a<10?(i=r("time.second"),r("time.singular",{time:i})):a<45?a+" "+r("time.seconds"):a<90?(i=r("time.minute"),r("time.singular",{time:i})):e<45?e+" "+r("time.minutes"):e<90?(i=r("time.hour"),r("time.singularAn",{time:i})):t<24?t+" "+r("time.hours"):""},a.prototype.toWeekdayString=function(){switch(this.calendarDate.daysPassed()){case 0:return r("date.today");case 1:return r("date.yesterday");case-1:return r("date.tomorrow");case 2:case 3:case 4:case 5:case 6:return n(this.date,"%A");default:return""}},a.prototype.toDateString=function(){var e;return e=t(this.calendarDate.occursThisYear()?"date.formats.thisYear":"date.formats.default"),n(this.date,e)},a.prototype.toTimeString=function(){return n(this.date,t("time.formats.default"))},a}()}.call(this),function(){var t,n=function(t,e){return function(){return t.apply(e,arguments)}};t=e.elementMatchesSelector,e.PageObserver=function(){function e(t,e){this.selector=t,this.callback=e,this.processInsertion=n(this.processInsertion,this),this.processMutations=n(this.processMutations,this)}return e.prototype.start=function(){if(!this.started)return this.observeWithMutationObserver()||this.observeWithMutationEvent(),this.started=!0},e.prototype.observeWithMutationObserver=function(){var t;if("undefined"!=typeof MutationObserver&&null!==MutationObserver)return t=new MutationObserver(this.processMutations),t.observe(document.documentElement,{childList:!0,subtree:!0}),!0},e.prototype.observeWithMutationEvent=function(){return addEventListener("DOMNodeInserted",this.processInsertion,!1),!0},e.prototype.findSignificantElements=function(e){var n;return n=[],(null!=e?e.nodeType:void 0)===Node.ELEMENT_NODE&&(t(e,this.selector)&&n.push(e),n.push.apply(n,e.querySelectorAll(this.selector))),n},e.prototype.processMutations=function(t){var e,n,r,a,i,o,s,u;for(e=[],n=0,a=t.length;n<a;n++)switch(o=t[n],o.type){case"childList":for(u=o.addedNodes,r=0,i=u.length;r<i;r++)s=u[r],e.push.apply(e,this.findSignificantElements(s))}return this.notify(e)},e.prototype.processInsertion=function(t){var e;return e=this.findSignificantElements(t.target),this.notify(e)},e.prototype.notify=function(t){if(null!=t?t.length:void 0)return"function"==typeof this.callback?this.callback(t):void 0},e}()}.call(this),function(){var t,n,r,a,i=function(t,e){return function(){return t.apply(e,arguments)}};r=e.parseDate,a=e.strftime,n=e.getI18nValue,t=e.config,e.Controller=function(){function o(){this.processElements=i(this.processElements,this),this.pageObserver=new e.PageObserver(s,this.processElements)}var s,u,c;return s="time[data-local]:not([data-localized])",o.prototype.start=function(){if(!this.started)return this.processElements(),this.startTimer(),this.pageObserver.start(),this.started=!0},o.prototype.startTimer=function(){var e;if(e=t.timerInterval)return null!=this.timer?this.timer:this.timer=setInterval(this.processElements,e)},o.prototype.processElements=function(t){var e,n,r;for(null==t&&(t=document.querySelectorAll(s)),n=0,r=t.length;n<r;n++)e=t[n],this.processElement(e);return t.length},o.prototype.processElement=function(t){var e,i,o,s,l;if(e=t.getAttribute("datetime"),i=t.getAttribute("data-format"),o=t.getAttribute("data-local"),s=r(e),!isNaN(s))return t.hasAttribute("title")||(l=a(s,n("datetime.formats.default")),t.setAttribute("title",l)),t.textContent=function(){switch(o){case"time":return u(t),a(s,i);case"date":return u(t),c(s).toDateString();case"time-ago":return c(s).toString();case"time-or-date":return c(s).toTimeOrDateString();case"weekday":return c(s).toWeekdayString();case"weekday-or-date":return c(s).toWeekdayString()||c(s).toDateString()}}()},u=function(t){return t.setAttribute("data-localized","")},c=function(t){return new e.RelativeTime(t)},o}()}.call(this),function(){var t,n,r,a;a=!1,t=function(){return document.attachEvent?"complete"===document.readyState:"loading"!==document.readyState},n=function(t){var e;return null!=(e="function"==typeof requestAnimationFrame?requestAnimationFrame(t):void 0)?e:setTimeout(t,17)},r=function(){var t;return t=e.getController(),t.start()},e.start=function(){if(!a)return a=!0,"undefined"!=typeof MutationObserver&&null!==MutationObserver||t()?r():n(r)},window.LocalTime===e&&e.start()}.call(this)}).call(this),"object"==typeof module&&module.exports?module.exports=e:"function"==typeof define&&define.amd&&define(e)}).call(this);
|
@@ -1,6 +1,4 @@
|
|
1
1
|
module LocalTimeHelper
|
2
|
-
DEFAULT_FORMAT = '%B %e, %Y %l:%M%P'
|
3
|
-
|
4
2
|
def local_time(time, options = nil)
|
5
3
|
time = utc_time(time)
|
6
4
|
|
@@ -15,7 +13,7 @@ module LocalTimeHelper
|
|
15
13
|
|
16
14
|
def local_date(time, options = nil)
|
17
15
|
options, format = extract_options_and_value(options, :format)
|
18
|
-
options[:format] = format ||
|
16
|
+
options[:format] = format || LocalTime.default_date_format
|
19
17
|
local_time time, options
|
20
18
|
end
|
21
19
|
|
@@ -26,11 +24,11 @@ module LocalTimeHelper
|
|
26
24
|
options[:data] ||= {}
|
27
25
|
options[:data].merge! local: type
|
28
26
|
|
29
|
-
time_tag time, time.strftime(
|
27
|
+
time_tag time, time.strftime(LocalTime.default_time_format), options
|
30
28
|
end
|
31
29
|
|
32
30
|
def local_time_ago(time, options = nil)
|
33
|
-
options,
|
31
|
+
options, * = extract_options_and_value(options, :type)
|
34
32
|
options[:type] = 'time-ago'
|
35
33
|
local_relative_time time, options
|
36
34
|
end
|
@@ -49,12 +47,12 @@ module LocalTimeHelper
|
|
49
47
|
if (i18n_format = I18n.t("time.formats.#{format}", default: [:"date.formats.#{format}", ''])).present?
|
50
48
|
i18n_format
|
51
49
|
elsif (date_format = Time::DATE_FORMATS[format] || Date::DATE_FORMATS[format])
|
52
|
-
date_format.is_a?(Proc) ?
|
50
|
+
date_format.is_a?(Proc) ? LocalTime.default_time_format : date_format
|
53
51
|
else
|
54
|
-
|
52
|
+
LocalTime.default_time_format
|
55
53
|
end
|
56
54
|
else
|
57
|
-
format.presence ||
|
55
|
+
format.presence || LocalTime.default_time_format
|
58
56
|
end
|
59
57
|
end
|
60
58
|
|
data/lib/local_time.rb
CHANGED
@@ -1,8 +1,10 @@
|
|
1
|
-
|
2
|
-
require 'active_support/all'
|
1
|
+
require 'rails'
|
3
2
|
require 'action_view'
|
4
3
|
require 'rails-dom-testing'
|
5
4
|
|
5
|
+
require 'local_time'
|
6
|
+
require_relative '../../app/helpers/local_time_helper'
|
7
|
+
|
6
8
|
require 'minitest/autorun'
|
7
9
|
begin
|
8
10
|
# 2.0.0
|
@@ -0,0 +1,47 @@
|
|
1
|
+
{addTimeEl, assert, defer, getText, setText, test, testAsync, testGroup, triggerEvent} = LocalTime.TestHelpers
|
2
|
+
{config} = LocalTime
|
3
|
+
{i18n} = config
|
4
|
+
|
5
|
+
testGroup "i18n", ->
|
6
|
+
testAsync "updating a value", (done) ->
|
7
|
+
now = moment()
|
8
|
+
values = i18n[config.defaultLocale].date
|
9
|
+
|
10
|
+
originalValue = values.today
|
11
|
+
values.today = "2day"
|
12
|
+
|
13
|
+
el = addTimeEl type: "weekday", datetime: now.toISOString()
|
14
|
+
defer ->
|
15
|
+
assert.equal getText(el), "2day"
|
16
|
+
assert.equal getText(el), "2day"
|
17
|
+
values.today = originalValue
|
18
|
+
done()
|
19
|
+
|
20
|
+
testAsync "adding a new locale", (done) ->
|
21
|
+
now = moment()
|
22
|
+
|
23
|
+
originalLocale = config.locale
|
24
|
+
config.locale = "es"
|
25
|
+
i18n.es = date: today: "hoy"
|
26
|
+
|
27
|
+
el = addTimeEl type: "weekday", datetime: now.toISOString()
|
28
|
+
defer ->
|
29
|
+
assert.equal getText(el), "hoy"
|
30
|
+
config.locale = originalLocale
|
31
|
+
done()
|
32
|
+
|
33
|
+
testAsync "falling back to the default locale", (done) ->
|
34
|
+
now = moment()
|
35
|
+
yesterday = moment().subtract("days", 1)
|
36
|
+
|
37
|
+
originalLocale = config.locale
|
38
|
+
config.locale = "es"
|
39
|
+
i18n.es = date: yesterday: "ayer"
|
40
|
+
|
41
|
+
elWithTranslation = addTimeEl type: "weekday", datetime: yesterday.toISOString()
|
42
|
+
elWithoutTranslation = addTimeEl type: "weekday", datetime: now.toISOString()
|
43
|
+
defer ->
|
44
|
+
assert.equal getText(elWithTranslation), "ayer"
|
45
|
+
assert.equal getText(elWithoutTranslation), "today"
|
46
|
+
config.locale = originalLocale
|
47
|
+
done()
|
@@ -0,0 +1,36 @@
|
|
1
|
+
{addTimeEl, assert, defer, getText, setText, test, testAsync, testGroup, triggerEvent} = LocalTime.TestHelpers
|
2
|
+
|
3
|
+
testGroup "localized", ->
|
4
|
+
for id in ["one", "two", "past", "future"]
|
5
|
+
test id, ->
|
6
|
+
assertLocalized id
|
7
|
+
|
8
|
+
test "date", ->
|
9
|
+
assertLocalized "date", "date"
|
10
|
+
|
11
|
+
test "unparseable time", ->
|
12
|
+
el = addTimeEl format: "%Y", datetime: ":("
|
13
|
+
setText el, "2013"
|
14
|
+
assert.equal getText(el), "2013"
|
15
|
+
|
16
|
+
|
17
|
+
assertLocalized = (id, type = "time") ->
|
18
|
+
switch type
|
19
|
+
when "time"
|
20
|
+
momentFormat = "MMMM D, YYYY h:mma"
|
21
|
+
compare = "toString"
|
22
|
+
when "date"
|
23
|
+
momentFormat = "MMMM D, YYYY"
|
24
|
+
compare = "dayOfYear"
|
25
|
+
|
26
|
+
el = document.getElementById id
|
27
|
+
|
28
|
+
assert.ok datetime = el.getAttribute "datetime"
|
29
|
+
assert.ok local = getText el
|
30
|
+
|
31
|
+
datetimeParsed = moment datetime
|
32
|
+
localParsed = moment local, momentFormat
|
33
|
+
|
34
|
+
assert.ok datetimeParsed.isValid()
|
35
|
+
assert.ok localParsed.isValid()
|
36
|
+
assert.equal datetimeParsed[compare](), localParsed[compare]()
|
@@ -0,0 +1,89 @@
|
|
1
|
+
{addTimeEl, assert, defer, getText, setText, test, testAsync, testGroup, triggerEvent} = LocalTime.TestHelpers
|
2
|
+
|
3
|
+
testGroup "relative date", ->
|
4
|
+
testAsync "this year", (done) ->
|
5
|
+
now = moment()
|
6
|
+
el = addTimeEl type: "date", datetime: now.toISOString()
|
7
|
+
defer ->
|
8
|
+
assert.equal getText(el), now.format("MMM D")
|
9
|
+
done()
|
10
|
+
|
11
|
+
testAsync "last year", (done) ->
|
12
|
+
before = moment().subtract("years", 1).subtract("days", 1)
|
13
|
+
el = addTimeEl type: "date", datetime: before.toISOString()
|
14
|
+
defer ->
|
15
|
+
assert.equal getText(el), before.format("MMM D, YYYY")
|
16
|
+
done()
|
17
|
+
|
18
|
+
testGroup "relative time or date", ->
|
19
|
+
testAsync "today", (done) ->
|
20
|
+
now = moment()
|
21
|
+
el = addTimeEl type: "time-or-date", datetime: now.toISOString()
|
22
|
+
defer ->
|
23
|
+
assert.equal getText(el), now.format("h:mma")
|
24
|
+
done()
|
25
|
+
|
26
|
+
testAsync "before today", (done) ->
|
27
|
+
before = moment().subtract("days", 1)
|
28
|
+
el = addTimeEl type: "time-or-date", datetime: before.toISOString()
|
29
|
+
defer ->
|
30
|
+
assert.equal getText(el), before.format("MMM D")
|
31
|
+
done()
|
32
|
+
|
33
|
+
testGroup "relative weekday", ->
|
34
|
+
testAsync "today", (done) ->
|
35
|
+
now = moment()
|
36
|
+
el = addTimeEl type: "weekday", datetime: now.toISOString()
|
37
|
+
defer ->
|
38
|
+
assert.equal getText(el), "today"
|
39
|
+
done()
|
40
|
+
|
41
|
+
testAsync "yesterday", (done) ->
|
42
|
+
yesterday = moment().subtract("days", 1)
|
43
|
+
el = addTimeEl type: "weekday", datetime: yesterday.toISOString()
|
44
|
+
defer ->
|
45
|
+
assert.equal getText(el), "yesterday"
|
46
|
+
done()
|
47
|
+
|
48
|
+
testAsync "this week", (done) ->
|
49
|
+
recent = moment().subtract("days", 3)
|
50
|
+
el = addTimeEl type: "weekday", datetime: recent.toISOString()
|
51
|
+
defer ->
|
52
|
+
assert.equal getText(el), recent.format("dddd")
|
53
|
+
done()
|
54
|
+
|
55
|
+
testAsync "before this week", (done) ->
|
56
|
+
before = moment().subtract("days", 8)
|
57
|
+
el = addTimeEl type: "weekday", datetime: before.toISOString()
|
58
|
+
defer ->
|
59
|
+
assert.equal getText(el), ""
|
60
|
+
done()
|
61
|
+
|
62
|
+
testGroup "relative weekday or date", ->
|
63
|
+
testAsync "today", (done) ->
|
64
|
+
now = moment()
|
65
|
+
el = addTimeEl type: "weekday-or-date", datetime: now.toISOString()
|
66
|
+
defer ->
|
67
|
+
assert.equal getText(el), "today"
|
68
|
+
done()
|
69
|
+
|
70
|
+
testAsync "yesterday", (done) ->
|
71
|
+
yesterday = moment().subtract("days", 1)
|
72
|
+
el = addTimeEl type: "weekday-or-date", datetime: yesterday.toISOString()
|
73
|
+
defer ->
|
74
|
+
assert.equal getText(el), "yesterday"
|
75
|
+
done()
|
76
|
+
|
77
|
+
testAsync "this week", (done) ->
|
78
|
+
recent = moment().subtract("days", 3)
|
79
|
+
el = addTimeEl type: "weekday-or-date", datetime: recent.toISOString()
|
80
|
+
defer ->
|
81
|
+
assert.equal getText(el), recent.format("dddd")
|
82
|
+
done()
|
83
|
+
|
84
|
+
testAsync "before this week", (done) ->
|
85
|
+
before = moment().subtract("days", 8)
|
86
|
+
el = addTimeEl type: "weekday-or-date", datetime: before.toISOString()
|
87
|
+
defer ->
|
88
|
+
assert.equal getText(el), before.format("MMM D")
|
89
|
+
done()
|
@@ -0,0 +1,48 @@
|
|
1
|
+
{addTimeEl, assert, defer, getText, setText, test, testAsync, testGroup, triggerEvent} = LocalTime.TestHelpers
|
2
|
+
|
3
|
+
momentMap =
|
4
|
+
"%a": "ddd"
|
5
|
+
"%A": "dddd"
|
6
|
+
"%b": "MMM"
|
7
|
+
"%B": "MMMM"
|
8
|
+
"%c": "toString()"
|
9
|
+
"%d": "DD"
|
10
|
+
"%e": "D"
|
11
|
+
"%H": "HH"
|
12
|
+
"%I": "hh"
|
13
|
+
"%l": "h"
|
14
|
+
"%m": "MM"
|
15
|
+
"%M": "mm"
|
16
|
+
"%p": "A"
|
17
|
+
"%P": "a"
|
18
|
+
"%S": "ss"
|
19
|
+
"%w": "e"
|
20
|
+
"%y": "YY"
|
21
|
+
"%Y": "YYYY"
|
22
|
+
|
23
|
+
testGroup "strftime", ->
|
24
|
+
for day in [0..30] by 6
|
25
|
+
do (day) ->
|
26
|
+
for hour in [0..24] by 6
|
27
|
+
do (hour) ->
|
28
|
+
|
29
|
+
for format, momentFormat of momentMap
|
30
|
+
do (format, momentFormat) ->
|
31
|
+
test "#{format} (+#{day} days, #{hour} hours)", ->
|
32
|
+
now = moment().add("days", day).add("hours", hour)
|
33
|
+
el = addTimeEl {format, datetime: now.toISOString()}
|
34
|
+
LocalTime.process(el)
|
35
|
+
|
36
|
+
assert.equal getText(el),
|
37
|
+
if func = momentFormat.match(/(\w+)\(\)/)?[1]
|
38
|
+
now.toDate()[func]()
|
39
|
+
else
|
40
|
+
now.format momentFormat
|
41
|
+
|
42
|
+
test "%Z Timezone (+#{day} days, #{hour} hours)", ->
|
43
|
+
now = moment().add("days", day).add("hours", hour)
|
44
|
+
el = addTimeEl format: "%Z", datetime: now.toISOString()
|
45
|
+
LocalTime.process(el)
|
46
|
+
|
47
|
+
text = getText el
|
48
|
+
assert.ok /^(\w{3,4}|UTC[\+\-]\d+)$/.test(text), "'#{text}' doesn't look like a timezone. System date: '#{new Date}'"
|
@@ -0,0 +1,43 @@
|
|
1
|
+
#= require moment
|
2
|
+
#= require sinon-timers
|
3
|
+
|
4
|
+
#= require_self
|
5
|
+
#= require_directory .
|
6
|
+
|
7
|
+
LocalTime.TestHelpers =
|
8
|
+
assert: QUnit.assert
|
9
|
+
testGroup: QUnit.module
|
10
|
+
test: QUnit.test
|
11
|
+
|
12
|
+
testAsync: (name, callback) ->
|
13
|
+
QUnit.test name, (assert) ->
|
14
|
+
done = assert.async()
|
15
|
+
callback(done)
|
16
|
+
|
17
|
+
addTimeEl: ({format, type, datetime} = {}) ->
|
18
|
+
format ?= "%Y"
|
19
|
+
type ?= "time"
|
20
|
+
datetime ?= "2013-11-12T12:13:00Z"
|
21
|
+
|
22
|
+
el = document.createElement "time"
|
23
|
+
el.setAttribute "data-local", type
|
24
|
+
el.setAttribute "data-format", format
|
25
|
+
el.setAttribute "datetime", datetime
|
26
|
+
document.body.appendChild el
|
27
|
+
el
|
28
|
+
|
29
|
+
setText: (el, text) ->
|
30
|
+
el.textContent = text
|
31
|
+
|
32
|
+
getText: (el) ->
|
33
|
+
# innerHTML works in all browsers so using it ensures we're
|
34
|
+
# reading the text content, not a potentially arbitrary property.
|
35
|
+
el.innerHTML
|
36
|
+
|
37
|
+
triggerEvent: (name, el = document) ->
|
38
|
+
event = document.createEvent "Events"
|
39
|
+
event.initEvent name, true, true
|
40
|
+
el.dispatchEvent event
|
41
|
+
|
42
|
+
defer: (callback) ->
|
43
|
+
setTimeout(callback, 1)
|
@@ -0,0 +1,56 @@
|
|
1
|
+
{addTimeEl, assert, defer, getText, setText, test, testAsync, testGroup, triggerEvent} = LocalTime.TestHelpers
|
2
|
+
|
3
|
+
testGroup "time ago", ->
|
4
|
+
test "a second ago", ->
|
5
|
+
assertTimeAgo "a second ago", "seconds", 9
|
6
|
+
|
7
|
+
test "seconds ago", ->
|
8
|
+
assertTimeAgo "44 seconds ago", "seconds", 44
|
9
|
+
|
10
|
+
test "a minute ago", ->
|
11
|
+
assertTimeAgo "a minute ago", "seconds", 89
|
12
|
+
|
13
|
+
test "minutes ago", ->
|
14
|
+
assertTimeAgo "44 minutes ago", "minutes", 44
|
15
|
+
|
16
|
+
test "an hour ago", ->
|
17
|
+
assertTimeAgo "an hour ago", "minutes", 89
|
18
|
+
|
19
|
+
test "hours ago", ->
|
20
|
+
assertTimeAgo "23 hours ago", "hours", 23
|
21
|
+
|
22
|
+
test "yesterday", ->
|
23
|
+
time = moment().subtract("days", 1).format "h:mma"
|
24
|
+
assertTimeAgo "yesterday at #{time}", "days", 1
|
25
|
+
|
26
|
+
test "tomorrow", ->
|
27
|
+
time = moment().add("days", 1).format "h:mma"
|
28
|
+
assertTimeAgo "tomorrow at #{time}", "days", -1
|
29
|
+
|
30
|
+
test "last week", ->
|
31
|
+
ago = moment().subtract "days", 5
|
32
|
+
day = ago.format "dddd"
|
33
|
+
time = ago.format "h:mma"
|
34
|
+
|
35
|
+
assertTimeAgo "#{day} at #{time}", "days", 5
|
36
|
+
|
37
|
+
test "this year", ->
|
38
|
+
clock = sinon.useFakeTimers(new Date(2013,11,11,11,11).getTime(), "Date")
|
39
|
+
date = moment().subtract("days", 7).format "MMM D"
|
40
|
+
assertTimeAgo "on #{date}", "days", 7
|
41
|
+
clock.restore()
|
42
|
+
|
43
|
+
test "last year", ->
|
44
|
+
date = moment().subtract("days", 366).format "MMM D, YYYY"
|
45
|
+
assertTimeAgo "on #{date}", "days", 366
|
46
|
+
|
47
|
+
test "next year", ->
|
48
|
+
date = moment().add("days", 366).format "MMM D, YYYY"
|
49
|
+
assertTimeAgo "on #{date}", "days", -366
|
50
|
+
|
51
|
+
assertTimeAgo = (string, unit, amount) ->
|
52
|
+
el = document.getElementById "ago"
|
53
|
+
el.setAttribute "data-local", "time-ago"
|
54
|
+
el.setAttribute "datetime", moment().subtract(unit, amount).utc().toISOString()
|
55
|
+
LocalTime.run()
|
56
|
+
assert.equal getText(el), string
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: local_time
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Javan Makhmali
|
@@ -9,78 +9,8 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
13
|
-
dependencies:
|
14
|
-
- !ruby/object:Gem::Dependency
|
15
|
-
name: coffee-rails
|
16
|
-
requirement: !ruby/object:Gem::Requirement
|
17
|
-
requirements:
|
18
|
-
- - ">="
|
19
|
-
- !ruby/object:Gem::Version
|
20
|
-
version: '0'
|
21
|
-
type: :runtime
|
22
|
-
prerelease: false
|
23
|
-
version_requirements: !ruby/object:Gem::Requirement
|
24
|
-
requirements:
|
25
|
-
- - ">="
|
26
|
-
- !ruby/object:Gem::Version
|
27
|
-
version: '0'
|
28
|
-
- !ruby/object:Gem::Dependency
|
29
|
-
name: rails
|
30
|
-
requirement: !ruby/object:Gem::Requirement
|
31
|
-
requirements:
|
32
|
-
- - ">="
|
33
|
-
- !ruby/object:Gem::Version
|
34
|
-
version: '0'
|
35
|
-
type: :development
|
36
|
-
prerelease: false
|
37
|
-
version_requirements: !ruby/object:Gem::Requirement
|
38
|
-
requirements:
|
39
|
-
- - ">="
|
40
|
-
- !ruby/object:Gem::Version
|
41
|
-
version: '0'
|
42
|
-
- !ruby/object:Gem::Dependency
|
43
|
-
name: rails-dom-testing
|
44
|
-
requirement: !ruby/object:Gem::Requirement
|
45
|
-
requirements:
|
46
|
-
- - ">="
|
47
|
-
- !ruby/object:Gem::Version
|
48
|
-
version: '0'
|
49
|
-
type: :development
|
50
|
-
prerelease: false
|
51
|
-
version_requirements: !ruby/object:Gem::Requirement
|
52
|
-
requirements:
|
53
|
-
- - ">="
|
54
|
-
- !ruby/object:Gem::Version
|
55
|
-
version: '0'
|
56
|
-
- !ruby/object:Gem::Dependency
|
57
|
-
name: blade
|
58
|
-
requirement: !ruby/object:Gem::Requirement
|
59
|
-
requirements:
|
60
|
-
- - "~>"
|
61
|
-
- !ruby/object:Gem::Version
|
62
|
-
version: 0.3.0
|
63
|
-
type: :development
|
64
|
-
prerelease: false
|
65
|
-
version_requirements: !ruby/object:Gem::Requirement
|
66
|
-
requirements:
|
67
|
-
- - "~>"
|
68
|
-
- !ruby/object:Gem::Version
|
69
|
-
version: 0.3.0
|
70
|
-
- !ruby/object:Gem::Dependency
|
71
|
-
name: blade-sauce_labs_plugin
|
72
|
-
requirement: !ruby/object:Gem::Requirement
|
73
|
-
requirements:
|
74
|
-
- - "~>"
|
75
|
-
- !ruby/object:Gem::Version
|
76
|
-
version: 0.3.0
|
77
|
-
type: :development
|
78
|
-
prerelease: false
|
79
|
-
version_requirements: !ruby/object:Gem::Requirement
|
80
|
-
requirements:
|
81
|
-
- - "~>"
|
82
|
-
- !ruby/object:Gem::Version
|
83
|
-
version: 0.3.0
|
12
|
+
date: 2017-08-07 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
84
14
|
description:
|
85
15
|
email: javan@basecamp.com
|
86
16
|
executables: []
|
@@ -89,18 +19,17 @@ extra_rdoc_files: []
|
|
89
19
|
files:
|
90
20
|
- MIT-LICENSE
|
91
21
|
- README.md
|
92
|
-
- app/assets/javascripts/
|
22
|
+
- app/assets/javascripts/local-time.js
|
93
23
|
- app/helpers/local_time_helper.rb
|
94
24
|
- lib/local_time.rb
|
95
25
|
- test/helpers/local_time_helper_test.rb
|
96
26
|
- test/javascripts/fixtures/body.html
|
97
|
-
- test/javascripts/src/
|
98
|
-
- test/javascripts/src/
|
99
|
-
- test/javascripts/src/
|
100
|
-
- test/javascripts/src/
|
101
|
-
- test/javascripts/src/
|
102
|
-
- test/javascripts/src/
|
103
|
-
- test/javascripts/src/time_ago_test.js.coffee
|
27
|
+
- test/javascripts/src/i18n_test.coffee
|
28
|
+
- test/javascripts/src/local_time_test.coffee
|
29
|
+
- test/javascripts/src/relative_date_test.coffee
|
30
|
+
- test/javascripts/src/strftime_test.coffee
|
31
|
+
- test/javascripts/src/test.coffee
|
32
|
+
- test/javascripts/src/time_ago_test.coffee
|
104
33
|
- test/javascripts/vendor/moment.js
|
105
34
|
- test/javascripts/vendor/sinon-timers.js
|
106
35
|
homepage:
|
@@ -123,19 +52,18 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
123
52
|
version: '0'
|
124
53
|
requirements: []
|
125
54
|
rubyforge_project:
|
126
|
-
rubygems_version: 2.
|
55
|
+
rubygems_version: 2.5.2
|
127
56
|
signing_key:
|
128
57
|
specification_version: 4
|
129
58
|
summary: Rails engine for cache-friendly, client-side local time
|
130
59
|
test_files:
|
131
60
|
- test/helpers/local_time_helper_test.rb
|
132
61
|
- test/javascripts/fixtures/body.html
|
133
|
-
- test/javascripts/src/
|
134
|
-
- test/javascripts/src/
|
135
|
-
- test/javascripts/src/
|
136
|
-
- test/javascripts/src/
|
137
|
-
- test/javascripts/src/
|
138
|
-
- test/javascripts/src/
|
139
|
-
- test/javascripts/src/time_ago_test.js.coffee
|
62
|
+
- test/javascripts/src/i18n_test.coffee
|
63
|
+
- test/javascripts/src/local_time_test.coffee
|
64
|
+
- test/javascripts/src/relative_date_test.coffee
|
65
|
+
- test/javascripts/src/strftime_test.coffee
|
66
|
+
- test/javascripts/src/test.coffee
|
67
|
+
- test/javascripts/src/time_ago_test.coffee
|
140
68
|
- test/javascripts/vendor/moment.js
|
141
69
|
- test/javascripts/vendor/sinon-timers.js
|
@@ -1,251 +0,0 @@
|
|
1
|
-
browserIsCompatible = ->
|
2
|
-
document.querySelectorAll and document.addEventListener
|
3
|
-
|
4
|
-
return unless browserIsCompatible()
|
5
|
-
|
6
|
-
# Older browsers do not support ISO8601 (JSON) timestamps in Date.parse
|
7
|
-
if isNaN Date.parse "2011-01-01T12:00:00-05:00"
|
8
|
-
parse = Date.parse
|
9
|
-
iso8601 = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(Z|[-+]?[\d:]+)$/
|
10
|
-
|
11
|
-
Date.parse = (dateString) ->
|
12
|
-
dateString = dateString.toString()
|
13
|
-
if matches = dateString.match iso8601
|
14
|
-
[_, year, month, day, hour, minute, second, zone] = matches
|
15
|
-
offset = zone.replace(":", "") if zone isnt "Z"
|
16
|
-
dateString = "#{year}/#{month}/#{day} #{hour}:#{minute}:#{second} GMT#{[offset]}"
|
17
|
-
parse dateString
|
18
|
-
|
19
|
-
weekdays = "Sunday Monday Tuesday Wednesday Thursday Friday Saturday".split " "
|
20
|
-
months = "January February March April May June July August September October November December".split " "
|
21
|
-
|
22
|
-
pad = (num) -> ('0' + num).slice -2
|
23
|
-
|
24
|
-
parseTimeZone = (time) ->
|
25
|
-
string = time.toString()
|
26
|
-
# Sun Aug 30 2015 10:22:57 GMT-0400 (NAME)
|
27
|
-
if name = string.match(/\(([\w\s]+)\)$/)?[1]
|
28
|
-
if /\s/.test(name)
|
29
|
-
# Sun Aug 30 2015 10:22:57 GMT-0400 (Eastern Daylight Time)
|
30
|
-
name.match(/\b(\w)/g).join("")
|
31
|
-
else
|
32
|
-
# Sun Aug 30 2015 10:22:57 GMT-0400 (EDT)
|
33
|
-
name
|
34
|
-
# Sun Aug 30 10:22:57 EDT 2015
|
35
|
-
else if name = string.match(/(\w{3,4})\s\d{4}$/)?[1]
|
36
|
-
name
|
37
|
-
# "Sun Aug 30 10:22:57 UTC-0400 2015"
|
38
|
-
else if name = string.match(/(UTC[\+\-]\d+)/)?[1]
|
39
|
-
name
|
40
|
-
else
|
41
|
-
""
|
42
|
-
|
43
|
-
strftime = (time, formatString) ->
|
44
|
-
day = time.getDay()
|
45
|
-
date = time.getDate()
|
46
|
-
month = time.getMonth()
|
47
|
-
year = time.getFullYear()
|
48
|
-
hour = time.getHours()
|
49
|
-
minute = time.getMinutes()
|
50
|
-
second = time.getSeconds()
|
51
|
-
|
52
|
-
formatString.replace /%([%aAbBcdeHIlmMpPSwyYZ])/g, ([match, modifier]) ->
|
53
|
-
switch modifier
|
54
|
-
when '%' then '%'
|
55
|
-
when 'a' then weekdays[day].slice 0, 3
|
56
|
-
when 'A' then weekdays[day]
|
57
|
-
when 'b' then months[month].slice 0, 3
|
58
|
-
when 'B' then months[month]
|
59
|
-
when 'c' then time.toString()
|
60
|
-
when 'd' then pad date
|
61
|
-
when 'e' then date
|
62
|
-
when 'H' then pad hour
|
63
|
-
when 'I' then pad strftime time, '%l'
|
64
|
-
when 'l' then (if hour is 0 or hour is 12 then 12 else (hour + 12) % 12)
|
65
|
-
when 'm' then pad month + 1
|
66
|
-
when 'M' then pad minute
|
67
|
-
when 'p' then (if hour > 11 then 'PM' else 'AM')
|
68
|
-
when 'P' then (if hour > 11 then 'pm' else 'am')
|
69
|
-
when 'S' then pad second
|
70
|
-
when 'w' then day
|
71
|
-
when 'y' then pad year % 100
|
72
|
-
when 'Y' then year
|
73
|
-
when 'Z' then parseTimeZone(time)
|
74
|
-
|
75
|
-
|
76
|
-
class CalendarDate
|
77
|
-
@fromDate: (date) ->
|
78
|
-
new this date.getFullYear(), date.getMonth() + 1, date.getDate()
|
79
|
-
|
80
|
-
@today: ->
|
81
|
-
@fromDate new Date
|
82
|
-
|
83
|
-
constructor: (year, month, day) ->
|
84
|
-
@date = new Date Date.UTC year, month - 1
|
85
|
-
@date.setUTCDate day
|
86
|
-
|
87
|
-
@year = @date.getUTCFullYear()
|
88
|
-
@month = @date.getUTCMonth() + 1
|
89
|
-
@day = @date.getUTCDate()
|
90
|
-
@value = @date.getTime()
|
91
|
-
|
92
|
-
equals: (calendarDate) ->
|
93
|
-
calendarDate?.value is @value
|
94
|
-
|
95
|
-
is: (calendarDate) ->
|
96
|
-
@equals calendarDate
|
97
|
-
|
98
|
-
isToday: ->
|
99
|
-
@is @constructor.today()
|
100
|
-
|
101
|
-
occursOnSameYearAs: (date) ->
|
102
|
-
@year is date?.year
|
103
|
-
|
104
|
-
occursThisYear: ->
|
105
|
-
@occursOnSameYearAs @constructor.today()
|
106
|
-
|
107
|
-
daysSince: (date) ->
|
108
|
-
if date
|
109
|
-
(@date - date.date) / (1000 * 60 * 60 * 24)
|
110
|
-
|
111
|
-
daysPassed: ->
|
112
|
-
@constructor.today().daysSince @
|
113
|
-
|
114
|
-
|
115
|
-
class RelativeTime
|
116
|
-
constructor: (@date) ->
|
117
|
-
@calendarDate = CalendarDate.fromDate @date
|
118
|
-
|
119
|
-
toString: ->
|
120
|
-
# Today: "Saved 5 hours ago"
|
121
|
-
if ago = @timeElapsed()
|
122
|
-
"#{ago} ago"
|
123
|
-
|
124
|
-
# Yesterday: "Saved yesterday at 8:15am"
|
125
|
-
# This week: "Saved Thursday at 8:15am"
|
126
|
-
else if day = @relativeWeekday()
|
127
|
-
"#{day} at #{@formatTime()}"
|
128
|
-
|
129
|
-
# Older: "Saved on Dec 15"
|
130
|
-
else
|
131
|
-
"on #{@formatDate()}"
|
132
|
-
|
133
|
-
toTimeOrDateString: ->
|
134
|
-
if @calendarDate.isToday()
|
135
|
-
@formatTime()
|
136
|
-
else
|
137
|
-
@formatDate()
|
138
|
-
|
139
|
-
timeElapsed: ->
|
140
|
-
ms = new Date().getTime() - @date.getTime()
|
141
|
-
sec = Math.round ms / 1000
|
142
|
-
min = Math.round sec / 60
|
143
|
-
hr = Math.round min / 60
|
144
|
-
|
145
|
-
if ms < 0
|
146
|
-
null
|
147
|
-
else if sec < 10
|
148
|
-
"a second"
|
149
|
-
else if sec < 45
|
150
|
-
"#{sec} seconds"
|
151
|
-
else if sec < 90
|
152
|
-
"a minute"
|
153
|
-
else if min < 45
|
154
|
-
"#{min} minutes"
|
155
|
-
else if min < 90
|
156
|
-
"an hour"
|
157
|
-
else if hr < 24
|
158
|
-
"#{hr} hours"
|
159
|
-
else
|
160
|
-
null
|
161
|
-
|
162
|
-
relativeWeekday: ->
|
163
|
-
switch @calendarDate.daysPassed()
|
164
|
-
when 0
|
165
|
-
"today"
|
166
|
-
when 1
|
167
|
-
"yesterday"
|
168
|
-
when 2,3,4,5,6
|
169
|
-
strftime @date, "%A"
|
170
|
-
|
171
|
-
formatDate: ->
|
172
|
-
format = "%b %e"
|
173
|
-
format += ", %Y" unless @calendarDate.occursThisYear()
|
174
|
-
strftime @date, format
|
175
|
-
|
176
|
-
formatTime: ->
|
177
|
-
strftime @date, '%l:%M%P'
|
178
|
-
|
179
|
-
relativeDate = (date) ->
|
180
|
-
new RelativeTime(date).formatDate()
|
181
|
-
|
182
|
-
relativeTimeAgo = (date) ->
|
183
|
-
new RelativeTime(date).toString()
|
184
|
-
|
185
|
-
relativeTimeOrDate = (date) ->
|
186
|
-
new RelativeTime(date).toTimeOrDateString()
|
187
|
-
|
188
|
-
relativeWeekday = (date) ->
|
189
|
-
if day = new RelativeTime(date).relativeWeekday()
|
190
|
-
day.charAt(0).toUpperCase() + day.substring(1)
|
191
|
-
|
192
|
-
|
193
|
-
domLoaded = false
|
194
|
-
|
195
|
-
update = (callback) ->
|
196
|
-
callback() if domLoaded
|
197
|
-
|
198
|
-
document.addEventListener "time:elapse", callback
|
199
|
-
|
200
|
-
if Turbolinks?.supported
|
201
|
-
document.addEventListener "page:update", callback
|
202
|
-
else
|
203
|
-
jQuery?(document).on "ajaxSuccess", (event, xhr) ->
|
204
|
-
callback() if jQuery.trim xhr.responseText
|
205
|
-
|
206
|
-
process = (selector, callback) ->
|
207
|
-
update ->
|
208
|
-
for element in document.querySelectorAll selector
|
209
|
-
callback element
|
210
|
-
|
211
|
-
document.addEventListener "DOMContentLoaded", ->
|
212
|
-
domLoaded = true
|
213
|
-
textProperty = if "textContent" of document.body then "textContent" else "innerText"
|
214
|
-
|
215
|
-
process "time[data-local]:not([data-localized])", (element) ->
|
216
|
-
datetime = element.getAttribute "datetime"
|
217
|
-
format = element.getAttribute "data-format"
|
218
|
-
local = element.getAttribute "data-local"
|
219
|
-
|
220
|
-
time = new Date Date.parse datetime
|
221
|
-
return if isNaN time
|
222
|
-
|
223
|
-
unless element.hasAttribute("title")
|
224
|
-
element.setAttribute "title", strftime(time, "%B %e, %Y at %l:%M%P %Z")
|
225
|
-
|
226
|
-
element[textProperty] =
|
227
|
-
switch local
|
228
|
-
when "date"
|
229
|
-
element.setAttribute "data-localized", true
|
230
|
-
relativeDate time
|
231
|
-
when "time"
|
232
|
-
element.setAttribute "data-localized", true
|
233
|
-
strftime time, format
|
234
|
-
when "time-ago"
|
235
|
-
relativeTimeAgo time
|
236
|
-
when "time-or-date"
|
237
|
-
relativeTimeOrDate time
|
238
|
-
when "weekday"
|
239
|
-
relativeWeekday(time) ? ""
|
240
|
-
when "weekday-or-date"
|
241
|
-
relativeWeekday(time) ? relativeDate(time)
|
242
|
-
|
243
|
-
run = ->
|
244
|
-
event = document.createEvent "Events"
|
245
|
-
event.initEvent "time:elapse", true, true
|
246
|
-
document.dispatchEvent event
|
247
|
-
|
248
|
-
setInterval run, 60 * 1000
|
249
|
-
|
250
|
-
# Public API
|
251
|
-
@LocalTime = {relativeDate, relativeTimeAgo, relativeTimeOrDate, relativeWeekday, run, strftime}
|
@@ -1,36 +0,0 @@
|
|
1
|
-
module "localized"
|
2
|
-
|
3
|
-
for id in ["one", "two", "past", "future"]
|
4
|
-
test id, ->
|
5
|
-
assertLocalized id
|
6
|
-
|
7
|
-
test "date", ->
|
8
|
-
assertLocalized "date", "date"
|
9
|
-
|
10
|
-
test "unparseable time", ->
|
11
|
-
el = addTimeEl format: "%Y", datetime: ":("
|
12
|
-
setText el, "2013"
|
13
|
-
run()
|
14
|
-
equal getText(el), "2013"
|
15
|
-
|
16
|
-
|
17
|
-
assertLocalized = (id, type = "time") ->
|
18
|
-
switch type
|
19
|
-
when "time"
|
20
|
-
momentFormat = "MMMM D, YYYY h:mma"
|
21
|
-
compare = "toString"
|
22
|
-
when "date"
|
23
|
-
momentFormat = "MMMM D, YYYY"
|
24
|
-
compare = "dayOfYear"
|
25
|
-
|
26
|
-
el = document.getElementById id
|
27
|
-
|
28
|
-
ok datetime = el.getAttribute "datetime"
|
29
|
-
ok local = getText el
|
30
|
-
|
31
|
-
datetimeParsed = moment datetime
|
32
|
-
localParsed = moment local, momentFormat
|
33
|
-
|
34
|
-
ok datetimeParsed.isValid()
|
35
|
-
ok localParsed.isValid()
|
36
|
-
equal datetimeParsed[compare](), localParsed[compare]()
|
@@ -1,25 +0,0 @@
|
|
1
|
-
module "page events"
|
2
|
-
|
3
|
-
test "document DOMContentLoaded", ->
|
4
|
-
el = addTimeEl()
|
5
|
-
triggerEvent "DOMContentLoaded"
|
6
|
-
ok getText el
|
7
|
-
|
8
|
-
test "document time:elapse", ->
|
9
|
-
el = addTimeEl()
|
10
|
-
triggerEvent "time:elapse"
|
11
|
-
ok getText el
|
12
|
-
|
13
|
-
test "document page:update with Turbolinks on", ->
|
14
|
-
el = addTimeEl()
|
15
|
-
triggerEvent "page:update"
|
16
|
-
ok not getText el
|
17
|
-
|
18
|
-
original = window.Turbolinks
|
19
|
-
window.Turbolinks = { supported: true }
|
20
|
-
|
21
|
-
triggerEvent "DOMContentLoaded"
|
22
|
-
triggerEvent "page:update"
|
23
|
-
ok getText el
|
24
|
-
|
25
|
-
window.Turbolinks = original
|
@@ -1,96 +0,0 @@
|
|
1
|
-
module "relative date"
|
2
|
-
|
3
|
-
test "this year", ->
|
4
|
-
now = moment()
|
5
|
-
el = addTimeEl type: "date", datetime: now.toISOString()
|
6
|
-
run()
|
7
|
-
|
8
|
-
equal getText(el), now.format("MMM D")
|
9
|
-
|
10
|
-
test "last year", ->
|
11
|
-
before = moment().subtract("years", 1).subtract("days", 1)
|
12
|
-
el = addTimeEl type: "date", datetime: before.toISOString()
|
13
|
-
run()
|
14
|
-
|
15
|
-
equal getText(el), before.format("MMM D, YYYY")
|
16
|
-
|
17
|
-
|
18
|
-
module "relative time or date"
|
19
|
-
|
20
|
-
|
21
|
-
test "today", ->
|
22
|
-
now = moment()
|
23
|
-
el = addTimeEl type: "time-or-date", datetime: now.toISOString()
|
24
|
-
run()
|
25
|
-
|
26
|
-
equal getText(el), now.format("h:mma")
|
27
|
-
|
28
|
-
test "before today", ->
|
29
|
-
before = moment().subtract("days", 1)
|
30
|
-
el = addTimeEl type: "time-or-date", datetime: before.toISOString()
|
31
|
-
run()
|
32
|
-
|
33
|
-
equal getText(el), before.format("MMM D")
|
34
|
-
|
35
|
-
|
36
|
-
module "relative weekday"
|
37
|
-
|
38
|
-
|
39
|
-
test "today", ->
|
40
|
-
now = moment()
|
41
|
-
el = addTimeEl type: "weekday", datetime: now.toISOString()
|
42
|
-
run()
|
43
|
-
|
44
|
-
equal getText(el), "Today"
|
45
|
-
|
46
|
-
test "yesterday", ->
|
47
|
-
yesterday = moment().subtract("days", 1)
|
48
|
-
el = addTimeEl type: "weekday", datetime: yesterday.toISOString()
|
49
|
-
run()
|
50
|
-
|
51
|
-
equal getText(el), "Yesterday"
|
52
|
-
|
53
|
-
test "this week", ->
|
54
|
-
recent = moment().subtract("days", 3)
|
55
|
-
el = addTimeEl type: "weekday", datetime: recent.toISOString()
|
56
|
-
run()
|
57
|
-
|
58
|
-
equal getText(el), recent.format("dddd")
|
59
|
-
|
60
|
-
test "before this week", ->
|
61
|
-
before = moment().subtract("days", 8)
|
62
|
-
el = addTimeEl type: "weekday", datetime: before.toISOString()
|
63
|
-
run()
|
64
|
-
|
65
|
-
equal getText(el), ""
|
66
|
-
|
67
|
-
module "relative weekday or date"
|
68
|
-
|
69
|
-
|
70
|
-
test "today", ->
|
71
|
-
now = moment()
|
72
|
-
el = addTimeEl type: "weekday-or-date", datetime: now.toISOString()
|
73
|
-
run()
|
74
|
-
|
75
|
-
equal getText(el), "Today"
|
76
|
-
|
77
|
-
test "yesterday", ->
|
78
|
-
yesterday = moment().subtract("days", 1)
|
79
|
-
el = addTimeEl type: "weekday-or-date", datetime: yesterday.toISOString()
|
80
|
-
run()
|
81
|
-
|
82
|
-
equal getText(el), "Yesterday"
|
83
|
-
|
84
|
-
test "this week", ->
|
85
|
-
recent = moment().subtract("days", 3)
|
86
|
-
el = addTimeEl type: "weekday-or-date", datetime: recent.toISOString()
|
87
|
-
run()
|
88
|
-
|
89
|
-
equal getText(el), recent.format("dddd")
|
90
|
-
|
91
|
-
test "before this week", ->
|
92
|
-
before = moment().subtract("days", 8)
|
93
|
-
el = addTimeEl type: "weekday-or-date", datetime: before.toISOString()
|
94
|
-
run()
|
95
|
-
|
96
|
-
equal getText(el), before.format("MMM D")
|
@@ -1,47 +0,0 @@
|
|
1
|
-
module "strftime"
|
2
|
-
|
3
|
-
momentMap =
|
4
|
-
"%a": "ddd"
|
5
|
-
"%A": "dddd"
|
6
|
-
"%b": "MMM"
|
7
|
-
"%B": "MMMM"
|
8
|
-
"%c": "toString()"
|
9
|
-
"%d": "DD"
|
10
|
-
"%e": "D"
|
11
|
-
"%H": "HH"
|
12
|
-
"%I": "hh"
|
13
|
-
"%l": "h"
|
14
|
-
"%m": "MM"
|
15
|
-
"%M": "mm"
|
16
|
-
"%p": "A"
|
17
|
-
"%P": "a"
|
18
|
-
"%S": "ss"
|
19
|
-
"%w": "e"
|
20
|
-
"%y": "YY"
|
21
|
-
"%Y": "YYYY"
|
22
|
-
|
23
|
-
for day in [0..30] by 6
|
24
|
-
do (day) ->
|
25
|
-
for hour in [0..24] by 6
|
26
|
-
do (hour) ->
|
27
|
-
|
28
|
-
for format, momentFormat of momentMap
|
29
|
-
do (format, momentFormat) ->
|
30
|
-
test "#{format} (+#{day} days, #{hour} hours)", ->
|
31
|
-
now = moment().add("days", day).add("hours", hour)
|
32
|
-
el = addTimeEl {format, datetime: now.toISOString()}
|
33
|
-
run()
|
34
|
-
|
35
|
-
equal getText(el),
|
36
|
-
if func = momentFormat.match(/(\w+)\(\)/)?[1]
|
37
|
-
now.toDate()[func]()
|
38
|
-
else
|
39
|
-
now.format momentFormat
|
40
|
-
|
41
|
-
test "%Z Timezone (+#{day} days, #{hour} hours)", ->
|
42
|
-
now = moment().add("days", day).add("hours", hour)
|
43
|
-
el = addTimeEl format: "%Z", datetime: now.toISOString()
|
44
|
-
run()
|
45
|
-
|
46
|
-
text = getText el
|
47
|
-
ok /^(\w{3,4}|UTC[\+\-]\d+)$/.test(text), "'#{text}' doesn't look like a timezone. System date: '#{new Date}'"
|
@@ -1,34 +0,0 @@
|
|
1
|
-
#= require moment
|
2
|
-
#= require sinon-timers
|
3
|
-
|
4
|
-
#= require_self
|
5
|
-
#= require_directory .
|
6
|
-
|
7
|
-
@addTimeEl = ({format, type, datetime} = {}) ->
|
8
|
-
format ?= "%Y"
|
9
|
-
type ?= "time"
|
10
|
-
datetime ?= "2013-11-12T12:13:00Z"
|
11
|
-
|
12
|
-
el = document.createElement "time"
|
13
|
-
el.setAttribute "data-local", type
|
14
|
-
el.setAttribute "data-format", format
|
15
|
-
el.setAttribute "datetime", datetime
|
16
|
-
document.body.appendChild el
|
17
|
-
el
|
18
|
-
|
19
|
-
@setText = (el, text) ->
|
20
|
-
textProperty = if "textContent" of el then "textContent" else "innerText"
|
21
|
-
el[textProperty] = text
|
22
|
-
|
23
|
-
@getText = (el) ->
|
24
|
-
# innerHTML works in all browsers so using it ensures we're
|
25
|
-
# reading the text content, not a potentially arbitrary property.
|
26
|
-
el.innerHTML
|
27
|
-
|
28
|
-
@triggerEvent = (name, el = document) ->
|
29
|
-
event = document.createEvent "Events"
|
30
|
-
event.initEvent name, true, true
|
31
|
-
el.dispatchEvent event
|
32
|
-
|
33
|
-
@run = ->
|
34
|
-
triggerEvent "time:elapse"
|
@@ -1,51 +0,0 @@
|
|
1
|
-
module "time ago"
|
2
|
-
|
3
|
-
test "a second ago", ->
|
4
|
-
assertTimeAgo "a second ago", "seconds", 9
|
5
|
-
|
6
|
-
test "seconds ago", ->
|
7
|
-
assertTimeAgo "44 seconds ago", "seconds", 44
|
8
|
-
|
9
|
-
test "a minute ago", ->
|
10
|
-
assertTimeAgo "a minute ago", "seconds", 89
|
11
|
-
|
12
|
-
test "minutes ago", ->
|
13
|
-
assertTimeAgo "44 minutes ago", "minutes", 44
|
14
|
-
|
15
|
-
test "an hour ago", ->
|
16
|
-
assertTimeAgo "an hour ago", "minutes", 89
|
17
|
-
|
18
|
-
test "hours ago", ->
|
19
|
-
assertTimeAgo "23 hours ago", "hours", 23
|
20
|
-
|
21
|
-
test "yesterday", ->
|
22
|
-
time = moment().subtract("days", 1).format "h:mma"
|
23
|
-
assertTimeAgo "yesterday at #{time}", "days", 1
|
24
|
-
|
25
|
-
test "last week", ->
|
26
|
-
ago = moment().subtract "days", 5
|
27
|
-
day = ago.format "dddd"
|
28
|
-
time = ago.format "h:mma"
|
29
|
-
|
30
|
-
assertTimeAgo "#{day} at #{time}", "days", 5
|
31
|
-
|
32
|
-
test "this year", ->
|
33
|
-
clock = sinon.useFakeTimers(new Date(2013,11,11,11,11).getTime(), "Date")
|
34
|
-
date = moment().subtract("days", 7).format "MMM D"
|
35
|
-
assertTimeAgo "on #{date}", "days", 7
|
36
|
-
clock.restore()
|
37
|
-
|
38
|
-
test "last year", ->
|
39
|
-
date = moment().subtract("days", 366).format "MMM D, YYYY"
|
40
|
-
assertTimeAgo "on #{date}", "days", 366
|
41
|
-
|
42
|
-
test "next year", ->
|
43
|
-
date = moment().add("days", 366).format "MMM D, YYYY"
|
44
|
-
assertTimeAgo "on #{date}", "days", -366
|
45
|
-
|
46
|
-
assertTimeAgo = (string, unit, amount) ->
|
47
|
-
el = document.getElementById "ago"
|
48
|
-
el.setAttribute "data-local", "time-ago"
|
49
|
-
el.setAttribute "datetime", moment().subtract(unit, amount).utc().toISOString()
|
50
|
-
run()
|
51
|
-
equal getText(el), string
|