Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add method parseEnglishDate for English date parsing #95

Merged
merged 1 commit into from
Nov 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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