summaryrefslogtreecommitdiffhomepage
path: root/desktop/packages/mullvad-vpn/src/shared/date-helper.ts
blob: be83473cfacd00eaee47a8352b3117e48c4a25ee (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
import { sprintf } from 'sprintf-js';

import { messages } from './gettext';
import { capitalize } from './string-helpers';

export type DateType = Date | string | number;

export enum DateComponent {
  day,
  hour,
  minute,
}

export function dateByAddingComponent(date: DateType, component: DateComponent, value: number) {
  const modifiedDate = new Date(date);
  switch (component) {
    case DateComponent.day:
      modifiedDate.setDate(modifiedDate.getDate() + value);
      break;
    case DateComponent.hour:
      modifiedDate.setHours(modifiedDate.getHours() + value);
      break;
    case DateComponent.minute:
      modifiedDate.setMinutes(modifiedDate.getMinutes() + value);
      break;
  }

  return modifiedDate;
}

export class DateDiff {
  private readonly fromDate: Date;
  private readonly toDate: Date;

  public constructor(fromDate: DateType, toDate: DateType) {
    this.fromDate = new Date(fromDate);
    this.toDate = new Date(toDate);
  }

  get milliseconds(): number {
    return this.toDate.getTime() - this.fromDate.getTime();
  }

  get seconds(): number {
    return this.floor(this.milliseconds / 1000);
  }

  get minutes(): number {
    return this.floor(this.seconds / 60);
  }

  get hours(): number {
    return this.floor(this.minutes / 60);
  }

  get days(): number {
    return this.floor(this.hours / 24);
  }

  get months(): number {
    const months = new Date(Math.abs(this.milliseconds)).getUTCMonth();
    const monthsWithSign = this.milliseconds >= 0 ? months : -months;
    return this.years * 12 + monthsWithSign;
  }

  get years(): number {
    const years = new Date(Math.abs(this.milliseconds)).getUTCFullYear() - 1970;
    return this.milliseconds >= 0 ? years : -years;
  }

  private floor(n: number): number {
    return n >= 0 ? Math.floor(n) : Math.ceil(n);
  }
}

export interface FormatDateOptions {
  suffix?: boolean;
  displayMonths?: boolean;
  capitalize?: boolean;
}

// If withSuffix is true then "left" will be added at the end of the remaining time.
// If noMonths is true then the following applies:
//  If a user has more than 2 years (730 days) left of time it should be displayed in whole years
//  rounded down If a user has less than 2 years left (e.g. 729 days) then this should be displayed
//  in days.
export function formatRelativeDate(
  fromDate: DateType,
  toDate: DateType,
  options?: FormatDateOptions,
): string {
  const diff = new DateDiff(fromDate, toDate);
  const years = Math.abs(diff.years);
  const months = Math.abs(diff.months);
  const days = Math.abs(diff.days);

  if (isNaN(years) || isNaN(months) || isNaN(days)) {
    return '';
  }

  let result = '';
  if (!options?.suffix) {
    if (options?.displayMonths ? years > 0 : days >= 730) {
      result = sprintf(messages.ngettext('1 year', '%d years', years), years);
    } else if (options?.displayMonths && months >= 3) {
      result = sprintf(messages.ngettext('1 month', '%d months', months), months);
    } else if (days > 0) {
      result = sprintf(messages.ngettext('1 day', '%d days', days), days);
    } else {
      result = messages.gettext('less than a day');
    }
  } else if (diff.milliseconds > 0) {
    if (options?.displayMonths ? years > 0 : days >= 730) {
      result = sprintf(messages.ngettext('1 year left', '%d years left', years), years);
    } else if (options?.displayMonths && months >= 3) {
      result = sprintf(messages.ngettext('1 month left', '%d months left', months), months);
    } else if (days > 0) {
      result = sprintf(messages.ngettext('1 day left', '%d days left', days), days);
    } else {
      result = messages.gettext('less than a day left');
    }
  }

  return options?.capitalize ? capitalize(result) : result;
}