jQuery UI datepicker localization in Orchard 1.4.1

Topics: Localization
May 6, 2012 at 5:29 AM

I have my Orchard site set to en-AU culture with dates in format dd/mm/yyyy.

In order to have jQuery UI datepicker correctly observe my current localization date format I had to add the following line in \Views\DatePickerLocalization.cshtml in the JQuery.UI module, just after the line that sets the regional[''] defaults.

$.datepicker.setDefaults($.datepicker.regional['']);

It seems just setting the regional defaults does nothing without making the above call.

Coordinator
May 6, 2012 at 10:12 PM

This has been raised already, though I closed it because it was working as expected for myself. Did you check if you have the correct translation file for the datetime loclization ? The one defining mm/dd/yyyy.

May 6, 2012 at 10:17 PM

I'm en-AU too & have experienced the same thing but was unable to convince - http://orchard.codeplex.com/workitem/18572
I still don't understand why & have been patching my install.  Perhaps Sebastien could take one more look... ;-)

May 7, 2012 at 1:32 PM

There's more to this. My development environment on my local machine here in Australia has en-AU regional settings. My production environment (a VPS hosted in the U.S) has its regional settings en-US (and unfortunately I can't change that due to limitations with my hosting provider).

The above change resolved the problem for me here on my development environment, but I've since noticed that in production, although the date picker is now working correctly, the field value itself is still being set to MM/DD/YYYY.

I've looked into the Orchard.Fields.Drivers.DateTimeFieldDriver.cs class and observed that while the "Display" methods are now using the localisation settings for the current tenant (en-AU for me), the "Editor" methods are still using ToLocalTime() and ToUniversalTime(), which to my knowledge will use the system's current regional setting (en-US for me on my production environment) and not the locale that has been set for the tenant in the general settings in Orchard.

I guess it would be ideal if Orchard could somehow set the thread locale of the web request to whatever the tenant's locale setting is, but if that's not possible for whatever reason, then I guess the alternative is to avoid using ToLocalTime() when reading the date out of the db, and avoid using ToUniversalTime() when writing the date back.

Below are the alternate versions of the "Editor" methods I've written that ensure the tenant's locale setting is observed when reading/writing date field values from/to the db.

1. In the first method below I use the current timezone from the work context when converting the date time value from UTC.
2. In the first method below I use the date and time format from the _dateTimeLocalization object when formatting the date and time.
3. In the second method below I use the  current timezone from the work context when converting the date time value back to UTC.


        protected override DriverResult Editor(ContentPart part, Fields.DateTimeField field, dynamic shapeHelper) {
           
            var settings = field.PartFieldDefinition.Settings.GetModel<DateTimeFieldSettings>();
            var value = field.DateTime;

            var currentCulture = Services.WorkContext.CurrentCulture;
            var currentTimeZone = Services.WorkContext.CurrentTimeZone;

            if (value != DateTime.MinValue)
            {
                value = TimeZoneInfo.ConvertTimeFromUtc(value, currentTimeZone);
            }
            
            var viewModel = new DateTimeFieldViewModel {
                Name = field.DisplayName,
                Date = value != DateTime.MinValue ? value.ToString(_dateTimeLocalization.ShortDateFormat.Text) : "",
                Time = value != DateTime.MinValue ? value.ToString(_dateTimeLocalization.ShortTimeFormat.Text) : "",
                ShowDate = settings.Display == DateTimeFieldDisplays.DateAndTime || settings.Display == DateTimeFieldDisplays.DateOnly,
                ShowTime = settings.Display == DateTimeFieldDisplays.DateAndTime || settings.Display == DateTimeFieldDisplays.TimeOnly,
                Hint = settings.Hint,
                Required = settings.Required
            };

            return ContentShape("Fields_DateTime_Edit", // this is just a key in the Shape Table
                () => shapeHelper.EditorTemplate(TemplateName: TemplateName, Model: viewModel, Prefix: GetPrefix(field, part))); 
        }

        protected override DriverResult Editor(ContentPart part, Fields.DateTimeField field, IUpdateModel updater, dynamic shapeHelper) {
            var viewModel = new DateTimeFieldViewModel();

            if(updater.TryUpdateModel(viewModel, GetPrefix(field, part), null, null)) {
                DateTime value;

                var settings = field.PartFieldDefinition.Settings.GetModel<DateTimeFieldSettings>();

                if (settings.Display == DateTimeFieldDisplays.DateOnly) {
                    viewModel.Time = new DateTime(1980, 1, 1).ToString(_dateTimeLocalization.ShortTimeFormat.Text);
                }

                if (settings.Display == DateTimeFieldDisplays.TimeOnly) {
                    viewModel.Date = new DateTime(1980,1,1).ToString(_dateTimeLocalization.ShortDateFormat.Text);
                }

                string parseDateTime = String.Concat(viewModel.Date, " ", viewModel.Time);
                var dateTimeFormat = _dateTimeLocalization.ShortDateFormat + " " + _dateTimeLocalization.ShortTimeFormat;

                if(settings.Required && (String.IsNullOrWhiteSpace(viewModel.Time) || String.IsNullOrWhiteSpace(viewModel.Date))) {
                    updater.AddModelError(GetPrefix(field, part), T("{0} is required", field.DisplayName));
                }

                if (DateTime.TryParseExact(parseDateTime, dateTimeFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out value)) {
                    field.DateTime = TimeZoneInfo.ConvertTimeToUtc(value, Services.WorkContext.CurrentTimeZone);
                }
                else {
                    updater.AddModelError(GetPrefix(field, part), T("{0} is an invalid date and time", field.DisplayName));
                    field.DateTime = DateTime.MinValue;
                }
            }
            
            return Editor(part, field, shapeHelper);
        }

On a side note, regarding the implementation of IDateTimeLocalization (in Orchard.Web\Core\Shapes\Localization\DateTimeLocalization.cs) that provides the date and time format strings (the _dateTimeLocalization object in the code above), I've noticed this uses the Localizer (reading the strings from the .po files for the tenant's locale).

I guess this provides a little more flexibility in controlling the exact formatting, but I can't help thinking that if the provided implementation used the .NET CultureInfo to retrieve format strings instead of the .po files, then date/time localization would work (albeit with a little less flexibility) for users that have set an alternate locale (in the Orchard general settings for the tenant) without requiring them to load the .po files for that locale (eg. en-AU). I know for me (in en-AU) the only real difference I care about compared to en-US is the date and time formats, so having to load alternate .po files just for this has been an additional step that I think could be avoided.

var currentDateTimeFormat = CultureInfo.GetCultureInfo(Services.WorkContext.CurrentCulture).DateTimeFormat;
currentDateTimeFormat.ShortDatePattern;
currentDateTimeFormat.ShortTimePattern;

 

That's just a quick thought, and I guess there is likely valid reasons why the .po files are used to store the date time formats.

Coordinator
May 7, 2012 at 4:38 PM

Ok guys, let me take a "third" look on this one. Seems like an issue with Aussies ;)

Coordinator
May 7, 2012 at 5:00 PM

So, it seems that as mjy78 is stating that the Editor method is not using the internal time zone and formatting methods. And just to confirm, the goal with Orchard is that it is not sensitive to the system culture, so if you are developing on en-AU and your server is on en-US that should impact nothing.

Let me write a little post about how it works ... and also fix this issue. Today will be my official Aussie + Chinese localization day

Coordinator
May 7, 2012 at 9:54 PM

I have found a bunch of issues while working on this one. Could you both check the 1.4.x branch ?

May 7, 2012 at 10:30 PM

Yes that's better, thanks so much!  Atleast as far as the date picker goes (and localization).  Haven't been using the time picker much, but that slider control is much easier to use than the previous time buttons, they were awkward.

Just one little thing I noticed though.  I'm using @Display(New.DatePickerLocalization()) for a date field in one of my custom views & was including just the jQueryUI_DatePicker script in the view to make it work.  No time picking in this view.  But it's broken now ($.timepicker is undefined) unless I also include the timepicker script, which of course is unnecessary.  The same applies to a custom content type with an added (Date only) DateTime field, the timepicker scripts & css are included unnecessarily.  Is there a way to conditionally execute the timepicker statements in the DatePickerLocalization shape? 

May 7, 2012 at 10:57 PM

Maybe they could be separate - DatePickerLocalization and TimePickerLocalization

May 7, 2012 at 11:11 PM

Just had a quick look over the change list and didn't notice any update to the Orchard.Fields.Drivers.DateTimeFieldDriver.cs class. This one is still using the system locale in the Editor methods.

Other than that (and Claire's comments) it all looks good to me! :o) Thanks.

Coordinator
May 7, 2012 at 11:34 PM

@mjy78: they are included in the sub repository for Orchard.Fields

Coordinator
May 7, 2012 at 11:35 PM

@planetClaire: Actually the DatePcikerLocalization needs a dependency on the time picker script. I will fix it.

Coordinator
May 7, 2012 at 11:36 PM

Also Claire, you can use Display.DatePickerLocalization(), which is equivalent (FYI)

May 8, 2012 at 1:19 AM
sebastienros wrote:

Also Claire, you can use Display.DatePickerLocalization(), which is equivalent (FYI)

Thanks for pointing that out.  Yes, the new dependencies are great too.

Now you've split the shape, I think you could:

    if (Model.ShowTime) {
         @Display(New.TimePickerLocalization())
    }

to optionally load the timepicker script & css only if needed.  Ditto for Model.ShowDate

Coordinator
May 8, 2012 at 4:55 PM

I'd be happy to do it, and even more to let you create a .patch file that I could apply ;) It's not simpler for me, but you'll feel even more comfortable with it next time ...

May 8, 2012 at 11:35 PM

OK, that's nice of you thanks Sebastien :-)

So, I've got my first fork now, but I need a bit of help with the Orchard.Fields subrepo.  Can I somehow fork that too as part of my fork of the main repo?  Because when I try to push my change, TortoiseHg tries to do this: "pushing subrepo src\orchard.web\modules\Orchard.Fields to https://hg.codeplex.com/orchardfields" which fails cos I'm not authorized.  Can you tell me how I can push to my own subrepo within my own fork please?

Coordinator
May 8, 2012 at 11:59 PM

It would require you to fork fields too, and point your orchard fork to this new sub repos. I was suggesting you to create a patch file instead of creating a fork. It's simpler for contributors. You can even create one patch for Orchard and on for Fields.

May 9, 2012 at 3:22 AM

Ah, well I was trying to make it easy for you as the docs say forks are preferred, but if you're happy then that's good.  I've added 2 patches to an issue: http://orchard.codeplex.com/workitem/18665

Aug 15, 2012 at 6:10 AM

How to apply these patches then?thanks.