Merge lp:~dgadomski/ubuntu-rssreader-app/relative-time-formatting into lp:~ubuntu-shorts-dev/ubuntu-rssreader-app/trunk

Proposed by Dariusz Gadomski
Status: Merged
Approved by: Joey Chan
Approved revision: 22
Merged at revision: 25
Proposed branch: lp:~dgadomski/ubuntu-rssreader-app/relative-time-formatting
Merge into: lp:~ubuntu-shorts-dev/ubuntu-rssreader-app/trunk
Diff against target: 520 lines (+116/-74)
10 files modified
ArticleFullImg.qml (+4/-3)
ArticleOneImgA.qml (+4/-3)
ArticleOneImgB.qml (+4/-3)
ArticleTextA.qml (+4/-3)
ArticleTextB.qml (+4/-3)
ArticleTwoImgA.qml (+4/-3)
ListColumnDelegate.qml (+2/-2)
ListColumnView.qml (+1/-1)
addDelegate.js (+19/-19)
dateutils.js (+70/-34)
To merge this branch: bzr merge lp:~dgadomski/ubuntu-rssreader-app/relative-time-formatting
Reviewer Review Type Date Requested Status
Ubuntu Phone Apps Jenkins Bot continuous-integration Approve
Ubuntu Shorts Developers Pending
Joey Chan Pending
David Planella Pending
Review via email: mp+176435@code.launchpad.net

Commit message

Improved relative time formatting utility.

Description of the change

This is my second approach to provide a more generic and flexible relative time formatting utility. This one incorporates an array of formatters which may be adjusted when needed.
There are 2 new formats added 'borrowed' from the calculator app: displaying that something happened yesterday ("Yesterday at %1") or within a week from now (by using the weekday name).

I have tested this version and it works just fine with gettext (all strings are extracted correctly for translations).

Unfortunately, since i18n is a QML context property (confirmed in Ubuntu UI toolkit source code: http://bazaar.launchpad.net/~ubuntu-sdk-team/ubuntu-ui-toolkit/trunk/view/head:/modules/Ubuntu/Components/plugin/plugin.cpp#L133) and not a function/component it cannot be simple ".import"-ed in JS. It needs to be passed there e.g. as an argument of the function.

I believe in this form it can be easily reused in ubuntu-calculator-app (it has all the features required there).

To post a comment you must log in.
Revision history for this message
Roman Shchekin (mrqtros) wrote :

Looks like all is ok, w8 while guys will review your code too :)

22. By Dariusz Gadomski

Merge with trunk.

Revision history for this message
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) :
review: Approve (continuous-integration)
Revision history for this message
Joey Chan (qqworini) wrote :

Hi Dariusz,

We gonna make some changes to the database, one of the major change is, store time value in Integer, not text like before, in order to "order" articles by time value.

So, I want to ask u to separate the "formatRelativeTime" function into two parts:

1. one is convert the original time value to an Integer which is seconds from 1970-01-01 00:00
2. convert the "seconds" Integer to current output

Hope u have time to help :)
Thanks

Joey

Revision history for this message
Dariusz Gadomski (dgadomski) wrote :

Hi Joey,

I have changed the implementation to suit your needs:
https://code.launchpad.net/~dgadomski/ubuntu-rssreader-app/formatRelativeTime-uses-seconds/+merge/183714

I hope this is what you expected.

Regards,
Dariusz

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'ArticleFullImg.qml'
2--- ArticleFullImg.qml 2013-07-09 16:08:27 +0000
3+++ ArticleFullImg.qml 2013-07-26 15:25:31 +0000
4@@ -1,12 +1,13 @@
5 import QtQuick 2.0
6 import Ubuntu.Components 0.1
7 import Ubuntu.Components.ListItems 0.1 as ListItem
8+import "./dateutils.js" as DateUtils
9
10 Column {
11 width: units.gu(26)
12 height: units.gu(20)
13 // anchors.fill: parent
14- property string rss_title: ""
15+ property variant rss_item
16 property variant imageArray: []
17 property variant rss_model
18 property int model_index
19@@ -42,7 +43,7 @@
20 Label
21 {
22 id: label_title
23- text: rss_title
24+ text: rss_item.title
25 anchors.bottom: parent.bottom
26 anchors.bottomMargin: units.gu(1)
27 anchors.horizontalCenter: parent.horizontalCenter
28@@ -67,7 +68,7 @@
29 Label
30 {
31 id: label_time
32- text: "13 minutes ago"
33+ text: DateUtils.formatRelativeTime(i18n, rss_item.pubdate)
34 color: "black"
35 fontSize: "medium"
36 }
37
38=== modified file 'ArticleOneImgA.qml'
39--- ArticleOneImgA.qml 2013-07-05 13:59:01 +0000
40+++ ArticleOneImgA.qml 2013-07-26 15:25:31 +0000
41@@ -1,13 +1,14 @@
42 import QtQuick 2.0
43 import Ubuntu.Components 0.1
44 import Ubuntu.Components.ListItems 0.1 as ListItem
45+import "./dateutils.js" as DateUtils
46
47 Column {
48 id: delegate_item
49 width: units.gu(34)
50 // height: units.gu(16)
51 // anchors.fill: parent
52- property string rss_title: ""
53+ property variant rss_item
54 property variant imageArray: []
55 property variant rss_model
56 property int model_index
57@@ -21,7 +22,7 @@
58 Label
59 {
60 id: label_title
61- text: rss_title
62+ text: rss_item.title
63 // anchors.horizontalCenter: parent.horizontalCenter
64 width: units.gu(14)
65 wrapMode: Text.WrapAtWordBoundaryOrAnywhere
66@@ -72,7 +73,7 @@
67 Label
68 {
69 id: label_time
70- text: "13 minutes ago"
71+ text: DateUtils.formatRelativeTime(i18n, rss_item.pubdate)
72 color: "black"
73 fontSize: "medium"
74 horizontalAlignment: Text.AlignRight
75
76=== modified file 'ArticleOneImgB.qml'
77--- ArticleOneImgB.qml 2013-07-05 13:59:01 +0000
78+++ ArticleOneImgB.qml 2013-07-26 15:25:31 +0000
79@@ -1,13 +1,14 @@
80 import QtQuick 2.0
81 import Ubuntu.Components 0.1
82 import Ubuntu.Components.ListItems 0.1 as ListItem
83+import "./dateutils.js" as DateUtils
84
85 Column {
86 id: delegate_item
87 width: units.gu(34)
88 // height: units.gu(16)
89 // anchors.fill: parent
90- property string rss_title: ""
91+ property variant rss_item
92 property variant imageArray: []
93 property variant rss_model
94 property int model_index
95@@ -59,7 +60,7 @@
96 Label
97 {
98 id: label_title
99- text: rss_title
100+ text: rss_item.title
101 // anchors.horizontalCenter: parent.horizontalCenter
102 width: units.gu(14)
103 wrapMode: Text.WrapAtWordBoundaryOrAnywhere
104@@ -91,7 +92,7 @@
105 Label
106 {
107 id: label_time
108- text: "13 minutes ago"
109+ text: DateUtils.formatRelativeTime(i18n, rss_item.pubdate)
110 color: "black"
111 fontSize: "medium"
112 }
113
114=== modified file 'ArticleTextA.qml'
115--- ArticleTextA.qml 2013-07-05 13:59:01 +0000
116+++ ArticleTextA.qml 2013-07-26 15:25:31 +0000
117@@ -1,12 +1,13 @@
118 import QtQuick 2.0
119 import Ubuntu.Components 0.1
120 import Ubuntu.Components.ListItems 0.1 as ListItem
121+import "./dateutils.js" as DateUtils
122
123 Column {
124 width: units.gu(30)
125 // height: units.gu(15)
126 // anchors.fill: parent
127- property string rss_title: ""
128+ property variant rss_item
129 property variant imageArray: []
130 property variant rss_model
131 property int model_index
132@@ -19,7 +20,7 @@
133 Label
134 {
135 id: label_title
136- text: rss_title
137+ text: rss_item.title
138 // anchors.horizontalCenter: parent.horizontalCenter
139 width: units.gu(30)
140 wrapMode: Text.WrapAtWordBoundaryOrAnywhere
141@@ -42,7 +43,7 @@
142 Label
143 {
144 id: label_time
145- text: "13 minutes ago"
146+ text: DateUtils.formatRelativeTime(i18n, rss_item.pubdate)
147 color: "black"
148 fontSize: "medium"
149 width: parent.width
150
151=== modified file 'ArticleTextB.qml'
152--- ArticleTextB.qml 2013-07-05 13:59:01 +0000
153+++ ArticleTextB.qml 2013-07-26 15:25:31 +0000
154@@ -1,12 +1,13 @@
155 import QtQuick 2.0
156 import Ubuntu.Components 0.1
157 import Ubuntu.Components.ListItems 0.1 as ListItem
158+import "./dateutils.js" as DateUtils
159
160 Column {
161 width: units.gu(30)
162 // height: units.gu(15)
163 // anchors.fill: parent
164- property string rss_title: ""
165+ property variant rss_item
166 property variant imageArray: []
167 property variant rss_model
168 property int model_index
169@@ -19,7 +20,7 @@
170 Label
171 {
172 id: label_title
173- text: rss_title
174+ text: rss_item.title
175 // anchors.horizontalCenter: parent.horizontalCenter
176 width: units.gu(30)
177 wrapMode: Text.WrapAtWordBoundaryOrAnywhere
178@@ -43,7 +44,7 @@
179 Label
180 {
181 id: label_time
182- text: "13 minutes ago"
183+ text: DateUtils.formatRelativeTime(i18n, rss_item.pubdate)
184 color: "black"
185 fontSize: "medium"
186 horizontalAlignment: Text.AlignRight
187
188=== modified file 'ArticleTwoImgA.qml'
189--- ArticleTwoImgA.qml 2013-07-05 13:59:01 +0000
190+++ ArticleTwoImgA.qml 2013-07-26 15:25:31 +0000
191@@ -1,13 +1,14 @@
192 import QtQuick 2.0
193 import Ubuntu.Components 0.1
194 import Ubuntu.Components.ListItems 0.1 as ListItem
195+import "./dateutils.js" as DateUtils
196
197 Column {
198 id: delegate_item
199 width: units.gu(34)
200 // height: units.gu(30)
201 // anchors.fill: parent
202- property string rss_title: ""
203+ property variant rss_item
204 property variant imageArray: []
205 property variant rss_model
206 property int model_index
207@@ -21,7 +22,7 @@
208 Label
209 {
210 id: label_title
211- text: rss_title
212+ text: rss_item.title
213 // anchors.horizontalCenter: parent.horizontalCenter
214 width: units.gu(14)
215 wrapMode: Text.WrapAtWordBoundaryOrAnywhere
216@@ -78,7 +79,7 @@
217 Label
218 {
219 id: label_time
220- text: "13 minutes ago"
221+ text: DateUtils.formatRelativeTime(i18n, rss_item.pubdate)
222 color: "black"
223 fontSize: "medium"
224 }
225
226=== modified file 'ListColumnDelegate.qml'
227--- ListColumnDelegate.qml 2013-07-13 18:28:20 +0000
228+++ ListColumnDelegate.qml 2013-07-26 15:25:31 +0000
229@@ -20,10 +20,10 @@
230 property real childrenMaxWidth: 0
231 property int modelIndex
232
233- function addItem(rss_title, rss_description, model, index)
234+ function addItem(rss_item, rss_description, model, index)
235 {
236 // console.log("delegate height: ", rss_item_delegate.height) ;
237- var newD = AddD.addDelegate(rss_title, ImgS.separate(rss_description), model, index);
238+ var newD = AddD.addDelegate(rss_item, ImgS.separate(rss_description), model, index);
239 getChildrenRect() ;
240 rss_item_delegate.width = childrenMaxWidth ;
241 //console.log("childrenSumHeight, column.height: ", childrenSumHeight, rss_item_delegate.height)
242
243=== modified file 'ListColumnView.qml'
244--- ListColumnView.qml 2013-07-13 18:28:20 +0000
245+++ ListColumnView.qml 2013-07-26 15:25:31 +0000
246@@ -104,7 +104,7 @@
247 // model++ ;
248 // currentIndex = model - 1 ;
249 // console.log("model index: ", i)
250- while (!currentItem.addItem(tempmodel.get(i).title, tempmodel.get(i).description, tempmodel, i))
251+ while (!currentItem.addItem(tempmodel.get(i), tempmodel.get(i).description, tempmodel, i))
252 {
253 // model++ ;
254 column_model.append( { "nothing": "nothing" } ) ;
255
256=== modified file 'addDelegate.js'
257--- addDelegate.js 2013-07-05 13:59:01 +0000
258+++ addDelegate.js 2013-07-26 15:25:31 +0000
259@@ -4,7 +4,7 @@
260 //var mob_component;
261 //var mob_sprite;
262
263-function addDelegate(rss_title, imageArray, model, index)
264+function addDelegate(rss_item, imageArray, model, index)
265 {
266 // console.log("add delegate start")
267 // component = undefined ;
268@@ -23,18 +23,18 @@
269 // component = Qt.createComponent("./ArticledelegateOneImgB.qml");
270 // sprite = component.createObject(rss_item_delegate, {"rss_title": rss_title, "imageArray": imageArray});
271 // console.log("delegate BBBBB")
272- newD = delegateOneImgA (rss_title, imageArray, model, index)
273+ newD = delegateOneImgA (rss_item, imageArray, model, index)
274 }
275 else if (rand > 70)
276 {
277 // component = Qt.createComponent("./ArticledelegateOneImgA.qml");
278 // sprite = component.createObject(rss_item_delegate, {"rss_title": rss_title, "imageArray": imageArray});
279 // console.log("delegate AAAAA")
280- newD = delegateOneImgB (rss_title, imageArray, model, index)
281+ newD = delegateOneImgB (rss_item, imageArray, model, index)
282 }
283 else
284 {
285- newD = delegateFullImg (rss_title, imageArray, model, index)
286+ newD = delegateFullImg (rss_item, imageArray, model, index)
287 }
288
289 }
290@@ -42,16 +42,16 @@
291 {
292 if (rand > 50)
293 {
294- newD = delegateTextA (rss_title/*, imageArray*/, model, index) ;
295+ newD = delegateTextA (rss_item/*, imageArray*/, model, index) ;
296 }
297 else
298 {
299- newD = delegateTextB (rss_title/*, imageArray*/, model, index) ;
300+ newD = delegateTextB (rss_item/*, imageArray*/, model, index) ;
301 }
302 }
303 else
304 {
305- newD = delegateTwoImgA (rss_title, imageArray, model, index)
306+ newD = delegateTwoImgA (rss_item, imageArray, model, index)
307 }
308
309 // else if (imageArray == undefined )
310@@ -79,12 +79,12 @@
311 return newD ;
312 }
313
314-function delegateOneImgA (rss_title, imageArray, model, index)
315+function delegateOneImgA (rss_item, imageArray, model, index)
316 {
317 var component;
318 var sprite;
319 component = Qt.createComponent("./ArticleOneImgA.qml");
320- sprite = component.createObject(rss_item_delegate, {"rss_title": rss_title, "imageArray": imageArray, "rss_model": model, "model_index": index});
321+ sprite = component.createObject(rss_item_delegate, {"rss_item": rss_item, "imageArray": imageArray, "rss_model": model, "model_index": index});
322 // console.log("delegate AAAAA")
323
324 if (sprite == null) {
325@@ -95,12 +95,12 @@
326 return sprite
327 }
328
329-function delegateOneImgB (rss_title, imageArray, model, index)
330+function delegateOneImgB (rss_item, imageArray, model, index)
331 {
332 var component;
333 var sprite;
334 component = Qt.createComponent("./ArticleOneImgB.qml");
335- sprite = component.createObject(rss_item_delegate, {"rss_title": rss_title, "imageArray": imageArray, "rss_model": model, "model_index": index});
336+ sprite = component.createObject(rss_item_delegate, {"rss_item": rss_item, "imageArray": imageArray, "rss_model": model, "model_index": index});
337 // console.log("delegate BBBBB")
338
339 if (sprite == null) {
340@@ -111,12 +111,12 @@
341 return sprite
342 }
343
344-function delegateTextA (rss_title, model, index)
345+function delegateTextA (rss_item, model, index)
346 {
347 var component;
348 var sprite;
349 component = Qt.createComponent("./ArticleTextA.qml");
350- sprite = component.createObject(rss_item_delegate, {"rss_title": rss_title, "rss_model": model, "model_index": index});
351+ sprite = component.createObject(rss_item_delegate, {"rss_item": rss_item, "rss_model": model, "model_index": index});
352 // console.log("delegate CCCCC")
353
354 if (sprite == null) {
355@@ -127,12 +127,12 @@
356 return sprite
357 }
358
359-function delegateTextB (rss_title, model, index)
360+function delegateTextB (rss_item, model, index)
361 {
362 var component;
363 var sprite;
364 component = Qt.createComponent("./ArticleTextB.qml");
365- sprite = component.createObject(rss_item_delegate, {"rss_title": rss_title, "rss_model": model, "model_index": index});
366+ sprite = component.createObject(rss_item_delegate, {"rss_item": rss_item, "rss_model": model, "model_index": index});
367 // console.log("delegate CCCCC")
368
369 if (sprite == null) {
370@@ -143,12 +143,12 @@
371 return sprite
372 }
373
374-function delegateFullImg (rss_title, imageArray, model, index)
375+function delegateFullImg (rss_item, imageArray, model, index)
376 {
377 var component;
378 var sprite;
379 component = Qt.createComponent("./ArticleFullImg.qml");
380- sprite = component.createObject(rss_item_delegate, {"rss_title": rss_title, "imageArray": imageArray, "rss_model": model, "model_index": index});
381+ sprite = component.createObject(rss_item_delegate, {"rss_item": rss_item, "imageArray": imageArray, "rss_model": model, "model_index": index});
382 // console.log("delegate DDDDD")
383
384 if (sprite == null) {
385@@ -159,12 +159,12 @@
386 return sprite
387 }
388
389-function delegateTwoImgA (rss_title, imageArray, model, index)
390+function delegateTwoImgA (rss_item, imageArray, model, index)
391 {
392 var component;
393 var sprite;
394 component = Qt.createComponent("./ArticleTwoImgA.qml");
395- sprite = component.createObject(rss_item_delegate, {"rss_title": rss_title, "imageArray": imageArray, "rss_model": model, "model_index": index});
396+ sprite = component.createObject(rss_item_delegate, {"rss_item": rss_item, "imageArray": imageArray, "rss_model": model, "model_index": index});
397
398 if (sprite == null) {
399 // Error Handling
400
401=== modified file 'dateutils.js'
402--- dateutils.js 2013-07-21 12:49:09 +0000
403+++ dateutils.js 2013-07-26 15:25:31 +0000
404@@ -18,46 +18,82 @@
405
406 .pragma library
407
408-var SECONDS_LIMIT = 60 * 1000;
409-var MINUTES_LIMIT = 60 * SECONDS_LIMIT;
410-var HOURS_LIMIT = 24 * MINUTES_LIMIT;
411-var DAYS_LIMIT = 30 * HOURS_LIMIT;
412-
413-// TODO: handle i18n
414-var FORMAT_LIMITS = [
415- { "limit": SECONDS_LIMIT, "formats": ["%0 second ago", "%0 seconds ago"] },
416- { "limit": MINUTES_LIMIT, "formats": ["%0 minute ago", "%0 minutes ago"] },
417- { "limit": HOURS_LIMIT, "formats": ["%0 hour ago", "%0 hours ago"] },
418- { "limit": DAYS_LIMIT, "formats": ["%0 day ago", "%0 days ago"] },
419-]
420-
421-// fallback if none of the above formats matched
422-// TODO: handle i18n
423-var DEFAULT_DATE_FORMAT = "MMM Do"
424+var SECONDS_LIMIT = 60 * 1000; // used as reference
425+var MINUTES_LIMIT = 60 * SECONDS_LIMIT; // print minutes up to 1h
426+var HOURS_LIMIT = 12 * MINUTES_LIMIT; // print hours up to 12h
427+var DAY_LIMIT = 24 * MINUTES_LIMIT; // print '<val> days ago' up to 30 days back
428+var DAYS_LIMIT = 30 * DAY_LIMIT; // print '<val> days ago' up to 30 days back
429
430 function parseDate(dateAsStr) {
431- return Date.parse(dateAsStr);
432+ return new Date(dateAsStr);
433 }
434
435-function formatPostTime(dateAsStr, i18n) {
436+function formatRelativeTime(i18n, dateAsStr) {
437+ // fallback if none of the other formatters matched
438+ function defaultFallbackFormat(then) {
439+ return Qt.formatDateTime(then, i18n.tr("MMMM d"))
440+ }
441+
442+ // Simple matches all diffs < limit, formats using the format function.
443+ function SimpleFormatter(limit, format) {
444+ this.matches = function (now, then, diff) { return diff < limit }
445+ this.format = format
446+ }
447+
448+ // Matches yesterday date
449+ function YesterdayFormatter() {
450+ this.matches = function (now, then, diff) {
451+ return diff < DAY_LIMIT && now.getDate() !== then.getDate();
452+ }
453+ this.format = function (now, then, diff) {
454+ return i18n.tr("Yesterday at %1").arg(
455+ Qt.formatDateTime(then, i18n.tr("h:mm AP")))
456+ }
457+ }
458+
459+ // Matches up to 7 days ago (formats date as a weekday + time)
460+ function WeekFormatter() {
461+ this.matches = function (now, then, diff) {
462+ return diff < 7 * DAY_LIMIT
463+ }
464+ this.format = function (now, then, diff) {
465+ return Qt.formatDateTime(then, i18n.tr("ddd, h:mm AP"));
466+ }
467+ }
468+
469+ // An array of formatting object processed from 0 up to a matching object.
470+ // If none of the object matches a default fallback formatter will be used.
471+ var FORMATTERS = [
472+ new SimpleFormatter(SECONDS_LIMIT, function (now, then, diff) { return i18n.tr("A few seconds ago...") }),
473+ new SimpleFormatter(MINUTES_LIMIT, function (now, then, diff) {
474+ var val = Math.floor(diff / SECONDS_LIMIT)
475+ return i18n.tr("%1 minute ago", "%1 minutes ago", val).arg(val)
476+ }),
477+ new SimpleFormatter(HOURS_LIMIT, function (now, then, diff) {
478+ var val = Math.floor(diff / MINUTES_LIMIT)
479+ return i18n.tr("%1 hour ago", "%1 hours ago", val).arg(val)
480+ }),
481+ new YesterdayFormatter(),
482+ new WeekFormatter(),
483+ new SimpleFormatter(DAYS_LIMIT, function (now, then, diff) {
484+ var val = Math.floor(diff / DAY_LIMIT)
485+ return i18n.tr("%1 day ago", "%1 days ago", val).arg(val)
486+ })
487+ ]
488+
489+ function formatDiff(now, then, diff) {
490+ for (var i=0; i<FORMATTERS.length; ++i) {
491+ var formatter = FORMATTERS[i]
492+ if (formatter.matches(now, then, diff)) {
493+ return formatter.format(now, then, diff)
494+ }
495+ }
496+ return defaultFallbackFormat(then)
497+ }
498+
499 var now = new Date();
500 var then = parseDate(dateAsStr);
501 var diff = now - then;
502- var formattedDiff = formatDiff(diff, then, i18n);
503- console.log("DDD DIFF", diff, formattedDiff)
504+ var formattedDiff = formatDiff(now, then, diff);
505 return formattedDiff;
506 }
507-
508-function formatDiff(diff, then, i18n) {
509- for (var i=0; i<FORMAT_LIMITS.length; ++i) {
510- var limit = FORMAT_LIMITS[i].limit;
511- if (diff < limit) {
512- // use previous limit to calculate number of current units
513- // e.g. to calculate hours divide by MINUTES_LIMIT
514- // (because MINUTES_LIMIT is 1 hours) etc.
515- var val = Math.floor(diff / (i > 1 ? FORMAT_LIMITS[i-1].limit : SECONDS_LIMIT))
516- return i18n.tr(FORMAT_LIMITS[i].formats[0], FORMAT_LIMITS[i].formats[1], val).arg(val)
517- }
518- }
519- return then.toString(i18n.tr(DEFAULT_DATE_FORMAT))
520-}

Subscribers

People subscribed via source and target branches