text: import convertTZ from "./timezone.js";
  
  /**
   * Converts date to CronDate
   * @constructor
   * 
   * @param {CronDate|date|string} [date] - Input date, if using string representation ISO 8001 (2015-11-24T19:40:00) local timezone is expected
   * @param {string} [timezone] - String representation of target timezone in Europe/Stockholm format.
   */
  function CronDate (date, timezone) {	
  
  	this.timezone = timezone;
  
  	if (date && date instanceof Date) {
  		this.fromDate(date);
  	} else if (date === void 0) {
  		this.fromDate(new Date());
  	} else if (date && typeof date === "string") {
  		this.fromString(date);
  	} else if (date instanceof CronDate) {
  		this.fromCronDate(date);
  	} else {
  		throw new TypeError("CronDate: Invalid type (" + typeof date + ") passed as parameter to CronDate constructor");
  	}
  }
  
  /**
   * Sets internals using a Date 
   * @private
   * 
   * @param {Date} date - Input date
   */
  CronDate.prototype.fromDate = function (date) {
  	
  	if (this.timezone) {
  		date = convertTZ(date, this.timezone);
  	}
  
  	this.milliseconds = date.getMilliseconds();
  	this.seconds = date.getSeconds();
  	this.minutes = date.getMinutes();
  	this.hours = date.getHours();
  	this.days = date.getDate();
  	this.months  = date.getMonth();
  	this.years = date.getFullYear();
  
  };
  
  /**
   * Sets internals by deep copying another CronDate
   * @private
   * 
   * @param {CronDate} date - Input date
   */
  CronDate.prototype.fromCronDate = function (date) {
  	this.timezone = date.timezone;
  	this.milliseconds = date.milliseconds;
  	this.seconds = date.seconds;
  	this.minutes = date.minutes;
  	this.hours = date.hours;
  	this.days = date.days;
  	this.months = date.months;
  	this.years = date.years;
  };
  
  /**
   * Reset internal parameters (seconds, minutes, hours) that may have exceeded their ranges
   * @private
   * 
   * @param {Date} date - Input date
   */
  CronDate.prototype.apply = function () {
  	const newDate = new Date(this.years, this.months, this.days, this.hours, this.minutes, this.seconds, this.milliseconds);
  	
  	this.milliseconds = newDate.getMilliseconds();
  	this.seconds = newDate.getSeconds();
  	this.minutes = newDate.getMinutes();
  	this.hours = newDate.getHours();
  	this.days = newDate.getDate();
  	this.months  = newDate.getMonth();
  	this.years = newDate.getFullYear();
  };
  
  /**
   * Sets internals by parsing a string
   * @private
   * 
   * @param {Date} date - Input date
   */
  CronDate.prototype.fromString = function (str) {
  
  	const parsedDate = this.parseISOLocal(str);
  
  	// Throw if we did get an invalid date
  	if( isNaN(parsedDate) ) {
  		throw new TypeError("CronDate: Provided string value for CronDate could not be parsed as date.");
  	}
  	
  	this.fromDate(parsedDate);
  };
  
  /**
   * Increment to next run time
   * @public
   * 
   * @param {string} pattern - The pattern used to increment current state
   * @param {boolean} [rerun=false] - If this is an internal incremental run
   * @return {CronDate|null} - Returns itself for chaining, or null if increment wasnt possible
   */
  CronDate.prototype.increment = function (pattern, rerun) {
  
  	if (!rerun) {
  		this.seconds += 1;
  	}
  
  	this.milliseconds = 0;
  
  	const 
  		origTime = this.getTime(),
  		
  		/**
  		 * Find next
  		 * 
  		 * @param {string} target
  		 * @param {string} pattern
  		 * @param {string} offset
  		 * @param {string} override
  		 * 
  		 * @returns {boolean}
  		 * 
  		 */
  		findNext = (target, pattern, offset, override) => {
  			
  			const startPos = (override === void 0) ? this[target] + offset : 0 + offset;
  
  			for( let i = startPos; i < pattern[target].length; i++ ) {
  
  				// If pattern matches and, in case of days, weekday matches, go on
  				if( pattern[target][i] && (target !== "days" || (pattern.daysOfWeek[this.getDate(true).getDay()])) ) {
  					
  					// Special handling for L (last day of month), when we are searching for days
  					if (target === "days" && pattern.lastDayOfMonth) {
  						let baseDate = this.getDate(true);
  
  						// Set days to one day after today, if month changes, then we are at the last day of the month
  						baseDate.setDate(i-offset+1);
  						if (baseDate.getMonth() !== this["months"]) {
  							this[target] = i-offset;
  							return true;
  						}
  					
  					// Normal handling
  					} else {
  						this[target] = i-offset;
  						return true;
  					}
  
  				}
  			}
  			return false;
  
  		},
  		
  		resetPrevious = (offset) => {
  			// Now when we have gone to next minute, we have to set seconds to the first match
  			// Now we are at 00:01:05 following the same example.
  			// 
  			// This goes all the way back to seconds, hence the reverse loop.
  			while(doing + offset >= 0) {
  
  				// Ok, reset current member(e.g. seconds) to first match in pattern, using 
  				// the same method as aerlier
  				// 
  				// Note the fourth parameter, stating that we should start matching the pattern
  				// from zero, instead of current time.
  				findNext(toDo[doing + offset][0], pattern, toDo[doing + offset][2], 0);
  
  				// Go back up, days -> hours -> minutes -> seconds
  				doing--;
  			}
  		};
  
  	// Array of work to be done, consisting of subarrays described below:
  	// [
  	//   First item is which member to process,
  	//   Second item is which member to increment if we didn't find a mathch in current item,
  	//   Third item is an offset. if months is handled 0-11 in js date object, and we get 1-12
  	//   from pattern. Offset should be -1
  	// ]
  	const toDo = [
  		["seconds", "minutes", 0],
  		["minutes", "hours", 0],
  		["hours", "days", 0],
  		["days", "months", -1],
  		["months", "years", 0]
  	];
  
  	// Ok, we're working our way trough the toDo array, top to bottom
  	// If we reach 5, work is done
  	let doing = 0;
  	while(doing < 5) {
  
  		// findNext sets the current member to next match in pattern
  		// If time is 00:00:01 and pattern says *:*:05, seconds will
  		// be set to 5
  
  		// Store current value at current level
  		let currentValue = this[toDo[doing][0]];
  		
  		// If pattern didn't provide a match, increment next value (e.g. minues)
  		if(!findNext(toDo[doing][0], pattern, toDo[doing][2])) {
  			this[toDo[doing][1]]++;
  
  			// Reset current level and previous levels
  			resetPrevious(0);
  
  		// If pattern provided a match, but changed current value ...
  		} else if (currentValue !== this[toDo[doing][0]]) {
  			// Reset previous levels
  			resetPrevious(-1);
  
  		}
  
  		// Bail out if an impossible pattern is used
  		if (this.years >= 4000) {
  			return null;
  		}
  
  		// Gp down, seconds -> minutes -> hours -> days -> months -> year
  		doing++;
  	}
  	
  	// If anything changed, recreate this CronDate and run again without incrementing
  	if (origTime != this.getTime()) {
  		this.apply();
  		return this.increment(pattern, true);
  	} else {
  		return this;
  	}
  
  };
  
  /**
   * Convert current state back to a javascript Date()
   * @public
   * 
   * @param {boolean} internal - If this is an internal call
   * @returns {Date}
   */
  CronDate.prototype.getDate = function (internal) {
  	const targetDate = new Date(this.years, this.months, this.days, this.hours, this.minutes, this.seconds, this.milliseconds);
  	if (internal || !this.timezone) {
  		return targetDate;
  	} else {
  		const offset = convertTZ(targetDate, this.timezone).getTime()-targetDate.getTime();
  		return new Date(targetDate.getTime()-offset);
  	}
  };
  
  /**
   * Convert current state back to a javascript Date() and return UTC milliseconds
   * @public
   * 
   * @param {boolean} internal - If this is an internal call
   * @returns {Date}
   */
  CronDate.prototype.getTime = function (internal) {
  	return this.getDate(internal).getTime();
  };
  
  /**
   * Takes a iso 8001 local date time string and creates a Date object
   * @private
   * 
   * @param {string} dateTimeString - an ISO 8001 format date and time string
   *                      with all components, e.g. 2015-11-24T19:40:00
   * @returns {Date|number} - Date instance from parsing the string. May be NaN.
   */
  CronDate.prototype.parseISOLocal = function (dateTimeString) {
  	const dateTimeStringSplit = dateTimeString.split(/\D/);
  
  	// Check for completeness
  	if (dateTimeStringSplit.length < 6) {
  		return NaN;
  	}
  
  	const
  		year = parseInt(dateTimeStringSplit[0], 10),
  		month = parseInt(dateTimeStringSplit[1], 10),
  		day = parseInt(dateTimeStringSplit[2], 10),
  		hour = parseInt(dateTimeStringSplit[3], 10),
  		minute = parseInt(dateTimeStringSplit[4], 10),
  		second = parseInt(dateTimeStringSplit[5], 10);
  
  	// Check parts for numeric
  	if( isNaN(year) || isNaN(month) || isNaN(day) || isNaN(hour) || isNaN(minute) || isNaN(second) ) {
  		return NaN;
  	} else {
  		let generatedDate;
  
  		// Check for UTC flag
  		if ((dateTimeString.indexOf("Z") > 0)) {
  
  			// Handle date as UTC
  			generatedDate = new Date(Date.UTC(year, month-1, day, hour, minute, second));
  
  			// Check generated date
  			if (year == generatedDate.getUTCFullYear()
  				&& month == generatedDate.getUTCMonth()+1
  				&& day == generatedDate.getUTCDate()
  				&& hour == generatedDate.getUTCHours()
  				&& minute == generatedDate.getUTCMinutes()
  				&& second == generatedDate.getUTCSeconds()) {
  				return generatedDate;
  			} else {
  				return NaN;
  			}
  		} else {
  
  			// Handle date as local time
  			generatedDate = new Date(year, month-1, day, hour, minute, second);
  
  			// Check generated date
  			if (year == generatedDate.getFullYear()
  				&& month == generatedDate.getMonth()+1
  				&& day == generatedDate.getDate()
  				&& hour == generatedDate.getHours()
  				&& minute == generatedDate.getMinutes()
  				&& second == generatedDate.getSeconds()) {
  				return generatedDate;
  			} else {
  				return NaN;
  			}
  		}
  	}
  };
  
  export { CronDate };