{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 时间日期和时间增量\n",
    "\n",
    "## datetime64 基础\n",
    "\n",
    "在 numpy 中，我们很方便的将字符串转换成时间日期类型 `datetime64`（`datetime` 已被 python 包含的日期时间库所占用）。\n",
    "\n",
    "`datatime64`是带单位的日期时间类型，其单位如下：\n",
    "\n",
    "日期单位 | 代码含义|时间单位 | 代码含义\n",
    ":---:|:---:|:---:|:---:\n",
    "Y | 年 |h | 小时\n",
    "M | 月 |m | 分钟\n",
    "W | 周 |s | 秒\n",
    "D | 天 |ms | 毫秒\n",
    "- | -  |us | 微秒\n",
    "- | -  |ns | 纳秒\n",
    "- | -  |ps | 皮秒\n",
    "- | -  |fs | 飞秒\n",
    "- | -  |as | 阿托秒\n",
    "\n",
    "注意：\n",
    "- 1秒 = 1000 毫秒（milliseconds）\n",
    "- 1毫秒 = 1000 微秒（microseconds）\n",
    "\n",
    "【例】从字符串创建 datetime64 类型时，默认情况下，numpy 会根据字符串自动选择对应的单位。\n",
    "```python\n",
    "import numpy as np\n",
    "\n",
    "a = np.datetime64('2020-03-01')\n",
    "print(a, a.dtype)  # 2020-03-01 datetime64[D]\n",
    "\n",
    "a = np.datetime64('2020-03')\n",
    "print(a, a.dtype)  # 2020-03 datetime64[M]\n",
    "\n",
    "a = np.datetime64('2020-03-08 20:00:05')\n",
    "print(a, a.dtype)  # 2020-03-08T20:00:05 datetime64[s]\n",
    "\n",
    "a = np.datetime64('2020-03-08 20:00')\n",
    "print(a, a.dtype)  # 2020-03-08T20:00 datetime64[m]\n",
    "\n",
    "a = np.datetime64('2020-03-08 20')\n",
    "print(a, a.dtype)  # 2020-03-08T20 datetime64[h]\n",
    "```\n",
    "\n",
    "\n",
    "\n",
    "\n",
    "\n",
    "【例】从字符串创建 datetime64 类型时，可以强制指定使用的单位。\n",
    "```python\n",
    "import numpy as np\n",
    "\n",
    "a = np.datetime64('2020-03', 'D')\n",
    "print(a, a.dtype)  # 2020-03-01 datetime64[D]\n",
    "\n",
    "a = np.datetime64('2020-03', 'Y')\n",
    "print(a, a.dtype)  # 2020 datetime64[Y]\n",
    "\n",
    "print(np.datetime64('2020-03') == np.datetime64('2020-03-01'))  # True\n",
    "print(np.datetime64('2020-03') == np.datetime64('2020-03-02'))  #False\n",
    "```\n",
    "\n",
    "由上例可以看出，2019-03 和 2019-03-01 所表示的其实是同一个时间。\n",
    "事实上，如果两个 datetime64 对象具有不同的单位，它们可能仍然代表相同的时刻。并且从较大的单位（如月份）转换为较小的单位（如天数）是安全的。\n",
    "\n",
    "\n",
    "【例】从字符串创建 datetime64 数组时，如果单位不统一，则一律转化成其中最小的单位。\n",
    "```python\n",
    "import numpy as np\n",
    "\n",
    "a = np.array(['2020-03', '2020-03-08', '2020-03-08 20:00'], dtype='datetime64')\n",
    "print(a, a.dtype)\n",
    "# ['2020-03-01T00:00' '2020-03-08T00:00' '2020-03-08T20:00'] datetime64[m]\n",
    "```\n",
    "\n",
    "\n",
    "【例】使用`arange()`创建 datetime64 数组，用于生成日期范围。\n",
    "```python\n",
    "import numpy as np\n",
    "\n",
    "a = np.arange('2020-08-01', '2020-08-10', dtype=np.datetime64)\n",
    "print(a)\n",
    "# ['2020-08-01' '2020-08-02' '2020-08-03' '2020-08-04' '2020-08-05'\n",
    "#  '2020-08-06' '2020-08-07' '2020-08-08' '2020-08-09']\n",
    "print(a.dtype)  # datetime64[D]\n",
    "\n",
    "a = np.arange('2020-08-01 20:00', '2020-08-10', dtype=np.datetime64)\n",
    "print(a)\n",
    "# ['2020-08-01T20:00' '2020-08-01T20:01' '2020-08-01T20:02' ...\n",
    "#  '2020-08-09T23:57' '2020-08-09T23:58' '2020-08-09T23:59']\n",
    "print(a.dtype)  # datetime64[m]\n",
    "\n",
    "a = np.arange('2020-05', '2020-12', dtype=np.datetime64)\n",
    "print(a)\n",
    "# ['2020-05' '2020-06' '2020-07' '2020-08' '2020-09' '2020-10' '2020-11']\n",
    "print(a.dtype)  # datetime64[M]\n",
    "```\n",
    "\n",
    "## datetime64 和 timedelta64 运算\n",
    "\n",
    "【例】timedelta64 表示两个 datetime64 之间的差。timedelta64 也是带单位的，并且和相减运算中的两个 datetime64 中的较小的单位保持一致。\n",
    "```python\n",
    "import numpy as np\n",
    "\n",
    "a = np.datetime64('2020-03-08') - np.datetime64('2020-03-07')\n",
    "b = np.datetime64('2020-03-08') - np.datetime64('2020-03-07 08:00')\n",
    "c = np.datetime64('2020-03-08') - np.datetime64('2020-03-07 23:00', 'D')\n",
    "\n",
    "print(a, a.dtype)  # 1 days timedelta64[D]\n",
    "print(b, b.dtype)  # 956178240 minutes timedelta64[m]\n",
    "print(c, c.dtype)  # 1 days timedelta64[D]\n",
    "\n",
    "a = np.datetime64('2020-03') + np.timedelta64(20, 'D')\n",
    "b = np.datetime64('2020-06-15 00:00') + np.timedelta64(12, 'h')\n",
    "print(a, a.dtype)  # 2020-03-21 datetime64[D]\n",
    "print(b, b.dtype)  # 2020-06-15T12:00 datetime64[m]\n",
    "```\n",
    "\n",
    "【例】生成 timedelta64时，要注意年（'Y'）和月（'M'）这两个单位无法和其它单位进行运算（一年有几天？一个月有几个小时？这些都是不确定的）。\n",
    "```python\n",
    "import numpy as np\n",
    "\n",
    "a = np.timedelta64(1, 'Y')\n",
    "b = np.timedelta64(a, 'M')\n",
    "print(a)  # 1 years\n",
    "print(b)  # 12 months\n",
    "\n",
    "c = np.timedelta64(1, 'h')\n",
    "d = np.timedelta64(c, 'm')\n",
    "print(c)  # 1 hours\n",
    "print(d)  # 60 minutes\n",
    "\n",
    "print(np.timedelta64(a, 'D'))\n",
    "# TypeError: Cannot cast NumPy timedelta64 scalar from metadata [Y] to [D] according to the rule 'same_kind'\n",
    "\n",
    "print(np.timedelta64(b, 'D'))\n",
    "# TypeError: Cannot cast NumPy timedelta64 scalar from metadata [M] to [D] according to the rule 'same_kind'\n",
    "```\n",
    "\n",
    "\n",
    "【例】timedelta64 的运算。\n",
    "```python\n",
    "import numpy as np\n",
    "\n",
    "a = np.timedelta64(1, 'Y')\n",
    "b = np.timedelta64(6, 'M')\n",
    "c = np.timedelta64(1, 'W')\n",
    "d = np.timedelta64(1, 'D')\n",
    "e = np.timedelta64(10, 'D')\n",
    "\n",
    "print(a)  # 1 years\n",
    "print(b)  # 6 months\n",
    "print(a + b)  # 18 months\n",
    "print(a - b)  # 6 months\n",
    "print(2 * a)  # 2 years\n",
    "print(a / b)  # 2.0\n",
    "print(c / d)  # 7.0\n",
    "print(c % e)  # 7 days\n",
    "```\n",
    "\n",
    "【例】numpy.datetime64 与 datetime.datetime 相互转换\n",
    "```python\n",
    "import numpy as np\n",
    "import datetime\n",
    "\n",
    "dt = datetime.datetime(year=2020, month=6, day=1, hour=20, minute=5, second=30)\n",
    "dt64 = np.datetime64(dt, 's')\n",
    "print(dt64, dt64.dtype)\n",
    "# 2020-06-01T20:05:30 datetime64[s]\n",
    "\n",
    "dt2 = dt64.astype(datetime.datetime)\n",
    "print(dt2, type(dt2))\n",
    "# 2020-06-01 20:05:30 <class 'datetime.datetime'>\n",
    "```\n",
    "\n",
    "## datetime64 的应用\n",
    "\n",
    "为了允许在只有一周中某些日子有效的上下文中使用日期时间，NumPy包含一组“busday”（工作日）功能。\n",
    "\n",
    "- `numpy.busday_offset(dates, offsets, roll='raise', weekmask='1111100', holidays=None, busdaycal=None, out=None)` First adjusts the date to fall on a valid day according to the roll rule, then applies offsets to the given dates counted in valid days.\n",
    "\n",
    "参数`roll`：{'raise', 'nat', 'forward', 'following', 'backward', 'preceding', 'modifiedfollowing', 'modifiedpreceding'}\n",
    "- 'raise' means to raise an exception for an invalid day.\n",
    "- 'nat' means to return a NaT (not-a-time) for an invalid day.\n",
    "- 'forward' and 'following' mean to take the first valid day later in time.\n",
    "- 'backward' and 'preceding' mean to take the first valid day earlier in time.\n",
    "\n",
    "\n",
    "【例】将指定的偏移量应用于工作日，单位天（'D'）。计算下一个工作日，如果当前日期为非工作日，默认报错。可以指定 `forward` 或 `backward` 规则来避免报错。（一个是向前取第一个有效的工作日，一个是向后取第一个有效的工作日）\n",
    "```python\n",
    "import numpy as np\n",
    "\n",
    "# 2020-07-10 星期五\n",
    "a = np.busday_offset('2020-07-10', offsets=1)\n",
    "print(a)  # 2020-07-13\n",
    "\n",
    "a = np.busday_offset('2020-07-11', offsets=1)\n",
    "print(a)\n",
    "# ValueError: Non-business day date in busday_offset\n",
    "\n",
    "a = np.busday_offset('2020-07-11', offsets=0, roll='forward')\n",
    "b = np.busday_offset('2020-07-11', offsets=0, roll='backward')\n",
    "print(a)  # 2020-07-13\n",
    "print(b)  # 2020-07-10\n",
    "\n",
    "a = np.busday_offset('2020-07-11', offsets=1, roll='forward')\n",
    "b = np.busday_offset('2020-07-11', offsets=1, roll='backward')\n",
    "print(a)  # 2020-07-14\n",
    "print(b)  # 2020-07-13\n",
    "```\n",
    "\n",
    "可以指定偏移量为 0 来获取当前日期向前或向后最近的工作日，当然，如果当前日期本身就是工作日，则直接返回当前日期。\n",
    "\n",
    "- `numpy.is_busday(dates, weekmask='1111100', holidays=None, busdaycal=None, out=None)` Calculates which of the given dates are valid days, and which are not.\n",
    "\n",
    "【例】返回指定日期是否是工作日。\n",
    "```python\n",
    "import numpy as np\n",
    "\n",
    "# 2020-07-10 星期五\n",
    "a = np.is_busday('2020-07-10')\n",
    "b = np.is_busday('2020-07-11')\n",
    "print(a)  # True\n",
    "print(b)  # False\n",
    "```\n",
    "\n",
    "【例】统计一个 `datetime64[D]` 数组中的工作日天数。\n",
    "```python\n",
    "import numpy as np\n",
    "\n",
    "# 2020-07-10 星期五\n",
    "begindates = np.datetime64('2020-07-10')\n",
    "enddates = np.datetime64('2020-07-20')\n",
    "a = np.arange(begindates, enddates, dtype='datetime64')\n",
    "b = np.count_nonzero(np.is_busday(a))\n",
    "print(a)\n",
    "# ['2020-07-10' '2020-07-11' '2020-07-12' '2020-07-13' '2020-07-14'\n",
    "#  '2020-07-15' '2020-07-16' '2020-07-17' '2020-07-18' '2020-07-19']\n",
    "print(b)  # 6\n",
    "```\n",
    "\n",
    "\n",
    "【例】自定义周掩码值，即指定一周中哪些星期是工作日。\n",
    "```python\n",
    "import numpy as np\n",
    "\n",
    "# 2020-07-10 星期五\n",
    "a = np.is_busday('2020-07-10', weekmask=[1, 1, 1, 1, 1, 0, 0])\n",
    "b = np.is_busday('2020-07-10', weekmask=[1, 1, 1, 1, 0, 0, 1])\n",
    "print(a)  # True\n",
    "print(b)  # False\n",
    "```\n",
    "\n",
    "- `numpy.busday_count(begindates, enddates, weekmask='1111100', holidays=[], busdaycal=None, out=None)`Counts the number of valid days between `begindates` and `enddates`, not including the day of `enddates`.\n",
    "\n",
    "【例】返回两个日期之间的工作日数量。\n",
    "```python\n",
    "import numpy as np\n",
    "\n",
    "# 2020-07-10 星期五\n",
    "begindates = np.datetime64('2020-07-10')\n",
    "enddates = np.datetime64('2020-07-20')\n",
    "a = np.busday_count(begindates, enddates)\n",
    "b = np.busday_count(enddates, begindates)\n",
    "print(a)  # 6\n",
    "print(b)  # -6\n",
    "```\n",
    "\n",
    "\n",
    "---\n",
    "<b>参考图文</b>\n",
    "\n",
    "- https://www.jianshu.com/p/336cd77d9914\n",
    "- https://www.cnblogs.com/gl1573/p/10549547.html#h2datetime64\n",
    "- https://www.numpy.org.cn/reference/arrays/datetime.html#%E6%97%A5%E6%9C%9F%E6%97%B6%E9%97%B4%E5%8D%95%E4%BD%8D\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.3"
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {},
   "number_sections": true,
   "sideBar": true,
   "skip_h1_title": false,
   "title_cell": "Table of Contents",
   "title_sidebar": "Contents",
   "toc_cell": false,
   "toc_position": {},
   "toc_section_display": true,
   "toc_window_display": true
  },
  "varInspector": {
   "cols": {
    "lenName": 16,
    "lenType": 16,
    "lenVar": 40
   },
   "kernels_config": {
    "python": {
     "delete_cmd_postfix": "",
     "delete_cmd_prefix": "del ",
     "library": "var_list.py",
     "varRefreshCmd": "print(var_dic_list())"
    },
    "r": {
     "delete_cmd_postfix": ") ",
     "delete_cmd_prefix": "rm(",
     "library": "var_list.r",
     "varRefreshCmd": "cat(var_dic_list()) "
    }
   },
   "types_to_exclude": [
    "module",
    "function",
    "builtin_function_or_method",
    "instance",
    "_Feature"
   ],
   "window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
