snippetpythonMinor
Translate datetime format from one standard to another
Viewed 0 times
formattranslatestandardoneanotherfromdatetime
Problem
In my python code, date formats are stored as "strftime and strptime syntax". For instance
On the client side, I use pickadate.js which format is rather
So I have to translate
I first thought
Then I thought about
So, to make it more elegant I used
But I think it still screams inefficiency. The string is browsed once for each sequence to replace if found.
Is there a way make it more efficient (ie. make the translation in only one iteration)?
Note: This question is mainly for the sake of curiosity and learning. This is probably one of the least important thing I have to care about on my project, especially concerning performance.
Obviously in this case, making it more efficient would be ridiculously imperceptible. If this helps you find an interest in this question, imagine another use case where I'd have a string of the size of an entire b
%d/%m/%Y %H:%M.On the client side, I use pickadate.js which format is rather
dd/mm/yyyy HH:i.So I have to translate
%d/%m/%Y %H:%M to dd/mm/yyyy HH:i before sending a datetime format to the client.I first thought
str.translate was the right tool for this. But actually, this is only for characters translation.Then I thought about
str.replace but it looks awful and inefficient to loop over str.replace:def format_to_pickadate_format(fmt):
trans_table = [
('%d', 'dd'),
('%a', 'ddd'),
('%A', 'dddd'),
('%m', 'mm'),
('%b', 'mmm'),
('%B', 'mmmm'),
('%y', 'yy'),
('%Y', 'yyyy'),
('%H', 'HH'),
('%M', 'i'),
('%p', 'A'),
]
for trans in trans_table:
fmt = fmt.replace(*trans)
return fmtSo, to make it more elegant I used
functools.reduce:from functools import reduce
def format_to_pickadate_format(fmt):
trans_table = [
('%d', 'dd'),
('%a', 'ddd'),
('%A', 'dddd'),
('%m', 'mm'),
('%b', 'mmm'),
('%B', 'mmmm'),
('%y', 'yy'),
('%Y', 'yyyy'),
('%H', 'HH'),
('%M', 'i'),
('%p', 'A'),
]
return reduce(lambda x, y: x.replace(*y), trans_table, fmt)But I think it still screams inefficiency. The string is browsed once for each sequence to replace if found.
Is there a way make it more efficient (ie. make the translation in only one iteration)?
Note: This question is mainly for the sake of curiosity and learning. This is probably one of the least important thing I have to care about on my project, especially concerning performance.
Obviously in this case, making it more efficient would be ridiculously imperceptible. If this helps you find an interest in this question, imagine another use case where I'd have a string of the size of an entire b
Solution
The
According to the pickadate.js documentation, you should escape any “rule” characters with an exclamation mark (
Doing string substitutions in multiple passes is, in general, a bad idea. Not only is it more work to traverse the string several times, there is also the possibility that operating on the output leads to incorrect results. Fortunately, you seem to be safe here, since the pickadate.js directives don't start with
…
If
Using
strftime function is relatively well known, even if the formatting directives are not completely standardized across platforms. On the other hand, pickadate.js is a relatively unknown library. It would therefore be helpful to cite its specification, preferably in a docstring. The use of doctests would also be a good idea here.According to the pickadate.js documentation, you should escape any “rule” characters with an exclamation mark (
!).Doing string substitutions in multiple passes is, in general, a bad idea. Not only is it more work to traverse the string several times, there is also the possibility that operating on the output leads to incorrect results. Fortunately, you seem to be safe here, since the pickadate.js directives don't start with
% like the strftime directives. Still, a one-pass solution is preferable, and Python gives you a way to do that with re.sub():re.sub(pattern, repl, string, count=0, flags=0)…
If
repl is a function, it is called for every non-overlapping occurrence of pattern. The function takes a single match object argument, and returns the replacement string.Using
re.sub(), and handling special cases where strings that appear to be strftime and pickadate.js directives should actually be treated literally…import re
def format_to_pickadate_format(fmt):
"""
Convert a strftime format string to a pickadate.js template.
http://amsul.ca/pickadate.js/date/#formats
http://amsul.ca/pickadate.js/time/#formats
>>> format_to_pickadate_format('%d/%m/%Y %H:%M')
'dd/mm/yyyy HH:i'
>>> format_to_pickadate_format('%Y %% d')
'yyyy % !d'
"""
TRANS = {
'%d': 'dd',
'%a': 'ddd',
'%A': 'dddd',
'%m': 'mm',
'%b': 'mmm',
'%B': 'mmmm',
'%y': 'yy',
'%Y': 'yyyy',
'%H': 'HH',
'%M': 'i',
'%p': 'A',
}
return re.sub(
r'%(.)|([^dmyhHiaA])|(.)',
lambda m: TRANS.get(m.group(), m.group(1)) or m.group(2) or '!' + m.group(3),
fmt
)Code Snippets
import re
def format_to_pickadate_format(fmt):
"""
Convert a strftime format string to a pickadate.js template.
http://amsul.ca/pickadate.js/date/#formats
http://amsul.ca/pickadate.js/time/#formats
>>> format_to_pickadate_format('%d/%m/%Y %H:%M')
'dd/mm/yyyy HH:i'
>>> format_to_pickadate_format('%Y %% d')
'yyyy % !d'
"""
TRANS = {
'%d': 'dd',
'%a': 'ddd',
'%A': 'dddd',
'%m': 'mm',
'%b': 'mmm',
'%B': 'mmmm',
'%y': 'yy',
'%Y': 'yyyy',
'%H': 'HH',
'%M': 'i',
'%p': 'A',
}
return re.sub(
r'%(.)|([^dmyhHiaA])|(.)',
lambda m: TRANS.get(m.group(), m.group(1)) or m.group(2) or '!' + m.group(3),
fmt
)Context
StackExchange Code Review Q#134800, answer score: 3
Revisions (0)
No revisions yet.