חגים וזמנים לששון
כמפתחים אנחנו מתייחסים אל מונח הזמן בצורה מאד פשוטה.
יש לנו אובייקט Date שמספק לנו מידע על זמן. אבל יש עם זה בעיות.
הנה בעיה שתסביר מדוע יש להתייחס לזמן בזהירות רבה.
נניח לרגע שיש לכם חברה שמוכרת מוצרים אונליין (אני יודע, זה מופרך ולעולם לא יתפוס, אבל נסו לדמיין).
אתם עוברים על הרשומות בבסיס הנתונים ופתאום אתם קולטים משהו: אין לכם מושג מתי התבצעו ההזמנות! כלומר, יש רישום של תאריך ושעה, אבל איזה תאריך ושעה? "אני יודע", אומר מתכנת הבקאנד הנאמן שלכם, "אנחנו רושמים הזמנות לפי השעון של השרת כמובן".
ברגע זה אתם קולטים שאי אפשר להבין מזה מתי לקוחות שלכם נוטים לעשות קניות! האם זה בבוקר, בצהריים, בערב? הזמן עבר נירמול ואין לכם מושג. אז אתם מבקשים מהמתכנתים שישנו את הטכניקה ושהפרונט יישלח את הזמן המקומי. וזה מה שקורה, אבל אז אתם מבינים שכאשר ממיינים את טבלת ההזמנות לפי תאריך ושעה אין לכם מושג אם שתי הזמנות סמוכות מייצגות את אותו הזמן בפועל.
במידה וישנם לקוחות ממקומות שונים בעולם, סביר להניח שהם ביצעו הזמנות באותה שעה שלהם, במערכת שלכם ההזמנות מופיעות בסמיכות, כי היא לא יודעת להבחין בין אזורי זמן שונים ולכם אין אפשרות לדעת מה הסדר האמיתי של ההזמנות לפי שעון המערכת.
כמובן שיש לבעיה הזאת פתרון, אבל היא באה להמחיש עד כמה בבסיס שלו זמן הוא מונח יחסי, ויש להתייחס אליו בכובד ראש.
המאמר הזה יהיה ראשון בסידרה שתחקור לעומק את הפתרונות שיש ב-JS ושאחרי שתקראו ותחכימו, תהפכו גם אתם לשליטי הזמן.
כבר נגמר הזמן?
שאלה מתי התחיל הזמן יש כל מיני גירסאות. הנוצרים גורסים שמדובר על יום ההולדת ה-4 של ישו, היהודים טוענים שכ-6000 שנה אחורה ולמדענים שיטות משלהם. אבל ל-JS יש תשובה אבסולוטית: הזמן התחיל באופן רשמי ב-1.1.1970. לא מאמינים לי? נסו את השורה הזאת בקונסולה:
new Date(0)
ואם JS אומרת את זה, אז זה נכון, נקודה. אז אם סבא וסבתא יגידו שהם זוכרים זמנים לפי 1970, תדעו שהם חלק מהקונספירציה ונתקו איתם קשר במיידי!
ב-JS אובייקט Date הוא זה שאמון על טיפול בזמן ומתחת לפני השטח שלו מסתתרים הרבה סודות ופרמטרים שנחקור כאן. אבל בראש ובראשונה בואו נענה על השאלה מה הזמן כרגע? נסו את השורה הבאה:
Date.now()
המספר שתקבלו יהיה מספר השניות שחלפו מאז אותו יום בו נברא העולם ב-1970 כאמור. מתי יעניינו אותנו מספר המילי-שניות? בעיקר כשנרצה למדוד ביצועים של המערכת. הקוד הבא יוכל לתת לכם מושג כמה זמן לקח תהליך במערכת:
const startTime = Date.now()
// Some complicated process…
const endTime = Date.now()
const duration = endTime - startTime
console.log({duration})
אבל רובינו בני אדם ואנחנו אוהבים להבין זמן במילים. אז בואו ננסה לראות מה מקבלים כאשר מדפיסים את הזמן באופן הזה:
new Date()
נקבל משהו כזה:
Wed May 04 2022 11:05:33 GMT+0300 (Israel Daylight Time)
בואו נבין מה אנחנו רואים כאן. קודם כל יש כאן פורמט שהוא מאד מילולי במהותו ויש בו הרבה מאוד תווים, מה שרומז שהוא לא יעיל ולכן גם לא מקובל בתור מידע לצד שרת, לכן בהמשך נחקור מתודות אחרות שנגישות דרך האובייקט הזה.
זמן ומרחב
מה שכן צריך לתפוס את העין שלכם הוא החלק הימני בטקסט הזה.
(GMT – Greenwich Mean Time) הוא אזור זמן שמתייחס לעיירה גריניץ' באנגליה, בה נמצא ה"אפס" שלנו מבחינת אזורי זמן. כל אזור זמן אחר יתייחס אליו. ולכן בישראל, שבה אנחנו שלוש שעות "לפני" גריניץ', אנחנו נקבל 3+ מבחינת אזור הזמן היחסי. בהמשך אנחנו נראה שלמושג הזה יש עוד שם: UTC – Coordinated Universal Time (אל תשאלו אותי למה ראשי התיבות לא בסדר הנכון).
מכיוון שאזורי הזמן היחסיים נעים בין 12+ ל-12- שעות, הגיוני שיהיה אפס לפני ה-3, אבל הסקרנים שביניכם בטח שואלים את עצמכם למה צריך אפסים אחרי ה-3? הרי אזורי זמן הם בשעות עגולות, לא? אז זהו שלא תמיד. באיראן, אפגניסטן והודו לדוגמא, אזור הזמן הוא ½5+ שעות. וזה עוד כלום. בנפאל הרחוקה זה ¾5+! עולם מוזר.
המידע הזה מאוד חיוני,
כאשר רוצים לדעת כמה שיותר על אירוע משמעותי במערכת. אפשר להבין ממנו שני דברים בבת אחת: הראשון לגבי האזור הכללי של המשתמש בעולם והשני הוא השעה האמיתית שלו לעומת השעה שלכם.
כאמור, לדעת מהו איזור הזמן לא מספיק בשביל להבין הכל, ולכן אובייקט הזמן מוסיף את המידע על המדינה עצמה. אם יצא לכם אי פעם להגדיר את הזמן במערכת ההפעלה של המחשב, בוודאי נתקלתם במצב שבו יכולתם להגדיר את אותו זמן יחסי, אבל בהקשר למדינה אחרת.
אם אתם עוקבים, בטח שמתם לב שכאן כתוב 2+, כאשר אובייקט ה-Date נתן 3+. מי צודק? JS תמיד כבר אמרנו? הסיבה לכך היא שעון הקיץ במקרה המסוים הזה. כאשר אני בודק את השעה במצרים, אני מגלה שהיא שעה אחת אחורה. המצרים מזיזים את השעון אחרת (אם בכלל).
לא סיימנו כאן. יש מדינות גדולות יותר מישראל. כמה גדולות? כל כך גדולות שיש להן יותר מאזור זמן אחד. ארה"ב לדוגמא. לכן במקרה של מדינות כאלה, אנחנו נראה אפשרויות הגדרה ברמה של עיר, שם של מחוז, או משהו כמו "החוף המזרחי". אם אי פעם קיבלתם זימון לפגישה מחו"ל, יכול להיות שראיתם את ראשי התיבות של אזורי זמן שונים: EDT, PDT, IST וכו'.
הזמן המדויק חשוב בעוד מובן: התאריך עצמו מתחלף כאשר באזורי זמן שונים השעות שונות, ולכן באותו הזמן יכולים להיות תאריכים שונים במקומות שונים.
שימו לב לדבר מאוד חשוב. הגדרה התאריך והזמן לקוחה ממערכת ההפעלה שלכם. אם תשנו את ההגדרות האלה באופן ידני, גם ההגדרות שתקבלו ב-JS ישתנו בהתאם!
הגיע הזמן
בואו נסתכל על כמה סטנדרטים של הצגה של זמן. אחת מהן כבר הכרנו, זאת בפועל המתודה:
.toString()
הנה עוד כמה
תאריך בלבד:
.toDateString()
// 'Wed May 04 2022'
זמן בלבד:
.toTimeString()
// '18:16:03 GMT+0300 (Israel Daylight Time)'
תאריך המתחשב בהגדרות הלוקאליות שלכם (סגנון כתיבת התאריך המקומי):
.toLocaleDateString()
// '5/8/2022'
זמן המתחשב בהגדרות הלוקאליות שלכם (סגנון כתיבת הזמן המקומי):
.toLocaleTimeString()
// '6:15:31 PM'
אם תרצו להעביר זמן סטנדרטי לשרת ללא אזור זמן:
.toISOString()
// '2022-05-08T15:15:10.344Z'
שימו לב לדבר חשוב. למרות שיש פורמט שמציג את אזור הזמן בתוצאה, פעמים רבות בצד של ה-API יחכה אובייקט של SQL DATETIME, אשר יסרב לקבל את אזור הזמן בתוך המחרוזת ובצדק! בסופו של דבר אמור להישמר זמן אבסולוטי בשרת, כזה שאפשר להשוות ולמיין בתוכו ללא חשש של אזורי זמן כאלה ואחרים. לכן המתודה האחרונה היא אחת המתאימות ביותר בד"כ.
אז איך בכל זאת מתחשבים באזור הזמן שלכם? יש עוד מתודה שחשוב להכיר:
.getTimezoneOffset()
// -180
היא תחזיר את סטיית הזמן לעומת UTC/GMT בדקות. את הזמן הזה כדאי לשמור בטבלת הפרופיל של המשתמש (בהנחה שלא מדובר על נווד), או בעמודה נפרדת של רשומות הכוללות זמן של אירועים.
הערה: הנתון הזה עלול להיות לא מדוייק במקרים מסויימים של שעון קיץ\חורף ולכן למטרות מקצועיות מומלץ להשתמש בכלים שמאפשרים קבלת אזור ופרטי הזמן המדוייקים. אפשר לקרוא על אחד מהפתרונות האלה כאן.
מעת לעת
עד עכשיו ראינו מתודות המשמשות אותנו לשמירה של ייצוגים שונים של הזמן הנוכחי. בואו ננצל באובייקט Date למטרה שימושית נוספת.
נניח שרוצים לבחור מועד אחר מעכשיו. נניח התאריך ה-2 במאי 2022. כדי להשיג תאריך מסוים נעביר פרמטרים לאובייקט, אבל יש לשים לב לסדר שלהם. יש להזין את הערכים לפי הערך הגבוה יותר (שנה) בסדר יורד.
בואו נייצר אותו:
new Date(2022, 5, 2)
// Thu Jun 02 2022 00:00:00 GMT+0300 (Israel Daylight Time)
רגע אחד! אמרנו מאי. כתבנו 5. מאי זה החודש החמישי. מה פתאום באתם לנו עם יוני? אז זהו שלא. בעולם הצודק תמיד של JS חודשים מתחילים ב-0. זה הגיוני, כי הרבה דברים מתחילים ב-0 במקום ב-1. אז בטח גם היום בחודש לא תקין. רגע, הוא דווקא תקין. גם השנה תקינה. זה רק החודש. רגע, מה?
אוקי, מוזר, אבל בראיון הטכני הבא שלנו נצא מה זה חכמים!
new Date(2022, 4, 2)
// Mon May 02 2022 00:00:00 GMT+0300 (Israel Daylight Time)
אם נרצה לדייק מבחינת שעות, דקות וכו', נוכל להוסיף עוד פרמטרים.
new Date(2022, 4, 2, 9, 25, 11)
// Mon May 02 2022 09:25:11 GMT+0300 (Israel Daylight Time)
מה קורה כאשר אנחנו מקבלים ייצוג זמן ממקור חיצוני? הרי אי אפשר להעביר תאריך בין מערכות. במקרה כזה ייתכן שנקבל את הזמן במילישניות. אם נזין ערך מספרי בודד הוא יחושב בתור התאריך שתואם למספר המילישניות שחלפו מאז אותו יום בו נוצר העולם:
new Date(2022)
// Thu Jan 01 1970 02:00:02 GMT+0200 (Israel Standard Time)
new Date(100000000000)
// Sat Mar 03 1973 11:46:40 GMT+0200 (Israel Standard Time)
מה יקרה אם נקבל מהשרת את הזמן בתור מחרוזת? אם היא תקנית, האובייקט הזריז שלנו יישמח לתרגם אותה:
new Date(‘Sun May 08 2022 18:47:54 GMT+0300 (Israel Daylight Time)’)
// Sun May 08 2022 18:47:54 GMT+0300 (Israel Daylight Time)
new Date('2022-05-08T15:48:35.062Z')
// Sun May 08 2022 18:48:35 GMT+0300 (Israel Daylight Time)
מדהים!
מה עכשיו?
במאמר זה הבנו את המשמעות והמורכבות של "עכשיו" ואיך אפשר לייצג אותו דרך אובייקט JS Date. ראינו גם איך אפשר לייצר תאריכים (ואת המוזרויות שלהם) באופן ידני.
במאמר הבא נראה איך אפשר "לייצר" זמן, איך אפשר לעשות עליו מניפולציה, למדוד הפרשים בין זמנים ועוד.