Reputation: 88378
I have three integer values, say
h := 3.
m := 19.
s := 8.
I would like to produce the string '03:19:08'
. I know how to turn a number into a string, and even pad it with a zero if necessary. So as a first pass I wrote this absolutely horrific code:
h < 10 ifTrue: [hs := '0', (h asString)] ifFalse: [hs := h asString].
m < 10 ifTrue: [ms := '0', (m asString)] ifFalse: [ms := m asString].
s < 10 ifTrue: [ss := '0', (s asString)] ifFalse: [ss := s asString].
Transcript show: hs, ':', ms, ':', ss.
Transcript nl.
Now obviously I need to clean this up and so was wondering, among other things what the most idiomatic Smalltalk approach would be here. Could it be something like (not legal Smalltalk obviously):
aCollectionWithHMS each [c | padWithZero] join ':'
I found a discussion on streams with a print method taking a separatedBy
argument but wouldn't there be a simpler way to do things just with strings?
Or perhaps there is a more elegant way to pad the three components and then I could just return hs, ':', ms, ':', ss
?
Or, is there an interface to POSIX time formatting (or something similar) common to all Smalltalks? I know GNU Smalltalk can link to C but this is way too much overkill for this simple problem IMHO.
EDIT
I got a little closer:
z := {h . m . s} collect: [:c | c < 10 ifTrue: ['0', c asString] ifFalse: [c asString]].
(Transcript show: ((z at: 1), ':', (z at: 2), ':', (z at: 3))) nl.
But the direct access of collection elements makes me sad. I found a page documenting the joining method asStringWith
but that method is unsupported, it seems in GNU Smalltalk.
Upvotes: 1
Views: 191
Reputation: 14858
Here is a way to do this in Pharo:
String streamContents: [:stream |
{h.m.s}
do: [:token | token printOn: stream base: 10 nDigits: 2]
separatedBy: [stream nextPut: $:]]
Explanation:
The streamContents:
message answers with the contents of the WriteStream
represented by the formal block argument stream
.
The do:separatedBy:
message enumerates the tokens h
, m
and s
evaluating the do:
block for each of them and inserting the evaluation of the second block between consecutive tokens.
The printOn:base:nDigits:
message dumps on the stream
the base 10
representation of the token
padded to 2
digits.
If the dialect you are using doesn't have the printOn:base:nDigits:
method (or any appropriate variation of it), you can do the following:
String streamContents: [:stream |
{h.m.s}
do: [:token |
token < 10 ifTrue: [stream nextPut: $0].
stream nextPutAll: token asString]
separatedBy: [stream nextPut: $:]]
Finally, if you think you will be using this a lot, I would recommend adding the message hhmmss
to Time
(instance side), implemented as above with self hours
instead of h
, etc. Then it would be a matter of sending
(Time hour: h minute: m second: s) hhmmss
assuming you have these three quantities instead of a Time
object, which would be unusual. Otherwise, you would only need something like
aTime hhmmss
ADDENDUM
Here is another way that will work on any dialect:
{h.m.s}
inject: ''
into: [:r :t | | pad colon |
pad := t < 10 ifTrue: ['0'] ifFalse: [''].
colon := r isEmpty ifTrue: [''] ifFalse: [':'].
r , colon, pad, t asString]
The inject:into:
method builds its result from the inject:
argument (the empty String
in this case) and keeps replacing the formal block argument r
with the value of the previous iteration. The second formal argument t
is replaced with the corresponding element of each iteration.
ADDENDUM 2
time := '00:00:00' copy.
{h asString. m asString. s asString} withIndexDo: [:t :i |
time at: i - 1 * 3 + 2 put: t last.
t size = 2 ifTrue: [time at: i - 1 * 3 + 1 put: t first]].
^time
The copy
is necessary to make sure that the literal is not modified.
Upvotes: 3