Feed Sign in with OpenID OpenID

Simon Willison’s Weblog

Datejs - A JavaScript Date Library. Building a date API around chaining—Date.today().next().thursday()—is a neat concept. I’d like to see that adapted for Python’s datetime library.

Tagged , , , ,

17 comments

  1. I believe you can do that with dateutil, like datetime.date.today() + dateutil.relativedelta.relativedelta( weekday=dateutil.relativedelta.TH)

    You may want to do some imports to make it easier, of course ;)

    Ian Bicking - 3rd December 2007 22:21 - #

  2. Nearly all of Datejs could be built as syntactic sugar on top of datetime. Throw in dateutil and/or parsedatetime for "human" date parsing and you'd have a pretty sweet library.

    I've always found datetime's API confusing, so doing a Datepy might not be a bad idea. I'm not sure the chaining concept is all that "Pythonic", but it might just work out OK.

    Jacob Kaplan-Moss - 3rd December 2007 22:33 - #

  3. You might want to check out pyfdate at http://www.ferg.org/pyfdate.

    Pyfdate uses chaining. In pyfdate, datetime objects (called Time objects) are, like Python's strings, immutable. Methods that "change" a datetime object return a new datetime object. So, for example, to find the date on which Memorial Day (the last Monday in May) falls this year, you can code:

    Time(2007,MAY,1).gotoMonthEnd().gotoWeekday(MONDAY , PREVIOUS,ignoreToday=False)

    Stephen Ferg - 6th December 2007 06:57 - #

  4. Date.today().next()

    Hold it right there! Surely the result of the above would be "tomorrow". Now, although I can imagine that calling thursday on a date might give you the next date that is a Thursday (or the same date if that refers to a Thursday), what about last Thursday? You'd have to have some kind of "direction" state in the date object.

    With methods named after days of the week and the "looks good when chained" aesthetics, one gets the feeling that this is a pretend DSL for programmers wanting to pretend that they're writing English.

    Paul Boddie - 6th December 2007 17:23 - #

  5. Paul's comment is correct.

    You need a direction: you need to specify whether you want the "next" in the future or in the past. The the PREVIOUS argument in the pyfdate example supplies this information. The alternative to PREVIOUS (in the past) is, as you might expect, NEXT (in the future).

    Also, if you ask for the next (future) Thursday and today is Thursday, do you want the result to be today, or a week from today? In the pyfdate example, the ignoreToday keyword argument supplies this information. If ignoreToday=true, you get a week from today; if ignoreToday=false, you get today.

    Stephen Ferg - 7th December 2007 15:07 - #

  6. Paul is also right about the method names. Naming methods for the English-language days of the week is a bad idea. Surely in this day of globalization we should be past this sort of English-centric thinking.

    Pyfdate solves this problem by supplying weekday and month names as constants (e.g. MONDAY), and it imports a file called pyfdate_local that can be customized for the local language. For example, a French user could put French weekday names in pyfdate_local and then use LUNDI instead of MONDAY.

    Stephen Ferg - 7th December 2007 15:13 - #

  7. @Stephen Ferg - We went back and forth on whether to provide culture-specific function and constant name support in Datejs, but in the end we decided it was a pretty foolish idea and would certainly introduce more problems than convenience.

    With Datejs, adding the culture-specific function names would be trivial. A few weeks ago I forked the project and created an almost 100% runtime dynamically generated culture-specific version of Datejs. All the function and constant names were pulled from the CultureInfo file. It was an interesting experiment, and I thought it would be useful, but in the end I scrapped the idea, mainly because of the reasons outlined below....

    1. With Datejs we're extending the native JavaScript Date object. The JavaScript language (and just about every other computer language I know) is written in English. Is that fair? Do I agree? No, but it is what it is. Breaking from that convention in Datejs would lead to errors and frankly, I don't think developers would want it, whether they're English speakers or not. Why wasn't Ruby written in Japanese?

    2. Documentation problems.

    3. Added bloat. I would argue that you always need to provide a constant function name throughout the API. Adding the culture-specific function names, although trivial to add, would be just for convenience and sugar. If you're doing one (english) or the other (culture-specific), the amount of code is about the same. If you add support for both options, you're replicating code and adding unnecessary bit weight. Maybe its because we're programming in JavaScript but we're more focused on creating the tightest cleanest solution possible. A server-side language API would provide more wiggle room. Although it's still not a good idea.

    3. The Datejs developer chooses to load the appropriate culture-specific date.js file for the *end users benefit*. The culture-specific strings (lundi vs. monday) are focused on providing convenience to the end user. They want to be able to type in 'lundi' into their date picker and expect it to work. As the developer we need consistency and guarantee that our code will work, independent of what the end user expects.

    What happens when that French project gets outsourced to India and now the developers are scrambling through the code to replace all the culture-specific 'lundi's with 'monday' just to try and get the project to compile?

    4. Where do you stop? If some of the API is culture-specific, why shouldn't it all be culture-specific? As a Russian speaking programmer, should I be able to do the following?

    Date.сегодня().добавить(5).часов(); //add 5 hours?

    Which as an API builder is a huge problem, because for all I know in Russian the logical order of the phrase may be spoken reversed from English or mean something completely different.

    Date.сегодня().часов().добавить(5); //add 5 hours?

    Which one of the above is more correct?

    And why does 'Date' have to be in "English"? Shouldn't I be able to do the following?

    Дата.сегодня().добавить(5).часов(); // add 5 hours?

    How about Chinese???...

    日期.今天().添加(5)小时(); // add 5 hours???

    5. API collisions. If you're dynamically creating the function names based on some sort of translated culture file, your odds of introducing naming collisions is high(er). The translated function name may be the same as another english function name in the API/library. I would rather avoid those problems.

    Actually, in Datejs pulling the function and constant names from one of the libraries 150+ supported CultureInfo language files would reduce the size of the library and provide cleaner code, but that still doesn't make it a good idea.

    Geoffrey McGill (Datejs) - 13th December 2007 03:54 - #

  8. @Stephen Ferg - Your MemorialDay/Pyfdate Use Case can be implemented in Datejs as follows...

    // Pyfdate
    Time(2007,MAY,1).gotoMonthEnd().gotoWeekday(MONDAY , PREVIOUS,ignoreToday=False)

    // Datejs - Memorial Day of this year.
    Date.may().final().monday();

    // If you want the next Memorial Day
    Date.next().may().final().monday();

    // If you want the previous Memorial Day
    Date.last().may().final().monday();

    In your Pyfdate code you also have to pass a Year, which then requires even more logic.

    Geoffrey McGill (Datejs) - 13th December 2007 03:56 - #

  9. @Stephen Ferg - I really can't think of a scenario where you would call Date.next().thursday() and expect it to give you back 'today' if today is Thursday. It just doesn't make sense. If today is Thursday and we decide to meet for beers 'next thursday', under what circumstances would you expect that to mean 'today'?

    In Datejs we have an .is() function which might help with this kind of use case.

    Example:

    if (!Date.today().is().thursday()) {
    return Date.today();
    } else {
    return Date.next().thursday();
    }

    // Or, shrinkified as follows...
    return (Date.tod().is().thu()) ? Date.tod() : Date.next().thu();

    Hope this helps.

    Geoffrey McGill (Datejs) - 13th December 2007 03:57 - #

  10. @Geoffrey McGill
    "I really can't think of a scenario where you would call Date.next().thursday() and expect it to give you back 'today' if today is Thursday."

    Here's an example in pyfdate (you may conceptualize this problem differently in Datejs, of course): You want to get the first Thursday in the current year. If January 1 is a Thursday, of course, you want it. In pyfdate you'd do this:

    result = Today(Today().year,1,1).gotoWeekday(THURSDAY, NEXT, useToday=True)

    Step - 16th December 2007 02:38 - #

  11. @Geoffrey McGill

    "In your Pyfdate code you also have to pass a Year, which then requires even more logic."

    Yes, but the logic is trivial. In pyfdate, I wouldn't even call it "logic"; it is just a year specification. For the sake of comparison, here is the code in pyfdate for obtaining Memorial Day for last/this/next year.

    # Memorial Day is the last Monday in May 
    # Memorial Day of this year (the long version)
    Time(Time().year,MAY,1).gotoMonthEnd().gotoWeekday (MONDAY,  PREVIOUS, useToday=True)
    
    # Memorial Day of this year (the short version)
    Time(Time().year,6,1).gotoWeekday(MONDAY, PREVIOUS, useToday=False)
    
    # Memorial Day of last year  
    Time(Time().year-1,6,1).gotoWeekday(MONDAY, PREVIOUS, useToday=False)
    
    # Memorial Day of next year  
    Time(Time().year+1,6,1).gotoWeekday(MONDAY, PREVIOUS, useToday=False)

    Stephen Ferg - 16th December 2007 02:57 - #

  12. @Geoffrey McGill
    """We went back and forth on whether to provide culture-specific function and constant name support in Datejs... 5. If you're dynamically creating the function names based on some sort of translated culture file, your odds of introducing naming collisions is high(er). The translated function name may be the same as another english function name in the API/library."""

    The case of pyfdate is somewhat different than Datejs, because the function names are not culture-specific. Only the names of constants are.

    Where Datejs has function (method?) names like "may" and "monday", in Pyfdate MAY and MONDAY are the names of constants. In the localization file you can, for instance, define two constants for the first day of the week: MONDAY = 1 and LUNDI = 1.That means that you can add new constants without affecting the pyfdate software at all: you don't have to recompile anything.

    Consistency is of course an issue. Datejs's strategy for cross-cultural consistency is to give the Datejs coder the choice of only one language (English) for method names. Similarly, pyfdate allows the pyfdate user who is concerned about possible migration to another to use only English-language constants (or for that matter, to use the numeric values of the constants.) But pyfdate also permits users who are not concerned about possible migration to another language to use local names if they wish to. And I think that there probably are many such users.

    """What happens when that French project gets outsourced to India and now the developers are scrambling through the code to replace all the culture-specific 'lundi's with 'monday' just to try and get the project to compile?"""

    If you want to mix French-language and English-language code in the same file: no problem. You just create a localization file with both French-language and English-language constants. But you do have to make a choice: when the software returns the weekdayName of the first day in the week, you have to specify in the localization file whether you get "Monday" or "lundi": i.e. English-language or French-language weekday names and month names.

    """As a Russian speaking programmer, should I be able to do the following? ... And why does 'Date' have to be in "English"? Shouldn't I be able to do the following? ..."""

    Pyfdate was written in the belief that a Russian-speaking programmer should be able to produce, as output, weekday names and month names in Russian, complete with Cyrillic script. So internationalizing the output was a primary concern. And pyfdate does that well, I think.

    Internationalizing the source code is a different kettle of fish. But with the release of integrated Unicode support in Python version 3, even that should become possible. It will then be possible to define Russian-language aliases for all of the pyfdate class names and method names, and code everything completely in Russian.

    """...in Russian the logical order of the phrase may be spoken reversed from English ..."""

    Pyfdate's syntax does not attempt to mirror English-language syntax the way Datejs's syntax does. So converting pyfdate to Russian wouldn't break it in the way that attempting a similar conversion might break Datejs.

    If I can summarize the discussion to this point:

    (1) Simon's wish for a Python date API that uses chaining (as Datejs does) is granted in pyfdate.

    (2) Paul (I think) and I have gut-level misgivings about an API that mimics English-language syntax.

    Despite (2), I think that it is great that you've developed Datejs. Datejs may be useful to a lot of people, and it could be an evolutionary step toward another Javascript library that might be more appealing to non-English speakers. So I say: Good Job!

    Stephen Ferg - 16th December 2007 04:16 - #

  13. I have a question about Datejs. Suppose I'm a French-speaking coder and I want to get the *French* weekday name (e.g. "lundi") of today's date. In pyfdate, I'd code:

    result = Today().weekdayname

    Is this something Datejs can do?

    Or will Datejs do something like return a weekday number, which it is the programmer's responsibility to map into the weekday name of whatever language he wishes?

    Stephen Ferg - 16th December 2007 04:26 - #

  14. @Stephen Ferg - If you have the French CultureInfo file loaded then calling .getDayName() will return the French Day Name. If you have the German CultureInfo file loaded, it will return the German Day Name.

    Example
    <pre>
    // Assume today is Monday/December

    // date-fr-FR.js (French-France) loaded
    Date.today().getDayName() // lundi

    // date-de-DE.js (German-Germany) loaded
    Date.today().getDayName() // Montag

    Same goes for the Month Names...

    // date-fr-FR.js (French-France) loaded
    Date.today().getMonthName() // décembre

    // date-de-DE.js (German-Germany) loaded
    Date.today().getMonthName() // Dezember
    </pre>
    I might add, we have pre-compiled 150+ separate CultureInfo files, so developers need not worry about having to translate the month or day names for any culture they wish to support. Just load up the appropriate CultureInfo file and you're good to go.

    All the CultureInfo files are located in the <code>/trunk/src/globalization/</code> folder.

    Geoffrey McGill (Datejs) - 18th December 2007 07:59 - #

  15. @Step - Just for completeness, as I know someone will find this comment in a search and wonder how to get the first Thursday of the current year with Datejs.

    // Datejs
    // Date.january().first().thursday()

    // Datejs abbreviated
    // Date.jan().first().thu()

    // Pyfdate
    // Today(Today().year,1,1).gotoWeekday(THURSDAY, NEXT, useToday=True)

    Geoffrey McGill (Datejs) - 18th December 2007 08:00 - #

  16. @Stephen Ferg
    "Yes, but the logic is trivial. In pyfdate, I wouldn't even call it "logic"; it is just a year specification. For the sake of comparison, here is the code in pyfdate for obtaining Memorial Day for last/this/next year."

    I was thinking more of the following scenario. Your application requires that you write a method to return the next or last Memorial day? The code samples you provide are clear and all, but more logic is required.

    # Memorial Day of this year (the long version)
    Time(Time().year,MAY,1).gotoMonthEnd().gotoWeekday (MONDAY, PREVIOUS, useToday=True)

    That will get you Memorial Day of the current Year, but now you have to determine whether that's the next or last Memorial Day. You can't simply just add/subtract a Year because you can't assume Today is before or after the current years Memorial Day.

    You're going to have to add a conditional 'if' statement in there to check. You need to find the current years Memorial Day, and then you might have to perform the calculation again, this time adding/subtracting a Year. It would depend on whether you need to find the next or last Memorial Day.

    Anyway you look at it, I don't think you're going to get any cleaner than Date.may().final().monday(), without providing a custom method like Date.getMemorialDay().

    Geoffrey McGill (Datejs) - 18th December 2007 08:02 - #

  17. This whole english language syntax discussion (for/against) seems to be focusing mainly on the (optional) syntactical sugar component of the Datejs library. With Datejs the API provides almost the exact same syntax as pyfdate if you want to kick it more old school.

    Example

    // pyfdate
    Time(2007,MAY,1).gotoMonthEnd().gotoWeekday (MONDAY, PREVIOUS, useToday=True)

    // Datejs
    new Date(2007, Date.MAY, 1).moveToLastDayOfMonth().moveToDayOfWeek(Date.MON DAY, -1);

    Although as a non English speaking programmer (or English for that matter) I still don't really see how the above is better or easier to understand than "Date.may().final().monday()".

    If you understand what .gotoMonthEnd() means, than I can only assume you understand what .final() means. The English month and day names in Datejs act as constants.

    You know, I just thought of another reason why using culture-specific month and day names is a bad idea. In many cultures the month and/or day names are comprised of two (or more) words. If the names include two words then you have to make a call whether to just join, thereby removing the space, or maybe add an underscore character as a separator. Culturefying the constants and/or function/method names is just unnecessarily problematic.

    Geoffrey McGill (Datejs) - 18th December 2007 08:04 - #

Sign in with OpenID

Auto-HTML: Line breaks are preserved; URLs will be converted in to links.

Manual XHTML: Enter your own, valid XHTML. Allowed tags are a, p, blockquote, ul, ol, li, dl, dt, dd, em, strong, dfn, code, q, samp, kbd, var, cite, abbr, acronym, sub, sup, br, pre

A django site