Skip to content

Commit

Permalink
feat: add method parseEnglishDate for English date parsing (#95)
Browse files Browse the repository at this point in the history
  • Loading branch information
aj3sh authored Nov 30, 2024
1 parent 6cf9d14 commit aeabb8d
Show file tree
Hide file tree
Showing 8 changed files with 361 additions and 22 deletions.
57 changes: 41 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,23 @@ import NepaliDate from 'nepali-datetime'

// Create a NepaliDate object for the current date and time
const now = new NepaliDate()
console.log(now.toString()) // Outputs: "2080-03-23 15:32:03.643"
console.log(now.toString()) // 2080-03-23 15:32:03.643

// Create a NepaliDate object from a Nepali date string
const date1 = new NepaliDate('2079-02-15 23:11')
console.log(date1.toString()) // Outputs: "2079-02-15 23:11:00"
console.log(date1.toString()) // 2079-02-15 23:11:00

// Parse Nepali date string
const date2 = new NepaliDate('Baisakh 18, 2080', 'MMMM D, YYYY')
console.log(date1.toString()) // Outputs: "2080-01-18 00:00:00"
console.log(date2.toString()) // 2080-01-18 00:00:00

// Format a NepaliDate object
const formattedDate = now.format('YYYY-MM-DD')
console.log(formattedDate) // Outputs: "2080-03-23"
console.log(formattedDate) // 2080-03-23

// Create a NepaliDate object from an English date
const date3 = NepaliDate.fromEnglishDate(2023, 6, 8)
console.log(englishDate.toString()) // Outputs: "2080-03-23 00:00:00"
// Create a NepaliDate object from an English date string
const date3 = NepaliDate.parseEnglishDate('2023-07-08', 'YYYY-MM-DD')
console.log(date3.toString()) // 2080-03-23 00:00:00
```

## Installation
Expand Down Expand Up @@ -143,9 +143,9 @@ Additionally, you can convert the corresponding English date to a string using t
- `formatEnglishDateInNepali(formatStr)`: Returns a string representation in the Nepali (Devanagari script) of the English Date in the specified format.

```javascript
const now = new NepaliDate(2079, 5, 3, 16, 14)
console.log(now.format('YYYY-MM-DD hh:mm A')) // Outputs: 2079-06-03 04:14 PM
console.log(now.formatEnglishDate('YYYY-MM-DD hh:mm A')) // Outputs: 2022-08-19 04:14 PM
const date = new NepaliDate(2079, 5, 3, 16, 14)
console.log(date.format('YYYY-MM-DD hh:mm A')) // 2079-06-03 04:14 PM
console.log(date.formatEnglishDate('YYYY-MM-DD hh:mm A')) // 2022-09-19 04:14 PM
```

The date formatting will follow the format codes mentioned below, which are similar to the date formats used in day.js.
Expand Down Expand Up @@ -205,19 +205,24 @@ console.log(now.getDateObject()) // Date 2022-09-18T18:15:00.000Z

#### Creating a NepaliDate object from an English date

You can create a `NepaliDate` object from an English calendar date using the `fromEnglishDate` method.
You can create a `NepaliDate` object from an English calendar date using the `parseEnglishDate` or `fromEnglishDate` method.

```javascript
const date = NepaliDate.fromEnglishDate(2023, 6, 8)
console.log(date.toString()) // Outputs: "2080-03-23 00:00:00"
const date1 = NepaliDate.parseEnglishDate('2023-07-08', 'YYYY-MM-DD')
console.log(date1.toString()) // 2080-03-23 00:00:00
const date2 = NepaliDate.fromEnglishDate(2023, 6, 8, 10, 15)
console.log(date2.toString()) // 2080-03-23 10:15:00
```

### dateConverter

The `dateConverter` module provides functions for converting dates between the Nepali and English calendars.
The `dateConverter` module provides core functions for converting dates between the Nepali and English calendars.

- `englishToNepali(year, month, day)`: Converts an English calendar date to a Nepali calendar date. Returns an array `[npYear, npMonth, npDay]` representing the Nepali date.
- `nepaliToEnglish(year, month, day)`: Converts a Nepali calendar date to an English calendar date. Returns an array `[enYear, enYear, enDay]` representing the English date.

- `englishToNepali(year, month, day)`: Converts an English calendar date to a Nepali calendar date. Returns an array `[yearNp, monthNp, dayNp]` representing the Nepali date.
- `nepaliToEnglish(year, month, day)`: Converts a Nepali calendar date to an English calendar date. Returns an array `[yearEn, monthEn, dayEn]` representing the English date.
> Note: Use 0 as the value for the months Baisakh and January (Javascript Logic 🤷).

```javascript
import dateConverter from 'nepali-datetime/dateConverter'
Expand All @@ -229,6 +234,26 @@ const [npYear, npMonth, npDay] = dateConverter.englishToNepali(2023, 5, 27)
const [enYear, enMonth, enDay] = dateConverter.nepaliToEnglish(2080, 2, 15)
```

#### Quick Date conversion using NepaliDate

The `NepaliDate` class can also be used for direct string-to-string date conversions, eliminating the need for custom parsing or formatting logic.

**English Date to Nepali Date**

```javascript
const enDate = '2024-11-25'
const npDate = NepaliDate.parseEnglishDate(enDate, 'YYYY-MM-DD').format('YYYY-MM-DD')
// 2081-08-10
```

**Nepali Date to English Date**

```javascript
const npDate = '2081-08-10'
const enDate = new NepaliDate(npDate).formatEnglishDate('YYYY-MM-DD')
// 2024-11-25
```

## Acknowledgements

This project was inspired by [nepali-date](https://github.com/sharingapples/nepali-date). We would like to express our gratitude to their team for their excellent work and ideas, which served as a motivation for this project.
Expand Down
21 changes: 20 additions & 1 deletion src/NepaliDate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
nepaliDateToString,
} from './format'

import { parse, parseFormat } from './parse'
import { parse, parseFormat, parseEnglishDateFormat } from './parse'
import { getDate, getNepalDateAndTime } from './utils'
import { validateTime } from './validators'

Expand Down Expand Up @@ -623,6 +623,25 @@ class NepaliDate {
const englishDate = getDate(year, month0, date, hour, minute, second, ms)
return new NepaliDate(englishDate)
}

/**
* Creates a NepaliDate instance by parsing a provided English Date and Time string
* with the given format.
*
* @param dateString - The English Date and time string.
* @param format - The format of the provided date-time string.
* @example
* const dateTimeString = '2024/11/23 14-05-23.789'
* const format = 'YYYY/MM/DD HH-mm-ss.SSS'
* const nepaliDate = NepaliDate.parseEnglishDate(dateTimeString, format)
*/
static parseEnglishDate(dateString: string, format: string): NepaliDate {
const [year, month0, day, hour, minute, second, ms] = parseEnglishDateFormat(
dateString,
format
)
return NepaliDate.fromEnglishDate(year, month0, day, hour, minute, second, ms)
}
}

NepaliDate.minimum = () =>
Expand Down
2 changes: 2 additions & 0 deletions src/parse/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { parseFormat, parse } from './parse'
export { parseEnglishDateFormat } from './parseEnglishDate'
8 changes: 4 additions & 4 deletions src/parse.ts → src/parse/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ import {
NEPALI_MONTHS_SHORT_EN,
WEEKDAYS_LONG_EN,
WEEKDAYS_SHORT_EN,
} from './constants'
import { parseFormatTokens, seqToRE } from './utils'
} from '../constants'
import { parseFormatTokens, seqToRE } from '../utils'

/**
* Parses date from the given string.
Expand Down Expand Up @@ -200,7 +200,7 @@ function getDateParams(
break
case 'A':
case 'a':
isPM = (match[i + 1] as string).toLowerCase() === 'pm'
isPM = match[i + 1].toLowerCase() === 'pm'
}
}

Expand All @@ -222,7 +222,7 @@ function getDateParams(
export function parseFormat(dateString: string, format: string): number[] {
const formatTokens = parseFormatTokens(format)
const { dateTokens, regex: formatRegex } = tokensToRegex(formatTokens)
const match = dateString.match(formatRegex)
const match = RegExp(formatRegex).exec(dateString)
if (!match) {
throw new Error('Invalid date format')
}
Expand Down
140 changes: 140 additions & 0 deletions src/parse/parseEnglishDate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import {
ENGLISH_MONTHS_EN,
ENGLISH_MONTHS_SHORT_EN,
WEEKDAYS_LONG_EN,
WEEKDAYS_SHORT_EN,
} from '../constants'
import { parseFormatTokens, seqToRE } from '../utils'

const TOKEN_TO_REGEX: { [key: string]: RegExp } = {
YY: /(\d\d)/,
YYYY: /(\d\d\d\d)/,
M: /(1[0-2]|0[1-9]|[1-9])/,
MM: /(1[0-2]|0[1-9]|[1-9])/,
D: /(3[0-2]|[1-2]\d|0[1-9]|[1-9]| [1-9])/,
DD: /(3[0-2]|[1-2]\d|0[1-9]|[1-9]| [1-9])/,
H: /(2[0-3]|[0-1]\d|\d)/,
HH: /(2[0-3]|[0-1]\d|\d)/,
hh: /(1[0-2]|0[1-9]|[1-9])/,
mm: /([0-5]\d|\d)/,
ss: /([0-5]\d|\d)/,
SSS: /(\d\d\d)/,
A: /(AM|PM)/,
a: /(am|pm)/,
MMMM: seqToRE(ENGLISH_MONTHS_EN),
MMM: seqToRE(ENGLISH_MONTHS_SHORT_EN),
dddd: seqToRE(WEEKDAYS_LONG_EN),
ddd: seqToRE(WEEKDAYS_SHORT_EN),
dd: seqToRE(WEEKDAYS_SHORT_EN),
d: /([0-6])/,
}

function tokensToRegex(arr: string[]): { dateTokens: string[]; regex: RegExp } {
const dateTokens: string[] = []
const regexParts: string[] = []

for (const token of arr) {
if (token in TOKEN_TO_REGEX) {
dateTokens.push(token)
regexParts.push(TOKEN_TO_REGEX[token].source)
} else {
regexParts.push(token.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'))
}
}

const regexString = regexParts.join('')

return {
dateTokens,
regex: new RegExp(`^${regexString}$`),
}
}

function getDateParams(
dateTokens: string[],
match: RegExpMatchArray
): { [key: string]: number } {
// month and day are set to 1 in default
let [year, month, day, hour, hour12, minute, second, ms] = [0, 1, 1, 0, 0, 0, 0, 0]
let isPM = false
let is12hourFormat = false

for (let i = 0; i < dateTokens.length; i++) {
const token = dateTokens[i]
const matchData = parseInt(match[i + 1])
switch (token) {
case 'YYYY':
year = matchData
break
case 'YY':
year = 2000 + parseInt(match[i])
break
case 'MM':
case 'M':
month = matchData
break
case 'MMMM':
month = ENGLISH_MONTHS_EN.indexOf(match[i + 1]) + 1
break
case 'MMM':
month = ENGLISH_MONTHS_SHORT_EN.indexOf(match[i + 1]) + 1
break
case 'DD':
case 'D':
day = matchData
break
case 'HH':
case 'H':
hour = matchData
break
case 'hh':
case 'h':
hour12 = matchData
is12hourFormat = true
break
case 'mm':
case 'm':
minute = matchData
break
case 'ss':
case 's':
second = matchData
break
case 'SSS':
ms = matchData
break
case 'A':
case 'a':
isPM = match[i + 1].toLowerCase() === 'pm'
}
}

if (is12hourFormat) {
hour = hour12 + (isPM ? 12 : 0)
}

return {
year,
month0: month - 1,
day,
hour,
minute,
second,
ms,
}
}

export function parseEnglishDateFormat(dateString: string, format: string): number[] {
const formatTokens = parseFormatTokens(format)
const { dateTokens, regex: formatRegex } = tokensToRegex(formatTokens)
const match = RegExp(formatRegex).exec(dateString)
if (!match) {
throw new Error('Invalid date format')
}

const { year, month0, day, hour, minute, second, ms } = getDateParams(
dateTokens,
match
)
return [year, month0, day, hour, minute, second, ms]
}
22 changes: 22 additions & 0 deletions tests/NepaliDate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,28 @@ describe('NepaliDate', () => {
}).toThrow('Date out of range')
})

it('should initialize by parsing English Date string with given format', () => {
const n1 = NepaliDate.parseEnglishDate(
'2024 August 13 14-05-23.789',
'YYYY MMMM DD HH-mm-ss.SSS'
)
expect(n1.toString()).toBe('2081-04-29 14:05:23.789')
})

it("should initialize by parsing English Date's year string with default month, day and time params", () => {
const n1 = NepaliDate.parseEnglishDate('2024', 'YYYY')
expect(n1.toString()).toBe('2080-09-16 00:00:00')
})

it("should throw error if English Date's year component is missed during parsing", () => {
expect(() => {
const _ = NepaliDate.parseEnglishDate(
'08/13 14-05-23.789',
'MM/DD HH-mm-ss.SSS'
)
}).toThrow('Date out of range')
})

it('checks for nepali date validity', () => {
// 373314600000
// Fri Oct 30 1981 18:30:00 GMT+0000
Expand Down
2 changes: 1 addition & 1 deletion tests/parse.test.ts → tests/parse/parse.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { parseFormat } from '../src/parse'
import { parseFormat } from '../../src/parse'

describe('parseFormat', () => {
it('should parse date string in valid format correctly', () => {
Expand Down
Loading

0 comments on commit aeabb8d

Please sign in to comment.