אזורי זמן ושעון קיץ: פתרונות מעשיים

נוסף ב-22/08/2007 00:42 על ידי דניאל כץ

במאמר הזה נציע מספר פתרונות מעשיים למקרים בהם צריך להציג שעה מסוימת למשתמשים באזורי זמן שונים.

ניקח לדוגמה מערכת פורום. דוד, מזין הודעה בשעה 13:00 ממחשב בישראל (GMT +2), אלי שגר באנגליה בגריניץ' (GMT) יראה בשעה 11:00 אצלו את ההודעה שזה עתה נוספה לפורום - בעוד שעתיים בשבילו. זה יקרה במקרה שהשרת כשירשום את ההודעה למסד הנתונים, יצרף אליה את שדה "שעת יצירה" לפי שעון ישראל. למרות שגם אם השרת ירשום את השעה לפי שעון גריניץ' עדיין תהיה אי נוחות לדוד לראות שהודעתו צורפה שעתיים לפני שהוא צירף אותה בפועל.

המסקנה המתבקשת היא, שלא משנה לפי איזה איזור זמן תשמור את הנתונים, צריך להמיר אותם לפני ההצגה עבור כל משתמש  לגופו. אנו נסקור כאן שני דרכים להתמודדות עם הבעיה ואת חסרונותיהם. בכל הדוגמאות השרת ישמור את הנתונים לפי שעון GMT.

דרך א'

אנו ניצור תופס חדש ונקרא לו userSubmit.aspx. קודם נכתוב את קוד הASP.NET הבא:

<%@ Page Language="VB" AutoEventWireup="false" EnableEventValidation="False"

    CodeFile="submitUser.aspx.vb" Inherits="submitUser" %>

 

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">

<head runat="server">

    <title>טוען...</title>

</head>

<body dir="rtl">

    <h1>

        טוען...</h1>

    <form id="form1" runat="server">

        <asp:HiddenField ID="offset" runat="server" />

 

        <script type="text/javascript">

            var dt = new Date();

            document.getElementById(fieldName).value = -(dt.getTimezoneOffset() / 60); // חשב בשעות

            document.forms[formName].submit(); // שלח מידע לשרת

        </script>

 

    </form>

</body>

</html>

נדגיש שיש להוסיף לדרקטיבה Page %> את הפרמטר "EnableEventValidation="False כדי לאפשר את הSubmit מJavaScript.

אחר כך נפתח את קובץ  הCodeBehind במקרה שלנו זה userSubmit.aspx.vb ונוסיף קוד לאירוע Load.

Partial Class submitUser

    Inherits System.Web.UI.Page

 

    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load

        If Not IsPostBack Then

            ClientScript.RegisterClientScriptBlock(GetType(Page), "varForm", "var formName = '" + form1.ClientID + "';", True)

            ClientScript.RegisterClientScriptBlock(GetType(Page), "varField", "var fieldName = '" + offset.ClientID + "';", True)

        Else

            Dim offset As String = Request.Form("offset")

 

            ' אם תווח השעות נכון

            If IsNumeric(offset) AndAlso CInt(offset) >= -12 AndAlso CInt(offset) <= 12 Then

 

                Session("offset") = offset

 

                ' חוזרים לפורום

                If Request.QueryString("returnurl") <> "" Then

                    Response.Redirect(Request.QueryString("returnurl"))

                End If

            End If

 

            ' משהו שהתבש חוזרים לדף הראשי

            Response.Redirect("~/")

        End If

    End Sub

End Class

מה שעשינו פה, זה יצרנו טופס (form) עם JavaScript ששולח לשרת את מספר השעות שיש להוסיף לזמן GMT (גריניץ') כדי לקבל את הזמן המקומי, ושמרנו את זה בSession.

כעת, כדי להשתמש במה שעשינו, אנו נוסיף לדף הפורום הפנייה (Response.Redirect) לדף שיצרנו, ונציג למשתמש את השעה שרשומה במסד הנתונים (GMT) + המספר שעות שיושב כרגע בSession. בצורה כזאת:

    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load

        If Session("offset") Is Nothing Then

            ' נקבל את המידע על הלקוח ונחזור לפה

            Response.Redirect("submitUser.aspx?returnurl=" + Request.Url.ToString)

        Else

            Dim delta As Integer = CInt(Session("offset"))

 

            For Each msg As ForumMessage In ForumDAL.GetAll()

 

                ' נחשב את השעה שנציג ללקוח

                Dim newTime As Date = msg.SentOn.AddHours(delta)

 

                '... ונוסיף את ההודעה לדף

            Next

        End If

    End Sub

המעלות:

  • מעט מאוד קוד JavaScript (טוב לאלרגיים שבינינו).

החסרונות:

  • נחוץ רענון נוסף בטעינת הפורום.
  • מחייב שימוש בSessions שלעיטים מכביד על האפליקציה כולה. (כמובן שתמיד אפשר להעביר את הנתון הdelta [הפרש זמן] כפרמטר Url ולהפתר מSessions אבל בינינו - זה די מסורבל)

דרך ב'

הרעיון הכללי: אנו לא נתחשב בצד השרת כלל בסביבת הלקוח, אלא נבצע את ההמרה של השעות על ידי JavaScript אחרי רינדור הדף.

בשביל זה, נכתוב קודם את פונקציית הJavaScript המתאימה:

function convertToLocalTime(year,month,day,hour,min,sec) {

    var localTime = new Date(year,month,day,hour,min,sec);

    var ms = localTime.getTime() - (localTime.getTimezoneOffset() * 60000);

    var time =  new Date(ms);

    return time;

}

 

function getShortTime(year,month,day,hour,min,sec)

{

    var dt = convertToLocalTime(year,month,day,hour,min,sec);

    return ZeroPad(dt.getHours()) + ':' + ZeroPad(dt.getMinutes());

}

 

function getShortDate(year,month,day,hour,min,sec) {

    var dt = convertToLocalTime(year,month,day,hour,min,sec);

    return ZeroPad(dt.getDate()) + '/' + ZeroPad(dt.getMonth()) + '/' + dt.getFullYear();

}

 

function getTime(year,month,day,hour,min,sec) {

    return convertToLocalTime(year,month,day,hour,min,sec).toLocaleTimeString();

}

 

function getDate(year,month,day,hour,min,sec) {

    return convertToLocalTime(year,month,day,hour,min,sec).toLocaleDateString();

}

 

function getLong(year,month,day,hour,min,sec) {

    return convertToLocalTime(year,month,day,hour,min,sec).toLocaleString();

}

 

function ZeroPad(val) {

    val = val + '';

    if (val.length == 1)

        val = '0' + val;

 

    return val;

}

לווריאציות השונות של הפונקציה אנו נזדקק אם נרצה לסגנן (Date Formatting) את התאריך בזמן ההצגה ללקוח. (כחלופה, ניתן לשלב כאן  את הקוד של Tor2k מקוד פרוג'קט)

ועכשיו את פונקציות הNET. הרלוונטיות:

Public Class LocalTime

 

    Public Shared Function ToLocalShortDate(ByVal val As Date) As String

        Return String.Format("<script type='text/javascript'>document.write(getShortDate({0}));</script>", ToParams(val))

    End Function

 

    Public Shared Function ToLocalShortTime(ByVal val As Date) As String

        Return String.Format("<script type='text/javascript'>document.write(getShortTime({0}));</script>", ToParams(val))

    End Function

 

    Public Shared Function ToLocalDate(ByVal val As Date) As String

        Return String.Format("<script type='text/javascript'>document.write(getDate({0}));</script>", ToParams(val))

    End Function

 

    Public Shared Function ToLocalTime(ByVal val As Date) As String

        Return String.Format("<script type='text/javascript'>document.write(getTime({0}));</script>", ToParams(val))

    End Function

 

    Public Shared Function ToLocalLong(ByVal val As Date) As String

        Return String.Format("<script type='text/javascript'>document.write(getLong({0}));</script>", ToParams(val))

    End Function

 

    Private Shared Function ToParams(ByVal val As Date) As String

        Return String.Format("{0},{1},{2},{3},{4},{5}", val.Year, val.Month, val.Day, val.Hour, val.Minute, val.Second)

    End Function

 

End Class

אחרי שהקוד מוכן, נוכל לשלבו בASP.NET בשיטת Late Bound Data Expressions. לדוגמה:

<%#LocalTime.ToLocalLong(CType(Container.DataItem, ForumMessage).sentOn)%>

המעלות בגישה זו:

  • אין צורך בSessions
  • אין התייחסות בקוד צד שרת למיקום הלקוח, וכל הלוגיקה נופלת על הJavaScript.

לסיכום

הכלל  בכל גישה אפשרית הוא שהשרת צריך לשמור את כל הזמנים והתאריכים לפי שעון GMT הנגיש דרך: My.Computer.Clock.GmtTime. וא"כ להמירו עבור כל משתמש ומשתמש.

הבעיות הידועות עם הנושא, קשורות לשעוני קיץ הנהוגים במדינות שונות בזמנים שונים (ולא תמיד קבועים), והיות שאין פתרון גורף לבעיה נצטרך לסמוך על יצרני הדפדפנים שיתחשבו בשעון קיץ כפי שמוגדר במערכת ההפעלה מתי שהם מחזירים בJavaScript את התוצאה של getTimezoneOffset. כמובן שכל הטרחה הזאת הייתה נחסכת אילו על פי תקן הHTTP היו מחויבים לשלוח בכל בקשה לשרת כותרת (Header) עם איזור הזמן כפי שמוגדר במערכת של המבקש.

גישות נוספות שקיימות:

  • לבקש מהמשתמש בעת ההרשמה לבחור ידנית את איזור הזמן שלו. (איזור מה ??)
  • לחשב את הDelta לפי ההפרש המעשי בין השעה שעל מחשב הלקוח לבין השעה שעל השרת. למרות שקרוב לודאי שזה יפתור את הבעיות הקשורות לשעון קיץ, זה לא פתרון מדויק לכן לא נסקור אותו כאן.

Tags: , ,

.NET

הוסף תגובה


(יציג את האייקון ה-Gravatar שלך)

  Country flag

biuquote
  • הערה
  • תצוגה מקדימה
Loading