التاريخ والوقت والمدة
فئة DateTime
فئة DateTime في Dart هي الأساس للعمل مع التواريخ والأوقات. تمثل نقطة في الزمن، إما بتوقيت UTC أو المنطقة الزمنية المحلية. فهم DateTime ضروري للجدولة والتسجيل ومعالجة البيانات وأي تطبيق حساس للوقت.
إنشاء كائنات DateTime
void main() {
// التاريخ والوقت الحالي
final now = DateTime.now();
print(now); // 2024-03-15 14:30:00.000
// تاريخ ووقت محدد
final specific = DateTime(2024, 3, 15, 14, 30, 0);
print(specific); // 2024-03-15 14:30:00.000
// وقت UTC
final utc = DateTime.utc(2024, 3, 15, 14, 30);
print(utc); // 2024-03-15 14:30:00.000Z
print(utc.isUtc); // true
// تاريخ فقط (الوقت الافتراضي 00:00:00)
final dateOnly = DateTime(2024, 3, 15);
print(dateOnly); // 2024-03-15 00:00:00.000
// الوصول للمكونات
print('السنة: ${now.year}');
print('الشهر: ${now.month}'); // 1-12
print('اليوم: ${now.day}'); // 1-31
print('الساعة: ${now.hour}'); // 0-23
print('الدقيقة: ${now.minute}'); // 0-59
print('الثانية: ${now.second}'); // 0-59
print('المللي ثانية: ${now.millisecond}');
print('المايكرو ثانية: ${now.microsecond}');
print('يوم الأسبوع: ${now.weekday}'); // 1=الإثنين ... 7=الأحد
}
DateTime في Dart تبدأ من 1 (يناير = 1، ديسمبر = 12) وأيام الأسبوع تتبع ISO 8601 (الإثنين = 1، الأحد = 7). هذا يختلف عن بعض اللغات الأخرى حيث الشهور تبدأ من 0 أو الأحد = 0.تحليل التواريخ مع DateTime.parse
طرق DateTime.parse و DateTime.tryParse تحوّل النصوص إلى كائنات DateTime. تقبل مجموعة فرعية من تنسيقات ISO 8601.
تحليل نصوص التواريخ
void main() {
// تنسيقات ISO 8601 القياسية
final d1 = DateTime.parse('2024-03-15');
print(d1); // 2024-03-15 00:00:00.000
final d2 = DateTime.parse('2024-03-15 14:30:00');
print(d2); // 2024-03-15 14:30:00.000
final d3 = DateTime.parse('2024-03-15T14:30:00Z'); // UTC
print(d3); // 2024-03-15 14:30:00.000Z
print(d3.isUtc); // true
final d4 = DateTime.parse('2024-03-15T14:30:00+05:00'); // مع إزاحة
print(d4.toUtc()); // 2024-03-15 09:30:00.000Z
// التحليل الآمن مع tryParse
final valid = DateTime.tryParse('2024-03-15');
final invalid = DateTime.tryParse('ليس-تاريخاً');
print(valid); // 2024-03-15 00:00:00.000
print(invalid); // null
// مهم: DateTime.parse لا يعالج تنسيقات مثل '03/15/2024'
// للتنسيقات المخصصة، استخدم حزمة intl (موضحة لاحقاً)
}
UTC مقابل الوقت المحلي
فهم الفرق بين UTC والوقت المحلي أمر حاسم للتطبيقات التي تتعامل مع مستخدمين في مناطق زمنية مختلفة، أو سجلات الخوادم، أو اتصالات API.
تحويل UTC والوقت المحلي
void main() {
// الوقت المحلي
final local = DateTime.now();
print('محلي: $local');
print('هل UTC: ${local.isUtc}'); // false
print('المنطقة الزمنية: ${local.timeZoneName}'); // مثل AST, EST, PST
print('الإزاحة: ${local.timeZoneOffset}'); // مثل 3:00:00.000000
// تحويل المحلي إلى UTC
final utc = local.toUtc();
print('UTC: $utc');
print('هل UTC: ${utc.isUtc}'); // true
// تحويل UTC إلى المحلي
final backToLocal = utc.toLocal();
print('العودة للمحلي: $backToLocal');
// إنشاء UTC مباشرة
final utcDirect = DateTime.utc(2024, 3, 15, 12, 0);
print(utcDirect); // 2024-03-15 12:00:00.000Z
print(utcDirect.toLocal()); // يعتمد على المنطقة الزمنية المحلية
// millisecondsSinceEpoch — طابع زمني عالمي
final epoch = local.millisecondsSinceEpoch;
print('مللي ثانية العصر: $epoch');
// إعادة البناء من العصر
final fromEpoch = DateTime.fromMillisecondsSinceEpoch(epoch);
print('من العصر: $fromEpoch');
final fromEpochUtc = DateTime.fromMillisecondsSinceEpoch(epoch, isUtc: true);
print('من العصر (UTC): $fromEpochUtc');
}
فئة Duration
فئة Duration تمثل فترة من الزمن. تُستخدم لحساب التواريخ، والمهلات، والتأخيرات، وقياس الوقت المنقضي.
العمل مع Duration
void main() {
// إنشاء كائنات Duration
final fiveMinutes = Duration(minutes: 5);
final oneHour = Duration(hours: 1);
final complex = Duration(hours: 2, minutes: 30, seconds: 15);
print(fiveMinutes); // 0:05:00.000000
print(oneHour); // 1:00:00.000000
print(complex); // 2:30:15.000000
// الوصول للمكونات
print('بالدقائق: ${complex.inMinutes}'); // 150
print('بالثواني: ${complex.inSeconds}'); // 9015
print('بالمللي ثانية: ${complex.inMilliseconds}'); // 9015000
// حساب المدة
final total = fiveMinutes + oneHour;
print('المجموع: $total'); // 1:05:00.000000
final difference = oneHour - fiveMinutes;
print('الفرق: $difference'); // 0:55:00.000000
final doubled = fiveMinutes * 2;
print('المضاعف: $doubled'); // 0:10:00.000000
// المدد السالبة
final negative = Duration(hours: -2);
print(negative); // -2:00:00.000000
print(negative.isNegative); // true
print(negative.abs()); // 2:00:00.000000
// المقارنة
print(fiveMinutes < oneHour); // true
print(fiveMinutes > oneHour); // false
print(fiveMinutes == Duration(seconds: 300)); // true
}
حساب التواريخ
يمكنك إضافة أو طرح Duration من DateTime لحساب التواريخ المستقبلية أو الماضية. للعمليات المدركة للتقويم (مثل إضافة أشهر)، تحتاج حسابات يدوية.
إضافة وطرح الوقت
void main() {
final now = DateTime(2024, 3, 15, 10, 0);
// إضافة مدة
final inTwoHours = now.add(Duration(hours: 2));
print(inTwoHours); // 2024-03-15 12:00:00.000
final tomorrow = now.add(Duration(days: 1));
print(tomorrow); // 2024-03-16 10:00:00.000
final nextWeek = now.add(Duration(days: 7));
print(nextWeek); // 2024-03-22 10:00:00.000
// طرح مدة
final yesterday = now.subtract(Duration(days: 1));
print(yesterday); // 2024-03-14 10:00:00.000
// الفرق بين تاريخين
final start = DateTime(2024, 1, 1);
final end = DateTime(2024, 12, 31);
final diff = end.difference(start);
print('الأيام بينهما: ${diff.inDays}'); // 365
// إضافة أشهر (مدرك للتقويم — يجب أن يتم يدوياً)
DateTime addMonths(DateTime date, int months) {
var year = date.year;
var month = date.month + months;
while (month > 12) {
month -= 12;
year++;
}
while (month < 1) {
month += 12;
year--;
}
// تقييد اليوم للنطاق الصالح للشهر المستهدف
final maxDay = DateTime(year, month + 1, 0).day;
final day = date.day > maxDay ? maxDay : date.day;
return DateTime(year, month, day, date.hour, date.minute, date.second);
}
final jan31 = DateTime(2024, 1, 31);
print(addMonths(jan31, 1)); // 2024-02-29 (سنة كبيسة، مقيّد!)
print(addMonths(jan31, 2)); // 2024-03-31
}
Duration(days: 30) لا تضيف بشكل صحيح “شهراً واحداً” لأن الأشهر لها أطوال مختلفة (28-31 يوماً). استخدم دائماً منطقاً مدركاً للتقويم عند العمل مع الأشهر أو السنوات. وبالمثل، إضافة Duration(days: 365) ليست دائماً “سنة واحدة” بسبب السنوات الكبيسة.مقارنة التواريخ
يوفر Dart عدة طرق لمقارنة كائنات DateTime. فهم دلالات المقارنة مهم للترتيب والتصفية والجدولة.
طرق مقارنة التواريخ
void main() {
final date1 = DateTime(2024, 3, 15);
final date2 = DateTime(2024, 6, 20);
final date3 = DateTime(2024, 3, 15);
// عوامل المقارنة
print(date1.isBefore(date2)); // true
print(date1.isAfter(date2)); // false
print(date1.isAtSameMomentAs(date3)); // true
// compareTo للترتيب
print(date1.compareTo(date2)); // سالب (date1 قبل date2)
print(date2.compareTo(date1)); // موجب
print(date1.compareTo(date3)); // 0 (متساويان)
// ترتيب قائمة من التواريخ
final dates = [
DateTime(2024, 12, 25),
DateTime(2024, 1, 1),
DateTime(2024, 7, 4),
DateTime(2024, 3, 15),
];
dates.sort((a, b) => a.compareTo(b));
print(dates.map((d) => '${d.month}/${d.day}').toList());
// [1/1, 3/15, 7/4, 12/25]
// التحقق مما إذا كان التاريخ ضمن نطاق
bool isInRange(DateTime date, DateTime start, DateTime end) {
return !date.isBefore(start) && !date.isAfter(end);
}
final checkDate = DateTime(2024, 5, 10);
print(isInRange(checkDate, date1, date2)); // true
}
تنسيق التواريخ مع حزمة intl
طريقة DateTime.toString() المدمجة في Dart تنتج تنسيق ISO، الذي ليس صديقاً للمستخدم. حزمة intl توفر DateFormat للتنسيق القابل للتخصيص والمدرك للغة.
تنسيق التواريخ مع DateFormat
import 'package:intl/intl.dart';
void main() {
final now = DateTime(2024, 3, 15, 14, 30, 0);
// تنسيقات محددة مسبقاً
print(DateFormat.yMMMd().format(now)); // Mar 15, 2024
print(DateFormat.yMMMMEEEEd().format(now)); // Friday, March 15, 2024
print(DateFormat.Hm().format(now)); // 14:30
print(DateFormat.jm().format(now)); // 2:30 PM
// أنماط مخصصة
print(DateFormat('yyyy-MM-dd').format(now)); // 2024-03-15
print(DateFormat('dd/MM/yyyy').format(now)); // 15/03/2024
print(DateFormat('EEEE, MMMM d, y').format(now)); // Friday, March 15, 2024
print(DateFormat('h:mm a').format(now)); // 2:30 PM
print(DateFormat('MMM d, y ''at'' h:mm a').format(now)); // Mar 15, 2024 at 2:30 PM
// تنسيق خاص بالمنطقة
print(DateFormat.yMMMd('ar').format(now)); // ١٥ مارس ٢٠٢٤
print(DateFormat.yMMMd('fr').format(now)); // 15 mars 2024
print(DateFormat.yMMMd('ja').format(now)); // 2024/03/15
// التحليل مع DateFormat
final parsed = DateFormat('MM/dd/yyyy').parse('03/15/2024');
print(parsed); // 2024-03-15 00:00:00.000
}
intl، أضفها إلى pubspec.yaml: dependencies: intl: ^0.19.0. الحزمة توفر أيضاً تنسيق الأرقام وقواعد الجمع وقدرات ترجمة الرسائل بخلاف التواريخ فقط.Stopwatch لقياس الوقت المنقضي
فئة Stopwatch توفر قياس وقت دقيق لتحليل الأداء والمقارنة المرجعية.
استخدام Stopwatch
void main() {
final stopwatch = Stopwatch();
// بدء القياس
stopwatch.start();
// محاكاة عمل
int sum = 0;
for (var i = 0; i < 1000000; i++) {
sum += i;
}
// التوقف والقراءة
stopwatch.stop();
print('المنقضي: ${stopwatch.elapsed}');
print('مللي ثانية: ${stopwatch.elapsedMilliseconds}');
print('مايكرو ثانية: ${stopwatch.elapsedMicroseconds}');
// إعادة التعيين وإعادة الاستخدام
stopwatch.reset();
print('بعد إعادة التعيين: ${stopwatch.elapsedMilliseconds}'); // 0
// توقيت اللفات
stopwatch.start();
// ... المرحلة 1 ...
final lap1 = stopwatch.elapsedMilliseconds;
// ... المرحلة 2 ...
final lap2 = stopwatch.elapsedMilliseconds;
stopwatch.stop();
print('المرحلة 1: ${lap1}ms');
print('المرحلة 2: ${lap2 - lap1}ms');
print('الإجمالي: ${stopwatch.elapsedMilliseconds}ms');
}
مثال عملي: حاسبة العمر
حساب عمر الشخص يتطلب معالجة دقيقة للأشهر والأيام، وليس فقط طرح السنوات.
حاسبة عمر دقيقة
class Age {
final int years;
final int months;
final int days;
Age(this.years, this.months, this.days);
@override
String toString() => '$years سنة، $months شهر، $days يوم';
}
Age calculateAge(DateTime birthDate, [DateTime? referenceDate]) {
final now = referenceDate ?? DateTime.now();
int years = now.year - birthDate.year;
int months = now.month - birthDate.month;
int days = now.day - birthDate.day;
if (days < 0) {
months--;
// أيام الشهر السابق
final prevMonth = DateTime(now.year, now.month, 0);
days += prevMonth.day;
}
if (months < 0) {
years--;
months += 12;
}
return Age(years, months, days);
}
void main() {
final birthday = DateTime(1995, 8, 15);
final today = DateTime(2024, 3, 15);
final age = calculateAge(birthday, today);
print('العمر: $age'); // العمر: 28 سنة، 7 شهر، 0 يوم
// الأيام حتى عيد الميلاد التالي
var nextBirthday = DateTime(today.year, birthday.month, birthday.day);
if (nextBirthday.isBefore(today) || nextBirthday.isAtSameMomentAs(today)) {
nextBirthday = DateTime(today.year + 1, birthday.month, birthday.day);
}
final daysUntil = nextBirthday.difference(today).inDays;
print('الأيام حتى عيد الميلاد التالي: $daysUntil'); // 153
}
مثال عملي: مؤقت العد التنازلي
مؤقت العد التنازلي يحسب الوقت المتبقي حتى تاريخ مستهدف ويعرضه بتنسيق قابل للقراءة.
مؤقت العد التنازلي
class Countdown {
final DateTime target;
Countdown(this.target);
Duration get remaining {
final now = DateTime.now();
if (now.isAfter(target)) return Duration.zero;
return target.difference(now);
}
bool get isExpired => DateTime.now().isAfter(target);
String get formatted {
final r = remaining;
if (r == Duration.zero) return 'انتهى!';
final days = r.inDays;
final hours = r.inHours % 24;
final minutes = r.inMinutes % 60;
final seconds = r.inSeconds % 60;
final parts = <String>[];
if (days > 0) parts.add('$days يوم');
if (hours > 0) parts.add('$hours ساعة');
if (minutes > 0) parts.add('$minutes دقيقة');
if (seconds > 0) parts.add('$seconds ثانية');
return parts.join('، ');
}
}
void main() {
// العد التنازلي لرأس السنة 2025
final newYear = DateTime(2025, 1, 1);
final countdown = Countdown(newYear);
print('الوقت حتى رأس السنة: ${countdown.formatted}');
print('هل انتهى: ${countdown.isExpired}');
}
مثال عملي: أدوات نطاق التاريخ
عمليات نطاق التاريخ شائعة في الجدولة والتقارير وتطبيقات التقويم.
فئة نطاق التاريخ
class DateRange {
final DateTime start;
final DateTime end;
DateRange(this.start, this.end) : assert(!end.isBefore(start));
Duration get duration => end.difference(start);
int get dayCount => duration.inDays + 1; // شامل
bool contains(DateTime date) {
return !date.isBefore(start) && !date.isAfter(end);
}
bool overlaps(DateRange other) {
return !end.isBefore(other.start) && !start.isAfter(other.end);
}
DateRange? intersection(DateRange other) {
if (!overlaps(other)) return null;
final s = start.isAfter(other.start) ? start : other.start;
final e = end.isBefore(other.end) ? end : other.end;
return DateRange(s, e);
}
/// يولّد جميع التواريخ في النطاق (شامل).
Iterable<DateTime> get days sync* {
var current = DateTime(start.year, start.month, start.day);
final endDate = DateTime(end.year, end.month, end.day);
while (!current.isAfter(endDate)) {
yield current;
current = current.add(Duration(days: 1));
}
}
/// يُرجع أيام العمل فقط (الإثنين-الجمعة).
Iterable<DateTime> get weekdays =>
days.where((d) => d.weekday <= 5);
@override
String toString() => '$start - $end ($dayCount يوم)';
}
void main() {
final q1 = DateRange(DateTime(2024, 1, 1), DateTime(2024, 3, 31));
final march = DateRange(DateTime(2024, 3, 1), DateTime(2024, 3, 31));
print('الربع الأول: ${q1.dayCount} يوم'); // الربع الأول: 91 يوم
print('الربع الأول يحتوي 15 مارس: ${q1.contains(DateTime(2024, 3, 15))}'); // true
print('الربع الأول يتداخل مع مارس: ${q1.overlaps(march)}'); // true
final overlap = q1.intersection(march);
print('التداخل: $overlap'); // 1-31 مارس
// أيام العمل في مارس 2024
final businessDays = march.weekdays.length;
print('أيام العمل في مارس: $businessDays'); // 21
}
مثال عملي: نظام جدولة بسيط
الجمع بين DateTime و Duration ونطاقات التواريخ لبناء نظام جدولة أساسي.
مُجدول الأحداث
class ScheduleEvent {
final String title;
final DateTime start;
final Duration duration;
ScheduleEvent(this.title, this.start, this.duration);
DateTime get end => start.add(duration);
bool conflictsWith(ScheduleEvent other) {
return start.isBefore(other.end) && end.isAfter(other.start);
}
@override
String toString() {
final endTime = end;
return '$title: ${_formatTime(start)} - ${_formatTime(endTime)}';
}
String _formatTime(DateTime dt) =>
'${dt.hour.toString().padLeft(2, '0')}:${dt.minute.toString().padLeft(2, '0')}';
}
class Scheduler {
final List<ScheduleEvent> _events = [];
List<ScheduleEvent> get events => List.unmodifiable(_events);
bool addEvent(ScheduleEvent event) {
// التحقق من التعارضات
for (final existing in _events) {
if (existing.conflictsWith(event)) {
print('تعارض: "${event.title}" يتداخل مع "${existing.title}"');
return false;
}
}
_events.add(event);
_events.sort((a, b) => a.start.compareTo(b.start));
return true;
}
List<ScheduleEvent> eventsOn(DateTime date) {
return _events.where((e) =>
e.start.year == date.year &&
e.start.month == date.month &&
e.start.day == date.day).toList();
}
}
void main() {
final scheduler = Scheduler();
final today = DateTime(2024, 3, 15);
scheduler.addEvent(ScheduleEvent(
'اجتماع الفريق',
DateTime(2024, 3, 15, 9, 0),
Duration(hours: 1),
));
scheduler.addEvent(ScheduleEvent(
'الغداء',
DateTime(2024, 3, 15, 12, 0),
Duration(minutes: 45),
));
// هذا سيُظهر تعارض
scheduler.addEvent(ScheduleEvent(
'مكالمة متعارضة',
DateTime(2024, 3, 15, 9, 30),
Duration(minutes: 30),
));
print('\nالجدول لليوم:');
for (final event in scheduler.eventsOn(today)) {
print(' $event');
}
// اجتماع الفريق: 09:00 - 10:00
// الغداء: 12:00 - 12:45
}
timezone لدعم مناطق IANA الزمنية المناسبة. DateTime المدمج في Dart يدعم فقط UTC والمنطقة الزمنية المحلية للنظام، وهو غير كافٍ للتطبيقات التي تجدول أحداثاً عبر مناطق زمنية متعددة.الملخص
يوفر Dart أدوات متينة لعمليات التاريخ والوقت والمدة. النقاط الرئيسية:
DateTimeيمثل نقطة في الزمن؛ أنشئ بالمنشئات أو.now()أو.parse().- خزّن/أرسل دائماً بـ UTC؛ حوّل للمحلي فقط للعرض.
Durationيمثل فترة من الزمن؛ استخدمadd()وsubtract()لحساب التواريخ.- إضافة الأشهر/السنوات تتطلب منطقاً مدركاً للتقويم —
Durationوحدها غير كافية. - استخدم حزمة
intl(DateFormat) للتنسيق المدرك للغة وتحليل نصوص التواريخ المخصصة. Stopwatchمثالي لقياس الوقت المنقضي في الكود الحساس للأداء.- ابنِ أدوات مثل
DateRangeلاكتشاف التداخل والتكرار وعدّ أيام العمل.