Dart 点将台 | DateTime 时间
一、 DateTime 的成员属性
DateTime 是和日期、时间相关的类,自身比较简单,本文从源码的角度看看内部实现,能不能寻找到一些有价值的知识,如果没有的话,就当是巩固基础啦。
当前sdk : Flutter 1.22.6
— Dart 2.10.5
源码位置: sky_engine/lib/core/date_time.dart
代码行数:877
父类或实现类: Comparable
1.静态常量
DateTime 中定义了很多静态常量,包括 星期
、 月份
、 每周有几天
、 每年有几个月
,可以说非常细致。由于属性名没加下划线,所以这些常量都是可以在外部使用的。
2.私有成员
除了上面的公开常量外,还有三个私有成员,分别是 int
型的 _value
; int
型的 _maxMillisecondsSinceEpoch
,表示最大时间。还有 RegExp
类型的正则对象用于格式化。
final int _value;
static const int _maxMillisecondsSinceEpoch = 8640000000000000;
static final RegExp _parseFormat =
RegExp(r'^([+-]?\d{4,6})-?(\d\d)-?(\d\d)' // Day part.
r'(?:[ T](\d\d)(?::?(\d\d)(?::?(\d\d)(?:[.,](\d+))?)?)?' // Time part.
r'( ?[zZ]| ?([-+])(\d\d)(?::?(\d\d))?)?)?$'); // Timezone part.
3.科普小常识:UTC
除此之外,还有一个 isUtc
的 final 成员。 UTC
即世界协调时间 (Universal Time Coordinated)
,也就是 0 时区的时间。本初子午线被定义为通过格林威治经线的位置,相对这条经线的时区向东递增,向西递减,每隔一个时区,相差一个小时。
final bool isUtc;
与之相对的是本地时间 (LocalTime)
。如现在是北京时间 2021/2/21 14:01:06
,北京在东八区,比 UTC 时间早 8 小时。那 UTC 时间就是 2021/2/21 06:01:06
。Dart 在提供了 toUtc
的方法获取 UTC 时间。
final DateTime dateTime = DateTime.now();
print(dateTime); // 2021-02-21 14:01:06.280487
print(dateTime.isUtc);//false
print(dateTime.toUtc());//2021-02-21 06:01:06.280487Z
二、DateTime 构造函数
DateTime 外界可用的有如下五个构造函数。下面来逐一看一下使用方式:
1.直接构造
在函数篇我们说过 参数列表中的 []
是可选参数,且参数必须按顺序传递。对于时间来说 []
的使用就恰到好处,约束使用者必须按顺序传递:年、月、日、时、分、秒、毫秒。
final DateTime birthday = DateTime(1994, 3, 28, 14, 28);
print(birthday); //1994-03-28 14:28:00.000
另外,可以看出构造方法是由 _internal
私有构造方法实现的,其中 DateTime._internal
是一个 external
方法,由内部实现。通过 DateTime
直接构造的对象, isUtc
为 false
。
external DateTime._internal(int year, int month, int day, int hour,
int minute, int second, int millisecond, int microsecond, bool isUtc);
2.DateTime.utc 构造
顾名思义,就是构造 utc
时间, isUtc
为 true
,其余和直接构造一致。注意: utc
的时间在 toString
时会在末尾加上一个 Z
字母。
final DateTime birthday = DateTime.utc(1994, 3, 28, 14, 28);
print(birthday); //1994-03-28 14:28:00.000Z
3.毫秒时间戳构造
这个也是很常用的一个构造,可用通过时间戳创建 DateTime
对象。它某时刻对比与 1970-01-01
经过的毫秒数值。
final DateTime birthday = DateTime.fromMillisecondsSinceEpoch(764836080000);
print(birthday); //1994-03-28 14:28:00.000
不知道你有没有发现一个小点,那就是上面的红框中有个 Z
,说明时间起始点是 UTC 时间。默认 isUtc 是 false,那么 DateTime.fromMillisecondsSinceEpoch(0)
创建的本地时,如下测试中,对于北京时间而言,即使是时间戳为 0 ,相较于 UTC 参考起点时间,也是过了 8 小时的。在这个构造中, isUtc
是可设置的参数,当你读取时间戳时,注意一点就行了。
final DateTime zeroLocal = DateTime.fromMillisecondsSinceEpoch(0);
final DateTime zeroUTC = DateTime.fromMillisecondsSinceEpoch(0,isUtc: true);
print(zeroLocal); //1970-01-01 08:00:00.000
print(zeroUTC); //1970-01-01 00:00:00.000Z
4.微秒时间戳构造
这个用法和要点与毫秒时间戳基本一致,就是微秒级别更加精细。
final DateTime birthday =
DateTime.fromMicrosecondsSinceEpoch(764836080000000);
print(birthday);//1994-03-28 14:28:00.000
5.当前时刻构造
DateTime.now
构造可以获取当前的时刻,是通过 DateTime._now
私有构造实现的,也是个 external
方法。
final DateTime dateTime = DateTime.now();
print(dateTime); // 2021-02-21 14:01:06.280487
三、DateTime 方法
1. get 关键字方法
DateTime 对象有很多 get 方法,它们都是 external
。
final DateTime birthday = DateTime(1994, 3, 28, 14, 28);
print('millisecondsSinceEpoch: ${birthday.millisecondsSinceEpoch}');
print('microsecondsSinceEpoch: ${birthday.microsecondsSinceEpoch}');
print('timeZoneName: ${birthday.timeZoneName}');
print('timeZoneOffset: ${birthday.timeZoneOffset}');
print('year: ${birthday.year}');
print('month: ${birthday.month}');
print('day: ${birthday.day}');
print('hour: ${birthday.hour}');
print('minute: ${birthday.minute}');
print('second: ${birthday.second}');
print('millisecond: ${birthday.millisecond}');
print('microsecond: ${birthday.microsecond}');
print('weekday: ${birthday.weekday}');
---->[打印结果]----
millisecondsSinceEpoch: 764836080000
microsecondsSinceEpoch: 764836080000000
timeZoneName: CST
timeZoneOffset: 8:00:00.000000
year: 1994
month: 3
day: 28
hour: 14
minute: 28
second: 0
millisecond: 0
microsecond: 0
weekday: 1
2.日期运算
add
: 日期的加法,传入 Duration
对象,返回日期。比如想知道 百日,两小时,两分钟
后是什么时间,操作如下。
final DateTime birthday = DateTime(1994, 3, 28, 14, 28);
// 1994-07-06 16:30:00.000
print(birthday.add(Duration(days: 100,hours: 2,minutes: 2)));
subtract
:日期的减法,传入另一个 Duration
对象,返回日期。比如想知道出生前100天,9小时是什么时间,操作如下。
final DateTime birthday = DateTime(1994, 3, 28, 14, 28);
// 1993-12-18 05:28:00.000
print(birthday.subtract(Duration(days: 100,hours: 9)));
difference
: 日期的减法,传入另一个日期,返回 Duration
对象,比如我想知道自己已经活了多少天,操作如下:(不知不觉,快10000天了,可怕)
final DateTime birthday = DateTime(1994, 3, 28, 14, 28);
final DateTime now = DateTime.now();
print(now.difference(birthday).inDays); // 9827
3.日期比较
isBefore
和 isAfter
比较两个日期的大小, isAtSameMomentAs
比较两个时间是否是同一时刻。注意一点,北京时间 2021/2/21 14:01:06
和 UTC 时间 2021/2/21 06:01:06
是同一时刻。但两个时间对象是不等的。
DateTime now = new DateTime.now();
DateTime later = now.add(const Duration(seconds: 5));
print(later.isBefore(now)); //false
print(later.isAfter(now)); //true
print(later.isAtSameMomentAs(now)); //false
print(now.isAtSameMomentAs(now.toUtc())); //true
print(now == now.toUtc()); //false
4.时间的解析
下面 DateTime.parse
的使用:
print(DateTime.parse('2012-02-27'));
print(DateTime.parse('2012-02-27 13:27:00'));
print(DateTime.parse('2012-02-27 13:27:00.123456789z'));
print(DateTime.parse('2012-02-27 13:27:00,123456789z'));
print(DateTime.parse('20120227 13:27:00'));
print(DateTime.parse('20120227T132700'));
print(DateTime.parse('20120227'));
print(DateTime.parse('+20120227'));
print(DateTime.parse('2012-02-27T14Z'));
print(DateTime.parse('2012-02-27T14+00:00'));
print(DateTime.parse('-123450101 00:00:00 Z'));
print(DateTime.parse('2002-02-27T14:00:00-0500'));
print(DateTime.parse('2002-02-27T19:00:00Z'));
---->[打印结果]----
2012-02-27 00:00:00.000
2012-02-27 13:27:00.000
2012-02-27 13:27:00.123456Z
2012-02-27 13:27:00.123456Z
2012-02-27 13:27:00.000
2012-02-27 13:27:00.000
2012-02-27 00:00:00.000
2012-02-27 00:00:00.000
2012-02-27 14:00:00.000Z
2012-02-27 14:00:00.000Z
-12345-01-01 00:00:00.000Z
2002-02-27 19:00:00.000Z
2002-02-27 19:00:00.000Z
时间解析可以通过 tryParse
和 parse
静态方法进行,两个本质是是一样的,只不过 tryParse
可以返回 null,并且进行了异常处理,核心逻辑还是 parse
方法。
static DateTime? tryParse(String formattedString) {
// TODO: Optimize to avoid throwing.
try {
return parse(formattedString);
} on FormatException {
return null;
}
}
parse
方法中通过 _parseFormat
正则表达式进行匹配解析。
static DateTime parse(String formattedString) {
var re = _parseFormat;
Match? match = re.firstMatch(formattedString);
if (match != null) {
int parseIntOrZero(String? matched) {
if (matched == null) return 0;
return int.parse(matched);
}
// Parses fractional second digits of '.(\d+)' into the combined
// microseconds. We only use the first 6 digits because of DateTime
// precision of 999 milliseconds and 999 microseconds.
int parseMilliAndMicroseconds(String? matched) {
if (matched == null) return 0;
int length = matched.length;
assert(length >= 1);
int result = 0;
for (int i = 0; i < 6; i++) {
result *= 10;
if (i < matched.length) {
result += matched.codeUnitAt(i) ^ 0x30;
}
}
return result;
}
int years = int.parse(match[1]!);
int month = int.parse(match[2]!);
int day = int.parse(match[3]!);
int hour = parseIntOrZero(match[4]);
int minute = parseIntOrZero(match[5]);
int second = parseIntOrZero(match[6]);
int milliAndMicroseconds = parseMilliAndMicroseconds(match[7]);
int millisecond =
milliAndMicroseconds ~/ Duration.microsecondsPerMillisecond;
int microsecond = milliAndMicroseconds
.remainder(Duration.microsecondsPerMillisecond) as int;
bool isUtc = false;
if (match[8] != null) {
// timezone part
isUtc = true;
String? tzSign = match[9];
if (tzSign != null) {
// timezone other than 'Z' and 'z'.
int sign = (tzSign == '-') ? -1 : 1;
int hourDifference = int.parse(match[10]!);
int minuteDifference = parseIntOrZero(match[11]);
minuteDifference += 60 * hourDifference;
minute -= sign * minuteDifference;
}
}
int? value = _brokenDownDateToValue(years, month, day, hour, minute,
second, millisecond, microsecond, isUtc);
if (value == null) {
throw FormatException("Time out of range", formattedString);
}
return DateTime._withValue(value, isUtc: isUtc);
} else {
throw FormatException("Invalid date format", formattedString);
}
}
到这里 DateTime
类的用法基本上都讲完了,本篇就这样,谢谢观看~