-
• #2
...such things get me going... from a private (messages) conversation:
Some food for thought... you will also see some typical dynamic Javascript constructs in this code.
In some contexts one likes to separate the data from the presentation... two classes are used: The first one is - obviously - the Javascript built-in
Date
class, and the second one is the related...Formatter
class. This allows a mix and match with different formatters and to include the formatter only when needed.new DateFormatter().format(); // string w/ today in default format var tsFrmttr = new DateFormatter("yyyyMMddHHmmssSSS"); tsFrmttr.format(); // now timestamp in human readable format
Since this may look like a bit cumbersome, short and very short form have been introduced:
var d = new Date((new Date().getTime()) - 24*60*60*1000); // an day ago DateFormatter.format(); // today in default format DateFormatter.format(null,"yyyyMMdd"); // today in sortable format DateFormatter.format(d); // yesterday in default format DateFormatter(null,null); // today in default format DateFormatter(d,"D, N d, y h:mx"); // yesterday same time
The code would look like something below.
var DateFormatter = (function(){ // for in-line use // DateFormatter.js module // // allObjects 20160507 // // yMdHmsSZhxXND are date and time pattern chars, escapable by ^ (default) // y-year, M-month, d-day, H-hour24, m-minute, s-second, S-milliSecond, Z-zone, // h-hour12, x/X-am|pm/AM|PM, N-Name of month, D-Name of Day, with consecutive // same chars defining length (1 char: variable length, numbers never truncated) // var DF = require("DateFormatter"); DF.updateDefaults({pattern:"d.M.y", ...}); // var df = new DF("optionalPattern"); log(df.format(optDate,optPattern)); // log(DF.format(optDate,optPattern)); log(DF(dateOrFalsy,patternOrFalsy)); // Absent|undefined|null|0|false,"" (=falsy) enforce default (date: now, pattern: // defaults.pattern). NOTE: DF() direct use requires ALWAYS both parms present. // // use: regular: constructor new F(optPatt) | direct: F(dateOrFalsy,pattOrFalsy) var F = function() { // F(pattern) | F(date,pattern) - falsy = default var a = arguments; if (a.length > 1) { return new F().format(a[0],a[1]); } this.pattern = ((a.length > 0) && a[0]) ? a[0] : this.__proto__.defaults.pattern; }, p = F.prototype; // regular use: f = new F(optPattern); f.format(optDateOrNull,optPattern); p.defaults = { pattern: "M/d/y", esc: "^", year: "yyyy", zone: "ESP" , dayLen:3, days: "SunMonTueWedThuFriSat" , monLen:3, mons: "JanFebMarAprMayJunJulAugSepOctNovDec" }; p.getDefaults = function() { return p.defaults; }; p.updateDefaults = function(defaults) { for (var d in defaults) p.defaults[d] = defaults[d]; return this; }; p.format = function(date, pattern) { var d = (date) ? date : new Date(), pt = (pattern) ? pattern : this.pattern, s = "", e = p.defaults.esc, pl = pt.length, i = 0, c, f, l; while (i < pl) { c = pt.charAt(i); i++; if (c === e) { if (i < pl) { s += pt.charAt(i); i++; } } else if ((f = this[c])) { l = 1; while ((i < pl) && (pt.charAt(i) === c)) { l++; i++; } s += f.apply(this,[c,d,l]); } else { s += c; } } return s; }; // short format ~class|static methods: F.format(optDateOrNull,optPattern); F.getDefaults = function() { return p.getDefaults(); }; F.updateDefaults = function(defaults) { p.updateDefaults(defaults); return F; }; F.format = function(date,pattern) { return (new F(pattern)).format(date); }; // core formatters p.y = function(c,d,l) { return this._fn(d.getFullYear(),c,l); }; p.M = function(c,d,l) { return (l < 3) ? this._fn(d.getMonth() + 1,c,l) : this.N(c,d,l); }; p.N = function(c,d,l) { return this._ft(d.getMonth(),c,l,p.defaults.mons,p.defaults.monLen); }; p.d = function(c,d,l) { return this._fn(d.getDate(),c,l); }; p.H = function(c,d,l) { return this._fn(d.getHours(),c,l); }; p.h = function(c,d,l) { return this._fn(d.getHours() % 12,c,l); }; p.m = function(c,d,l) { return this._fn(d.getMinutes(),c,l); }; p.s = function(c,d,l) { return this._fn(d.getSeconds(),c,l); }; p.S = function(c,d,l) { return this._fn(d.getMilliseconds(),c,l); }; p.x = function(c,d,l) { return ((d.getHours() < 12) ? "am" : "pm"); }; p.X = function(c,d,l) { return ((d.getHours() < 12) ? "AM" : "PM"); }; p.D = function(c,d,l) { return this._ft(d.getDay(),c,l,p.defaults.days,p.defaults.dayLen); }; p.Z = function(c,d,l) { return this._ft(0,c,l,p.defaults.zone,p.defaults.zone.length); }; p._ft = function(val,c,len,t,tLen) { // log(c + ": " + val + " as " + len + " max " + tLen + " @ " + val * tLen); var r = t.substr(val * tLen,tLen), l; return (len === 1) ? (((l = r.indexOf(" ")) < 0) ? r : r.substring(0,l) ): r.substr(0,len); }; p._fn = function(val,c,len) { // log(c + ": " + val + " in " + len); var r = "" + val; return ((c === "y") ? (len === 1) ? r.substr(0 - p.defaults.year.length) : r : (r.length < len) ? ("000" + r).substr(0 - len) : r ); }; // function log(v) { console.log(v); } // exports = F; // for module use return F; // for in-line use })(); // for in-line use // DateFormatter = require("DateFormatter"); // for module use
Attachment: DateFormatter.js and DateFormatter.min.js as modules to be placed in project sandbox modules folder.
To be continued in next post...
2 Attachments
-
• #3
Test code:
function log(s) { if (typeof window !== "undefined") { var n = document.getElementById("console"); n.innerHTML = n.innerHTML + "\n" + s; } else { console.log(s); } } function l(pattern, date, assertValue) { // for demo convenience var formatter = new DateFormatter(pattern); var formatted = formatter.format(date); var ok = (assertValue) ? ((formatted === assertValue) ? ": ----} ok" : ": ----> *** NOK ***") : ""; log(formatted + " {---- " + ((pattern) ? '"' + pattern + '"' : "default") + ok); } function onInit(ts) { if (ts) setTime(ts); log("- yMNdHhmsSxXDZ are date and time pattern chars, escapable by ^ (default)"); log("- d is a date a day ago same time; m is 75 days, 12 hours and 30 mins ago."); var ts = new Date().getTime(); var d = new Date(ts - 24*60*60*1000); // yesterday same time var m = new Date(ts - 75*24*60*60*1000 - 12*60*60*1000 - 30*60*1000); // 75 d 12 h 30 m ago log("----- normal invocation format ------"); l(); // now in default format l("M/d/y"); // now (today) in / format l("MM/dd/yyyy"); // now (today) in fixed format for numbers l("MM/dd/yy"); // now (today) fixed format for numbers l("^Mont^h b^y na^me: NNN(3) N(all)"); // now (this month) l("D, N d, yyyy"); // now (today) in week day month by name and day, full year l("D, N d, yyyy h:mm X"); // now in week day month by name and day, full year, AM/PM time l("D, N d, yyyy h:mm:ss X",d); // yesterday same time in... l("D, N d, yyyy h:mm:ss X",m); // about 75 days ago in... l("ti^me: hh:mmx"); // now (hr) in am/pm l("d.M.y"); // now (tdday) in . format l("dd.MM.yyyy"); // fixed format for numbers l("dd.MM.yy"); // fixed format for numbers l("h X Z E^spruino ti^me zone ;-)"); // now in h format and time zone log("----- short invocation format ------"); log(DateFormatter.format() + ' {-- DateFormatter.format()'); // today in default format log(DateFormatter.format(null,"yyyyMMddHHmmss.SSS") + ' {-- ...(null,"yyy...")'); // timestamp in sortable format log(DateFormatter.format(m) + ' {-- DateFormatter.format(m)'); // about 75 days ago log("----- very short invocation format ------"); log(DateFormatter(null,null) + ' {-- DateFormatter(null,null)'); // today default log(DateFormatter(m,null) + ' {-- DateFormatter(m,null)'); // about 75 days ago log(DateFormatter(d,"D, N d, y h:mx") + ' {-- DateFormatter(d,"D, N d, y h:mx")'); log("----- update defaults (default: d.M.y, esc: ~, ... some or all) ------"); DateFormatter.updateDefaults({ pattern: "d.M.y", esc: "~", year: "yy", zone: "MEZ-1" , dayLen:2, days: "SoMoDiMiDoFrSa" , monLen:9, mons: "Januar Februar Maerz April Mai Juni " + "Juli August SeptemberOktober November Dezember " }); log("----- normal invocation format ------"); l("~Monat bei~m ~Na~men: NNN N"); l("D, d. N yyyy"); // jetzt in Wochentag Tag. Monat Jahr l("D, d. N yyyy H:mm"); // jetzt in Wochentag Tag. Monat Jahr und Zeit l("D, d. N yyyy H:mm:ss",d); // gestern um die gleiche Zeit in Wochentag... l("D, d. N yyyy H:mm:ss",m); // ungefaehr vor 75 Tagen in... l("~Zeit: HH:mm"); l("H~h Z"); // jetzt in H format mit Zeitzone } onInit(1462603718.328); // Sat, May 7, 2016 6:48:38 AM // parameter derived from browser debugger: new Date().getTime() / 1000 // rerun without parameter - in console type: onInit();
Test output:
1v85 Copyright 2016 G.Williams >echo(0); - yMNdHhmsSxXDZ are date and time pattern chars, escapable by ^ (default) - d is a date a day ago same time, m is 75 days, 12 hours and 30 mins ago. ----- normal invocation format ------ 5/7/2016 {---- default 5/7/2016 {---- "M/d/y" 05/07/2016 {---- "MM/dd/yyyy" 05/07/2016 {---- "MM/dd/yy" Month by name: May(3) May(all) {---- "^Mont^h b^y na^me: NNN(3) N(all)" Sat, May 7, 2016 {---- "D, N d, yyyy" Sat, May 7, 2016 6:48 AM {---- "D, N d, yyyy h:mm X" Fri, May 6, 2016 6:48:38 AM {---- "D, N d, yyyy h:mm:ss X" Sun, Feb 21, 2016 6:18:38 PM {---- "D, N d, yyyy h:mm:ss X" time: 06:48am {---- "ti^me: hh:mmx" 7.5.2016 {---- "d.M.y" 07.05.2016 {---- "dd.MM.yyyy" 07.05.2016 {---- "dd.MM.yy" 6 AM ESP Espruino time zone ;-) {---- "h X Z E^spruino ti^me zone ;-)" ----- short invocation format ------ 5/7/2016 {-- DateFormatter.format() 20160507064838.250 {-- ...(null,"yyy...") 2/21/2016 {-- DateFormatter.format(m) ----- very short invocation format ------ 5/7/2016 {-- DateFormatter(null,null) 2/21/2016 {-- DateFormatter(m,null) Fri, May 6, 2016 6:48am {-- DateFormatter(d,"D, N d, y h:mx") ----- update defaults (default: d.M.y, esc: ~, ... some or all) ------ ----- normal invocation format ------ Monat beim Namen: Mai Mai {---- "~Monat bei~m ~Na~men: NNN N" Sa, 7. Mai 2016 {---- "D, d. N yyyy" Sa, 7. Mai 2016 6:48 {---- "D, d. N yyyy H:mm" Fr, 6. Mai 2016 6:48:38 {---- "D, d. N yyyy H:mm:ss" So, 21. Februar 2016 18:18:38 {---- "D, d. N yyyy H:mm:ss" Zeit: 06:48 {---- "~Zeit: HH:mm" 6h MEZ-1 {---- "H~h Z" =undefined >
Attached html can be clicked and it runs right away same code in the browser.
Placed in a module named
DateFormatter
and pulled in withvar DF = require("DateFormatter");
it is a bit less verbose...To be continued in next post...
1 Attachment
-
• #4
Formatting pattern characters:
// yMNdHhmsSxXDZ are date and time pattern chars, escape-able with ^ (default).
- |
y
: year... "y" as defined in prototype defaultsyear
(= "yy" or "yyyy"), "yy" or "yyyy" for short and full year format. - |
M
: Month... "M" single or two digits, "MM" two digits with leading 0 for months 1..9. "MMM".."MMM..M" is the same as "NNN" and "NNN..N". SeeN
. - |
N
: Name of month... "N" as defined in prototype defaultsmons
string ("JanFebMar..."
or"January__February_March____..."
) with fix length ofmonLen
(3
or9
, respectively), the length of longest month name. String is extracted with mons.substr(month * monLen, monLen). "N..N" will take a substring of length equal to the number of "N"s. (Note that the underscores stand for blanks.) d
: day... "d" single or two digits, "dd" two digits with leading 0 for days 1..9.H
: Hours in 24H format... "H" single or two digits, "HH" two digits with leading 0 for hours 0..9.h
: hours in 12h format... "h" single or two digits, "hh" two digits with leading 0 for hours 0..9. To display related am or pm and AM or PM seex
andX
.- ```s```: **seconds**... "s" single or two digits, "ss" two digits with leading 0 for secconds 0..9. - ```S```: **milliSeconds**... "S" single, two, or three digits, "SSS" three digits with two and one leading **0**(s) for seconds **1..9** and **10..99**, respectively. - ```x```: **am** and **pm** (lowercase)... "x" always puts **am** or **pm** in the output. See also ```X``` (uppercase) and ```h``` (lowercase). - ```X```: **AM** and **PM** (uppercase)... "X" always puts **AM** or **PM** in the output. See also ```x``` (lowercase) and ```h``` (lowercase). - ```D```: **Day name**... "D" as defined in prototype defaults ```days``` string (```"MonTueWed..."``` or ```"Monday___Tuesday__Wednesday..."``` with fix length of ```dayLen``` (```3``` or ```9```, respectively), the length of longest week day name. String is extracted with ***days.substr(day * dayLen, dayLen)***. "D..D" will take a substring of the length equal to the number of "D"s. (Note that the underscores stand for blanks.) - ```Z```: **Time zone**... "Z" as defined in prototype defaults ```zone``` string. "Z..Z" will take a substring of the length equal to the number of "Z"s. - ```any other character```... **as is**, for example: ```"in yyyy"``` for "in 1999" - ```^```: **escape character**... "^" as defined in prototype defaults ```esc``` string (```"^"```). The escape character enables the use of date and time pattern characters **as is**; for example: ```"T^he ti^me i^s now: h:mm X"``` for "The time is now: 3:45 PM". Formatting pattern examples: 1. ```M/d/y``` ---> **2/29/1956 or 2/29/56**, depending on prototype defaults ```year: "yyyyy"``` or ```"yy"``` 2. ```yyyy/MM/dd``` ---> **1956/02/29**, date as sortable string 3. ```D, N d, y``` ---> **Wednesday, February 29, 1956** (with prototype defaults ```days: "Sunday___Monday___Tuesday__Wednesday...", dayLen: 9``` and equally for ```mons: "January__Feburary_...", monLen: 9```). 4. ```DDD, NNN d, y``` ---> **Wed, Feb 29, 1956** (with prototype defaults ```days: "Sunday___Monday___Tuesday__Wednesday...", dayLen: 9```; or ```"SunMonTueWed..."``` and ```3```; and analog for ```mons``` and ```monLen```). 5. ```DDD, NNN d, y``` ---> **Wed, Feb 29, 1956** (with prototype defaults ```days: "SunMonTueWed...", dayLen: 3``` and ```mons: "JanFebMar...", monLen: 3```). 6. ```D, N d, y``` ---> **Wed, Feb 29, 1956** (with prototype defaults ```days: "SunMonTueWed...", dayLen: 3``` and ```mons: "JanFebMar...", monLen: 3```). 7. ```DD, NNNNNN d, yy``` ---> **We, Feb 29, 56** (with prototype defaults ```days: "SunMonTueWed...", dayLen: 3``` and ```mons: "JanFebMar...", monLen: 3```). 8. ```Begin: yyyy-mm-dd_HH:mm:ss.SSS``` ---> **Begin: 1956-02-29_00:00:00.000** 9. ```En^d: yyyy-mm-dd_HH:mm:ss.SSS``` ---> **End: 1956-02-29_23:59:59.999** ***Usage Notes:*** 1. Consecutive same date and time pattern characters define the length (as reasonable) 2. Characters in the template other than date and time pattern characters are just pushed to the output 3. To use pattern characters as plain template characters, they have to be ***escaped*** with **"^**" as defined in prototype defaults ```esc```. 4. Prototype defaults can be updated with a defaults object that includes the property names and values to update. A mixin / merge into the existing properties allows the selective / partial update. 5. Prototype defaults are shared and a change affects all formatters... with one exception see next item). 6. Common to all usages is pulling the module or placing the code in-line and assigning result to a variable, for example ```var DF = ....;"``` (DateFormatter class, hence uppercase). ```var DF = require("DateFormatter");```` when code placed as module in local sandbox modules folder. 7. The usual or regular form of usage is creating a formatter with optionally passing the template / pattern: ```var df = new DF();```. If no template or pattern is passed, the prototype default is taken and stored in the formatter instance. Therefore, a once created formatter holds the template / pattern that was actual at the time of formatter creation. To use the formatter, its methods ```df.format()```, ```df.getDefaults()``` and ```df.updateDefaults()``` are invoked. ```.format()``` accepts ***none***, ***one*** and ***two parameter*** parameters: *first is date*, and *second is pattern*. For *missing* or falsy (**undefined*, *null*, *0*, "", false) parameters the respective default is taken: *now* for *date*, and *prototpe.defaults.pattern* for *pattern*. To show, for example, now in ***non-default*** pattern, invocation is with ***null and non-default pattern***: ```.format(null,"yyyy-MM-dd")``` shows something like 2016-05-07. 8. A shorter form of usage is invocation of formatter class (function name) methods ```DF.format()```, ```DF.getDefaults()``` and ```DF.updateDefaults()```. Parameter handling is the same as for regular usage. 9. The shortest - bit messy - version is the use of the constructor function (class definition function) ```DF(...)```. This version requires always two parameters - both ***date*** and ***pattern*** - to be supplied, of which one or both can be specified as "**false**" (undefined, null, 0, "", false) to force use of respective default. For example - shortest form: ```DF(0,0)``` - returns now in the format as defined in the prototype defaults property "**pattern**". Common to below usage examples is this snippet:
// assuming now/today is: Wed, May 17, 2016, 1:45:59 PM, 704 milliseconds
// assuming module's defaults = {pattern:"M/d/y",year:"yyyy",zone:"ESP"}
function log(v) { console.log(v); }
var myDate = new Date(1955,1,29,13,45,59,123); // 1955-02-29_13:45:59.123
var DF = require("DateFormatter"); // hold on to module as class and function***Examples related to Usage Note 7. (with snippet a runnable sequence):***
var DF = require("DateFormatter"); // hold on to module as class (and function)
var df = new DF(); // create instance using class' default formatting pattern
log(df.pattern); // M/d/y - logs instance's default formatting pattern
log(df.format()); // 5/17/2016 - logs now in instance's default pattern
log(df.format(myDate)); // 2/29/1955 - logs myDate in instance's default pattern
log(df.format(null,"d.M.y")); // 17.5.2016 - logs now in specified format
log(df.format(myDate,"d.M.y")); // 29.2.1955 - logs myDate in specified pattern
df.pattern = "d.M.y"; // change instance's formatting pattern
log(df.pattern); // d.M.y - logs instance's default formatting pattern
df.updateDefaults({pattern:"yyyy-MM-dd", year:"yy", Z:"PSD"}); // update shared defaults, but not the default formatting pattern of existing instances.
var df2 = new DF(); // create 2nd df picks up new default formatting pattern
log(df.pattern); // d.M.y - instance's default pattern - still the 'old' one
log(df.format()); // 17.5.16 - now in instance's still 'old' default, but...
log(df.format(myDate)); // 29.2.55 - ...in inst.'s default but y from new defaults
log(df.format(null,"d.M.yyyy")); // 17.5.2016 - logs now in specified format
log(df.format(myDate,"d.M.y")); // 29.2.55 - myDate as spec'd, y as new defaults
log(df.format(null,"d.M.yyyy")); // 17.5.2016 - logs now in specified format
log(df.format(myDate,"dd.MM.yyyy")); // 29.02.1955 - myDate in spec'd pattern
log(df2.format()); // 2016-05-17 - logs now in instance's default pattern
log(df2.format(y-m-d)); // 16-5-17 - now in inst.'s default but y from defaults
log(df2.format(null,"M/d/yyyy")); // 5/17/2016 - logs now in specified format***Examples related to Usage Note 8. (with snippet a runnable sequence):***
var DF = require("DateFormatter"); // hold on to module as class (and function)
log(DF.getDefaults().pattern); // M/d/y - logs default formatting pattern
log(DF.format()); // 5/17/2016 - logs now in shared default pattern
log(DF.format(myDate)); // 2/29/1955 - logs myDate in shared default pattern
log(DF.format(null,"d.M.y")); // 17.5.2016 - logs now in specified format
log(DF.format(myDate,"d.M.y")); // 29.2.1955 - logs myDate in specified pattern
DF.getDefaults().pattern = "d.M.y"; // change shared default pattern, BUT IT IS NOT the recommended way
log(DF.getDefaults().pattern); // d.M.y - logs shared default formatting pattern
DF.updateDefaults({pattern:"yyyy-MM-dd", year:"yy", Z:"PSD"}); // update shared defaults the RECOMMENDED way
log(DF.getDefaults().pattern); // yyyy-MM-dd - shared default pattern
log(DF.format()); // 2016-05-17 - logs now in shared default pattern
log(DF.format(myDate)); // 1955-02-29 - myDate in shared default pattern
log(DF.format(null,"d.M.yyyy")); // 17.5.2016 - logs now in specified pattern
log(DF.format(myDate,"d.M.y")); // 29.2.55 - myDate in spec'd, y from new defaults
log(DF.format(null,"d.M.yyyy")); // 17.5.2016 - logs now in specified pattern
log(DF.format(myDate,"dd.MM.yyyy")); // 29.02.1955 - logs myDate in spec'd pattern***Examples related to Usage Note 9. (with snippet a runnable sequence):***
var fD = require("DateFormatter"); // hold on to module as formatDate() function
log(fD.getDefaults().pattern); // M/d/y - logs default formatting pattern
log(fD(0,0)); // 5/17/2016 - logs now in default pattern
log(fD(myDate,null)); // 2/29/1955 - logs myDate in shared default pattern
log(fD(null,"d.M.y")); // 17.5.2016 - logs now in specified format
log(fD(myDate,"d.M.y")); // 29.2.1955 - logs myDate in specified pattern
fD.getDefaults().pattern = "d.M.y"; // change shared default pattern, BUT IT IS NOT the recommended way
log(fD.getDefaults().pattern); // d.M.y - logs shared default formatting pattern
fD.updateDefaults({pattern:"yyyy-MM-dd", year:"yy", Z:"PSD"}); // update shared defaults the RECOMMENDED way
log(fD.getDefaults().pattern); // yyyy-MM-dd - shared default pattern
log(fD(false,undefined)); // 2016-05-17 - logs now in shared default pattern
log(fD(myDate,0)); // 1955-02-29 - myDate in shared default pattern
log(fD(null,"d.M.yyyy")); // 17.5.2016 - logs now in specified pattern
log(fD(myDate,"d.M.y")); // 29.2.55 - myDate in spec'd, y from new defaults
log(fD(null,"d.M.yyyy")); // 17.5.2016 - logs now in specified pattern
log(fD(myDate,"dd.MM.yyyy")); // 29.02.1955 - logs myDate in spec'd pattern
```At a later time, I may put this up as a module. Until then, it still needs some hardening.
Closing comments in next post...
- |
-
• #5
Some closing comments:
Parameters are unfortunately positional... the first for
.format()
method - instance or class - is always the date (or falsy for the default, which is now), and the second is the formatting pattern (or falsy for the default, which is the default in the module or the formatting pattern passed on construction). falsy values - values that evaluate to false (see MDN Glossary) - will 'provoke' the default. Therefore, for the last example:xxy.format(0,'MM/dd/yy');
is the shortest form with an instance.Adding... some more examples based on the description of items 7. through 9. of Notes of previous post.
Since I know that not all users are inclined to use objects (constructed from classes), I provided also the class (or static) method usage, and a plain function usage.
For the class (or static) method usage, you assign the module to a variable:
var DF = require("DateFormatter");
and do formatting withDF.format();
, just the same way as with objects. With the class (or static) method usage you can have only one default format: the class default, which of course, you can change withDF.updateDefaults({pattern:"yyyy-MM-dd"});
(the formatting pattern is just an example).For the functional usage, you assign the module to a variable:
var fd = require("DateFormatter");
and do formatting withfd(parm1,parm2);
. Note that it isf
ormatd
ate()
versus DF for DateFormatter class and df for instance of DateFormatter class. With the functional usage, you always have to provide two (2) parameters, or in other words, always two (2) parameters have to be present. The javascript functions knows based on the number of parms whether to behave as plain function or as constructor. parm1 one is either a date or falsy, and parm2 is a formatting pattern or falsy.Btw, if you always provide the format, you need only one instance of formatter. If you always use the same format or have a prevailing format, create a formatter with that format:
var df = new (require("DateFormatter"))("MM/dd/yy");
, and use it then anywhere in the code. -
• #6
@allObjects: wow - great stuff - looks like you coded your own date.js and more ;-)
my intention is to code a small and simple helper to get a human readable date/time
a little change for a shorter the length checker:
//var HH = this.getHours().toString(); //HH = HH.length > 1 ? HH : '0' + HH; var HH = ("0"+this.getHours().toString()).slice(-2);
-
• #7
@MaBe, the whole came out of a personal conversation that had less in mind to get a formatter going but more so to show the separation of concerns: model / data separate from presentation. Therefore, I would not not extend the Date 'class'... last but not least to prevent future conflicts, when one day Date will extend...
The DateFormatter then just became the icing on the cake - a Kürlauf! ...and freebee... ;)
-
• #8
@allObjects - thanks for sharing !
-
• #9
The 'complete' (or almost perfect separation) would go one step further: extract the internationalization (i18n) out of the formatter itself and have it as a separate modules, one for each language... loaded automatically correctly by the locale information by the system or overridable (with a parm in require module and/or constructor. Since the argument in
require(argument)
as a literal ("xxx...") works on build time (upload time), for runtime - when it is a variable - Espruino firmware tries to read from the memory card (as present in original Espruino board). -
• #11
As far as I can tell,
toDateString
shouldn't take any format arguments? https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toDateStringIf there is a date formatting that's part of the JS standard then I'd be interested in implementing it - but memory is too scarce to add non-standard functionality, especially if it can be done easily with JS.
-
• #12
Ok so the solution is clear to me - do it in JS and use the code allObjects share.
-
• #13
@allObjects btw - now reading Cross-cutting_concern to understand what you pointed out:
to show the separation of concerns: model / data separate from presentation.
-
• #14
Subject domains are like silos and in silos separation of concerns is applied.
For example, the date subject has subject specific data (and calculation/behavior) and subject specific formatting. Other subjects are: Numbers, bank accounts, address, etc. Subjects are also composition of other subjects.
So far, we just talked about subject specific separation of concerns.
Cross-cutting concerns are concerns that are across all subjects / across the silos / horizontal - not subject specific - and this is for example logging, security/access control/... etc. Most of the time, these cross-cutting things are services/servers and clients to domain specific clients.
In other words, 'the world' is diced and sliced in two dimensions, and some of the pieces are logically and physically 'shared': The logging has a domain specific component and a cross domains shared generic component: invocation of the logging - what to log (and often also some of the configuration info of the logging)- is a domain specific and the actual writing/appending to the log is generic and common to all domains.
There are programming paradigms to generalize and separate also the specific components to keep the domain code as concise as possible. A good example for that is aspect oriented (ao) programming.
Javascript is especially nice to this idea, because it can be dynamically / at runtime changed... (in java, it is a bit more difficult, but still doable... therefore annotations are used to 'inject' the code at build / compile time). In JavaScript, ao can be applied even when only programming with functions.
For an example, let's assume we have a
add(summand1, summand2){ ... }
function:function add(summand1, summand2) { return summand1 + summand2; }
Now, we want to have (configurable, conditional) logging for before and after - in other words: log the parameters (input) and log the returned result (output).
Easy, p(l)easy! you say... but it may not really be pleasant...
function add(summand1, summand2) { console.log("add(summand1:" + summand1 + ", " + summand2 +"){..."); var result = summand1 + summand2; console.log("add(summand1:" + summand1 + ", " + summand2 +"){...}:" + result); return result; }
Ooooookaaaaay... you got your (1st round of) logging (the configurable/conditional we put off for later... and ...never), but you also change the code not just by adding but also in structure: you had to temp store the result for the logging the way the it was.
function add(summand1, summand2) { return summand1 + summand2; } // function to add before and after logging to add function while keeping add's implementation function addLogging() { var add_f = add; add = function(summand1, summand2) { console.log("add(summand1:" + summand1 + ", " + summand2 +"){..."); var result = add_f(summand1, summand2); console.log("add(summand1:" + summand1 + ", " + summand2 +"){...}: " + result); return result; }; add._logging = add_f; } // remove logging function removeLogging() { add = add._logging; } // using the last result and add() function in a watch on BTN1 // even last (result) turns green LED on and red LED off // odd last (result) turns red LED on and green LED off var last = 0; setWatch(function(){ last = add(last,1); digitalWrite(LED1, last % 2); digitalWrite(LED2, (last + 1) % 2); }, BTN1, {repeat: true, edge: "falling", debounce:20});
Go through these steps after uploading of above code snippet:
Enter
add(1,2)
in console: Console shows=3
.Enter
addLogging();
to add logging to theadd()
function.Enter
add(1,2)
in console again: console shows the logging and=3
:>add(1,2) add(summand1:1, 2){... add(summand1:1, 2){...}: 3 =3 >
Entering
removeLogging();
will remove logging from theadd()
function.Enter
add(1,2)
in console again: console shows just=3
again.
Now, we want to use the button BTN1.
Pressing button BTN1 will alternately lite red and green (Pico and Original) onboard LED. But how do we know if odd result shows red LED on, and even green LED?
We add logging and compare log output in console with lit LED.
Conclusion: This brief exercise shows conceptually how logging can be added without changing the implementation.
NOTE: Real world is much more sophisticated and does not have issues like this example (after upload enter in console
bad = add
, thenaddLogging()
, thenadd(1,2)
, and finallybad(1,2)
. What do you observe and what is the explanation?) -
• #15
Possible applications use for Bangle.js clocks with date.
Like to continue this discussion , because there will be requests for something like
Looking at the MDN docs there's a standard for Intl (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat) that I reckon could be implemented as a JS library reasonably easily?
named in https://github.com/espruino/Espruino/issues/1703
What about extend "DateFormatter" to pass names for your local days and month so it’s not blowing up the module.
eg days and mons as long and/or short format
days: "SundayMondayTuesdayWednesday...", dayLen: 9 and equally for mons: "JanuaryFeburary_...", monLen: 9)
To make a clock app very flexible, same code for all locales.
it could be injected via app customized page eg Route ViewerWhat do you think about this idea?
-
• #16
As much as I (still) enjoy reading thru the solution, I feel it is "too fat" for casually formatting a date (and time) on BangleJS... BUT: If an application has the subject of date(s) and time(s), then it is most likely already the smallest 'module' for it's comprehensiveness, flexibility and efficiency (w/ @MaBe 's shorter length checker/stretcher).
Here is another - cheap - Date Formatter:
// DF: BangleJS Date Formatter let DF = // cheap Date Formatter { l: null // last used; for reuse pass -1 for d as date , f: function(d,p) { // format(optDateOrMillis,optPattern) - 02/29/2020 var d=this.i(d),s=this.s(p=p||this.p),b; return (p.split(s).map(v=>( ((b=((b=v.length)<2)?0:b)?"000":"") + this.x[v.charAt(0)](d) ).substr(-b)).join(s) ); } , d: function(d,ds) { return this.t(d,"n",ds||this.ds); } // d name , m: function(d,ms) { return this.t(d,"m",ms||this.ms); } // m name , c: function(d) { return [DF.d(d),DF.m(-1),DF.f(-1,"d y")].join(" "); } , t: function(d,x,ts){ return ts.split(",")[this.x[x](this.i(d))]; } , ds: "Sun,Mon,Tue,Wed,Thu,Fri,Sat" // Monday,Tuesday ..." , ms: ",Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec" // January,... , p: "mm/dd/yy" // pattern , x: {m:d=>d.getMonth()+1,d:d=>d.getDate(),y:d=>d.getFullYear() ,n:d=>d.getDay()} , s: function(p) { var s,i=-1,l=p.length; while(++i<l) { // 1st sep if ("mdy".indexOf(s=p.charAt(i))<0) return s; } return"/"; } , i: function(d) { // ret Date as is, today, from millis or last used return this.l = (isNaN(d)||d=="") ? d||new Date() : (d<0) ? this.l : new Date(d); } };
Usage:
02/15/20 - DF.f() today 2/15 - DF.f(-1,"m/d") from used 15.2. - DF.f("","m.d")+"." today 2020-02-15 - DF.f(null,"yyyy-mm-dd") today Sat Feb 15, 2020 - DF.d(),DF.m(-1),DF.f(-1,"d")+",",DF.f(-1,"y") Sat Feb 15 2020 - DF.d(),DF.m(-1),DF.f(-1,"d y") Sat Feb 15 2020 - DF.c() - today, combined, like Date.toString() Sat Feb 15 2020 - DF.l.toString().substr(0,15) - today from used Sat Feb 15 2020 08:49:34 GMT-0800 (Pacific Standard Time) - DF.i(-1) - today from last used Sat Feb 15 2020 08:49:34 GMT-0800 (Pacific Standard Time) - DF.l - today from used
Attached .html file can be directly executed to run formatter and example in browser. It can also be downloaded, modified, and played with.
The formatter can be modularized and then composed with a base module and extended with - for example - the week day and month names and combined formatting functions - and last but not lest - with the time formatting functions.
The base module with just the number formatter is very compact and looks then like this:
let DF = // cheap Date Formatter { l: null // last used; for reuse pass -1 for d as date , f: function(d,p) { // format(optDateOrMillis,optPattern) - 02/29/2020 var d=this.i(d),s=this.s(p=p||this.p),b; return (p.split(s).map(v=>( ((b=((b=v.length)<2)?0:b)?"000":"") + this.x[v.charAt(0)](d) ).substr(-b)).join(s) ); } , p: "mm/dd/yy" // pattern , x: {m:d=>d.getMonth()+1,d:d=>d.getDate(),y:d=>d.getFullYear() } , s: function(p) { var s,i=-1,l=p.length; while(++i<l) { // 1st sep if ("mdy".indexOf(s=p.charAt(i))<0) return s; } return"/"; } , i: function(d) { // ret Date as is, today, from millis or last used return this.l=(isNaN(d)||d=="")?d||new Date():(d<0)?this.l:new Date(d);} };
1 Attachment
-
• #17
Great, thanks for sharing.
I will use this to build a class with using ds and ms from
@locale
.store the county specific locale for day and month, eg
de-DE
var loc = { "ds" : "So,Mo,Di,Mi,Do,Fr,Sa", "ms":"Jan,Feb,Maerz,Apr,Mai,Jun,Jul,Aug,Sept,Okt,Nov,Dez" }; require("Storage").write("@locale",JSON.stringify(loc));
and than use it to init the DF class
class DF { constructor(ds, ms) { this.ds = ds; this.ms = ms; } f() { ......} .... i() {.....} } loc = require("Storage").read("@locale"); df = new DF( loc.ds, loc.ms);
or something similar ;-)
-
• #18
I intentionally stayed away from instance / constructor idea (to save space). I would rather look for a i18n method that puts not just ms and ds but also p in place (month and day names and pattern) and return DF
, i18n: function(l) { // optional locale var p,j=require("Storage").read(l||"@locale"); for(p in j)this[p]=j[p]; return this; }
and
let DF = require("DF").i18n();
I'm though not sure how this becomes part of Bangle.js code... I can see that Bangle.js is somehow a bit different in handling uploaded code... applications... and I could see like modules being shared between multiple apps... so setup looks different.
PS: @MaBe, did you intentionally use Maerz vs März? Do we have a 7/8 bit issue w/ Espruino? ...I guess we have, since font(s) (intended to be used) would need i18n as well (most fonts cover only ASCII 32..126 (decimal)). In other words: half-baked i18n. :/<
-
• #20
https://lh.2xlibre.net/locale/de_DE/
LC_TIME entries can be used and find via de_DE or any other language
Edit: We can use the glibc export as base to create a subset.
-
• #21
Using glibc export for an example how locale could look
So this peace of code is not a app but a locale saver
- lives in Bangle.js App config page
- User select his locale from a pull down
- only store selected local in @locale
- app can use it via reading @locale
What do you think?
var locales = { "en_US" : { mon : "January,February,March,April,May,June,July,August,September,October,November,December", abmon : "Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec", day : "Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday", abday : "Sun,Mon,Tue,Wed,Thu,Fri,Sat" }, "de_DE" : { mon : "Januar,Februar,März,April,Mai,Juni,Juli,August,September,Oktober,November,Dezember", abmon : "Jan,Feb,Mär,Apr,Mai,Jun,Jul,Aug,Sep,Okt,Nov,Dez", day : "Sonntag,Montag,Dienstag,Mittwoch,Donnerstag,Freitag,Samstag", abday : "So,Mo,Di,Mi,Do,Fr,Sa" }, "fr_FR": { mon : "janvier,février,mars,avril,mai,juin,juillet,août,septembre,octobre,novembre,décembre", abmon : "anv.,févr.,mars,avril,mai,juin,juil.,août,sept.,oct.,nov.,déc.", day : "dimanche,lundi,mardi,mercredi,jeudi,vendredi,samedi", abday : "dim.,lun.,mar.,mer.,jeu.,ven.sam." }, "it_IT": { mon : "gennaio,febbraio,marzo,aprile,maggio,giugno,luglio,agosto,settembre,ottobre,novembre,dicembre", abmon : "gen,feb,mar,apr,mag,giu,lug,ago,set,ott,nov,dic", day : "domenica,lunedì,martedì,mercoledì,giovedì,venerdì, sabato", abday : "dom,lun,mar,mer,gio,ven,sab" } }; // save my relevant locale setting for for day, abday, mon, abmon var locale = "en_US"; require("Storage").write("@locale",JSON.stringify(locales[locale])); // read my locale setting for day, abday, mon, abmon myLocale = require("Storage").readJSON("@locale"); // console.log(myLocale.day.split(',')); console.log(myLocale.abday.split(',')); console.log(myLocale.mon.split(',')); console.log(myLocale.abmon.split(',')); // output >[ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ] [ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" ] [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ] [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ] >
- lives in Bangle.js App config page
-
• #22
Tue 2020.02.18
'What do you think?'
While I have only been taking sneak peaks at this Bangle.js development, I must say @MaBe that the concept and presentation are succinct, tidy and easy to understand. Nicely Done. Kudos!
As I understand this then, for uncommon Locales, would a developer then use the Local Helper Library (link in #20 post) to create their own
var locales
assignment, thus reducing the code bloat and avoidance of unnecessary memory usage then? or, is the intent to make this part of the core, and somehow override the set of Locales?EDIT: Wed 2020.03.04
Answered in post #34Following the usage of that now defined Locale, that it's form would then be enhanced by the use of @allObjects Date Formatter code in post #2 or post #16?
-
• #23
This looks really promising. Definitely something that could be pulled into Bangle.js.
I guess one question is we have similar issues with time (12/24 hr), distance, speed, and maybe even numbers (
,/.
for separators). Perhaps we should have a 'Locale' library that handles all of these in one go. It might make sense (at least initially) to have it as a JS module/app to allow quick iteration.My suggestion would be:
- Add something like
Bangle.getLocaleString(value, inputUnits)
- egBangle.getLocaleString(new Date(),'time')
which executed the JS in a file calledlocale
if it existed, or just output a 'standard' format. - Add a new 'Locale' app which had a settings app which contained all @MaBe's strings, as well as the magic
locale
JS file that did all the magic
A second option is to still have the app and
locale
file, but to do something like this for each app that uses it:var locale = {} try {locale = require("locale");} catch(e){} if (!locale.getTime) locale.getTime = d=>d.toISOString().match(/T(.....)/)[1]; // ... console.log(locale.getTime(new Date()));
That'd then be compatible with all devices including the emulator (which doesn't support apps yet)
- Add something like
-
• #24
glad you like it :)
Yes it would be nice to have some more locale entries like this, or even more.
... decimal_point : ",", thousands_sep : ".", currency_symbol : "€", int_curr_symbol : "EUR", yesstr : "ja", nostr : "nein", am_pm : "" ....
would something like this make sense to be a part of Espruinos Date() class ?