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

Date format generated by formatRFC3339 is incompatible with Golang standard lib RFC3339! #1911

Open
derpycoder opened this issue Aug 21, 2020 · 9 comments
Assignees

Comments

@derpycoder
Copy link

derpycoder commented Aug 21, 2020

The documentation for formatRFC3339 suggests following output in comment:

// Represent 18 September 2019 in ISO 3339 format:
const result = formatRFC3339(new Date(2019, 8, 18, 19, 0, 52))
//=> '2019-09-18T19:00:52Z'

However, what it actually outputs is:

//-> '2019-09-18T19:00:52+5.5:30'

Which doesn't sit well with Golang standard lib, RFC3339 format!

Replace the prior date with later date in Go Playground and you'll see the issue.

P.S. I used StackBlitz to run date-fns.

@derpycoder derpycoder changed the title RFC3339 format generated by formatRFC3339 is incompatible with Golang standard lib RFC3339! Date format generated by formatRFC3339 is incompatible with Golang standard lib RFC3339! Aug 21, 2020
@derpycoder
Copy link
Author

Same goes for formatISO!

Am I doing something wrong??

@imballinst
Copy link
Contributor

hi @abhijit-kar, thanks for submitting this issue! Which version of date-fns did you use?

The 5.5 part is a bit interesting, it should have been fixed with #1599 and release 2.10.0.

With regards to the Z part -- it's varying depending on your timezone. For example, this is a screencap of the Go playground with your timezone (I suppose):

image

It's also documented in https://tools.ietf.org/html/rfc3339#section-5.6, where time-offset = "Z" / time-numoffset. I hope this helps!

@derpycoder
Copy link
Author

I used latest 2.15.0 & prior 2.9.0 versions.

That resulted in "2020-08-22T19:19:53+5.5:30" & "2020-08-22T19:23:02+5.5:30" respectively, both of which errored out when I fed them to Golang.

@imballinst
Copy link
Contributor

imballinst commented Aug 22, 2020

Ah yes, my bad -- that PR only does the trick for formatISO. However, I think the output for formatISO should be correct. See the following screenshot and Runkit playground link:

image

I will go ahead and fix the format* functions. Thanks for reporting!

@piotrl
Copy link
Contributor

piotrl commented Aug 22, 2020

I also proposed fix few weeks ago with tests for all other kinds of strange timezones: #1890

@abhijit-kar for a workaround until library with fix is released (not sure how urgent it's for you), just fork the function :)

See fork I made:

function formatRFC3339(dirtyDate, dirtyOptions) {
  if (arguments.length < 1) {
    throw new TypeError(
      `1 arguments required, but only ${arguments.length} present`
    )
  }

  const originalDate = toDate(dirtyDate)

  if (!isValid(originalDate)) {
    throw new RangeError('Invalid time value')
  }

  const options = dirtyOptions || {}
  const fractionDigits =
    options.fractionDigits == null ? 0 : toInteger(options.fractionDigits)

  // Test if fractionDigits is between 0 and 3 _and_ is not NaN
  if (!(fractionDigits >= 0 && fractionDigits <= 3)) {
    throw new RangeError('fractionDigits must be between 0 and 3 inclusively')
  }

  const day = addLeadingZeros(originalDate.getDate(), 2)
  const month = addLeadingZeros(originalDate.getMonth() + 1, 2)
  const year = originalDate.getFullYear()

  const hour = addLeadingZeros(originalDate.getHours(), 2)
  const minute = addLeadingZeros(originalDate.getMinutes(), 2)
  const second = addLeadingZeros(originalDate.getSeconds(), 2)

  let fractionalSecond = ''
  if (fractionDigits > 0) {
    const milliseconds = originalDate.getMilliseconds()
    const fractionalSeconds = Math.floor(
      milliseconds * Math.pow(10, fractionDigits - 3)
    )
    fractionalSecond = '.' + addLeadingZeros(fractionalSeconds, fractionDigits)
  }

  let offset = ''
  const tzOffset = originalDate.getTimezoneOffset()

  if (tzOffset !== 0) {
    const absoluteOffset = Math.abs(tzOffset)
    const hourOffset = addLeadingZeros(toInteger(absoluteOffset / 60), 2)
    const minuteOffset = addLeadingZeros(absoluteOffset % 60, 2)
    // If less than 0, the sign is +, because it is ahead of time.
    const sign = tzOffset < 0 ? '+' : '-'

    offset = `${sign}${hourOffset}:${minuteOffset}`
  } else {
    offset = 'Z'
  }

  return `${year}-${month}-${day}T${hour}:${minute}:${second}${fractionalSecond}${offset}`
}

function addLeadingZeros(number, targetLength) {
  var sign = number < 0 ? '-' : ''
  var output = Math.abs(number).toString()
  while (output.length < targetLength) {
    output = '0' + output
  }
  return sign + output
}

function toInteger (dirtyNumber) {
  if (dirtyNumber === null || dirtyNumber === true || dirtyNumber === false) {
    return NaN
  }

  var number = Number(dirtyNumber)

  if (isNaN(number)) {
    return number
  }

  return number < 0 ? Math.ceil(number) : Math.floor(number)
}

@imballinst imballinst assigned piotrl and unassigned imballinst Aug 23, 2020
@derpycoder
Copy link
Author

derpycoder commented Aug 24, 2020

It is fine Piotr, take your time.

I will have to get lots of other things working, before I'll be needing this.

I was merely making a POC, when I discovered this issue.

@piotrl
Copy link
Contributor

piotrl commented Aug 27, 2020

Fixed in #1890 and released under v2.16.0

@RaVbaker
Copy link

Isn't the issue related to the fact that method getTimezoneOffset is used to display the date object but it actually depends on the server set timezone? I experienced same issues when parsing and formatting dates using parseISO/formatISO and I'm getting different results in different environments for a value like this: "2020-10-19T14:03:34-0100"

@piotrl
Copy link
Contributor

piotrl commented Oct 27, 2020

@RaVbaker what do you mean by "the server set timezone"?

date-fns will always get timezone local to environment it runs function

if you run this function on node.js - then it'll get value from server
if you run this function on browser - it'll get value from computer that runs the browser, so environments in different timezones will give different results.

btw. I'd love to close this issue as it's done, but I don't have privileges :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants