local_time 3.0.2 → 3.0.3

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.
@@ -0,0 +1,109 @@
1
+ import LocalTime from "local_time"
2
+
3
+ const { addTimeEl, assert, defer, getText, testAsync, testGroup, getTitle } = LocalTime.TestHelpers
4
+ const { config } = LocalTime
5
+
6
+ testGroup("format24 (24h time)", () => {
7
+ testAsync("default behavior", (done) => {
8
+ const now = moment()
9
+
10
+ const el = addTimeEl({ format: "%-l:%M%P", format24: "%H:%M", datetime: now.toISOString() })
11
+ defer(() => {
12
+ assert.equal(getText(el), now.format("h:mma"))
13
+ done()
14
+ })
15
+ })
16
+
17
+ testAsync("turned off", (done) => {
18
+ const now = moment()
19
+ const originalFormat24 = config.useFormat24
20
+ config.useFormat24 = false
21
+
22
+ const el = addTimeEl({ format: "%-l:%M%P", format24: "%H:%M", datetime: now.toISOString() })
23
+ defer(() => {
24
+ assert.equal(getText(el), now.format("h:mma"))
25
+ config.useFormat24 = originalFormat24
26
+ done()
27
+ })
28
+ })
29
+
30
+ testAsync("turned on", (done) => {
31
+ const now = moment()
32
+ const originalFormat24 = config.useFormat24
33
+ config.useFormat24 = true
34
+
35
+ const el = addTimeEl({ format: "%-l:%M%P", format24: "%H:%M", datetime: now.toISOString() })
36
+ defer(() => {
37
+ assert.equal(getText(el), now.format("HH:mm"))
38
+ config.useFormat24 = originalFormat24
39
+ done()
40
+ })
41
+ })
42
+
43
+ testAsync("fallback for missing data-format24 values", (done) => {
44
+ const now = moment()
45
+ const originalFormat24 = config.useFormat24
46
+ config.useFormat24 = true
47
+
48
+ const el = addTimeEl({ format: "%-l:%M%P", datetime: now.toISOString() })
49
+ defer(() => {
50
+ assert.equal(getText(el), now.format("h:mma"))
51
+ config.useFormat24 = originalFormat24
52
+ done()
53
+ })
54
+ })
55
+
56
+ testAsync("turned off for relative time elements", (done) => {
57
+ const ago = moment().subtract("days", 5)
58
+ const originalFormat24 = config.useFormat24
59
+ config.useFormat24 = false
60
+
61
+ const el = addTimeEl({ type: "time-ago", datetime: ago.toISOString() })
62
+ defer(() => {
63
+ assert.equal(getText(el), `${ago.format("dddd")} at ${ago.format("h:mma")}`)
64
+ config.useFormat24 = originalFormat24
65
+ done()
66
+ })
67
+ })
68
+
69
+ testAsync("turned on for relative time elements", (done) => {
70
+ const ago = moment().subtract("days", 5)
71
+ const originalFormat24 = config.useFormat24
72
+ config.useFormat24 = true
73
+
74
+ const el = addTimeEl({ type: "time-ago", datetime: ago.toISOString() })
75
+ defer(() => {
76
+ assert.equal(getText(el), `${ago.format("dddd")} at ${ago.format("HH:mm")}`)
77
+ config.useFormat24 = originalFormat24
78
+ done()
79
+ })
80
+ })
81
+
82
+ testAsync("element title when turned off", (done) => {
83
+ const ago = moment().subtract("days", 5)
84
+ const originalFormat24 = config.useFormat24
85
+ config.useFormat24 = false
86
+
87
+ const el = addTimeEl({ type: "time-ago", datetime: ago.toISOString() })
88
+ defer(() => {
89
+ const regex = new RegExp(ago.format('MMMM D, YYYY [at] h:mma') + " (\\w{3,4}|UTC[+-]d+)")
90
+ assert.ok(regex.test(getTitle(el)), `'${getTitle(el)}' doesn't look correct, it should match regex ${regex}`)
91
+ config.useFormat24 = originalFormat24
92
+ done()
93
+ })
94
+ })
95
+
96
+ testAsync("element title when turned on", (done) => {
97
+ const ago = moment().subtract("days", 5)
98
+ const originalFormat24 = config.useFormat24
99
+ config.useFormat24 = true
100
+
101
+ const el = addTimeEl({ type: "time-ago", datetime: ago.toISOString() })
102
+ defer(() => {
103
+ const regex = new RegExp(ago.format('MMMM D, YYYY [at] HH:mm') + " (\\w{3,4}|UTC[+-]d+)")
104
+ assert.ok(regex.test(getTitle(el)), `'${getTitle(el)}' doesn't look correct, it should match regex ${regex}`)
105
+ config.useFormat24 = originalFormat24
106
+ done()
107
+ })
108
+ })
109
+ })
@@ -0,0 +1,55 @@
1
+ import LocalTime from "local_time"
2
+
3
+ const { addTimeEl, assert, defer, getText, setText, test, testAsync, testGroup, triggerEvent } = LocalTime.TestHelpers
4
+ const { config } = LocalTime
5
+ const { i18n } = config
6
+
7
+ testGroup("i18n", () => {
8
+ testAsync("updating a value", (done) => {
9
+ const now = moment()
10
+ const values = i18n[config.defaultLocale].date
11
+
12
+ const originalValue = values.today
13
+ values.today = "2day"
14
+
15
+ const el = addTimeEl({ type: "weekday", datetime: now.toISOString() })
16
+ defer(() => {
17
+ assert.equal(getText(el), "2day")
18
+ values.today = originalValue
19
+ done()
20
+ })
21
+ })
22
+
23
+ testAsync("adding a new locale", (done) => {
24
+ const now = moment()
25
+
26
+ const originalLocale = config.locale
27
+ config.locale = "es"
28
+ i18n.es = { date: { today: "hoy" } }
29
+
30
+ const el = addTimeEl({ type: "weekday", datetime: now.toISOString() })
31
+ defer(() => {
32
+ assert.equal(getText(el), "hoy")
33
+ config.locale = originalLocale
34
+ done()
35
+ })
36
+ })
37
+
38
+ testAsync("falling back to the default locale", (done) => {
39
+ const now = moment()
40
+ const yesterday = moment().subtract("days", 1)
41
+
42
+ const originalLocale = config.locale
43
+ config.locale = "es"
44
+ i18n.es = { date: { yesterday: "ayer" } }
45
+
46
+ const elWithTranslation = addTimeEl({ type: "weekday", datetime: yesterday.toISOString() })
47
+ const elWithoutTranslation = addTimeEl({ type: "weekday", datetime: now.toISOString() })
48
+ defer(() => {
49
+ assert.equal(getText(elWithTranslation), "ayer")
50
+ assert.equal(getText(elWithoutTranslation), "today")
51
+ config.locale = originalLocale
52
+ done()
53
+ })
54
+ })
55
+ })
@@ -0,0 +1,54 @@
1
+ import LocalTime from "local_time"
2
+
3
+ const { addTimeEl, assert, defer, getText, setText, test, testAsync, testGroup, triggerEvent } = LocalTime.TestHelpers
4
+
5
+ testGroup("localized", () => {
6
+ for (var id of [ "one", "two", "past", "future" ]) {
7
+ test(id, () => {
8
+ assertLocalized(id)
9
+ })
10
+ }
11
+
12
+ test("date", () => {
13
+ assertLocalized("date", "date")
14
+ })
15
+
16
+ test("unparseable time", () => {
17
+ const el = addTimeEl({ format: "%Y", datetime: ":(" })
18
+ setText(el, "2013")
19
+ assert.equal(getText(el), "2013")
20
+ })
21
+ })
22
+
23
+ test("processed timestamp", () => {
24
+ const el = addTimeEl({ type: "time-or-date", datetime: moment().toISOString() })
25
+ assert.notOk(el.getAttribute("data-processed-at"))
26
+ LocalTime.run()
27
+ assert.ok(el.getAttribute("data-processed-at"))
28
+ })
29
+
30
+ function assertLocalized(id, type = "time") {
31
+ let compare, datetime, local, momentFormat
32
+ switch (type) {
33
+ case "time":
34
+ momentFormat = "MMMM D, YYYY h:mma"
35
+ compare = "toString"
36
+ break
37
+ case "date":
38
+ momentFormat = "MMMM D, YYYY"
39
+ compare = "dayOfYear"
40
+ break
41
+ }
42
+
43
+ const el = document.getElementById(id)
44
+
45
+ assert.ok(datetime = el.getAttribute("datetime"))
46
+ assert.ok(local = getText(el))
47
+
48
+ const datetimeParsed = moment(datetime)
49
+ const localParsed = moment(local, momentFormat)
50
+
51
+ assert.ok(datetimeParsed.isValid())
52
+ assert.ok(localParsed.isValid())
53
+ assert.equal(datetimeParsed[compare](), localParsed[compare]())
54
+ }
@@ -0,0 +1,121 @@
1
+ import LocalTime from "local_time"
2
+
3
+ const { addTimeEl, assert, defer, getText, testAsync, testGroup, stubNow } = LocalTime.TestHelpers
4
+
5
+ testGroup("relative date", () => {
6
+ testAsync("this year", (done) => {
7
+ const now = moment()
8
+ const el = addTimeEl({ type: "date", datetime: now.toISOString() })
9
+ defer(() => {
10
+ assert.equal(getText(el), now.format("MMM D"))
11
+ done()
12
+ })
13
+ })
14
+
15
+ testAsync("last year", (done) => {
16
+ const before = moment().subtract("years", 1).subtract("days", 1)
17
+ const el = addTimeEl({ type: "date", datetime: before.toISOString() })
18
+ defer(() => {
19
+ assert.equal(getText(el), before.format("MMM D, YYYY"))
20
+ done()
21
+ })
22
+ })
23
+ })
24
+
25
+ testGroup("relative time or date", () => {
26
+ testAsync("today", (done) => {
27
+ const now = moment()
28
+ const el = addTimeEl({ type: "time-or-date", datetime: now.toISOString() })
29
+ defer(() => {
30
+ assert.equal(getText(el), now.format("h:mma"))
31
+ done()
32
+ })
33
+ })
34
+
35
+ testAsync("before today", (done) => {
36
+ const before = moment().subtract("days", 1)
37
+ const el = addTimeEl({ type: "time-or-date", datetime: before.toISOString() })
38
+ defer(() => {
39
+ assert.equal(getText(el), before.format("MMM D"))
40
+ done()
41
+ })
42
+ })
43
+ })
44
+
45
+ testGroup("relative weekday", () => {
46
+ testAsync("today", (done) => {
47
+ const now = moment()
48
+ const el = addTimeEl({ type: "weekday", datetime: now.toISOString() })
49
+ defer(() => {
50
+ assert.equal(getText(el), "today")
51
+ done()
52
+ })
53
+ })
54
+
55
+ testAsync("yesterday", (done) => {
56
+ const yesterday = moment().subtract("days", 1)
57
+ const el = addTimeEl({ type: "weekday", datetime: yesterday.toISOString() })
58
+ defer(() => {
59
+ assert.equal(getText(el), "yesterday")
60
+ done()
61
+ })
62
+ })
63
+
64
+ testAsync("this week", (done) => {
65
+ const recent = moment().subtract("days", 3)
66
+ const el = addTimeEl({ type: "weekday", datetime: recent.toISOString() })
67
+ defer(() => {
68
+ assert.equal(getText(el), recent.format("dddd"))
69
+ done()
70
+ })
71
+ })
72
+
73
+ testAsync("before this week", (done) => {
74
+ const before = moment().subtract("days", 8)
75
+ const el = addTimeEl({ type: "weekday", datetime: before.toISOString() })
76
+ defer(() => {
77
+ assert.equal(getText(el), "")
78
+ done()
79
+ })
80
+ })
81
+ })
82
+
83
+ testGroup("relative weekday or date", () => {
84
+ testAsync("today", (done) => {
85
+ const now = moment()
86
+ const el = addTimeEl({ type: "weekday-or-date", datetime: now.toISOString() })
87
+ defer(() => {
88
+ assert.equal(getText(el), "today")
89
+ done()
90
+ })
91
+ })
92
+
93
+ testAsync("yesterday", (done) => {
94
+ const yesterday = moment().subtract("days", 1)
95
+ const el = addTimeEl({ type: "weekday-or-date", datetime: yesterday.toISOString() })
96
+ defer(() => {
97
+ assert.equal(getText(el), "yesterday")
98
+ done()
99
+ })
100
+ })
101
+
102
+ testAsync("this week", (done) => {
103
+ const recent = moment().subtract("days", 3)
104
+ const el = addTimeEl({ type: "weekday-or-date", datetime: recent.toISOString() })
105
+ defer(() => {
106
+ assert.equal(getText(el), recent.format("dddd"))
107
+ done()
108
+ })
109
+ })
110
+
111
+ testAsync("before this week", (done) => {
112
+ stubNow(`${moment().year()}-03-20`, () => {
113
+ const before = moment().subtract("days", 8)
114
+ const el = addTimeEl({ type: "weekday-or-date", datetime: before.toISOString() })
115
+ defer(() => {
116
+ assert.equal(getText(el), before.format("MMM D"))
117
+ done()
118
+ })
119
+ })
120
+ })
121
+ })
@@ -0,0 +1,151 @@
1
+ import LocalTime from "local_time"
2
+
3
+ const { addTimeEl, assert, defer, getText, setText, test, testAsync, testGroup, triggerEvent } = LocalTime.TestHelpers
4
+
5
+ const momentMap = {
6
+ "%a": "ddd",
7
+ "%A": "dddd",
8
+ "%b": "MMM",
9
+ "%B": "MMMM",
10
+ "%c": "toString()",
11
+ "%d": "DD",
12
+ "%-d": "D",
13
+ "%e": "D",
14
+ "%H": "HH",
15
+ "%-H": "H",
16
+ "%I": "hh",
17
+ "%-I": "h",
18
+ "%l": "h",
19
+ "%m": "MM",
20
+ "%-m": "M",
21
+ "%M": "mm",
22
+ "%-M": "m",
23
+ "%p": "A",
24
+ "%P": "a",
25
+ "%S": "ss",
26
+ "%-S": "s",
27
+ "%w": "e",
28
+ "%y": "YY",
29
+ "%Y": "YYYY"
30
+ }
31
+
32
+ function stubDateToLocaleString(stubImplementation, callback) {
33
+ const original = Date.prototype.toLocaleString
34
+ Date.prototype.toLocaleString = stubImplementation
35
+ try {
36
+ return callback()
37
+ } finally {
38
+ Date.prototype.toLocaleString = original
39
+ }
40
+ }
41
+
42
+ function stubDateToString(stubImplementation, callback) {
43
+ const original = Date.prototype.toString
44
+ Date.prototype.toString = stubImplementation
45
+ try {
46
+ return callback()
47
+ } finally {
48
+ Date.prototype.toString = original
49
+ }
50
+ }
51
+
52
+ testGroup("strftime", () => {
53
+ for (let day = 0; day <= 30; day += 6) {
54
+ for (let hour = 0; hour <= 24; hour += 6) {
55
+ for (const format in momentMap) {
56
+ if (Object.prototype.hasOwnProperty.call(momentMap, format)) {
57
+ const momentFormat = momentMap[ format ]
58
+
59
+ test(`${format} (+${day} days, ${hour} hours)`, () => {
60
+ const now = moment().add("days", day).add("hours", hour)
61
+ const el = addTimeEl({ format, datetime: now.toISOString() })
62
+ LocalTime.process(el)
63
+
64
+ if (momentFormat.includes("toString()")) {
65
+ assert.equal(getText(el), now.toDate().toString())
66
+ } else {
67
+ assert.equal(getText(el), now.format(momentFormat))
68
+ }
69
+ })
70
+ }
71
+ }
72
+
73
+ test(`%Z Timezone (+${day} days, ${hour} hours)`, () => {
74
+ const now = moment().add("days", day).add("hours", hour)
75
+ const el = addTimeEl({ format: "%Z", datetime: now.toISOString() })
76
+ LocalTime.process(el)
77
+
78
+ const text = getText(el)
79
+ assert.ok(/^(\w{3,4}|UTC[\+\-]\d+)$/.test(text), `'${text}' doesn't look like a timezone. System date: '${new Date}'`)
80
+ })
81
+ }
82
+ }
83
+ })
84
+
85
+ testGroup("strftime time zones", () => {
86
+ for (var timeZone of Array.from(Object.keys(LocalTime.knownEdgeCaseTimeZones))) {
87
+ ((timeZone => test(`edge-case time zone ${timeZone}`, () => {
88
+ const stub = () => `Thu Nov 30 2023 14:22:57 GMT-0000 (${timeZone})`
89
+
90
+ stubDateToLocaleString(stub, () => {
91
+ const el = addTimeEl({ format: "%Z", datetime: "2023-11-30T14:22:57Z" })
92
+ LocalTime.process(el)
93
+
94
+ assert.equal(getText(el), LocalTime.knownEdgeCaseTimeZones[timeZone])
95
+ })
96
+ })))(timeZone)
97
+ }
98
+
99
+ test("time zones Intl can abbreviate are parsed correctly", () => {
100
+ const stub = (_, options) => {
101
+ if (options.timeZoneName === "long") {
102
+ return "Thu Nov 30 2023 14:22:57 GMT-0800 (Alaska Daylight Time)" // not a known edge-case
103
+ } else if (options.timeZoneName === "short") {
104
+ return "11/30/2023, 2:22:57 PM AKDT" // possible to abbreviate
105
+ }
106
+ }
107
+
108
+ stubDateToLocaleString(stub, () => {
109
+ const el = addTimeEl({ format: "%Z", datetime: "2023-11-30T14:22:57Z" })
110
+ LocalTime.process(el)
111
+
112
+ assert.equal(getText(el), "AKDT")
113
+ })
114
+ })
115
+
116
+ test("time zones Intl can't abbreviate are parsed by our heuristic", () => {
117
+ const dateToStringStub = () => "Sat Dec 02 2023 17:20:26 GMT-0600 (Central Standard Time)"
118
+ const dateToLocaleStringStub = (_, options) => {
119
+ if (options.timeZoneName === "long") {
120
+ return "Thu Nov 30 2023 14:22:57 GMT+0700 (Central Twilight Time)" // not a known edge-case
121
+ } else if (options.timeZoneName === "short") {
122
+ return "11/30/2023, 2:22:57 PM GMT+7" // not possible to abbreviate
123
+ }
124
+ }
125
+
126
+ stubDateToString(dateToStringStub, () => stubDateToLocaleString(dateToLocaleStringStub, () => {
127
+ const el = addTimeEl({ format: "%Z", datetime: "2023-11-30T14:22:57Z" })
128
+ LocalTime.process(el)
129
+
130
+ assert.equal(getText(el), "CST")
131
+ }))
132
+ })
133
+
134
+ test("time zones Intl can't abbreviate and our heuristic can't parse display GMT offset", () => {
135
+ const dateToStringStub = () => ""
136
+ const dateToLocaleStringStub = (_, options) => {
137
+ if (options.timeZoneName === "long") {
138
+ return "Thu Nov 30 2023 14:22:57 GMT+0700 (Central Twilight Time)" // not a known edge-case
139
+ } else if (options.timeZoneName === "short") {
140
+ return "11/30/2023, 2:22:57 PM GMT+7" // not possible to abbreviate
141
+ }
142
+ }
143
+
144
+ stubDateToString(dateToStringStub, () => stubDateToLocaleString(dateToLocaleStringStub, () => {
145
+ const el = addTimeEl({ format: "%Z", datetime: "2023-11-30T14:22:57Z" })
146
+ LocalTime.process(el)
147
+
148
+ assert.equal(getText(el), "GMT+7")
149
+ }))
150
+ })
151
+ })
@@ -0,0 +1,66 @@
1
+ import LocalTime from "local_time"
2
+
3
+ LocalTime.start()
4
+
5
+ LocalTime.TestHelpers = {
6
+ assert: QUnit.assert,
7
+ testGroup: QUnit.module,
8
+ test: QUnit.test,
9
+
10
+ testAsync(name, callback) {
11
+ QUnit.test(name, (assert) => {
12
+ const done = assert.async()
13
+ callback(done)
14
+ })
15
+ },
16
+
17
+ addTimeEl(param = {}) {
18
+ let { format, type, datetime, format24 } = param
19
+ if (!format) format = "%Y"
20
+ if (!type) type = "time"
21
+ if (!datetime) datetime = "2013-11-12T12:13:00Z"
22
+
23
+ const el = document.createElement("time")
24
+ el.setAttribute("data-local", type)
25
+ el.setAttribute("data-format", format)
26
+ el.setAttribute("datetime", datetime)
27
+ if (format24) el.setAttribute("data-format24", format24)
28
+
29
+ document.body.appendChild(el)
30
+ return el
31
+ },
32
+
33
+ setText(el, text) {
34
+ el.textContent = text
35
+ },
36
+
37
+ getText(el) {
38
+ // innerHTML works in all browsers so using it ensures we're
39
+ // reading the text content, not a potentially arbitrary property.
40
+ return el.innerHTML
41
+ },
42
+
43
+ getTitle(el) {
44
+ return el.getAttribute("title")
45
+ },
46
+
47
+ triggerEvent(name, el = document) {
48
+ const event = document.createEvent("Events")
49
+ event.initEvent(name, true, true)
50
+ el.dispatchEvent(event)
51
+ },
52
+
53
+ defer(callback) {
54
+ setTimeout(callback, 1)
55
+ },
56
+
57
+ stubNow(dateString, callback) {
58
+ const originalNow = moment.now
59
+ try {
60
+ moment.now = () => new Date(dateString).getTime()
61
+ callback()
62
+ } finally {
63
+ moment.now = originalNow
64
+ }
65
+ }
66
+ }
@@ -0,0 +1,72 @@
1
+ import LocalTime from "local_time"
2
+
3
+ const { addTimeEl, assert, defer, getText, setText, test, testAsync, testGroup, triggerEvent } = LocalTime.TestHelpers
4
+
5
+ testGroup("time ago", () => {
6
+ test("a second ago", () => {
7
+ assertTimeAgo("a second ago", "seconds", 9)
8
+ })
9
+
10
+ test("seconds ago", () => {
11
+ assertTimeAgo("44 seconds ago", "seconds", 44)
12
+ })
13
+
14
+ test("a minute ago", () => {
15
+ assertTimeAgo("a minute ago", "seconds", 89)
16
+ })
17
+
18
+ test("minutes ago", () => {
19
+ assertTimeAgo("44 minutes ago", "minutes", 44)
20
+ })
21
+
22
+ test("an hour ago", () => {
23
+ assertTimeAgo("an hour ago", "minutes", 89)
24
+ })
25
+
26
+ test("hours ago", () => {
27
+ assertTimeAgo("23 hours ago", "hours", 23)
28
+ })
29
+
30
+ test("yesterday", () => {
31
+ const time = moment().subtract("days", 1).format("h:mma")
32
+ assertTimeAgo(`yesterday at ${time}`, "days", 1)
33
+ })
34
+
35
+ test("tomorrow", () => {
36
+ const time = moment().add("days", 1).format("h:mma")
37
+ assertTimeAgo(`tomorrow at ${time}`, "days", -1)
38
+ })
39
+
40
+ test("last week", () => {
41
+ const ago = moment().subtract("days", 5)
42
+ const day = ago.format("dddd")
43
+ const time = ago.format("h:mma")
44
+
45
+ assertTimeAgo(`${day} at ${time}`, "days", 5)
46
+ })
47
+
48
+ test("this year", () => {
49
+ const clock = sinon.useFakeTimers(new Date(2013, 11, 11, 11, 11).getTime(), "Date")
50
+ const date = moment().subtract("days", 7).format("MMM D")
51
+ assertTimeAgo(`on ${date}`, "days", 7)
52
+ clock.restore()
53
+ })
54
+
55
+ test("last year", () => {
56
+ const date = moment().subtract("days", 366).format("MMM D, YYYY")
57
+ assertTimeAgo(`on ${date}`, "days", 366)
58
+ })
59
+
60
+ test("next year", () => {
61
+ const date = moment().add("days", 366).format("MMM D, YYYY")
62
+ assertTimeAgo(`on ${date}`, "days", -366)
63
+ })
64
+ })
65
+
66
+ function assertTimeAgo(string, unit, amount) {
67
+ const el = document.getElementById("ago")
68
+ el.setAttribute("data-local", "time-ago")
69
+ el.setAttribute("datetime", moment().subtract(unit, amount).utc().toISOString())
70
+ LocalTime.run()
71
+ assert.equal(getText(el), string)
72
+ }