umbraco-community / Our.Umbraco.OpeningHours

An Umbraco property editor for handling opening hours
MIT License
9 stars 13 forks source link

Add a way to set the local time zone #23

Closed davym closed 7 years ago

davym commented 7 years ago

Currently, users select their opening and closing times based on local time (in my case CST). However, my production server is set to UTC, which it's highly recommended to not change. The problem I'm having is that on a production server everything is off by 6 hours. Things like IsCurrentlyOpen don't work much of the time because the server is always "ahead" of the values set in by the user in Umbraco. Would it be possible to have a way to localize everything somehow?

bjarnef commented 7 years ago

I have had some issues with this on Umbraco Cloud, because the servers it set to use UTC time and doesn't change when we in Denmark are changing from normal time to summer time.

Explanation from Umbraco Cloud support:

Servers are set to UTC -> "Daylight Saving Time is not observed by this time zone." The time on the servers don't change according to summer / winter time. Our time changes: During summer: UTC +2 During winter: UTC +1 So, to sum up: During our summer time the time difference to the servers will be +2 hours.

In a project I have been working on I have defined a variable for this, instead of using IsCurrentlyOpen property. I have an 12 Umbraco nodes, because of their complex opening hours. Each node has a start date property and a opening hours property.

DateTime utcDateTime = DateTime.UtcNow;
TimeZoneInfo timeZone = TimeZoneInfo.FindSystemTimeZoneById("Romance Standard Time"); // (GMT+01:00) Brussels, Copenhagen, Madrid, Paris
DateTime now = TimeZoneInfo.ConvertTimeFromUtc(utcDateTime, timeZone);

var openingHoursNode = openingHoursNodes
            .Where(x => x.HasValue("openingHoursStartDate"))
            .Where(x => x.GetPropertyValue<DateTime>("openingHoursStartDate").Month == now.Month)
            .Where(x => x.GetPropertyValue<DateTime>("openingHoursStartDate").Year == now.Year)
            .FirstOrDefault();

OpeningHoursModel openingHours = openingHoursNode.GetOpeningHours("openingHours");

 var todayOpeningHours = openingHours.TodaysOpeningHours();

TimeSpan tsOpen = todayOpeningHours.IsOpen ? todayOpeningHours.Items[0].Opens : new TimeSpan(0, 0, 0);
TimeSpan tsClose = todayOpeningHours.IsOpen ? todayOpeningHours.Items[0].Closes : new TimeSpan(0, 0, 0);

DateTime dtStart = new DateTime(now.Year, now.Month, now.Day, tsOpen.Hours, tsOpen.Minutes, tsOpen.Seconds);
DateTime dtEnd = new DateTime(now.Year, now.Month, now.Day, tsClose.Hours, tsClose.Minutes, tsClose.Seconds);

bool isCurrentlyOpen = todayOpeningHours.IsOpen && dtStart <= now && dtEnd >= now;
abjerner commented 7 years ago

@davym @bjarnef I had a look at the code, and came up with a few new classes to handle time zones. You can now get the an offset-based model with the following snippet (where timeZone is an instance of TomeZoneInfo - as shown in Bjarne's example):

// Get the new model based on the time zone
OpeningHoursOffsetModel openingHoursOffset = Model.GetOpeningHours("openingHours", timeZone);

or alternatively as:

// Get the old model
OpeningHoursModel openingHours = Model.GetOpeningHours("openingHours");

// Get the new model based on the time zone
OpeningHoursOffsetModel openingHoursOffset = new OpeningHoursOffsetModel(openingHours, timeZone);

This means that you can now do something like the example below, and it should reflect the specified time zone:

@using Our.Umbraco.OpeningHours.Extensions
@using Our.Umbraco.OpeningHours.Model.Offset
@inherits UmbracoViewPage

@{

    // Get the time zone
    TimeZoneInfo timeZone = TimeZoneInfo.FindSystemTimeZoneById("Romance Standard Time"); // (GMT+01:00) Brussels, Copenhagen, Madrid, Paris

    // Get the new model based on the time zone
    OpeningHoursOffsetModel openingHoursOffset = Model.GetOpeningHours("openingHours", timeZone);

    <pre>@openingHoursOffset.IsCurrentlyOpen</pre>

    <table border="1">
        <tbody>
            @foreach (OpeningHoursDayOffset day in openingHoursOffset.GetUpcomingDays(14)) {
                <tr>
                    <td>@day.Date.ToString("yyyy-MM-dd")</td>
                    <td>@day.Date.ToString("dddd")</td>
                    <td>
                        @if (day.IsOpen) {
                            foreach (var item in day.Items) {
                                <span>@item.Opens.ToString("HH:mm") &ndash; @item.Closes.ToString("HH:mm")</span>
                            }
                        } else {
                            <em>Closed</em>
                        }
                    </td>
                </tr>
            }
        </tbody>
    </table>

}

It works so far with the testing that I have done, and also seems to handle daylight savings correctly. I'd however like to do some more testing before pushing a new release. Any feedback would also help towards this :D

abjerner commented 7 years ago

@davym @bjarnef Did anyone of you try this out? I you haven't encountered any issues, I should probably look into pushing a new release...