I love strftime. It has been around for such a long time that it is almost second nature to me. And yet, Dart does not have strftime, which makes me sad.
Even when first reported, the Dart maintainers indicated that they favored an internationalization approach to date formatting. I have remained skeptical ever since. Today, I am going to give it a try. I still think it likely that I will end up writing my own strftime package in Dart, but let's see first.
That said, I have to admit that the default date/time format in Dart is very much to my liking. The only date/time format that I like is ISO 8601, which is just what Dart does. In my HTTP logging code, I have been directly interpolating a
DateTime
object (now
) into a string:log(req) { req.response.done.then((res){ var now = new DateTime.now(); print('[${now}] "${req.method} ${req.uri.path}" ${logStatusCode(res)}'); }); }The result is a very sensible iso8601 string with millisecond precision:
[2013-09-02 13:32:04.825] "DELETE /widgets/958c9320-f0d0-11e2-c5d0-df3be6475337" 204 [2013-09-02 13:32:04.829] "GET /widgets/958c9320-f0d0-11e2-c5d0-df3be6475337" 404I am not used to seeing that level of precision in HTTP logs, but I rather like it. Still, what if I wanted to remove it and add the numeric timezone (e.g. -0500) to make the timestamp more Apache-like?
I start by adding the
intl
packages to the list of dependencies in my project:name: plummbur_kruk description: A real pretend server for testing. #... dependencies: # ... intl: anyThen I Pub install my newest dependency (along with its dependencies):
➜ plummbur-kruk git:(master) ✗ pub install Resolving dependencies............................ Downloading logging 0.6.19 from hosted... Downloading unmodifiable_collection 0.6.19 from hosted... Downloading intl 0.6.19 from hosted... Downloading analyzer_experimental 0.6.19 from hosted... Downloading args 0.6.19 from hosted... Dependencies installed!Finally, I import the
intl
package into my code:library plummbur_kruk; import 'dart:io'; import 'package:json/json.dart' as JSON; import 'package:intl/intl.dart'; // ...Now, to add the timezone to the iso8601 date, I ought to be able to use the default format with the time zone appended. The
add_jz()
method ought to do the trick:// ... final DateFormat _timestamp = new DateFormat().add_jz(); String get timestamp { return _timestamp.format(new DateTime.now()); } // ...Only that blows up pretty badly:
Uncaught Error: UnimplementedError Stack Trace: #0 _DateFormatPatternField.formatTimeZone (package:intl/src/date_format_field.dart:385:5) #1 _DateFormatPatternField.formatField (package:intl/src/date_format_field.dart:174:38) #2 _DateFormatPatternField.format (package:intl/src/date_format_field.dart:112:25) #3 DateFormat.format.<anonymous closure> (package:intl/date_format.dart:231:63) #4 List.forEach (dart:core-patch/growable_array.dart:182:8) #5 DateFormat.format (package:intl/date_format.dart:231:26) #6 timestamp (package:plummbur_kruk/server.dart:226:27) #7 log.<anonymous closure> (package:plummbur_kruk/server.dart:219:15) ...After a bit of digging, I find that I have a pretty darn good knack for finding the one thing in libraries that is not quite done:
// packages/intl/src/date_format_field.dart // ... String formatTimeZoneId(DateTime date) { // TODO: implement time zone support throw new UnimplementedError(); } // ...Darn it.
Well, I am not going to get exactly what I want from the
DateFormat
class, but I would still like to get a feel for how it works. As I said, I rather prefer the ISO 8601 format for date formatting, but if I wanted more of a standard Apache log along the lines of:[02/Sep/2013:09:28:08 -0400]How easy is it to make?
Actually, it is quite easy:
final DateFormat _timestamp = new DateFormat('dd/MMM/y HH:mm:ss');This produces:
Now: 02/Sep/2013 22:22:30Aside from Apache's ridiculous colon-before-the-time thing, this is exactly what I want. But there is no way that I am going to remember those “skeleton” formats.
The thing that strftime always had going for it (and even the
date
command) was that the hours, minutes, and seconds are all capital letters:$ date +%H:%M:%S 22:28:15Anything beyond that and I can pretty much infer given that times are uppercase:
date +'%Y-%m-%d %H:%M:%S' 2013-09-02 22:29:50The mixing and matching of case in Dart's
DateFormat
feels awkward in comparison. The documentation makes mention of standards (ICU/JDK), so hopefully it will be comfortable for others. I can live with it as is.Before calling it a night, I give the internationalization a whirl. The way this works in Dart is that a top-level
initializeDateFormat()
call is made. The return value is a Future, which completes when the specified locale is loaded (from the filesystem or from a webserver), at which point code can run. For a server-side test, I use:import 'package:intl/intl.dart'; import 'package:intl/date_symbol_data_local.dart'; main() { initializeDateFormatting(null, null).then((_) { // timestamp here... }); }The arguments to
initializeDateFormatting()
are a little different depending on which file is imported. The one that I choose, date_symbol_data_local
actually ignores both arguments—hence the two null
s. For other imports, the first argument indicates the locale to be loaded, which cuts down on bandwidth. With the import that I have chosen, I just load all of the locales. To use a locale, I supply it as the second argument to a
DateFormat
constructor:main() { initializeDateFormatting(null, null).then((_) { print('Now: ${timestamp}'); }); } final DateFormat _timestamp = new DateFormat('dd/MMM/y HH:mm:ss', 'fr_FR'); String get timestamp { return _timestamp.format(new DateTime.now()); }The result is:
$ dart date_format_test.dart Now: 02/sept./2013 23:48:50Which is (I guess) the short month version of September in French.
I am still unsure about using this in an actual application. Does the entire application have to wait until the locale data is loaded? If not that, then does each individual field get wrapped in that Future? Questions for another day. For now, it is good to know that internationalization is baked into the language from the outset.
Day #862
No comments:
Post a Comment