Repository: iamkun/dayjs

Problem statement:
Getting wrong startOf('day') in a different timezone
**Describe the bug**
On calculating startOf('day') in a different timezone, I am getting the wrong result. Note that I tried this at 6 PM on November 12 in Asia/Kolkata timezone.
Code - `dayjs.tz(new Date()).tz('America/New_York').startOf('day').toString()`
Result - `"Thu, 12 Nov 2020 15:30:00 GMT"`

**Expected behavior**
It should be `Thu, 12 Nov 2020 05:00:00 GMT`

**Information**
 - Day.js Version - Tried in browser console on day.js.org website
 - OS - MacOS Mojave
 - Browser - Chrome 86.0.4240.193 
 - Time zone: Asia/Kolkata



Hints:
I have a reproduction for this in the dayjs test suite, but I'm not sure how to fix it.  Here is how I reproduced it in `test/timezone.test.js`:

```js
it('startOf day across timezones', () => {
  const originalDay = dayjs('2010-01-01T00:00:00Z').tz('UTC')
  const startOfDay = originalDay.startOf('day')
  expect(startOfDay.valueOf()).toEqual(originalDay.valueOf())
})
```

(I also had to extend `dayjs` with the `timezone` plugin to make this run.)

I'm going to keep at this and see if I can come up with a fix, but if a maintainer has an idea I'd love to hear it since I'm not very familiar with the dayjs internals.
I meant to paste the output from this test, sorry about that.  Here it is:

```
$ env TZ=America/Whitehorse yarn run test-tz
yarn run v1.22.10
$ date && jest test/timezone.test --coverage=false
Sun Nov 15 11:43:34 MST 2020
 FAIL  test/timezone.test.js
  ✓ Add Time days (DST) (9ms)
  ✓ Utc Offset (1ms)
  ✓ Diff (DST) (5ms)
  ✓ UTC add day in DST (2ms)
  ✓ UTC and utcOffset (1ms)
  ✓ UTC diff in DST (1ms)
  ✕ startOf day across timezones (13ms)

  ● startOf day across timezones

    expect(received).toEqual(expected)

    Expected value to equal:
      1262304000000
    Received:
      1262275200000

      77 |   const originalDay = dayjs('2010-01-01T00:00:00Z').tz('UTC')
      78 |   const startOfDay = originalDay.startOf('day')
    > 79 |   expect(startOfDay.valueOf()).toEqual(originalDay.valueOf())
      80 | })
      81 |

      at Object.<anonymous> (test/timezone.test.js:79:32)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 6 passed, 7 total
Snapshots:   0 total
Time:        0.823s, estimated 1s
Ran all test suites matching /test\/timezone.test/i.
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
```

Patch:
diff --git a/src/plugin/timezone/index.js b/src/plugin/timezone/index.js
--- a/src/plugin/timezone/index.js
+++ b/src/plugin/timezone/index.js
@@ -114,6 +114,17 @@ export default (o, c, d) => {
     return result && result.value
   }
 
+  const oldStartOf = proto.startOf
+  proto.startOf = function (units, startOf) {
+    if (!this.$x || !this.$x.$timezone) {
+      return oldStartOf.call(this, units, startOf)
+    }
+
+    const withoutTz = d(this.format('YYYY-MM-DD HH:mm:ss:SSS'))
+    const startOfWithoutTz = oldStartOf.call(withoutTz, units, startOf)
+    return startOfWithoutTz.tz(this.$x.$timezone, true)
+  }
+
   d.tz = function (input, arg1, arg2) {
     const parseFormat = arg2 && arg1
     const timezone = arg2 || arg1 || defaultTimezone


Test patch:
diff --git a/test/plugin/timezone.test.js b/test/plugin/timezone.test.js
--- a/test/plugin/timezone.test.js
+++ b/test/plugin/timezone.test.js
@@ -290,3 +290,17 @@ describe('CustomPraseFormat', () => {
     expect(dayjs.tz('10/15/2020 12:30', 'MM/DD/YYYY HH:mm', DEN).unix()).toBe(result)
   })
 })
+
+describe('startOf and endOf', () => {
+  it('corrects for timezone offset in startOf', () => {
+    const originalDay = dayjs.tz('2010-01-01 00:00:00', NY)
+    const startOfDay = originalDay.startOf('day')
+    expect(startOfDay.valueOf()).toEqual(originalDay.valueOf())
+  })
+
+  it('corrects for timezone offset in endOf', () => {
+    const originalDay = dayjs.tz('2009-12-31 23:59:59.999', NY)
+    const endOfDay = originalDay.endOf('day')
+    expect(endOfDay.valueOf()).toEqual(originalDay.valueOf())
+  })
+})
diff --git a/test/timezone.test.js b/test/timezone.test.js
--- a/test/timezone.test.js
+++ b/test/timezone.test.js
@@ -1,9 +1,11 @@
 import MockDate from 'mockdate'
 import moment from 'moment'
 import dayjs from '../src'
+import timezone from '../src/plugin/timezone'
 import utc from '../src/plugin/utc'
 
 dayjs.extend(utc)
+dayjs.extend(timezone)
 
 beforeEach(() => {
   MockDate.set(new Date())