From aacf7cc2b5f5a1fc8b506bedada670b506ba2b30 Mon Sep 17 00:00:00 2001 From: Eric Amodio Date: Thu, 14 Sep 2017 21:33:04 -0400 Subject: [PATCH] Reworks date parsing, formatting etc for perf Isolates moment.js --- CHANGELOG.md | 8 ++++++ src/annotations/annotations.ts | 11 ++++---- src/annotations/gutterBlameAnnotationProvider.ts | 3 +-- src/annotations/hoverBlameAnnotationProvider.ts | 3 +-- src/git/formatters/commit.ts | 7 +++-- src/git/git.ts | 8 +++--- src/git/models/commit.ts | 18 ++++++++++++- src/git/parsers/blameParser.ts | 3 +-- src/git/parsers/logParser.ts | 5 ++-- src/git/parsers/stashParser.ts | 5 ++-- src/gitCodeLensProvider.ts | 3 +-- src/gitService.ts | 7 +++-- src/messages.ts | 3 +-- src/quickPicks/commitDetails.ts | 3 +-- src/quickPicks/commitFileDetails.ts | 3 +-- src/quickPicks/common.ts | 5 ++-- src/system.ts | 1 + src/system/date.ts | 33 ++++++++++++++++++++++++ 18 files changed, 87 insertions(+), 42 deletions(-) create mode 100644 src/system/date.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f81350..5f974de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [5.0.1] - 2017-09-14 +### Added + +### Changed +- Optimizes date handling (parsing and formatting) to increase blame annotation performance and reduce memory consumption + +### Fixed + ## [5.0.0] - 2017-09-12 ### Added - Adds an all-new `GitLens` custom view to the Explorer activity diff --git a/src/annotations/annotations.ts b/src/annotations/annotations.ts index 8647779..116cfc8 100644 --- a/src/annotations/annotations.ts +++ b/src/annotations/annotations.ts @@ -1,10 +1,9 @@ -import { Strings } from '../system'; +import { Dates, Strings } from '../system'; import { DecorationInstanceRenderOptions, DecorationOptions, MarkdownString, ThemableDecorationRenderOptions } from 'vscode'; import { DiffWithCommand, ShowQuickCommitDetailsCommand } from '../commands'; import { IThemeConfig, themeDefaults } from '../configuration'; import { GlyphChars } from '../constants'; import { CommitFormatter, GitCommit, GitDiffChunkLine, GitService, GitUri, ICommitFormatOptions } from '../gitService'; -import * as moment from 'moment'; interface IHeatmapConfig { enabled: boolean; @@ -28,13 +27,13 @@ const escapeMarkdownRegEx = /[`\>\#\*\_\-\+\.]/g; export class Annotations { - static applyHeatmap(decoration: DecorationOptions, date: Date, now: moment.Moment) { + static applyHeatmap(decoration: DecorationOptions, date: Date, now: number) { const color = this._getHeatmapColor(now, date); (decoration.renderOptions!.before! as any).borderColor = color; } - private static _getHeatmapColor(now: moment.Moment, date: Date) { - const days = now.diff(moment(date), 'days'); + private static _getHeatmapColor(now: number, date: Date) { + const days = Dates.dateDaysFromNow(date, now); if (days <= 2) return '#ffeca7'; if (days <= 7) return '#ffdd8c'; @@ -65,7 +64,7 @@ export class Annotations { message = `\n\n> ${message}`; } - const markdown = new MarkdownString(`[\`${commit.shortSha}\`](${ShowQuickCommitDetailsCommand.getMarkdownCommandArgs(commit.sha)})   __${commit.author}__, ${moment(commit.date).fromNow()}   _(${moment(commit.date).format(dateFormat)})_${message}`); + const markdown = new MarkdownString(`[\`${commit.shortSha}\`](${ShowQuickCommitDetailsCommand.getMarkdownCommandArgs(commit.sha)})   __${commit.author}__, ${commit.fromNow()}   _(${commit.formatDate(dateFormat)})_${message}`); markdown.isTrusted = true; return markdown; } diff --git a/src/annotations/gutterBlameAnnotationProvider.ts b/src/annotations/gutterBlameAnnotationProvider.ts index 3dd708b..756f9e1 100644 --- a/src/annotations/gutterBlameAnnotationProvider.ts +++ b/src/annotations/gutterBlameAnnotationProvider.ts @@ -6,7 +6,6 @@ import { Annotations, endOfLineIndex } from './annotations'; import { BlameAnnotationProviderBase } from './blameAnnotationProvider'; import { GlyphChars } from '../constants'; import { GitBlameCommit, ICommitFormatOptions } from '../gitService'; -import * as moment from 'moment'; export class GutterBlameAnnotationProvider extends BlameAnnotationProviderBase { @@ -32,7 +31,7 @@ export class GutterBlameAnnotationProvider extends BlameAnnotationProviderBase { tokenOptions: tokenOptions }; - const now = moment(); + const now = Date.now(); const offset = this.uri.offset; const renderOptions = Annotations.gutterRenderOptions(this._config.theme, cfg.heatmap); const dateFormat = this._config.defaultDateFormat; diff --git a/src/annotations/hoverBlameAnnotationProvider.ts b/src/annotations/hoverBlameAnnotationProvider.ts index d4171f9..10c9e21 100644 --- a/src/annotations/hoverBlameAnnotationProvider.ts +++ b/src/annotations/hoverBlameAnnotationProvider.ts @@ -4,7 +4,6 @@ import { FileAnnotationType } from './annotationController'; import { Annotations, endOfLineIndex } from './annotations'; import { BlameAnnotationProviderBase } from './blameAnnotationProvider'; import { GitBlameCommit } from '../gitService'; -import * as moment from 'moment'; export class HoverBlameAnnotationProvider extends BlameAnnotationProviderBase { @@ -18,7 +17,7 @@ export class HoverBlameAnnotationProvider extends BlameAnnotationProviderBase { const cfg = this._config.annotations.file.hover; - const now = moment(); + const now = Date.now(); const offset = this.uri.offset; const renderOptions = Annotations.hoverRenderOptions(this._config.theme, cfg.heatmap); const dateFormat = this._config.defaultDateFormat; diff --git a/src/git/formatters/commit.ts b/src/git/formatters/commit.ts index 63b9bf1..a25aa8a 100644 --- a/src/git/formatters/commit.ts +++ b/src/git/formatters/commit.ts @@ -2,7 +2,6 @@ import { Strings } from '../../system'; import { GitCommit } from '../models/commit'; import { Formatter, IFormatOptions } from './formatter'; -import * as moment from 'moment'; import { GlyphChars } from '../../constants'; export interface ICommitFormatOptions extends IFormatOptions { @@ -20,7 +19,7 @@ export interface ICommitFormatOptions extends IFormatOptions { export class CommitFormatter extends Formatter { get ago() { - const ago = moment(this._item.date).fromNow(); + const ago = this._item.fromNow(); return this._padOrTruncate(ago, this._options.tokenOptions!.ago); } @@ -30,12 +29,12 @@ export class CommitFormatter extends Formatter } get authorAgo() { - const authorAgo = `${this._item.author}, ${moment(this._item.date).fromNow()}`; + const authorAgo = `${this._item.author}, ${this._item.fromNow()}`; return this._padOrTruncate(authorAgo, this._options.tokenOptions!.authorAgo); } get date() { - const date = moment(this._item.date).format(this._options.dateFormat!); + const date = this._item.formatDate(this._options.dateFormat!); return this._padOrTruncate(date, this._options.tokenOptions!.date); } diff --git a/src/git/git.ts b/src/git/git.ts index dd98ce9..74c5c12 100644 --- a/src/git/git.ts +++ b/src/git/git.ts @@ -21,9 +21,9 @@ export * from './remotes/provider'; let git: IGit; -// `--format=%H -%nauthor %an%nauthor-date %ai%ncommitter %cn%ncommitter-date %ci%nparents %P%nsummary %B%nfilename ?` -const defaultLogParams = [`log`, `--name-status`, `--full-history`, `-M`, `--date=iso8601`, `--format=%H -%nauthor %an%nauthor-date %ai%nparents %P%nsummary %B%nfilename ?`]; -const defaultStashParams = [`stash`, `list`, `--name-status`, `--full-history`, `-M`, `--format=%H -%nauthor-date %ai%nreflog-selector %gd%nsummary %B%nfilename ?`]; +const defaultBlameParams = [`blame`, `--root`, `--incremental`]; +const defaultLogParams = [`log`, `--name-status`, `--full-history`, `-M`, `--format=%H -%nauthor %an%nauthor-date %at%nparents %P%nsummary %B%nfilename ?`]; +const defaultStashParams = [`stash`, `list`, `--name-status`, `--full-history`, `-M`, `--format=%H -%nauthor-date %at%nreflog-selector %gd%nsummary %B%nfilename ?`]; let defaultEncoding = 'utf8'; export function setDefaultEncoding(encoding: string) { @@ -180,7 +180,7 @@ export class Git { static blame(repoPath: string | undefined, fileName: string, sha?: string, options: { ignoreWhitespace?: boolean, startLine?: number, endLine?: number } = {}) { const [file, root] = Git.splitPath(fileName, repoPath); - const params = [`blame`, `--root`, `--incremental`]; + const params = [...defaultBlameParams]; if (options.ignoreWhitespace) { params.push('-w'); diff --git a/src/git/models/commit.ts b/src/git/models/commit.ts index 5c4b93b..651e9b4 100644 --- a/src/git/models/commit.ts +++ b/src/git/models/commit.ts @@ -1,5 +1,5 @@ 'use strict'; -import { Strings } from '../../system'; +import { Dates, Strings } from '../../system'; import { Uri } from 'vscode'; import { GlyphChars } from '../../constants'; import { Git } from '../git'; @@ -73,6 +73,22 @@ export class GitCommit { return Uri.file(path.resolve(this.repoPath, this.originalFileName || this.fileName)); } + private _dateFormatter?: Dates.IDateFormatter; + + formatDate(format: string) { + if (this._dateFormatter === undefined) { + this._dateFormatter = Dates.toFormatter(this.date); + } + return this._dateFormatter.format(format); + } + + fromNow() { + if (this._dateFormatter === undefined) { + this._dateFormatter = Dates.toFormatter(this.date); + } + return this._dateFormatter.fromNow(); + } + getFormattedPath(separator: string = Strings.pad(GlyphChars.Dot, 2, 2)): string { return GitUri.getFormattedPath(this.fileName, separator); } diff --git a/src/git/parsers/blameParser.ts b/src/git/parsers/blameParser.ts index 47e0508..caaaf54 100644 --- a/src/git/parsers/blameParser.ts +++ b/src/git/parsers/blameParser.ts @@ -1,7 +1,6 @@ 'use strict'; import { Strings } from '../../system'; import { Git, GitAuthor, GitBlame, GitBlameCommit, GitCommitLine } from './../git'; -import * as moment from 'moment'; import * as path from 'path'; interface BlameEntry { @@ -134,7 +133,7 @@ export class GitBlameParser { } } - commit = new GitBlameCommit(repoPath!, entry.sha, fileName!, entry.author, moment(`${entry.authorDate} ${entry.authorTimeZone}`, 'X +-HHmm').toDate(), entry.summary!, []); + commit = new GitBlameCommit(repoPath!, entry.sha, fileName!, entry.author, new Date(entry.authorDate as any * 1000), entry.summary!, []); if (fileName !== entry.fileName) { commit.originalFileName = entry.fileName; diff --git a/src/git/parsers/logParser.ts b/src/git/parsers/logParser.ts index 08db403..2d7eb9d 100644 --- a/src/git/parsers/logParser.ts +++ b/src/git/parsers/logParser.ts @@ -3,7 +3,6 @@ import { Strings } from '../../system'; import { Range } from 'vscode'; import { Git, GitAuthor, GitCommitType, GitLog, GitLogCommit, GitStatusFileStatus, IGitStatusFile } from './../git'; // import { Logger } from '../../logger'; -import * as moment from 'moment'; import * as path from 'path'; interface LogEntry { @@ -87,7 +86,7 @@ export class GitLogParser { break; case 'author-date': - entry.authorDate = `${lineParts[1]}T${lineParts[2]}${lineParts[3]}`; + entry.authorDate = lineParts[1]; break; case 'parents': @@ -231,7 +230,7 @@ export class GitLogParser { } } - commit = new GitLogCommit(type, repoPath!, entry.sha, relativeFileName, entry.author, moment(entry.authorDate).toDate(), entry.summary!, entry.status, entry.fileStatuses, undefined, entry.originalFileName); + commit = new GitLogCommit(type, repoPath!, entry.sha, relativeFileName, entry.author, new Date(entry.authorDate! as any * 1000), entry.summary!, entry.status, entry.fileStatuses, undefined, entry.originalFileName); commit.parentShas = entry.parentShas!; if (relativeFileName !== entry.fileName) { diff --git a/src/git/parsers/stashParser.ts b/src/git/parsers/stashParser.ts index 41baa68..7bf3228 100644 --- a/src/git/parsers/stashParser.ts +++ b/src/git/parsers/stashParser.ts @@ -1,7 +1,6 @@ 'use strict'; import { Git, GitStash, GitStashCommit, GitStatusFileStatus, IGitStatusFile } from './../git'; // import { Logger } from '../../logger'; -import * as moment from 'moment'; interface StashEntry { sha: string; @@ -42,7 +41,7 @@ export class GitStashParser { switch (lineParts[0]) { case 'author-date': - entry.date = `${lineParts[1]}T${lineParts[2]}${lineParts[3]}`; + entry.date = lineParts[1]; break; case 'summary': @@ -120,7 +119,7 @@ export class GitStashParser { let commit = commits.get(entry.sha); if (commit === undefined) { - commit = new GitStashCommit(entry.stashName, repoPath, entry.sha, entry.fileNames, moment(entry.date).toDate(), entry.summary, undefined, entry.fileStatuses) as GitStashCommit; + commit = new GitStashCommit(entry.stashName, repoPath, entry.sha, entry.fileNames, new Date(entry.date! as any * 1000), entry.summary, undefined, entry.fileStatuses) as GitStashCommit; commits.set(entry.sha, commit); } } diff --git a/src/gitCodeLensProvider.ts b/src/gitCodeLensProvider.ts index e6d59a9..300f3d6 100644 --- a/src/gitCodeLensProvider.ts +++ b/src/gitCodeLensProvider.ts @@ -6,7 +6,6 @@ import { BuiltInCommands, DocumentSchemes, ExtensionKey } from './constants'; import { CodeLensCommand, CodeLensLocations, ICodeLensLanguageLocation, IConfig } from './configuration'; import { GitBlame, GitBlameCommit, GitBlameLines, GitService, GitUri } from './gitService'; import { Logger } from './logger'; -import * as moment from 'moment'; export class GitRecentChangeCodeLens extends CodeLens { @@ -254,7 +253,7 @@ export class GitCodeLensProvider implements CodeLensProvider { if (blame === undefined) return lens; const recentCommit = Iterables.first(blame.commits.values()); - title = `${recentCommit.author}, ${moment(recentCommit.date).fromNow()}`; + title = `${recentCommit.author}, ${recentCommit.fromNow()}`; if (this._config.codeLens.debug) { title += ` [${SymbolKind[lens.symbolKind]}(${lens.range.start.character}-${lens.range.end.character}), Lines (${lens.blameRange.start.line + 1}-${lens.blameRange.end.line + 1}), Commit (${recentCommit.shortSha})]`; } diff --git a/src/gitService.ts b/src/gitService.ts index 7ce8314..4b8f4db 100644 --- a/src/gitService.ts +++ b/src/gitService.ts @@ -9,7 +9,6 @@ import { GitUri, IGitCommitInfo, IGitUriData } from './git/gitUri'; import { Logger } from './logger'; import * as fs from 'fs'; import * as ignore from 'ignore'; -import * as moment from 'moment'; import * as path from 'path'; export { GitUri, IGitCommitInfo }; @@ -568,7 +567,7 @@ export class GitService extends Disposable { Iterables.forEach(blame.commits.values(), (c, i) => { if (c.isUncommitted) return; - const decoration = `${GlyphChars.ArrowDropRight} ${c.author}, ${moment(c.date).format(dateFormat)}`; + const decoration = `${GlyphChars.ArrowDropRight} ${c.author}, ${c.formatDate(dateFormat)}`; const uri = GitService.toReferenceGitContentUri(c, i + 1, commitCount, c.originalFileName, decoration, dateFormat); locations.push(new Location(uri, new Position(0, 0))); if (c.sha === selectedSha) { @@ -888,7 +887,7 @@ export class GitService extends Disposable { Iterables.forEach(log.commits.values(), (c, i) => { if (c.isUncommitted) return; - const decoration = `${GlyphChars.ArrowDropRight} ${c.author}, ${moment(c.date).format(dateFormat)}`; + const decoration = `${GlyphChars.ArrowDropRight} ${c.author}, ${c.formatDate(dateFormat)}`; const uri = GitService.toReferenceGitContentUri(c, i + 1, commitCount, c.originalFileName, decoration, dateFormat); locations.push(new Location(uri, new Position(0, 0))); if (c.sha === selectedSha) { @@ -1138,7 +1137,7 @@ export class GitService extends Disposable { } // NOTE: Need to specify an index here, since I can't control the sort order -- just alphabetic or by file location - return Uri.parse(`${scheme}:${pad(data.index || 0)} ${GlyphChars.Dot} ${encodeURIComponent(message)} ${GlyphChars.Dot} ${moment(commit.date).format(dateFormat)} ${GlyphChars.Dot} ${encodeURIComponent(uriPath)}?${JSON.stringify(data)}`); + return Uri.parse(`${scheme}:${pad(data.index || 0)} ${GlyphChars.Dot} ${encodeURIComponent(message)} ${GlyphChars.Dot} ${commit.formatDate(dateFormat)} ${GlyphChars.Dot} ${encodeURIComponent(uriPath)}?${JSON.stringify(data)}`); } private static _toGitUriData(commit: IGitUriData, index?: number, originalFileName?: string, decoration?: string): T { diff --git a/src/messages.ts b/src/messages.ts index 1763fd2..f4c15af 100644 --- a/src/messages.ts +++ b/src/messages.ts @@ -3,7 +3,6 @@ import { commands, ExtensionContext, Uri, window } from 'vscode'; import { BuiltInCommands } from './constants'; import { GitCommit } from './gitService'; import { Logger } from './logger'; -import * as moment from 'moment'; export type SuppressedKeys = 'suppressCommitHasNoPreviousCommitWarning' | 'suppressCommitNotFoundWarning' | @@ -34,7 +33,7 @@ export class Messages { static showCommitHasNoPreviousCommitWarningMessage(commit?: GitCommit): Promise { if (commit === undefined) return Messages._showMessage('info', `Commit has no previous commit`, SuppressedKeys.CommitHasNoPreviousCommitWarning); - return Messages._showMessage('info', `Commit ${commit.shortSha} (${commit.author}, ${moment(commit.date).fromNow()}) has no previous commit`, SuppressedKeys.CommitHasNoPreviousCommitWarning); + return Messages._showMessage('info', `Commit ${commit.shortSha} (${commit.author}, ${commit.fromNow()}) has no previous commit`, SuppressedKeys.CommitHasNoPreviousCommitWarning); } static showCommitNotFoundWarningMessage(message: string): Promise { diff --git a/src/quickPicks/commitDetails.ts b/src/quickPicks/commitDetails.ts index 9049d4e..e8f4f9a 100644 --- a/src/quickPicks/commitDetails.ts +++ b/src/quickPicks/commitDetails.ts @@ -7,7 +7,6 @@ import { GlyphChars } from '../constants'; import { getGitStatusOcticon, GitCommit, GitLog, GitLogCommit, GitService, GitStashCommit, GitStatusFile, GitStatusFileStatus, GitUri, IGitCommitInfo, IGitStatusFile, RemoteResource } from '../gitService'; import { Keyboard, KeyCommand, KeyNoopCommand, Keys } from '../keyboard'; import { OpenRemotesCommandQuickPickItem } from './remotes'; -import * as moment from 'moment'; import * as path from 'path'; export class CommitWithFileStatusQuickPickItem extends OpenFileCommandQuickPickItem { @@ -299,7 +298,7 @@ export class CommitDetailsQuickPick { const pick = await window.showQuickPick(items, { matchOnDescription: true, matchOnDetail: true, - placeHolder: `${commit.shortSha} ${Strings.pad(GlyphChars.Dot, 1, 1)} ${commit.author ? `${commit.author}, ` : ''}${moment(commit.date).fromNow()} ${Strings.pad(GlyphChars.Dot, 1, 1)} ${commit.message}`, + placeHolder: `${commit.shortSha} ${Strings.pad(GlyphChars.Dot, 1, 1)} ${commit.author ? `${commit.author}, ` : ''}${commit.fromNow()} ${Strings.pad(GlyphChars.Dot, 1, 1)} ${commit.message}`, ignoreFocusOut: getQuickPickIgnoreFocusOut(), onDidSelectItem: (item: QuickPickItem) => { scope.setKeyCommand('right', item); diff --git a/src/quickPicks/commitFileDetails.ts b/src/quickPicks/commitFileDetails.ts index ee143e2..bb6e839 100644 --- a/src/quickPicks/commitFileDetails.ts +++ b/src/quickPicks/commitFileDetails.ts @@ -7,7 +7,6 @@ import { GlyphChars } from '../constants'; import { GitLog, GitLogCommit, GitService, GitUri, RemoteResource } from '../gitService'; import { Keyboard, KeyCommand, KeyNoopCommand } from '../keyboard'; import { OpenRemotesCommandQuickPickItem } from './remotes'; -import * as moment from 'moment'; import * as path from 'path'; export class OpenCommitFileCommandQuickPickItem extends OpenFileCommandQuickPickItem { @@ -275,7 +274,7 @@ export class CommitFileDetailsQuickPick { const pick = await window.showQuickPick(items, { matchOnDescription: true, - placeHolder: `${commit.getFormattedPath()} ${Strings.pad(GlyphChars.Dot, 1, 1)} ${isUncommitted ? `Uncommitted ${GlyphChars.ArrowRightHollow} ` : '' }${commit.shortSha} ${Strings.pad(GlyphChars.Dot, 1, 1)} ${commit.author}, ${moment(commit.date).fromNow()} ${Strings.pad(GlyphChars.Dot, 1, 1)} ${commit.message}`, + placeHolder: `${commit.getFormattedPath()} ${Strings.pad(GlyphChars.Dot, 1, 1)} ${isUncommitted ? `Uncommitted ${GlyphChars.ArrowRightHollow} ` : '' }${commit.shortSha} ${Strings.pad(GlyphChars.Dot, 1, 1)} ${commit.author}, ${commit.fromNow()} ${Strings.pad(GlyphChars.Dot, 1, 1)} ${commit.message}`, ignoreFocusOut: getQuickPickIgnoreFocusOut(), onDidSelectItem: (item: QuickPickItem) => { scope.setKeyCommand('right', item as KeyCommand); diff --git a/src/quickPicks/common.ts b/src/quickPicks/common.ts index 32a907a..539fe25 100644 --- a/src/quickPicks/common.ts +++ b/src/quickPicks/common.ts @@ -7,7 +7,6 @@ import { GlyphChars } from '../constants'; import { GitCommit, GitLogCommit, GitStashCommit } from '../gitService'; import { Keyboard, KeyboardScope, KeyMapping, Keys } from '../keyboard'; // import { Logger } from '../logger'; -import * as moment from 'moment'; export function getQuickPickIgnoreFocusOut() { const cfg = workspace.getConfiguration(ExtensionKey).get('advanced')!; @@ -174,12 +173,12 @@ export class CommitQuickPickItem implements QuickPickItem { if (commit instanceof GitStashCommit) { this.label = message; this.description = ''; - this.detail = `${GlyphChars.Space} ${commit.stashName} ${Strings.pad(GlyphChars.Dot, 1, 1)} ${moment(commit.date).fromNow()} ${Strings.pad(GlyphChars.Dot, 1, 1)} ${commit.getDiffStatus()}`; + this.detail = `${GlyphChars.Space} ${commit.stashName} ${Strings.pad(GlyphChars.Dot, 1, 1)} ${commit.fromNow()} ${Strings.pad(GlyphChars.Dot, 1, 1)} ${commit.getDiffStatus()}`; } else { this.label = message; this.description = `${Strings.pad('$(git-commit)', 1, 1)} ${commit.shortSha}`; - this.detail = `${GlyphChars.Space} ${commit.author}, ${moment(commit.date).fromNow()}${(commit.type === 'branch') ? ` ${Strings.pad(GlyphChars.Dot, 1, 1)} ${(commit as GitLogCommit).getDiffStatus()}` : ''}`; + this.detail = `${GlyphChars.Space} ${commit.author}, ${commit.fromNow()}${(commit.type === 'branch') ? ` ${Strings.pad(GlyphChars.Dot, 1, 1)} ${(commit as GitLogCommit).getDiffStatus()}` : ''}`; } } } \ No newline at end of file diff --git a/src/system.ts b/src/system.ts index fa321ab..90e8186 100644 --- a/src/system.ts +++ b/src/system.ts @@ -1,5 +1,6 @@ 'use strict'; export * from './system/array'; +export * from './system/date'; // export * from './system/disposable'; // export * from './system/element'; // export * from './system/event'; diff --git a/src/system/date.ts b/src/system/date.ts new file mode 100644 index 0000000..730c996 --- /dev/null +++ b/src/system/date.ts @@ -0,0 +1,33 @@ +'use strict'; +import * as moment from 'moment'; + +const MillisecondsPerMinute = 60000; // 60 * 1000 +const MillisecondsPerDay = 86400000; // 24 * 60 * 60 * 1000 + +export namespace Dates { + + export interface IDateFormatter { + fromNow: () => string; + format: (format: string) => string; + } + + export function dateDaysFromNow(date: Date, now: number = Date.now()) { + const startOfDayLeft = startOfDay(now); + const startOfDayRight = startOfDay(date); + + const timestampLeft = startOfDayLeft.getTime() - startOfDayLeft.getTimezoneOffset() * MillisecondsPerMinute; + const timestampRight = startOfDayRight.getTime() - startOfDayRight.getTimezoneOffset() * MillisecondsPerMinute; + + return Math.round((timestampLeft - timestampRight) / MillisecondsPerDay); + } + + export function startOfDay(date: Date | number) { + const newDate = new Date(typeof date === 'number' ? date : date.getTime()); + newDate.setHours(0, 0, 0, 0); + return newDate; + } + + export function toFormatter(date: Date): IDateFormatter { + return moment(date); + } +} \ No newline at end of file