Formatting a date with Moment Tz - ios

I'm struggling with formatting a date to insert into a calendar. I'm working on an Ionic project and I'm attempting to leverage moment.js timezones. The data is being passed into the application in several pieces. I am receiving a millisecond date time stamp, a 24-hour string for time and a string of the timezone. The date does not contain the time. What I want to achieve is creating a date object from these pieces and then converting the date into the user's local timezone to add it to their device calendar.
example of data passed to app
The date of the event: August 14, 2018 17:00
time = 17:00
date = 1534204800
timezone = AEDT
The destination timezone is based on the user's location.
let timeFormatter = new Date();
timeFormatter.setMilliseconds(date);
let momentHrMin = moment(timeFormatter.toDateString() + " " + time);
//WP sever is on GMT get the day, month and yr
let momentTZDate = momentTz.unix(date);
momentTZDate.tz('GMT');
let day = momentTZDate.days();
let month = momentTZDate.month();
let yr = momentTZDate.year();
//set the correct timezone on the ecpoh/unix DATE with momentTz.
momentTZDate.tz(this.eventDetails.eventDetail.timezoneOffset);
// Lastly set the date so the timezone conversions are correct
momentTZDate.set(
{
day: day,
month: month,
year: yr,
hour: momentHrMin.hour(),
minute: momentHrMin.minute(),
second: 0,
millisecond: 0
}
);

I found the cause of my struggles with the timezones. The abbreviated AEDT is not an accepted value for Moment.tz. AEDT could represent multiple timezones. For example, American Eastern, Australian Eastern, etc... Simply changing momentTZDate.tz("AEDT"); to momentTZDate.tz("Australia/Brisbane"); fixed my issue. Below is my typescript code I used to get through this issue. I won't say its the best but it works.
private getCalDate(date:number, time: string): Date {
// create an object in which the hours and minutes can be parse from(do to free text entry on the backend moment handles different inputs)
let timeFormatter = new Date();
timeFormatter.setMilliseconds(date);
let momentHrMin = moment(timeFormatter.toDateString() + " " + time);
//WP sever is on GMT get the day, month and yr
let momentTZDate = momentTz.unix(date);
momentTZDate.tz('GMT');
let day = momentTZDate.days();
let month = momentTZDate.month();
let yr = momentTZDate.year();
//set the correct timezone on the ecpoh/unix DATE with momentTz.
momentTZDate.tz("Australia/Brisbane");
// Lastly set the date so the timezone conversions are correct
momentTZDate.set(
{
day: day,
month: month,
year: yr,
hour: momentHrMin.hour(),
minute: momentHrMin.minute(),
second: 0,
millisecond: 0
}
);
return momentTZDate.toDate();
}

Related

Number of days in month returns wrong value after 10:00 PM

I am having a small issue with getting the total days in a month using Swift.
I have extended the Date class and created this function:
func daysInMonth() -> Int {
print(self.day) ##30
print(self.month) ##12
print(self) ## 2021-11-30 23:46:29 +0000
print(Calendar.current.range(of: .day, in: .month, for: self)?.count) ##31
return Calendar.current.range(of: .day, in: .month, for: self)?.count ?? 0
}
I have set the Date&Time to the 30th of November, at 11:45 PM in the settings of my Mac, in Preferences.
I called the above function at 11:46 PM and obtained the above results (inline, next to the print statements).
The date output is correct as well as the day. The month output is wrong and the result is 31 days in the month of November.
If I run this exact same code before 10:00 PM, I get the right result which is 30 days.
Does anyone know why this is happening?
Thank you,
Paprika
It's a GMT offset issue combined with the current day in a month.
When you create a date without set a day, it will be set to the first day of the month.
So, if your timezone offset is for example -4 means your are 4 hours behind the GMT 0 and by default the timezone defined at Calendar.current is equal the system timezone. So what it means? Means you'll obtain the previous month if you test it in a boundary of 23 + (-4) or the next month if your offset is positive.
You can test this behaviour copying'n paste the following code in the Playground.
func getDaysInMonth(month: Int, year: Int, offset: Int = 0) -> Int? {
let someDate = DateComponents(year: year, month: month, hour: 3)
var current = Calendar.current
let timezone = TimeZone(secondsFromGMT: 60 * 60 * offset)!
current.timeZone = timezone
guard let someDay = current.date(from: someDate) else { return nil }
print("date: \(someDay)") // this will always
return someDay.daysInCurrentMonth
}
for hour in -12...12 {
print("hour: \(hour)\ndays: \(getDaysInMonth(month: 10, year: 2021, offset: hour) ?? -1)")
print("---\n")
}
extension Date {
var daysInCurrentMonth: Int? {
Calendar.current.range(of: .day, in: .month, for: self)?.count
}
}
Notice the days will change starting by your current system time zone (notice only the month will change).
How to fix this?
In your case, I guess you just want to show how many days a month have, so you can just set the to zero like this:
TimeZone(secondsFromGMT: 0)
Do this change at a instance of Calendar.current and check if it works for you.
It appears there something wrong with your Date extension methods for .day and .month.
Without seeing code it's hard to determine what the problem is though. Below is some code for returning the current month (Int) and current numbered day of month (Int)
extension Date
{
var month: Int
{
let date = Date()
let calendar = Calendar.current
let components = calendar.dateComponents([.month], from: date)
return components.month
}
var day: Int
{
let date = Date()
let calendar = Calendar.current
let components = calendar.dateComponents([.day], from: self)
return components.day
}
}
Please also ensure your time/date settings are correct on your mac/simulator/device. If these are wrong - it could have been jumping to a different month if you were in a timezone that was ahead a few hours.

FSCalendar select day selects previous day at 23:00

I'm using FSCalendar in a Swift app, and when user selects a day, I'm printing the selected day, and the it prints the previous day, at 23:00. I'm not sure why and how can I solve this. I'm in spain. Maybe it's related with where you are and your local hour?
This is how I'm printing the selected day:
extension CalendarDataViewViewController: FSCalendarDataSource {
func calendar(_ calendar: FSCalendar, didSelect date: Date, at monthPosition: FSCalendarMonthPosition) {
let df = DateFormatter()
df.dateFormat = "yyyy-MM-dd hh:mm:ss"
let now = df.string(from: date)
logger.debug("Date: \(date)")
}
}
And this is what it's printed when I select 18 march:
21:01:24.646 💚 DEBUG CalendarDataViewViewController.calendar():258 - Date: 2021-03-17 23:00:00 +0000
Your code creates a date formatter, converts the returned date to a date string with that formatter, and then ignores that and simply prints the date, which is being displayed in UTC. (Note the output Date: 2021-03-17 23:00:00 +0000)
Change your log command to read:
logger.debug("Date: \(now)")
And by the way, the variable name now is a terrible choice for holding a user-selected date that is not the current date.
I'd suggest renaming the returned date parameter selectedDate and the String output of the formatter as selectedDateString
Edit:
Consider this code:
import Foundation
func dateStringFromDate(_ inputDate: Date) -> String {
let df = DateFormatter()
df.dateFormat = "yyyy-MM-dd hh:mm:ss a"
let dateString = df.string(from: inputDate)
return dateString
}
func isoDateStringFromDate(_ inputDate: Date) -> String {
let df = ISO8601DateFormatter()
df.formatOptions = .withInternetDateTime
df.timeZone = TimeZone.current //Force the formatter to express the time in the current time zone, including offset
let dateString = df.string(from: inputDate)
return dateString
}
let now = Date()
print("Current timezone = \(TimeZone.current)")
print("now in 'raw' format = \(now)")
let localizedDateString = DateFormatter.localizedString(from: now,
dateStyle: .medium,
timeStyle: .medium)
print("localizedString for the current date = \(localizedDateString)")
print("dateStringFromDate = \(dateStringFromDate(now))")
print("isoDateStringFromDate = \(isoDateStringFromDate(now))")
Right now, at about 9:16 PM EDT on Thursday March 18th, that logs the following:
Current timezone = America/New_York (current)
now in 'raw' format = 2021-03-19 01:16:52 +0000
localizedString for the current date = Mar 18, 2021 at 9:16:52 PM
dateStringFromDate = 2021-03-18 09:16:52 PM
isoDateStringFromDate = 2021-03-18T21:16:52-04:00
The 'raw' date format is in GMT, with an offset value of 0. In that form, in GMT, the calendar date is already March 19th. (Because GMT is 4 hours ahead of EDT)
The class function NSDateFormatter.localizedString(from:dateStyle:timeStyle) displays a date in the current time zone and using the device's locale settings. The dateStyle and timeStyle parameters give you the option to choose whether or not, and in what format (short, medium, or long) to display the date or time.
An ISO8601DateFormatter displays the date following the conventions in the ISO8601 standard. The isoDateStringFromDate(:) function above uses the .withInternetDateTime option to express the date in the ISO8601 "internet date and time" format. I forced that date to be in the local time zone, so it displays the date with a -4 hour offset from GMT (since it is EDT, eastern daylight savings time where I live.)
The function dateStringFromDate(_:) is a slight variation on your function. It returns a date string in the current time zone, using 12 hour times and an AM/PM string.

Converting 12 digit ticks to Date

I am trying to convert 12 digit c# date time ticks formatted time. (648000000000). With the help of following link I added an extension to my code How to get 18-digit current timestamp in Swift?.
extension Date {
init(ticks: UInt64) {
self.init(timeIntervalSince1970: Double(ticks)/10_000_000 - 62_135_596_800)
}
}
let date = Date(ticks: 648000000000)
When I try to see result date it prints following;
0001-01-03 18:00:00 +0000
However, when I try to convert it hour and minute format output is irrelevant like 19:55
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "HH:mm"
dateFormatter.string(from: date)
My first question is how can I format it to get only 18:00. Second is why it is printing 18:00 when I print only date, but 19:55 when I format it?
Just don't subtract 62_135_596_800
extension Date {
init(ticks: UInt64) {
self.init(timeIntervalSince1970: Double(ticks)/10_000_000)
}
}
1970-01-01 18:00:00 +0000
The other problem: When you create date and print it, the string is formatted in UTC time zone (offset GMT+0). But DateFormatter returns string representation dependent on its time zone, which is the local timezone by default.
You can fix your code just by setting dateFormatter's timeZone to UTC
dateFormatter.timeZone = TimeZone(secondsFromGMT: 0)
18:00
Apparently your timestamp represents a duration as the number of 100-nanosecond ticks, not a date. If you divide the number by 10^7 then you get the number of seconds. These can be printed as a duration with a DateComponentsFormatter.
Example:
let ticks = 648000000000
let seconds = TimeInterval(ticks) / 10_000_000
let fmt = DateComponentsFormatter()
fmt.allowedUnits = [.hour, .minute]
print(fmt.string(from: seconds)!)
18:00
The duration is 64800 = 18 * 60 * 60 seconds, that are exactly 18 hours.

1st april dates of 80s failed to parse in iOS 10.0

I found that DateFormatter date(from:) method can't parse a couple of specific dates. Method returns nil for the 1st april of 1981-1984 years. Is it a bug of Foundation? What can we do to perform parsing of such dates?
Xcode 8.0, iOS SDK 10.0. Here is a screenshot of a short playground example:
This problem occurs if daylight saving time starts exactly on
midnight, as it was the case in Moscow in the years 1981–1984 (see for example Clock Changes in Moscow, Russia (Moskva)).
This was also observed in
Why does NSDateFormatter return nil date for these 4 time zones? and
Why NSDateFormatter is returning null for a 19/10/2014 in a Brazilian time zone?
For example, at midnight of April 1st 1984, the clocks were adjusted one hour forward, which means that the date "1984-04-01 00:00"
does not exist in that timezone:
let dFmt = DateFormatter()
dFmt.dateFormat = "yyyy-MM-dd"
dFmt.timeZone = TimeZone(identifier: "Europe/Moscow")
print(dFmt.date(from: "1984-04-01")) // nil
As a solution, you can tell the date formatter to be "lenient":
dFmt.isLenient = true
and then it will return the first valid date on that day:
dFmt.isLenient = true
if let date = dFmt.date(from: "1984-04-01") {
dFmt.dateFormat = "yyyy-MM-dd HH:mm:ss"
print(dFmt.string(from: date))
}
// 1984-04-01 01:00:00
A different solution
was given by rob mayoff, which is to make the date formatter use noon instead of midnight as the
default date. Here is a translation of rob's code from Objective-C to Swift:
let noon = DateComponents(calendar: dFmt.calendar, timeZone: dFmt.timeZone,
year: 2001, month: 1, day: 1, hour: 12, minute: 0, second: 0)
dFmt.defaultDate = noon.date
if let date = dFmt.date(from: "1984-04-01") {
dFmt.dateFormat = "yyyy-MM-dd HH:mm:ss"
print(dFmt.string(from: date))
}
// 1984-04-01 12:00:00

Neat way to get end of day for date?

I am querying a database for values between a startDate and an endDate. I am therefore looking for a neat way to get the NSDate for the end of the day for a date. Just like you can get startOfDayForDate().
I guess you could do :
let cal = NSCalendar.currentCalendar()
let startOfDay = cal.startOfDayForDate(NSDate())
let aDay:NSTimeInterval = 60*60*23 + 60*59 + 59
let endofDay = startOfDay.dateByAddingTimeInterval(aDay)
Is adding component.day + 1 to startOfDayForDate the correct method to get the "end of the day" for a date, or is there a better method?
A better way would be to get the start of the next day and subtract 0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001 second.
Do you get what I want to say? It's very hard to define the end of the day. 23:59 is certainly not the end of the day, there is almost a whole minute left until the next day. And even 23:59:59 is not the end of a day. Because there is an infinite amount of fraction seconds between this time and the start of the next day. As far as I know NSDate supports nanoseconds out of the box, so in the current implementation there are at least 1 000 000 000 possible NSDates between 23:59:59 and the next day.
That's why you should see if you can find a way to use the start of the next day.
For example, instead of if (startOfDay <= date && date <= endOfDay) you could use if (startOfDay <= date && date < startOfNextDay).
To calculate the start of the next day you have to use NSCalendar. In iOS8 Apple added a couple of nice methods to make these calculations short:
let calendar = NSCalendar.currentCalendar()
let startOfDay = calendar.startOfDayForDate(NSDate())
let startOfNextDay = calendar.dateByAddingUnit(.CalendarUnitDay, value: 1, toDate: startOfDay, options: nil)!
EDIT: Since you now state that you want to query a database you don't need to find the end of the day. Check if the date is on or after the start of the day, and before the start of the next day.
Try this:
var endOfDay: Date? {
var components = DateComponents()
components.day = 1
components.second = -1
var calendar = Calendar.current
calendar.timeZone = TimeZone(abbreviation: "GMT")!
return calendar.date(bySettingHour: 11, minute: 59, second: 59, of: Date())
}

Resources