5
\$\begingroup\$

In my python code, date formats are stored as "strftime and strptime syntax". For instance %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 fmt

So, 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 book, and a translation table of 10,000 or 100,000 entries. Doesn't it look dumb to read the book 10,000 or 100,000 times to make the translation?

\$\endgroup\$
5
  • 1
    \$\begingroup\$ What is the purpose of this? Why do you need to transfer date formats between the client and the server? Can't you just choose one format and stick to it, even if it is represented differently on each side? \$\endgroup\$ – 301_Moved_Permanently Jul 14 '16 at 8:12
  • \$\begingroup\$ The main reason is that the format is different according to the locale of the client. So the serveur and the client must be synced on which format to use. If you have a more intelligent way to do it, I open to it. But I am still interested in the main question. :P \$\endgroup\$ – Antoine Pinsard Jul 14 '16 at 8:18
  • \$\begingroup\$ pickadate.js provides au way to use a field for displaying the date and another hidden field to submit the data. I originally tried to use this feature, but it was troublesome to provide initial data / repopulate the form with latest submitted data. \$\endgroup\$ – Antoine Pinsard Jul 14 '16 at 8:23
  • \$\begingroup\$ In such cases, it is often easier to use a common intermediate representation, such as timestamps. Is it possible to convert the date to timestamp in pickadate.js before sending? \$\endgroup\$ – 301_Moved_Permanently Jul 14 '16 at 9:12
  • \$\begingroup\$ Not as far as I know, it would require additional JS to convert the data, which is something I'd like to avoid as much as possible and is one of the reason why I didn't want to use the "hidden submit field" feature. Anyway, we are going a bit out of the scope of this question. My code above works, and I have no issue in keeping it as-is. But I'd like to know if there is a way to make it look more efficient (though given the small size of the string and the translation table, it would be imperceptible). \$\endgroup\$ – Antoine Pinsard Jul 14 '16 at 9:32
3
\$\begingroup\$

The 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
    )
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.