{"action":"created","issue":{"url":"https://api.github.com/repos/pydata/pandas/issues/7905","labels_url":"https://api.github.com/repos/pydata/pandas/issues/7905/labels{/name}","comments_url":"https://api.github.com/repos/pydata/pandas/issues/7905/comments","events_url":"https://api.github.com/repos/pydata/pandas/issues/7905/events","html_url":"https://github.com/pydata/pandas/pull/7905","id":39350413,"number":7905,"title":"ENH: Add BusinessHour offset","user":{"login":"sinhrks","id":1696302,"avatar_url":"https://avatars.githubusercontent.com/u/1696302?v=3","gravatar_id":"","url":"https://api.github.com/users/sinhrks","html_url":"https://github.com/sinhrks","followers_url":"https://api.github.com/users/sinhrks/followers","following_url":"https://api.github.com/users/sinhrks/following{/other_user}","gists_url":"https://api.github.com/users/sinhrks/gists{/gist_id}","starred_url":"https://api.github.com/users/sinhrks/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sinhrks/subscriptions","organizations_url":"https://api.github.com/users/sinhrks/orgs","repos_url":"https://api.github.com/users/sinhrks/repos","events_url":"https://api.github.com/users/sinhrks/events{/privacy}","received_events_url":"https://api.github.com/users/sinhrks/received_events","type":"User","site_admin":false},"labels":[{"url":"https://api.github.com/repos/pydata/pandas/labels/Enhancement","name":"Enhancement","color":"4E9A06"},{"url":"https://api.github.com/repos/pydata/pandas/labels/Frequency","name":"Frequency","color":"0052cc"}],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":5,"created_at":"2014-08-02T14:56:55Z","updated_at":"2015-01-18T21:32:10Z","closed_at":null,"pull_request":{"url":"https://api.github.com/repos/pydata/pandas/pulls/7905","html_url":"https://github.com/pydata/pandas/pull/7905","diff_url":"https://github.com/pydata/pandas/pull/7905.diff","patch_url":"https://github.com/pydata/pandas/pull/7905.patch"},"body":"Closes #2469. Create ``BusinessHour`` offset to specify business hours on ``BusinessDay``. Appreciated if any feedbacks regarding bahaviours, keywords, etc.\r\n\r\n### Basic\r\nBy default, use 9:00 - 17:00 as business hours. Adding ``BusinessHour`` will increment timestamp by hourly on the days belong to ``BusinessDay`` offset.\r\n``` \r\nbh = pd.offsets.BusinessHour()\r\nrepr(bh)\r\n# <BusinessHour: BH=09:00-17:00>\r\n\r\npd.Timestamp('2014-08-01 10:00') + bh\r\n# 2014-08-01 11:00:00\r\npd.Timestamp('2014-08-01 08:00') + bh\r\n# 2014-08-01 10:00:00\r\n\r\n# move to next Business Day\r\npd.Timestamp('2014-08-01 19:00') + bh\r\n# 2014-08-04 10:00:00\r\n\r\n# if exceeds the closing time, remaining are added to next day\r\npd.Timestamp('2014-08-01 16:45') + bh\r\n# 2014-08-04 09:45:00\r\n\r\n# if n != 1\r\npd.Timestamp('2014-08-01 10:00') + pd.offsets.BusinessHour(2)\r\n# 2014-08-01 12:00:00\r\n\r\npd.Timestamp('2014-08-01 10:00') + pd.offsets.BusinessHour(-3)\r\n# 2014-07-31 15:00:00\r\n\r\n# Create Index\r\npd.date_range('2014-07-08 10:00', freq='BH', periods=40)\r\n# [2014-07-08 10:00:00, ..., 2014-07-14 17:00:00]\r\n# Length: 40, Freq: BH, Timezone: None\r\n```\r\n\r\n### Specify Opening/Closing Hour\r\nAllow to specify opening/closing time using ``start`` and ``end`` keyword by ``hour:minute`` string or ``datetime.time``.\r\n\r\n```\r\nbh = pd.offsets.BusinessHour(start='11:00', end=datetime.time(20, 0))\r\nrepr(bh)\r\n# <BusinessHour: BH=11:00-20:00>\r\n\r\npd.Timestamp('2014-08-01 10:00') + bh\r\n# 2014-08-01 12:00:00\r\npd.Timestamp('2014-08-01 13:00') + bh\r\n# 2014-08-01 14:00:00\r\npd.Timestamp('2014-08-01 19:00') + bh\r\n# 2014-08-04 11:00:00\r\n\r\n# if end < start, it will be midnight business hour\r\nbh = pd.offsets.BusinessHour(start='17:00', end='9:00')\r\nrepr(bh)\r\n# <BusinessHour: BH=17:00-09:00>\r\n\r\npd.Timestamp('2014-07-31 10:00') + bh\r\n# 2014-07-31 18:00:00\r\npd.Timestamp('2014-07-31 23:00') + bh\r\n# 2014-08-01 00:00:00\r\npd.Timestamp('2014-07-31 01:00') + bh\r\n# 2014-07-31 02:00:00\r\npd.Timestamp('2014-08-01 13:00') + bh\r\n# 2014-08-01 18:00:00\r\n```\r\n\r\n### Edge cases\r\n``onOffset`` should include both edges.\r\n```\r\npd.offsets.BusinessHour().onOffset(pd.Timestamp('2014-08-01 09:00'))\r\n# True\r\n\r\npd.offsets.BusinessHour().onOffset(pd.Timestamp('2014-08-01 17:00'))\r\n# True\r\n```\r\n\r\nIf result is on the end-edge of business hour, move to next \r\n```\r\n# not 2014-08-01 17:00\r\npd.Timestamp('2014-08-01 16:00') + pd.offsets.BusinessHour()\r\n# 2014-08-04 09:00\r\n\r\n# not 2014-08-01 09:00\r\npd.Timestamp('2014-08-01 10:00') - pd.offsets.BusinessHour()\r\n# 2014-07:31 17:00\r\n```\r\n\r\nIn case of midnight business hour, distinguish the date by its opening hour\r\n```\r\n# 2014-08-02 is Saturday, but handled as valid because its business hour starts on 08-01\r\npd.Timestamp('2014-08-02 01:00') + pd.offsets.BusinessHour(start='17:00', end='9:00')\r\n# 2014-08:02 02:00\r\n```\r\n\r\n### Remainings:\r\n- [x] doc\r\n- impl & tests\r\n    - [x] Index creation\r\n        - [x] Confirm better conditions whether inferred_freq results in `BH` or ``H``\r\n    - [x] ``normalize``\r\n    - ~~tsplot~~ (This can't be done without #5148, skipped)\r\n    - [x] Correct edge handling for ``rollback`` and ``rollforward``\r\n    - [x] handling ``nanosecond`` (minor issue, but current ``apply_wraps`` may add preserved ``nanosecond`` after the closing time)\r\n- tests\r\n    - [x] ``n`` larger than business shours \r\n    - [x] Specifying ``start`` and ``end``\r\n    - [x] midnight business hours\r\n    - [x] Error cases for initialization\r\n    - [x] Better fix for ``test_apply_out_of_range`` timezone check, which fails because of DST difference."},"comment":{"url":"https://api.github.com/repos/pydata/pandas/issues/comments/70427055","html_url":"https://github.com/pydata/pandas/pull/7905#issuecomment-70427055","issue_url":"https://api.github.com/repos/pydata/pandas/issues/7905","id":70427055,"user":{"login":"jreback","id":953992,"avatar_url":"https://avatars.githubusercontent.com/u/953992?v=3","gravatar_id":"","url":"https://api.github.com/users/jreback","html_url":"https://github.com/jreback","followers_url":"https://api.github.com/users/jreback/followers","following_url":"https://api.github.com/users/jreback/following{/other_user}","gists_url":"https://api.github.com/users/jreback/gists{/gist_id}","starred_url":"https://api.github.com/users/jreback/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jreback/subscriptions","organizations_url":"https://api.github.com/users/jreback/orgs","repos_url":"https://api.github.com/users/jreback/repos","events_url":"https://api.github.com/users/jreback/events{/privacy}","received_events_url":"https://api.github.com/users/jreback/received_events","type":"User","site_admin":false},"created_at":"2015-01-18T21:32:10Z","updated_at":"2015-01-18T21:32:10Z","body":"@sinhrks can you rebase / revisit"}}