diff --git a/lib/datasource/npm/get.ts b/lib/datasource/npm/get.ts
index 642b5c55a587baab366cfc39b37ced0f18697a11..9a567cd0fd8b9d0464b3e4d0d29d57167d5b26f3 100644
--- a/lib/datasource/npm/get.ts
+++ b/lib/datasource/npm/get.ts
@@ -2,7 +2,7 @@ import { OutgoingHttpHeaders } from 'http';
 import url from 'url';
 import is from '@sindresorhus/is';
 import delay from 'delay';
-import moment from 'moment';
+import { DateTime } from 'luxon';
 import registryAuthToken from 'registry-auth-token';
 import getRegistryUrl from 'registry-auth-token/registry-url';
 import { logger } from '../../logger';
@@ -218,7 +218,12 @@ export async function getDependency(
       if (res.time?.[version]) {
         release.releaseTimestamp = res.time[version];
         release.canBeUnpublished =
-          moment().diff(moment(release.releaseTimestamp), 'days') === 0;
+          DateTime.local()
+            .startOf('day')
+            .diff(
+              DateTime.fromISO(release.releaseTimestamp).startOf('day'),
+              'days'
+            ).days === 0;
       }
       if (res.versions[version].deprecated) {
         release.isDeprecated = true;
diff --git a/lib/datasource/npm/index.spec.ts b/lib/datasource/npm/index.spec.ts
index 00dfde3e164bcef47af58ccbea0c26e7ce7b5cc6..2c1633deca9afa20bc842cee001025cbb0b9a13a 100644
--- a/lib/datasource/npm/index.spec.ts
+++ b/lib/datasource/npm/index.spec.ts
@@ -1,4 +1,4 @@
-import moment from 'moment';
+import { DateTime } from 'luxon';
 import nock from 'nock';
 import _registryAuthToken from 'registry-auth-token';
 import { getPkgReleases } from '..';
@@ -171,7 +171,7 @@ describe(getName(__filename), () => {
     expect(getRelease(res, '0.0.2').canBeUnpublished).toBeUndefined();
   });
   it('should return canBeUnpublished=true', async () => {
-    npmResponse.time['0.0.2'] = moment().subtract(6, 'hours').format();
+    npmResponse.time['0.0.2'] = DateTime.local().minus({ hours: 6 }).toISO();
     nock('https://registry.npmjs.org').get('/foobar').reply(200, npmResponse);
     const res = await getPkgReleases({ datasource, depName: 'foobar' });
     expect(getRelease(res, '0.0.1').canBeUnpublished).toBe(false);
diff --git a/lib/workers/branch/schedule.ts b/lib/workers/branch/schedule.ts
index afadff754360f2182f2c7a6e8fab0ddc8b4a0c44..009f76a608eb95b71d682ca3393bf21b4d09c062 100644
--- a/lib/workers/branch/schedule.ts
+++ b/lib/workers/branch/schedule.ts
@@ -1,6 +1,6 @@
 import later from '@breejs/later';
 import is from '@sindresorhus/is';
-import moment from 'moment-timezone';
+import { DateTime } from 'luxon';
 import { RenovateConfig } from '../../config';
 import { logger } from '../../logger';
 
@@ -16,7 +16,7 @@ function fixShortHours(input: string): string {
 export function hasValidTimezone(
   timezone: string
 ): [boolean] | [boolean, string] {
-  if (!moment.tz.zone(timezone)) {
+  if (!DateTime.local().setZone(timezone).isValid) {
     return [false, `Invalid schedule: Unsupported timezone ${timezone}`];
   }
   return [true];
@@ -92,8 +92,8 @@ export function isScheduledNow(config: RenovateConfig): boolean {
     logger.warn(errorMessage);
     return true;
   }
-  let now = moment();
-  logger.trace(`now=${now.format()}`);
+  let now = DateTime.local();
+  logger.trace(`now=${now.toISO()}`);
   // Adjust the time if repo is in a different timezone to renovate
   if (config.timezone) {
     logger.debug({ timezone: config.timezone }, 'Found timezone');
@@ -103,15 +103,15 @@ export function isScheduledNow(config: RenovateConfig): boolean {
       return true;
     }
     logger.debug('Adjusting now for timezone');
-    now = now.tz(config.timezone);
-    logger.trace(`now=${now.format()}`);
+    now = now.setZone(config.timezone);
+    logger.trace(`now=${now.toISO()}`);
   }
-  // Get today in text form, e.g. "Monday";
-  const currentDay = now.format('dddd');
+  const currentDay = now.weekday;
   logger.trace(`currentDay=${currentDay}`);
   // Get the number of seconds since midnight
-  const currentSeconds =
-    now.hours() * 3600 + now.minutes() * 60 + now.seconds();
+  const currentSeconds = now
+    .startOf('second')
+    .diff(now.startOf('day'), 'seconds').seconds;
   logger.trace(`currentSeconds=${currentSeconds}`);
   // Support a single string but massage to array for processing
   logger.debug(`Checking ${configSchedule.length} schedule(s)`);
@@ -124,7 +124,7 @@ export function isScheduledNow(config: RenovateConfig): boolean {
     return parsedSchedule.schedules.some((schedule) => {
       // Check if months are defined
       if (schedule.M) {
-        const currentMonth = parseInt(now.format('M'), 10);
+        const currentMonth = now.month;
         if (!schedule.M.includes(currentMonth)) {
           logger.debug(
             `Does not match schedule because ${currentMonth} is not in ${String(
@@ -136,19 +136,10 @@ export function isScheduledNow(config: RenovateConfig): boolean {
       }
       // Check if days are defined
       if (schedule.d) {
-        // We need to compare text instead of numbers because
-        // 'moment' adjusts day of week for locale while 'later' does not
-        // later days run from 1..7
-        const dowMap = [
-          null,
-          'Sunday',
-          'Monday',
-          'Tuesday',
-          'Wednesday',
-          'Thursday',
-          'Friday',
-          'Saturday',
-        ];
+        // We need to map because 'luxon' uses monday as first day
+        // and later uses sundays as first day of week
+        // http://bunkat.github.io/later/time-periods.html#day-of-week
+        const dowMap = [6, 7, 1, 2, 3, 4, 5, 6];
         const scheduledDays = schedule.d.map((day) => dowMap[day]);
         logger.trace({ scheduledDays }, `scheduledDays`);
         if (!scheduledDays.includes(currentDay)) {
@@ -162,8 +153,7 @@ export function isScheduledNow(config: RenovateConfig): boolean {
       }
       if (schedule.D) {
         logger.debug({ schedule_D: schedule.D }, `schedule.D`);
-        // moment outputs as string but later outputs as integer
-        const currentDayOfMonth = parseInt(now.format('D'), 10);
+        const currentDayOfMonth = now.day;
         if (!schedule.D.includes(currentDayOfMonth)) {
           return false;
         }
@@ -189,7 +179,7 @@ export function isScheduledNow(config: RenovateConfig): boolean {
         }
       }
       // Check for week of year
-      if (schedule.wy && !schedule.wy.includes(now.week())) {
+      if (schedule.wy && !schedule.wy.includes(now.weekNumber)) {
         return false;
       }
       logger.debug(`Matches schedule ${scheduleText}`);
diff --git a/lib/workers/repository/process/limits.spec.ts b/lib/workers/repository/process/limits.spec.ts
index ae31e9a46d1cb96bd301abda1531efa6089a9d40..58f0e479c3684425b2ad8a3b61f9f1a2ca143094 100644
--- a/lib/workers/repository/process/limits.spec.ts
+++ b/lib/workers/repository/process/limits.spec.ts
@@ -1,4 +1,4 @@
-import moment from 'moment';
+import { DateTime } from 'luxon';
 import { RenovateConfig, getConfig, platform } from '../../../../test/util';
 import { PrState } from '../../../types';
 import { BranchConfig } from '../../common';
@@ -16,7 +16,7 @@ describe('workers/repository/process/limits', () => {
       config.prHourlyLimit = 2;
       platform.getPrList.mockResolvedValueOnce([
         {
-          createdAt: moment().toISOString(),
+          createdAt: DateTime.local().toISO(),
           sourceBranch: null,
           title: null,
           state: null,
diff --git a/lib/workers/repository/process/limits.ts b/lib/workers/repository/process/limits.ts
index 088c84752d6be574aac349f9d71eff3cb67f8f3d..7a434c5bf7ed9317181ec13da6095339c1e94172 100644
--- a/lib/workers/repository/process/limits.ts
+++ b/lib/workers/repository/process/limits.ts
@@ -1,4 +1,4 @@
-import moment from 'moment';
+import { DateTime } from 'luxon';
 import { RenovateConfig } from '../../../config';
 import { logger } from '../../../logger';
 import { Pr, platform } from '../../../platform';
@@ -11,15 +11,13 @@ export async function getPrHourlyRemaining(
   if (config.prHourlyLimit) {
     logger.debug('Calculating hourly PRs remaining');
     const prList = await platform.getPrList();
-    const currentHourStart = moment({
-      hour: moment().hour(),
-    });
+    const currentHourStart = DateTime.local().startOf('hour');
     logger.debug(`currentHourStart=${String(currentHourStart)}`);
     try {
       const soFarThisHour = prList.filter(
         (pr) =>
           pr.sourceBranch !== config.onboardingBranch &&
-          moment(pr.createdAt).isAfter(currentHourStart)
+          DateTime.fromISO(pr.createdAt) > currentHourStart
       );
       const prsRemaining = config.prHourlyLimit - soFarThisHour.length;
       logger.debug(`PR hourly limit remaining: ${prsRemaining}`);
diff --git a/package.json b/package.json
index 730f127f5b83dc5063c8f30aa4c63886215c14f8..c1b556e968ce0e9dcdb1fbb67c60b330bfe7291d 100644
--- a/package.json
+++ b/package.json
@@ -152,8 +152,6 @@
     "markdown-it": "12.0.2",
     "markdown-table": "2.0.0",
     "minimatch": "3.0.4",
-    "moment": "2.29.1",
-    "moment-timezone": "0.5.31",
     "node-emoji": "1.10.0",
     "p-all": "3.0.0",
     "p-map": "4.0.0",
@@ -211,7 +209,6 @@
     "@types/luxon": "1.25.0",
     "@types/markdown-it": "10.0.2",
     "@types/markdown-table": "2.0.0",
-    "@types/moment-timezone": "0.5.13",
     "@types/nock": "10.0.3",
     "@types/node": "12.19.1",
     "@types/node-emoji": "1.8.1",
diff --git a/yarn.lock b/yarn.lock
index d9390d5ebb35c13ea7750b507e3c2a5eb3cfd58d..2fcaf11d0d619d63295f93e8565e2d8a4a212135 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1742,13 +1742,6 @@
   resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.0.tgz#69a23a3ad29caf0097f06eda59b361ee2f0639f6"
   integrity sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY=
 
-"@types/moment-timezone@0.5.13":
-  version "0.5.13"
-  resolved "https://registry.yarnpkg.com/@types/moment-timezone/-/moment-timezone-0.5.13.tgz#0317ccc91eb4c7f4901704166166395c39276528"
-  integrity sha512-SWk1qM8DRssS5YR9L4eEX7WUhK/wc96aIr4nMa6p0kTk9YhGGOJjECVhIdPEj13fvJw72Xun69gScXSZ/UmcPg==
-  dependencies:
-    moment ">=2.14.0"
-
 "@types/nock@10.0.3":
   version "10.0.3"
   resolved "https://registry.yarnpkg.com/@types/nock/-/nock-10.0.3.tgz#dab1d18ffbccfbf2db811dab9584304eeb6e1c4c"
@@ -7203,14 +7196,7 @@ modify-values@^1.0.0:
   resolved "https://registry.yarnpkg.com/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022"
   integrity sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==
 
-moment-timezone@0.5.31:
-  version "0.5.31"
-  resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.31.tgz#9c40d8c5026f0c7ab46eda3d63e49c155148de05"
-  integrity sha512-+GgHNg8xRhMXfEbv81iDtrVeTcWt0kWmTEY1XQK14dICTXnWJnT0dxdlPspwqF3keKMVPXwayEsk1DI0AA/jdA==
-  dependencies:
-    moment ">= 2.9.0"
-
-moment@2.29.1, "moment@>= 2.9.0", moment@>=2.14.0, moment@^2.19.3:
+moment@^2.19.3:
   version "2.29.1"
   resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3"
   integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==