CodeGym /مدونة جافا /Random-AR /كيف لا تضيع في الوقت المناسب: DateTime والتقويم
John Squirrels
مستوى
San Francisco

كيف لا تضيع في الوقت المناسب: DateTime والتقويم

نشرت في المجموعة
أهلاً! اليوم سنبدأ العمل مع نوع بيانات جديد لم نواجهه من قبل، وهو التواريخ. كيف لا تضيع في الوقت المناسب: التاريخ والوقت والتقويم - 1لا أعتقد أنني بحاجة إلى شرح ما هو التاريخ. :) من حيث المبدأ، يمكننا تخزين التاريخ والوقت الحاليين في سلسلة Java عادية.
public class Main {
   public static void main(String[] args) {

       String date = "June 11, 2018";
       System.out.println(date);
   }
}
لكن هذا النهج له عيوب كثيرة. تم تصميم الفصل Stringللعمل مع النص، وأساليبه مناسبة لهذه المهمة. إذا كنا بحاجة إلى التعامل مع التاريخ بطريقة ما (إضافة ساعتين، على سبيل المثال)، Stringفلن يعمل ذلك بشكل جيد. أو إذا أردنا عرض التاريخ والوقت الحاليين عند تجميع البرنامج. Stringلا يساعد هنا أيضًا: بحلول الوقت الذي تكتب فيه الكود وتقوم بتشغيله، سيكون الوقت قد تغير وستعرض وحدة التحكم معلومات خاطئة. ولهذا السبب قدم منشئو Java عدة فئات للعمل مع التواريخ والوقت. أول هذه هوjava.util.Date

فئة التاريخ

لقد حددنا اسمها الكامل، لأن حزمة Java أخرى تحتوي على java.sql.Dateالفئة. لا تخلط بينهما! أول شيء يجب أن تعرفه عنه هو أنه يخزن التاريخ كعدد المللي ثانية التي مرت منذ 1 يناير 1970. حتى أن نظام الوقت هذا له اسم خاص به: " Unix-time " وهو نهج مثير للاهتمام إلى حد ما، هل توافق؟ :) الشيء الثاني الذي يستحق التذكر هو: إذا قمت بإنشاء Dateكائن باستخدام المُنشئ الافتراضي، فإن النتيجة تمثل التاريخ والوقت الحاليين في لحظة إنشاء الكائن . تذكر أننا قلنا أن التاريخ الذي يمثله Stringسيواجه صعوبة في مثل هذه المهمة؟ يتعامل الفصل Dateمع الأمر بسهولة.
public class Main {
   public static void main(String[] args) {

       Date date = new Date();
       System.out.println(date);
   }
}
قم بتشغيل هذا الرمز عدة مرات، وسترى أن الوقت يتغير بشكل متكرر. :) هذا ممكن لأنه يتم تخزين الوقت بالمللي ثانية: وهي وحدات زمنية صغيرة للغاية، وبالتالي فإن النتائج دقيقة للغاية. فئة Dateمُنشئ آخر: يمكنك تمرير العدد الدقيق للميلي ثانية منذ 00:00 في 1 يناير 1970 إلى التاريخ المطلوب، وسيتم إنشاء كائن التاريخ المقابل:
public class Main {
   public static void main(String[] args) {

       Date date = new Date(1212121212121L);
       System.out.println(date);
   }
}
إخراج وحدة التحكم: الجمعة 30 مايو 04:20:12 بتوقيت جرينتش 2008 نحصل على 30 مايو 2008. تشير كلمة "Fri" إلى يوم الأسبوع (الجمعة، duh)، وGMT هي المنطقة الزمنية (توقيت غرينتش). يتم تمرير المللي ثانية كـ longs، لأن عدد المللي ثانية لا يتناسب عادةً مع ملف int. إذًا، ما هي العمليات ذات التواريخ التي قد يتعين علينا إجراؤها؟ حسنًا، الأمر الأكثر وضوحًا بالطبع هو المقارنة . لتحديد ما إذا كان تاريخ ما يأتي قبل تاريخ آخر أم بعده. ويمكن القيام بذلك بعدة طرق. على سبيل المثال، يمكنك استدعاء Date.getTime()التابع، الذي يُرجع عدد المللي ثانية التي انقضت منذ منتصف ليل الأول من يناير عام 1970. ما عليك سوى استدعائها على كائنين من كائنات التاريخ ومقارنة النتائج:
public class Main {
   public static void main(String[] args) {

       Date date1 = new Date();

       Date date2 = new Date();

       System.out.println((date1.getTime() > date2.getTime())?
               "date1 is later than date2" : "date1 is earlier than date2");
   }
}
الإخراج: date1 أقدم من date2 ولكن هناك أيضًا طريقة أكثر ملاءمة، أي باستخدام طرق خاصة توفرها فئة Date: before()و after()و equals(). كل منهم بإرجاع قيمة منطقية. تتحقق الطريقة before()مما إذا كان تاريخنا أقدم من التاريخ الذي تم تمريره كوسيطة:
public class Main {
   public static void main(String[] args) throws InterruptedException {

       Date date1 = new Date();

       Thread.sleep(2000);// Suspend the program for 2 seconds
       Date date2 = new Date();

       System.out.println(date1.before(date2));
   }
}
مخرجات وحدة التحكم: صحيح وبالمثل، after()تتحقق الطريقة لمعرفة ما إذا كان التاريخ لدينا متأخرًا عن التاريخ الذي تم تمريره كوسيطة:
public class Main {
   public static void main(String[] args) throws InterruptedException {

       Date date1 = new Date();

       Thread.sleep(2000);// Suspend the program for 2 seconds
       Date date2 = new Date();

       System.out.println(date1.after(date2));
   }
}
إخراج وحدة التحكم: خطأ في الأمثلة التي لدينا، قمنا "بوضع البرنامج في وضع السكون" لمدة ثانيتين، بحيث يكون التاريخان مختلفين. على أجهزة الكمبيوتر السريعة، قد يكون الوقت بين إنشاء date1و و أقل من مللي ثانية واحدة، مما يتسبب في إرجاع كليهما و إرجاع خطأ. لكن في هذه الحالة ستعود الطريقة صحيحة! بعد كل شيء، فإنه يقارن عدد المللي ثانية منذ 00:00 يوم 1 يناير 1970 لكل تاريخ. تعتبر الكائنات متساوية فقط إذا كانت تطابق المللي ثانية : date2before()after()equals()
public static void main(String[] args) {

   Date date1 = new Date();
   Date date2 = new Date();

   System.out.println(date1.getTime());
   System.out.println(date2.getTime());

   System.out.println(date1.equals(date2));
}
إليك شيء آخر عليك الانتباه إليه. إذا قمت بفتح الوثائق الخاصة بالفئة Dateعلى موقع ويب Oracle ، فسترى أن العديد من أساليبها ومنشئاتها قد تم وضع علامة عليها كمهملة (أي لا يوصى باستخدامها). إليك ما يقوله منشئو Java حول أجزاء من الفئات التي تم إهمالها:
"عنصر البرنامج الموضح بـDeprecated هو شيء لا يُنصح المبرمجون باستخدامه، عادةً لأنه خطير، أو بسبب وجود بديل أفضل."
هذا لا يعني أنه لا يمكن استخدام هذه الأساليب على الإطلاق. إذا حاولت تشغيل تعليمات برمجية باستخدام أساليب مهملة في IDE، فمن المرجح أن تعمل. على سبيل المثال، فكر في الطريقة المهملة Date.getHours()، والتي تُرجع عدد الساعات المرتبطة بكائن ما Date.
public static void main(String[] args) {

   Date date1 = new Date();

   System.out.println(date1.getHours());
}
إذا بدأت الكود في الساعة 14:21 (2:21 مساءً)، فسيعرض الرقم 14. كما ترون، تم شطب الطريقة المهملة، لكنها لا تزال تعمل. لا تتم إزالة هذه الأساليب حتى لا يتم كسر الجزء الضخم من التعليمات البرمجية الموجودة التي تستخدمها. وبعبارة أخرى، هذه الأساليب ليست "مكسورة" ولا "إزالة". لا يُنصح باستخدامها ببساطة نظرًا لتوفر بديل أكثر ملاءمة. وبالمناسبة، تشير الوثائق على وجه التحديد إلى هذا البديل:
كيف لا تضيع في الوقت المناسب: التاريخ والوقت والتقويم - 2
تم نقل معظم Dateأساليب الفصل إلى الفصل المحسن والموسع Calendar. سنتعرف على هذا الفصل بعد ذلك. :)

فئة التقويم

قدم JDK 1.1 فئة جديدة: Calendar. لقد جعل العمل مع التواريخ في Java أسهل إلى حد ما من ذي قبل. التنفيذ الوحيد للفصل Calendarالذي سنعمل معه هو GregorianCalendarالفصل. تطبق التقويم الغريغوري الذي تتبعه معظم دول العالم. ميزته الرئيسية هي أنه يمكنه العمل مع التواريخ بتنسيق أكثر ملاءمة. على سبيل المثال، يمكن:
  • أضف شهرًا أو يومًا إلى التاريخ الحالي
  • التحقق مما إذا كانت السنة سنة كبيسة؛
  • إرجاع المكونات الفردية للتاريخ (على سبيل المثال، استخراج رقم الشهر من تاريخ كامل)
  • كما أنه يحتوي على نظام مناسب جدًا من الثوابت (سنرى الكثير منها أدناه).
تحسين مهم آخر للفئة Calendarهو ثابت Calendar.ERA الخاص بها : يمكنك الإشارة إلى تاريخ قبل العصر المشترك (قبل الميلاد - قبل المسيح) أو في العصر المشترك (AD - Anno Domini). دعونا ننظر إلى كل هذا مع الأمثلة. لنقم بإنشاء calendarكائن بتاريخ 25 يناير 2017:
public static void main(String[] args) {

  Calendar calendar = new GregorianCalendar(2017, 0 , 25);
}
في Calendarالفصل (وكذلك Dateالفصل في هذا الشأن)، تبدأ الأشهر من الصفر ، لذلك نمرر الرقم 0 باعتباره الوسيط الثاني. عند العمل مع Calendarالفصل، من المهم أن نفهم أن هذا مجرد تقويم ، وليس تاريخًا فرديًا. كيف لا تضيع في الوقت المناسب: التاريخ والوقت والتقويم - 3 التاريخ هو مجرد أرقام قليلة تشير إلى فترة زمنية محددة. التقويم عبارة عن نظام كامل يتيح لك القيام بالكثير من الأشياء باستخدام التواريخ. :) هذا واضح تمامًا إذا حاولت عرض الكائن Calendar: الإخراج: java.util.GregorianCalendar[time=?,areFieldsSet=false,areAllFieldsSet=false,lenient=true,zone=sun.util.calendar.ZoneInfo[id= "أوروبا/لندن"، الإزاحة=0,dstSavings=0,useDaylight=false,transitions=79,lastRule=null],firstDayOfWeek=2,minimalDaysInFirstWeek=1,ERA=?,YEAR=2017,MONTH=0,WEEK_OF_YEAR=? ,WEEK_OF_MONTH=?,DAY_OF_MONTH=25,DAY_OF_YEAR=?,DAY_OF_WEEK=?,DAY_OF_WEEK_IN_MONTH=?,AM_PM=0,HOUR=0,HOUR_OF_DAY=0,MINUTE=0,SECOND=0,MILLISECOND=?,ZONE_OFFSET=?,DST_O فسسيت =؟] انظر مقدار المعلومات التي تحصل عليها! يحتوي التقويم على مجموعة من الخصائص التي لا يمتلكها التاريخ العادي، ويتم عرضها جميعًا (هذه هي الطريقة التي toString()تعمل بها الطريقة في Calendarالفصل الدراسي). إذا كنت تريد فقط الحصول على تاريخ بسيط من التقويم، أي Dateكائن، فاستخدم Calendar.getTime()الطريقة (الاسم ليس هو الأكثر منطقية، ولكن ماذا يمكنك أن تفعل؟):
public static void main(String[] args) {

   Calendar calendar = new GregorianCalendar(2017, 0 , 25);
   Date date = calendar.getTime();
   System.out.println(date);
}
الإخراج: الأربعاء 25 يناير 00:00:00 بتوقيت جرينتش 2017 الآن أخذنا التقويم و"قللناه" إلى تاريخ عادي. دعنا نذهب أبعد من ذلك. بالإضافة إلى تحديد الأشهر حسب أرقامها، يمكنك استخدام قيم الحقول الثابتةCalendar للفئة . هذه الثوابت هي حقول ثابتة للفئة ذات قيمة محددة مسبقًا لا يمكن تغييرها. يعد هذا في الواقع خيارًا أفضل، لأن استخدامها يؤدي إلى تحسين إمكانية قراءة التعليمات البرمجية الخاصة بك. Calendar
public static void main(String[] args) {
   GregorianCalendar calendar = new GregorianCalendar(2017, Calendar.JANUARY , 25);
}
Calendar.JANUARY هو أحد الثوابت التي تمثل أشهر السنة. باستخدام هذه الثوابت المسماة، لن ينسى أحد، على سبيل المثال، أن الرقم 3 يعني أبريل، وليس الشهر الثالث الذي نحب أن نسميه مارس. ما عليك سوى كتابة Calendar.APRIL ، وبذلك تكون قد انتهيت. :) يمكن تحديد جميع حقول التقويم (الرقم والشهر والدقائق والثواني وما إلى ذلك) بشكل منفصل باستخدام الطريقةset(). هذه الطريقة مريحة للغاية، لأنCalendarالفصل يحتوي على ثابت لكل حقل، ومن السهل جدًا قراءة الكود الناتج. في المثال الأخير، قمنا بإنشاء تاريخ، لكننا لم نحدد وقتًا له. لنضبط الوقت على 19:42:12
public static void main(String[] args) {
   Calendar calendar = new GregorianCalendar();
   calendar.set(Calendar.YEAR, 2017);
   calendar.set(Calendar.MONTH, 0);
   calendar.set(Calendar.DAY_OF_MONTH, 25);
   calendar.set(Calendar.HOUR_OF_DAY, 19);
   calendar.set(Calendar.MINUTE, 42);
   calendar.set(Calendar.SECOND, 12);

   System.out.println(calendar.getTime());
}
الإخراج: الأربعاء 25 يناير 19:42:12 بتوقيت جرينتش 2017 نطلق على set()الطريقة، ونمرر ثابتًا (اعتمادًا على الحقل الذي نريد تغييره) والقيمة الجديدة للحقل. اتضح أن هذه set()الطريقة هي نوع من "الضبط الفائق" الذي يعرف كيفية تعيين القيمة ليس فقط لحقل واحد، ولكن للعديد من الحقول. :) Calendarيستخدم الفصل add()طريقة إضافة وطرح القيم. تقوم بالتمرير في الحقل الذي تريد تغييره، ورقمًا (بالضبط المبلغ الذي تريد إضافته/طرحه من القيمة الحالية). على سبيل المثال، لنحصل على تاريخ قبل شهرين من التاريخ الذي أنشأناه:
public static void main(String[] args) {
   Calendar calendar = new GregorianCalendar(2017, Calendar.JANUARY , 25);
   calendar.set(Calendar.HOUR, 19);
   calendar.set(Calendar.MINUTE, 42);
   calendar.set(Calendar.SECOND, 12);

   calendar.add(Calendar.MONTH, -2); // To subtract, pass a negative number
   System.out.println(calendar.getTime());
}
الناتج: الجمعة 25 نوفمبر 19:42:12 بتوقيت جرينتش 2016 جيد جدًا! لقد حصلنا على التاريخ منذ شهرين. ولم يتسبب ذلك في تغيير الشهر فحسب، بل تغير العام أيضًا من 2017 إلى 2016. وبطبيعة الحال، عند تحويل التواريخ، يتم حساب السنة الحالية تلقائيًا دون الحاجة إلى تتبعها يدويًا. ولكن إذا كنت بحاجة لسبب ما إلى تعطيل هذا السلوك، فيمكنك القيام بذلك. يمكن لهذه roll()الطريقة إضافة وطرح القيم دون التأثير على القيم المتبقية . على سبيل المثال، مثل هذا:
public static void main(String[] args) {
   Calendar calendar = new GregorianCalendar(2017, Calendar.JANUARY , 25);
   calendar.set(Calendar.HOUR, 10);
   calendar.set(Calendar.MINUTE, 42);
   calendar.set(Calendar.SECOND, 12);

   calendar.roll(Calendar.MONTH, -2);
   System.out.println(calendar.getTime());
}
لقد فعلنا نفس الشيء تمامًا كما في المثال السابق: لقد استغرقنا شهرين من التاريخ الحالي. لكن الكود يفعل الآن شيئًا مختلفًا: لقد تغير الشهر من يناير إلى نوفمبر، لكن السنة ظلت دون تغيير — 2017! الإخراج: السبت 25 نوفمبر، الساعة 10:42:12 بتوقيت جرينتش 2017 . كما قلنا أعلاه، يمكننا الحصول على جميع Calendarالحقول بشكل منفصل. نحن نفعل ذلك بالطريقة get():
public static void main(String[] args) {
   GregorianCalendar calendar = new GregorianCalendar(2017, Calendar.JANUARY , 25);
   calendar.set(Calendar.HOUR, 10);
   calendar.set(Calendar.MINUTE, 42);
   calendar.set(Calendar.SECOND, 12);

   System.out.println("Year: " + calendar.get(Calendar.YEAR));
   System.out.println("Month: " + calendar.get(Calendar.MONTH));
   System.out.println("Week in the month: " + calendar.get(Calendar.WEEK_OF_MONTH));// Week in this month?

   System.out.println("Day: " + calendar.get(Calendar.DAY_OF_MONTH));

   System.out.println("Hours: " + calendar.get(Calendar.HOUR));
   System.out.println("Minutes: " + calendar.get(Calendar.MINUTE));
   System.out.println("Seconds: " + calendar.get(Calendar.SECOND));
   System.out.println("Milliseconds: " + calendar.get(Calendar.MILLISECOND));

}
الإخراج: السنة: 2017 الشهر: 0 الأسبوع في الشهر: 5 اليوم: 25 الساعات: 10 دقائق: 42 ثانية: 12 ميلي ثانية: 0 لذا، بالإضافة إلى Calendar"المحدد الفائق" للفصل، هناك أيضًا "مُحرِّك فائق" ". :) بالطبع، هناك جانب آخر مثير للاهتمام في هذا الفصل وهو العمل مع العصور. لإنشاء تاريخ "BC"، ستحتاج إلى استخدام حقل Calendar.ERA. على سبيل المثال، لنقم بإنشاء تاريخ لمعركة كاناي، حيث هزم هانيبال الجيش الروماني. حدث هذا في 2 أغسطس 216 ق.م:
public static void main(String[] args) {
   GregorianCalendar cannae = new GregorianCalendar(216, Calendar.AUGUST, 2);
   cannae.set(Calendar.ERA, GregorianCalendar.BC);

   DateFormat df = new SimpleDateFormat("MMM dd, yyy GG");
   System.out.println(df.format(cannae.getTime()));
}
استخدمنا هنا SimpleDateFormatالفصل لطباعة التاريخ بتنسيق يسهل علينا فهمه (يشير الحرفان "GG" إلى أننا نريد عرض العصر). الإخراج: 02 أغسطس 216 ق.م. يحتوي الفصل Calendarعلى العديد من الأساليب والثوابت. يمكنك أن تقرأ عنها في الوثائق . إذا لم يعجبك تنسيق التاريخ هذا، السبت 25 نوفمبر، الساعة 10:42:12 بتوقيت جرينتش 2017، فيمكنك استخدامه SimpleDateFormatلجعله كما تريده بسهولة.
public static void main(String[] args) {

   SimpleDateFormat dateFormat = new SimpleDateFormat("EEEE, MMMM d, yyyy");
   Calendar calendar = new GregorianCalendar(2017, Calendar.JANUARY , 25);
   calendar.set(Calendar.HOUR, 10);
   calendar.set(Calendar.MINUTE, 42);
   calendar.set(Calendar.SECOND, 12);

   calendar.roll(Calendar.MONTH, -2);
   System.out.println(dateFormat.format(calendar.getTime()));
}
الإخراج: السبت 25 نوفمبر 2017 هذا أفضل بكثير، أليس كذلك؟ :)
تعليقات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION