Dart 点将台 | DateTime 时间

一、 DateTime 的成员属性

DateTime 是和日期、时间相关的类,自身比较简单,本文从源码的角度看看内部实现,能不能寻找到一些有价值的知识,如果没有的话,就当是巩固基础啦。

当前sdk : Flutter 1.22.6Dart 2.10.5

源码位置: sky_engine/lib/core/date_time.dart

代码行数:877  

父类或实现类: Comparable

1.静态常量

DateTime 中定义了很多静态常量,包括 星期月份每周有几天每年有几个月 ,可以说非常细致。由于属性名没加下划线,所以这些常量都是可以在外部使用的。

2.私有成员

除了上面的公开常量外,还有三个私有成员,分别是 int 型的 _valueint 型的 _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(19943281428);
print(birthday); //1994-03-28 14:28:00.000

另外,可以看出构造方法是由 _internal 私有构造方法实现的,其中 DateTime._internal 是一个 external 方法,由内部实现。通过 DateTime 直接构造的对象, isUtcfalse

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 时间, isUtctrue ,其余和直接构造一致。注意: utc 的时间在 toString 时会在末尾加上一个 Z 字母。


  final DateTime birthday = DateTime.utc(19943281428);
  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(19943281428);
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(19943281428);
// 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(19943281428);
// 1993-12-18 05:28:00.000
print(birthday.subtract(Duration(days: 100,hours: 9)));

difference : 日期的减法,传入另一个日期,返回 Duration 对象,比如我想知道自己已经活了多少天,操作如下:(不知不觉,快10000天了,可怕)

final DateTime birthday = DateTime(19943281428);
final DateTime now = DateTime.now();
print(now.difference(birthday).inDays); // 9827

3.日期比较

isBeforeisAfter 比较两个日期的大小, 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

时间解析可以通过 tryParseparse 静态方法进行,两个本质是是一样的,只不过 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 == nullreturn 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 == nullreturn 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 类的用法基本上都讲完了,本篇就这样,谢谢观看~